Compare commits
4 Commits
8513cdd86e
...
a485a36897
Author | SHA1 | Date | |
---|---|---|---|
a485a36897 | |||
738591b05e | |||
b5f8f69f69 | |||
1a21c882a2 |
|
@ -65,6 +65,14 @@ class QuestConsumer(WebsocketConsumer):
|
||||||
'message': data
|
'message': data
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
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):
|
||||||
"""
|
"""
|
||||||
|
|
103
quest/events.py
103
quest/events.py
|
@ -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
|
||||||
|
@ -12,8 +13,8 @@ import random
|
||||||
import bleach
|
import bleach
|
||||||
from django.utils.timezone import localtime
|
from django.utils.timezone import localtime
|
||||||
|
|
||||||
from quest.models import Message, Quest, Post, DiceCall, DiceRoll
|
from quest.models import *
|
||||||
from quest.forms import DiceCallForm
|
from quest.forms import DiceCallForm, PollForm
|
||||||
|
|
||||||
def message(socket, data):
|
def message(socket, data):
|
||||||
"""
|
"""
|
||||||
|
@ -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
|
||||||
|
@ -209,10 +210,53 @@ def dice_post(socket, data):
|
||||||
dc.save()
|
dc.save()
|
||||||
|
|
||||||
data = {}
|
data = {}
|
||||||
data['post_text'] = post_text
|
|
||||||
data['post_type'] = 'dice'
|
|
||||||
data['date'] = localtime(p.timestamp).strftime('%Y-%m-%d %H:%M')
|
|
||||||
data['post_id'] = p.id
|
data['post_id'] = p.id
|
||||||
|
data['post_type'] = 'dice'
|
||||||
|
data['post_text'] = post_text
|
||||||
|
data['date'] = localtime(p.timestamp).strftime('%Y-%m-%d %H:%M')
|
||||||
|
socket.send('new_post', data)
|
||||||
|
|
||||||
|
|
||||||
|
def poll_post(socket, data):
|
||||||
|
"""
|
||||||
|
Called when the QM makes a new dice post.
|
||||||
|
"""
|
||||||
|
quest_id = data.get('quest_id')
|
||||||
|
page_num = data.get('page_num')
|
||||||
|
form = PollForm(data)
|
||||||
|
if not form.is_valid():
|
||||||
|
return # error message?
|
||||||
|
form = form.cleaned_data
|
||||||
|
|
||||||
|
p = Post(
|
||||||
|
quest=Quest.objects.get(id=quest_id),
|
||||||
|
page_num=page_num,
|
||||||
|
post_type='poll',
|
||||||
|
post_text="Poll"
|
||||||
|
)
|
||||||
|
p.save()
|
||||||
|
pl = Poll(
|
||||||
|
post=p,
|
||||||
|
multi_choice=form.pop('multi_choice'),
|
||||||
|
allow_writein=form.pop('allow_writein'),
|
||||||
|
open=True
|
||||||
|
)
|
||||||
|
pl.save()
|
||||||
|
options = []
|
||||||
|
for key, option in form.items():
|
||||||
|
o = PollOption(
|
||||||
|
poll=pl,
|
||||||
|
text=option
|
||||||
|
)
|
||||||
|
o.save()
|
||||||
|
options.append(o)
|
||||||
|
|
||||||
|
data = {}
|
||||||
|
data['post_type'] = 'poll'
|
||||||
|
data['post_id'] = p.id
|
||||||
|
data['post_text'] = "Poll"
|
||||||
|
data['date'] = localtime(p.timestamp).strftime('%Y-%m-%d %H:%M')
|
||||||
|
data['options'] = [(o.id, o.text) for o in options]
|
||||||
socket.send('new_post', data)
|
socket.send('new_post', data)
|
||||||
|
|
||||||
|
|
||||||
|
@ -262,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:
|
||||||
|
|
|
@ -17,3 +17,22 @@ class DiceCallForm(forms.Form):
|
||||||
diceRollsTaken = forms.IntegerField(
|
diceRollsTaken = forms.IntegerField(
|
||||||
min_value=1, max_value=99, required=False)
|
min_value=1, max_value=99, required=False)
|
||||||
diceStrict = forms.BooleanField(required=False)
|
diceStrict = forms.BooleanField(required=False)
|
||||||
|
|
||||||
|
|
||||||
|
class PollForm(forms.Form):
|
||||||
|
"""
|
||||||
|
The form for the QM making new polls.
|
||||||
|
"""
|
||||||
|
multi_choice = forms.BooleanField(required=False)
|
||||||
|
allow_writein = forms.BooleanField(required=False)
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super(PollForm, self).__init__(*args, **kwargs)
|
||||||
|
data = args[0] # it's not nice to assume
|
||||||
|
options = {k: v for k, v in data.items() if k.startswith('pollOption')}
|
||||||
|
if len(options) > 20:
|
||||||
|
return
|
||||||
|
for key, value in options.items():
|
||||||
|
if not value:
|
||||||
|
continue
|
||||||
|
self.fields[key] = forms.CharField(max_length=200)
|
||||||
|
|
|
@ -68,16 +68,16 @@
|
||||||
{% 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 options.get(post.id, []) %}
|
{% for option in poll_options.filter(poll=post.poll).order_by("id") %}
|
||||||
<tr id="optionRow-{{ option[0] }}">
|
<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[0] }}" onchange="pollVote({{ post.id }}, {{ option[0] }})"{% if post.id != quest.open_post_id %} 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[0] }}"></label>
|
<label for="pollInput-{{ option.id }}"></label>
|
||||||
</td>
|
</td>
|
||||||
<td class="option_text">{{ option[2] }}</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>
|
||||||
{# if post.id == quest.open_post_id and post.id|is_write_in %}
|
{# if post.id == quest.open_post_id and post.id|is_write_in %}
|
||||||
<div id="writeinContainer">
|
<div id="writeinContainer">
|
||||||
|
@ -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>
|
||||||
|
|
47
quest/migrations/0010_auto_20180829_1308.py
Normal file
47
quest/migrations/0010_auto_20180829_1308.py
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
# Generated by Django 2.1 on 2018-08-29 17:08
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||||
|
('quest', '0009_diceroll'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Poll',
|
||||||
|
fields=[
|
||||||
|
('post', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, primary_key=True, serialize=False, to='quest.Post')),
|
||||||
|
('multi_choice', models.BooleanField()),
|
||||||
|
('allow_writein', models.BooleanField()),
|
||||||
|
('open', models.BooleanField()),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='PollOption',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('text', models.CharField(max_length=200)),
|
||||||
|
('poll', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='quest.Poll')),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='PollVote',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('ip_address', models.GenericIPAddressField()),
|
||||||
|
('option', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='quest.PollOption')),
|
||||||
|
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='dicecall',
|
||||||
|
name='open',
|
||||||
|
field=models.BooleanField(),
|
||||||
|
),
|
||||||
|
]
|
20
quest/migrations/0011_auto_20180902_2007.py
Normal file
20
quest/migrations/0011_auto_20180902_2007.py
Normal 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),
|
||||||
|
),
|
||||||
|
]
|
|
@ -73,7 +73,7 @@ class DiceCall(models.Model):
|
||||||
MinValueValidator(1)
|
MinValueValidator(1)
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
open = models.BooleanField(default=False)
|
open = models.BooleanField()
|
||||||
|
|
||||||
|
|
||||||
class DiceRoll(models.Model):
|
class DiceRoll(models.Model):
|
||||||
|
@ -91,6 +91,42 @@ class DiceRoll(models.Model):
|
||||||
total = models.IntegerField()
|
total = models.IntegerField()
|
||||||
|
|
||||||
|
|
||||||
|
class Poll(models.Model):
|
||||||
|
"""
|
||||||
|
An object representing polls made by the QM.
|
||||||
|
"""
|
||||||
|
post = models.OneToOneField(
|
||||||
|
Post,
|
||||||
|
on_delete=models.CASCADE,
|
||||||
|
primary_key=True,
|
||||||
|
)
|
||||||
|
multi_choice = models.BooleanField()
|
||||||
|
allow_writein = models.BooleanField()
|
||||||
|
open = models.BooleanField()
|
||||||
|
|
||||||
|
|
||||||
|
class PollOption(models.Model):
|
||||||
|
"""
|
||||||
|
Represents options (choices) attached to a given poll. These can be
|
||||||
|
added by the QM upon creation, or by users through write-ins.
|
||||||
|
"""
|
||||||
|
poll = models.ForeignKey(Poll, on_delete=models.CASCADE)
|
||||||
|
text = models.CharField(max_length=200)
|
||||||
|
|
||||||
|
|
||||||
|
class PollVote(models.Model):
|
||||||
|
"""
|
||||||
|
Represents a user voting for an option.
|
||||||
|
"""
|
||||||
|
option = models.ForeignKey(PollOption, on_delete=models.CASCADE)
|
||||||
|
user = models.ForeignKey(
|
||||||
|
settings.AUTH_USER_MODEL,
|
||||||
|
on_delete=models.CASCADE,
|
||||||
|
blank=True,
|
||||||
|
null=True)
|
||||||
|
ip_address = models.GenericIPAddressField()
|
||||||
|
|
||||||
|
|
||||||
class PageTitle(models.Model):
|
class PageTitle(models.Model):
|
||||||
"""
|
"""
|
||||||
Represents the title of a quest page.
|
Represents the title of a quest page.
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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>';
|
||||||
|
@ -139,3 +137,15 @@ function open_post(post_id) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
function insertPollOption() {
|
||||||
|
let opts = document.getElementById('pollOptions');
|
||||||
|
let num = opts.children.length+1;
|
||||||
|
let temp = document.createElement('template');
|
||||||
|
temp.innerHTML = '<div><input type="text" name="pollOption-' + num + '" class="pollOption" placeholder="Option ' + num + '" maxlength="200" /></div>';
|
||||||
|
opts.appendChild(temp.content);
|
||||||
|
}
|
||||||
|
function removePollOption() {
|
||||||
|
let opts = document.getElementById('pollOptions');
|
||||||
|
if (opts.children.length == 0) { return; }
|
||||||
|
opts.children[opts.children.length-1].outerHTML = '';
|
||||||
|
}
|
||||||
|
|
|
@ -5,7 +5,7 @@ Quest and quest accessory views.
|
||||||
from django.shortcuts import render
|
from django.shortcuts import render
|
||||||
from django.http import HttpResponse
|
from django.http import HttpResponse
|
||||||
|
|
||||||
from .models import Quest, DiceRoll
|
from .models import Quest, DiceRoll, PollOption, PollVote
|
||||||
|
|
||||||
def index(request):
|
def index(request):
|
||||||
"""
|
"""
|
||||||
|
@ -23,5 +23,8 @@ def quest(request, quest_id, page_num=1):
|
||||||
messages = quest.message_set.all()
|
messages = quest.message_set.all()
|
||||||
posts = quest.post_set.all()
|
posts = quest.post_set.all()
|
||||||
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_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)
|
||||||
|
|
35
real_ip.conf
Normal file
35
real_ip.conf
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
# This is an example nginx configuration file for setting the real IP from
|
||||||
|
# CloudFlare for the app to use. Nginx must be built with the
|
||||||
|
# `http_realip_module` module enabled. The virtual host/server block
|
||||||
|
# serving the site should contain the line
|
||||||
|
# `proxy_set_header X-Real-IP $http_cf_connecting_ip;`.
|
||||||
|
# The list of IP prefixes should be updated periodically from
|
||||||
|
# `https://www.cloudflare.com/ips/`. See `titivillus/middleware.py` for how
|
||||||
|
# to use the new header in application.
|
||||||
|
|
||||||
|
# Setting the real IP from CloudFlare
|
||||||
|
set_real_ip_from 103.21.244.0/22;
|
||||||
|
set_real_ip_from 103.22.200.0/22;
|
||||||
|
set_real_ip_from 103.31.4.0/22;
|
||||||
|
set_real_ip_from 104.16.0.0/12;
|
||||||
|
set_real_ip_from 108.162.192.0/18;
|
||||||
|
set_real_ip_from 131.0.72.0/22;
|
||||||
|
set_real_ip_from 141.101.64.0/18;
|
||||||
|
set_real_ip_from 162.158.0.0/15;
|
||||||
|
set_real_ip_from 172.64.0.0/13;
|
||||||
|
set_real_ip_from 173.245.48.0/20;
|
||||||
|
set_real_ip_from 188.114.96.0/20;
|
||||||
|
set_real_ip_from 190.93.240.0/20;
|
||||||
|
set_real_ip_from 197.234.240.0/22;
|
||||||
|
set_real_ip_from 198.41.128.0/17;
|
||||||
|
set_real_ip_from 2400:cb00::/32;
|
||||||
|
set_real_ip_from 2606:4700::/32;
|
||||||
|
set_real_ip_from 2803:f800::/32;
|
||||||
|
set_real_ip_from 2405:b500::/32;
|
||||||
|
set_real_ip_from 2405:8100::/32;
|
||||||
|
set_real_ip_from 2c0f:f248::/32;
|
||||||
|
set_real_ip_from 2a06:98c0::/29;
|
||||||
|
|
||||||
|
# use any of the following two
|
||||||
|
real_ip_header CF-Connecting-IP;
|
||||||
|
#real_ip_header X-Forwarded-For;
|
18
titivillus/asgi_middleware.py
Normal file
18
titivillus/asgi_middleware.py
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Some custom asgi middleware for titivillus.
|
||||||
|
"""
|
||||||
|
from channels.auth import AuthMiddlewareStack
|
||||||
|
|
||||||
|
class XRealIPMiddleware(object):
|
||||||
|
"""
|
||||||
|
Attempts to set the get the visitor's real IP by grabbing the
|
||||||
|
`X-Real-IP` header from the request.
|
||||||
|
"""
|
||||||
|
def __init__(self, inner):
|
||||||
|
self.inner = inner
|
||||||
|
def __call__(self, scope):
|
||||||
|
headers = dict(scope['headers'])
|
||||||
|
if b'x-real-ip' in headers:
|
||||||
|
scope['client'][0] = headers[b'x-real-ip'].decode('utf-8')
|
||||||
|
return self.inner(scope)
|
20
titivillus/middleware.py
Normal file
20
titivillus/middleware.py
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Some custom middleware for titivillus.
|
||||||
|
"""
|
||||||
|
|
||||||
|
class XRealIPMiddleware(object):
|
||||||
|
"""
|
||||||
|
Attempts to set the get the visitor's real IP by grabbing the
|
||||||
|
`X-Real-IP` header from the request.
|
||||||
|
"""
|
||||||
|
def __init__(self, get_response):
|
||||||
|
self.get_response = get_response
|
||||||
|
def __call__(self, request):
|
||||||
|
try:
|
||||||
|
real_ip = request.META['HTTP_X_REAL_IP']
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
request.META['REMOTE_ADDR'] = real_ip
|
||||||
|
return self.get_response(request)
|
|
@ -6,9 +6,14 @@ from channels.auth import AuthMiddlewareStack
|
||||||
from channels.routing import ProtocolTypeRouter, URLRouter
|
from channels.routing import ProtocolTypeRouter, URLRouter
|
||||||
|
|
||||||
import quest.routing
|
import quest.routing
|
||||||
|
from .asgi_middleware import XRealIPMiddleware
|
||||||
|
|
||||||
|
CustomMiddlewareStack = lambda inner: XRealIPMiddleware(
|
||||||
|
AuthMiddlewareStack(inner)
|
||||||
|
)
|
||||||
|
|
||||||
application = ProtocolTypeRouter({
|
application = ProtocolTypeRouter({
|
||||||
'websocket': AuthMiddlewareStack(
|
'websocket': CustomMiddlewareStack(
|
||||||
URLRouter(
|
URLRouter(
|
||||||
quest.routing.websocket_urlpatterns
|
quest.routing.websocket_urlpatterns
|
||||||
)
|
)
|
||||||
|
|
|
@ -48,6 +48,7 @@ MIDDLEWARE = [
|
||||||
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
||||||
'django.contrib.messages.middleware.MessageMiddleware',
|
'django.contrib.messages.middleware.MessageMiddleware',
|
||||||
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
||||||
|
'titivillus.middleware.XRealIPMiddleware',
|
||||||
]
|
]
|
||||||
|
|
||||||
ROOT_URLCONF = 'titivillus.urls'
|
ROOT_URLCONF = 'titivillus.urls'
|
||||||
|
|
Loading…
Reference in New Issue
Block a user