poll voting works

This commit is contained in:
iou1name 2018-09-03 17:59:41 -04:00
parent 738591b05e
commit a485a36897
8 changed files with 122 additions and 24 deletions

View File

@ -66,6 +66,14 @@ class QuestConsumer(WebsocketConsumer):
} }
) )
def self_send(self, event, data={}):
"""
Like `send`, except only sends events to the paired websocket
instead of the entire group.
"""
data = json.dumps({'event': event, 'data': data})
self.dispatch_send({'message': data})
def dispatch_send(self, event): def dispatch_send(self, event):
""" """
Receives events from other consumers in the group and relays the Receives events from other consumers in the group and relays the

View File

@ -4,6 +4,7 @@ Individual functions for handling WebSocket events. Gets called by the
QuestConsumer object in consumers.py. QuestConsumer object in consumers.py.
""" """
# TODO: quest owner only events # TODO: quest owner only events
# TODO: try/except on database calls
import re import re
import time import time
import types import types
@ -43,9 +44,9 @@ def message(socket, data):
for quote in quotes: for quote in quotes:
msg_id = quote.replace(">>", "") msg_id = quote.replace(">>", "")
msg = '<a class="quotelink" ' msg = '<a class="quotelink" '
msg += 'href="javascript:scrollToMsg(\'' + msg_id + '\')" ' msg += 'href="javascript:scroll_to_msg(\'' + msg_id + '\')" '
msg += 'onmousemove="showPreview(event, \'' + msg_id + '\')" ' msg += 'onmousemove="show_preview(event, \'' + msg_id + '\')" '
msg += 'onmouseout="clearPreview()">' + quote + '</a>' msg += 'onmouseout="clear_preview()">' + quote + '</a>'
message = message.replace(quote, msg) message = message.replace(quote, msg)
# handle image # handle image
@ -255,7 +256,7 @@ def poll_post(socket, data):
data['post_id'] = p.id data['post_id'] = p.id
data['post_text'] = "Poll" data['post_text'] = "Poll"
data['date'] = localtime(p.timestamp).strftime('%Y-%m-%d %H:%M') data['date'] = localtime(p.timestamp).strftime('%Y-%m-%d %H:%M')
data['options'] = [o.text for o in options] data['options'] = [(o.id, o.text) for o in options]
socket.send('new_post', data) socket.send('new_post', data)
@ -305,6 +306,49 @@ def open_post(socket, data):
socket.send('open_post', data) socket.send('open_post', data)
def vote(socket, data):
"""
Called when a player votes in a poll.
"""
quest_id = data.get('quest_id')
post_id = data.get('post_id')
option_id = data.get('option_id')
polarity = data.get('polarity')
ip_address = socket.scope['client'][0]
user = socket.scope['user']
if polarity == False:
v = PollVote.objects.get(ip_address=ip_address, option__id=option_id)
v.delete()
else:
p = Poll.objects.get(post_id=post_id)
if p.multi_choice == False:
votes = PollVote.objects.filter(
ip_address=ip_address,
option__poll=p
)
for vote in votes:
vote.delete()
data = {}
data['option_id'] = vote.option.id
data['polarity'] = False
socket.send('vote', data)
socket.self_send('set_option_box', data)
v = PollVote(
option=PollOption.objects.get(id=option_id),
ip_address=ip_address
)
if user.username:
v.user = user
v.save()
data = {}
data['option_id'] = option_id
data['polarity'] = polarity
socket.send('vote', data)
events = {} events = {}
for obj in dir(): for obj in dir():
if type(locals()[obj]) == types.FunctionType: if type(locals()[obj]) == types.FunctionType:

View File

@ -68,14 +68,14 @@
{% elif post.post_type == "poll" %} {% elif post.post_type == "poll" %}
<h3>{{ post.post_text }} - {% if post.poll.open %}Open{% else %}Closed{% endif %}</h3> <h3>{{ post.post_text }} - {% if post.poll.open %}Open{% else %}Closed{% endif %}</h3>
<table class="poll" id="poll-{{ post.id }}"> <table class="poll" id="poll-{{ post.id }}">
{% for option in poll_options.filter(poll=post.poll) %} {% for option in poll_options.filter(poll=post.poll).order_by("id") %}
<tr id="optionRow-{{ post.id }}"> <tr id="optionRow-{{ option.id }}">
<td class="pollCheckBox"> <td class="pollCheckBox">
<input type="checkbox" {# if ip_address in poll_votes.get(option[0], []) %}checked="true"{% endif #} id="pollInput-{{ option.id }}" onchange="pollVote({{ post.id }}, {{ option.id }})"{% if not post.poll.open %} disabled{% endif %}/> <input type="checkbox" {# if ip_address in poll_votes.get(option.id) %}checked="true"{% endif #} id="pollInput-{{ option.id }}" onchange="vote({{ post.id }}, {{ option.id }})"{% if not post.poll.open %} disabled{% endif %}/>
<label for="pollInput-{{ option.id }}"></label> <label for="pollInput-{{ option.id }}"></label>
</td> </td>
<td class="option_text">{{ option.text }}</td> <td class="option_text">{{ option.text }}</td>
<td class="optionVotes">{# poll_votes.get(option[0], [])|length #}</td> <td class="optionVotes">{{ poll_votes.filter(option=option).count() }}</td>
</tr> </tr>
{% endfor %} {% endfor %}
</table> </table>
@ -130,8 +130,8 @@
<div><input type="text" name="pollOption-2" class="pollOption" placeholder="Option 2" maxlength="200" /></div> <div><input type="text" name="pollOption-2" class="pollOption" placeholder="Option 2" maxlength="200" /></div>
</div> </div>
<hr> <hr>
<input type="checkbox" name="pollAllowMultipleChoices" />Allow multiple choices<br /> <input type="checkbox" name="multi_choice" />Allow multiple choices<br />
<input type="checkbox" name="pollAllowUserOptions" />Allow user-created options<br /> <input type="checkbox" name="allow_writein" />Allow user-created options<br />
<input type="submit" name="submit" value="Submit" /> <input type="submit" name="submit" value="Submit" />
</form> </form>
</div> </div>

View File

@ -0,0 +1,20 @@
# Generated by Django 2.1 on 2018-09-03 00:07
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('quest', '0010_auto_20180829_1308'),
]
operations = [
migrations.AlterField(
model_name='pollvote',
name='user',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL),
),
]

View File

@ -121,8 +121,9 @@ class PollVote(models.Model):
option = models.ForeignKey(PollOption, on_delete=models.CASCADE) option = models.ForeignKey(PollOption, on_delete=models.CASCADE)
user = models.ForeignKey( user = models.ForeignKey(
settings.AUTH_USER_MODEL, settings.AUTH_USER_MODEL,
on_delete=models.CASCADE on_delete=models.CASCADE,
) blank=True,
null=True)
ip_address = models.GenericIPAddressField() ip_address = models.GenericIPAddressField()

View File

@ -42,7 +42,6 @@ socket.events['message'] = function(data) {
} }
} }
socket.events['new_post'] = function(data) { socket.events['new_post'] = function(data) {
//deactivate_post();
let qposts = document.getElementById('questPosts'); let qposts = document.getElementById('questPosts');
let post_str = '<div class="questPost '; let post_str = '<div class="questPost ';
if (data.post_type === 'text') { if (data.post_type === 'text') {
@ -61,10 +60,9 @@ socket.events['new_post'] = function(data) {
} else if (data.post_type === 'poll') { } else if (data.post_type === 'poll') {
post_str += '<h3>' + data.post_text + ' - Open</h3>'; post_str += '<h3>' + data.post_text + ' - Open</h3>';
post_str += '<table class="poll" id="poll-' + data.post_id + '">'; post_str += '<table class="poll" id="poll-' + data.post_id + '">';
post_str += '<col/><col/><col/>'; for (let i = 0; i < data.options.length; i++) {
for (i = 0; i < data.options.length; i++) { post_str += '<tr id="optionRow-' + data.options[i] + '">';
post_str += '<tr id="optionRow-' + data.options[i][0] + '">'; post_str += '<td class="pollCheckBox"><input type="checkbox" id="pollInput-' + data.options[i][0] + '" onchange="vote(' + data.post_id + ',' + data.options[i][0] + ')"/>';
post_str += '<td class="pollCheckBox"><input type="checkbox" id="pollInput-' + data.options[i][0] + '" onchange="pollVote(' + data.post_id + ',' + data.options[i][0] + ')"/>';
post_str += '<label for="pollInput-' + data.options[i][0] + '"></label></td>'; post_str += '<label for="pollInput-' + data.options[i][0] + '"></label></td>';
post_str += '<td class="option_text">' + data.options[i][1] + '</td>'; post_str += '<td class="option_text">' + data.options[i][1] + '</td>';
post_str += '<td class="optionVotes">0</td></tr>'; post_str += '<td class="optionVotes">0</td></tr>';
@ -105,7 +103,7 @@ socket.events['update_post'] = function(data) {
let row = post.children[1].insertRow(-1); let row = post.children[1].insertRow(-1);
let cell = row.insertCell(0); let cell = row.insertCell(0);
cell.className = 'pollCheckBox'; cell.className = 'pollCheckBox';
cell.innerHTML = '<input type="checkbox" id="pollInput-' + data.options_id + '" onchange="pollVote(' + data.post_id + ',' + data.options_id + ')"/>'; cell.innerHTML = '<input type="checkbox" id="pollInput-' + data.options_id + '" onchange="vote(' + data.post_id + ',' + data.options_id + ')"/>';
cell.innerHTML += '<label for="pollInput-' + data.options_id + '"></label>'; cell.innerHTML += '<label for="pollInput-' + data.options_id + '"></label>';
cell = row.insertCell(1); cell = row.insertCell(1);
@ -117,6 +115,31 @@ socket.events['update_post'] = function(data) {
cell.innerHTML = "0"; cell.innerHTML = "0";
} }
} }
socket.events['vote'] = function(data) {
let row = document.getElementById('optionRow-' + data.option_id);
if (data.polarity) {
row.cells[2].textContent = Number(row.cells[2].textContent) + 1;
} else {
row.cells[2].textContent = Number(row.cells[2].textContent) - 1;
}
let table = row.parentElement.parentElement;
arr = Array.prototype.slice.call(table.rows);
arr.sort(sort_by_votes);
let new_tbody = document.createElement('tbody');
for (let i = 0; i < arr.length; i++) {
new_tbody.appendChild(arr[i]);
}
table.replaceChild(new_tbody, table.children[0]);
}
socket.events['set_option_box'] = function(data) {
document.getElementById('pollInput-' + data.option_id).checked = data.polarity;
}
/* Websocket send */
function vote(post_id, option_id) {
let polarity = document.getElementById('pollInput-' + option_id).checked;
socket.send('vote', {post_id: post_id, option_id: option_id, polarity: polarity, quest_id: quest_id});
}
/* Helpers */ /* Helpers */
function padToTwo(number) { function padToTwo(number) {
@ -129,6 +152,9 @@ function strftime(date) {
date_str += padToTwo(date.getHours()) + ':' + padToTwo(date.getMinutes()) + ':' + padToTwo(date.getSeconds()); date_str += padToTwo(date.getHours()) + ':' + padToTwo(date.getMinutes()) + ':' + padToTwo(date.getSeconds());
return date_str; return date_str;
} }
function sort_by_votes(a, b) {
return b.cells[2].textContent.localeCompare(a.cells[2].textContent);
}
/* DOM editing */ /* DOM editing */
function quote(message_id) { function quote(message_id) {
@ -136,7 +162,7 @@ function quote(message_id) {
textbox.value += '>>' + message_id + '\n'; textbox.value += '>>' + message_id + '\n';
textbox.focus(); textbox.focus();
} }
function showPreview(event, message_id) { function show_preview(event, message_id) {
let elem = document.getElementById('msg-' + message_id); let elem = document.getElementById('msg-' + message_id);
if (!elem) { return; } if (!elem) { return; }
let preview = document.getElementById('preview'); let preview = document.getElementById('preview');
@ -149,11 +175,11 @@ function showPreview(event, message_id) {
preview.style.left = x; preview.style.left = x;
preview.style.maxWidth = maxWidth; preview.style.maxWidth = maxWidth;
} }
function clearPreview() { function clear_preview() {
document.getElementById('preview').innerHTML = ''; document.getElementById('preview').innerHTML = '';
document.getElementById('preview').style.display = 'none'; document.getElementById('preview').style.display = 'none';
} }
function scrollToMsg(message_id) { function scroll_to_msg(message_id) {
let elem = document.getElementById('msg-' + message_id); let elem = document.getElementById('msg-' + message_id);
if (!elem) { return; } if (!elem) { return; }
elem.scrollIntoView(); elem.scrollIntoView();

View File

@ -1,6 +1,5 @@
/* Websocket receive */ /* Websocket receive */
socket.events['new_post'] = function(data) { socket.events['new_post'] = function(data) {
//deactivate_post();
let qposts = document.getElementById('questPosts'); let qposts = document.getElementById('questPosts');
let post_str = '<div class="questPost '; let post_str = '<div class="questPost ';
if (data.post_type === 'text') { if (data.post_type === 'text') {
@ -28,10 +27,9 @@ socket.events['new_post'] = function(data) {
} else if (data.post_type === 'poll') { } else if (data.post_type === 'poll') {
post_str += '<h3>' + data.post_text + ' - Open</h3>'; post_str += '<h3>' + data.post_text + ' - Open</h3>';
post_str += '<table class="poll" id="poll-' + data.post_id + '">'; post_str += '<table class="poll" id="poll-' + data.post_id + '">';
post_str += '<col/><col/><col/>';
for (i = 0; i < data.options.length; i++) { for (i = 0; i < data.options.length; i++) {
post_str += '<tr id="optionRow-' + data.options[i][0] + '">'; post_str += '<tr id="optionRow-' + data.options[i][0] + '">';
post_str += '<td class="pollCheckBox"><input type="checkbox" id="pollInput-' + data.options[i][0] + '" onchange="pollVote(' + data.post_id + ',' + data.options[i][0] + ')"/>'; post_str += '<td class="pollCheckBox"><input type="checkbox" id="pollInput-' + data.options[i][0] + '" onchange="vote(' + data.post_id + ',' + data.options[i][0] + ')"/>';
post_str += '<label for="pollInput-' + data.options[i][0] + '"></label></td>'; post_str += '<label for="pollInput-' + data.options[i][0] + '"></label></td>';
post_str += '<td class="option_text">' + data.options[i][1] + '</td>'; post_str += '<td class="option_text">' + data.options[i][1] + '</td>';
post_str += '<td class="optionVotes">0</td></tr>'; post_str += '<td class="optionVotes">0</td></tr>';

View File

@ -25,5 +25,6 @@ def quest(request, quest_id, page_num=1):
dice_rolls = DiceRoll.objects.filter(dicecall__post__quest__id=quest_id) dice_rolls = DiceRoll.objects.filter(dicecall__post__quest__id=quest_id)
poll_options = PollOption.objects.filter(poll__post__quest__id=quest_id) poll_options = PollOption.objects.filter(poll__post__quest__id=quest_id)
poll_votes = PollVote.objects.filter(option__poll__post__quest__id=quest_id) poll_votes = PollVote.objects.filter(option__poll__post__quest__id=quest_id)
ip_address = request.META['REMOTE_ADDR']
context = locals() context = locals()
return render(request, 'quest/quest.html', context) return render(request, 'quest/quest.html', context)