add to poll table, custom checkboxes

This commit is contained in:
iou1name 2018-07-13 08:17:29 -04:00
parent 0d2f7d0edc
commit 75fdbd6bd7
7 changed files with 247 additions and 88 deletions

View File

@ -2,6 +2,7 @@
""" """
A Flask-based questing platform. A Flask-based questing platform.
""" """
#TODO: look into how cloudflare proxying interacts with nginx
import os import os
import time import time
@ -9,6 +10,7 @@ from flask import Flask
from flask_paranoid import Paranoid from flask_paranoid import Paranoid
from flask_session import Session from flask_session import Session
from flask_socketio import SocketIO from flask_socketio import SocketIO
from werkzeug.contrib.fixers import ProxyFix
import database as db import database as db
from views import views from views import views
@ -51,6 +53,7 @@ class ReverseProxied(object):
app = Flask(__name__) app = Flask(__name__)
app.wsgi_app = ReverseProxied(app.wsgi_app) app.wsgi_app = ReverseProxied(app.wsgi_app)
app.wsgi_app = ProxyFix(app.wsgi_app)
app.register_blueprint(views) app.register_blueprint(views)
app.config['MAX_CONTENT_LENGTH'] = 16 * 1024 * 1024 app.config['MAX_CONTENT_LENGTH'] = 16 * 1024 * 1024
app.config['SESSION_TYPE'] = 'filesystem' app.config['SESSION_TYPE'] = 'filesystem'
@ -96,6 +99,38 @@ def get_dice_challenge(post_id):
return db.get_dice_call(post_id)[3] return db.get_dice_call(post_id)[3]
@app.template_filter("num_votes")
def num_votes(option_id):
"""
Returns the number of IPs who voted for this option.
"""
return db.get_num_votes(option_id)
@app.template_filter("split_options")
def split_options(options):
"""
Splits a polls options into a list.
"""
return options.split("\n")
@app.template_filter("get_rolls")
def get_rolls(post_id):
"""
Gets the dice rolls for the template.
"""
return db.get_dice_rolls(post_id=post_id)
@app.template_filter("get_options")
def get_options(post_id):
"""
Gets the poll options for the template.
"""
return db.get_poll_options(post_id=post_id)
@app.after_request @app.after_request
def minify(res): def minify(res):
""" """

View File

@ -6,57 +6,6 @@ CREATE TABLE `users` (
PRIMARY KEY (`user_id`) PRIMARY KEY (`user_id`)
) ENGINE=InnoDB CHARSET utf8mb4; ) ENGINE=InnoDB CHARSET utf8mb4;
CREATE TABLE `quest_meta` (
`quest_id` SMALLINT UNSIGNED NOT NULL AUTO_INCREMENT,
`canon_title` VARCHAR(300) DEFAULT NULL,
`ident_title` VARCHAR(300) DEFAULT NULL,
`owner_id` SMALLINT UNSIGNED DEFAULT NULL,
`open_post_id` SMALLINT UNSIGNED DEFAULT NULL,
PRIMARY KEY (`quest_id`)
) ENGINE=InnoDB CHARSET utf8mb4;
CREATE TABLE `quest_data` (
`post_id` MEDIUMINT UNSIGNED NOT NULL AUTO_INCREMENT,
`quest_id` SMALLINT UNSIGNED NOT NULL,
`post_type` ENUM('text', 'dice', 'poll') NOT NULL,
`post` MEDIUMTEXT NOT NULL,
`timestamp` INT UNSIGNED NOT NULL,
PRIMARY KEY (`post_id`)
) ENGINE=InnoDB CHARSET utf8mb4;
CREATE TABLE `dice_calls` (
`post_id` MEDIUMINT UNSIGNED NOT NULL,
`dice_roll` TEXT NOT NULL,
`strict` BOOLEAN DEFAULT FALSE,
`dice_challenge` SMALLINT UNSIGNED,
`rolls_taken` TINYINT UNSIGNED,
PRIMARY KEY (`post_id`)
) ENGINE=InnoDB CHARSET utf8mb4;
CREATE TABLE `dice_rolls` (
`message_id` MEDIUMINT UNSIGNED NOT NULL,
`quest_id` SMALLINT UNSIGNED NOT NULL,
`post_id` MEDIUMINT UNSIGNED NOT NULL,
`roll_dice` TEXT NOT NULL,
`roll_results` TEXT NOT NULL,
`roll_total` SMALLINT UNSIGNED NOT NULL,
PRIMARY KEY (`message_id`)
) ENGINE=InnoDB CHARSET utf8mb4;
CREATE TABLE `polls` (
`post_id` MEDIUMINT UNSIGNED NOT NULL,
`multi_choice` BOOLEAN DEFAULT FALSE,
`allow_writein` BOOLEAN DEFAULT FALSE,
PRIMARY KEY (`post_id`)
) ENGINE=InnoDB CHARSET utf8mb4;
CREATE TABLE `poll_options` (
`post_id` MEDIUMINT UNSIGNED NOT NULL,
`quest_id` SMALLINT UNSIGNED NOT NULL,
`option_text` VARCHAR(200) NOT NULL,
`ips_voted` TEXT DEFAULT NULL
) ENGINE=InnoDB CHARSET utf8mb4;
CREATE TABLE `chat_messages` ( CREATE TABLE `chat_messages` (
`message_id` MEDIUMINT UNSIGNED NOT NULL AUTO_INCREMENT, `message_id` MEDIUMINT UNSIGNED NOT NULL AUTO_INCREMENT,
`room_id` MEDIUMINT UNSIGNED NOT NULL, `room_id` MEDIUMINT UNSIGNED NOT NULL,
@ -65,3 +14,66 @@ CREATE TABLE `chat_messages` (
`message` TEXT NOT NULL, `message` TEXT NOT NULL,
PRIMARY KEY (`message_id`) PRIMARY KEY (`message_id`)
) ENGINE=InnoDB CHARSET utf8mb4; ) ENGINE=InnoDB CHARSET utf8mb4;
CREATE TABLE `quest_meta` (
`quest_id` SMALLINT UNSIGNED NOT NULL AUTO_INCREMENT,
`canon_title` VARCHAR(300) DEFAULT NULL,
`ident_title` VARCHAR(300) DEFAULT NULL,
`owner_id` SMALLINT UNSIGNED DEFAULT NULL,
`open_post_id` SMALLINT UNSIGNED DEFAULT NULL,
PRIMARY KEY (`quest_id`),
FOREIGN KEY (`owner_id`) REFERENCES `users`(`user_id`)
) ENGINE=InnoDB CHARSET utf8mb4;
CREATE TABLE `quest_data` (
`post_id` MEDIUMINT UNSIGNED NOT NULL AUTO_INCREMENT,
`quest_id` SMALLINT UNSIGNED NOT NULL,
`post_type` ENUM('text', 'dice', 'poll') NOT NULL,
`post` MEDIUMTEXT NOT NULL,
`timestamp` INT UNSIGNED NOT NULL,
PRIMARY KEY (`post_id`),
FOREIGN KEY (`quest_id`) REFERENCES `quest_meta`(`quest_id`)
) ENGINE=InnoDB CHARSET utf8mb4;
CREATE TABLE `dice_calls` (
`post_id` MEDIUMINT UNSIGNED NOT NULL,
`dice_roll` TEXT NOT NULL,
`strict` BOOLEAN DEFAULT FALSE,
`dice_challenge` SMALLINT UNSIGNED,
`rolls_taken` TINYINT UNSIGNED,
FOREIGN KEY (`post_id`) REFERENCES `quest_data`(`post_id`)
) ENGINE=InnoDB CHARSET utf8mb4;
CREATE TABLE `dice_rolls` (
`message_id` MEDIUMINT UNSIGNED NOT NULL,
`quest_id` SMALLINT UNSIGNED NOT NULL,
`post_id` MEDIUMINT UNSIGNED NOT NULL,
`roll_dice` TEXT NOT NULL,
`roll_results` TEXT NOT NULL,
`roll_total` SMALLINT UNSIGNED NOT NULL,
FOREIGN KEY (`message_id`) REFERENCES `chat_messages`(`message_id`),
FOREIGN KEY (`post_id`) REFERENCES `dice_calls`(`post_id`)
) ENGINE=InnoDB CHARSET utf8mb4;
CREATE TABLE `polls` (
`post_id` MEDIUMINT UNSIGNED NOT NULL,
`quest_id` SMALLINT UNSIGNED NOT NULL,
`multi_choice` BOOLEAN DEFAULT FALSE,
`allow_writein` BOOLEAN DEFAULT FALSE,
FOREIGN KEY (`post_id`) REFERENCES `quest_data`(`post_id`),
FOREIGN KEY (`quest_id`) REFERENCES `quest_meta`(`quest_id`)
) ENGINE=InnoDB CHARSET utf8mb4;
CREATE TABLE `poll_options` (
`option_id` MEDIUMINT UNSIGNED NOT NULL AUTO_INCREMENT,
`post_id` MEDIUMINT UNSIGNED NOT NULL,
`option_text` VARCHAR(200) NOT NULL,
PRIMARY KEY (`option_id`),
FOREIGN KEY (`post_id`) REFERENCES `polls`(`post_id`)
) ENGINE=InnoDB CHARSET utf8mb4;
CREATE TABLE `poll_votes` (
`option_id` MEDIUMINT UNSIGNED NOT NULL,
`ip_address` VARCHAR(32) NOT NULL,
FOREIGN KEY (`option_id`) REFERENCES `poll_options`(`option_id`)
) ENGINE=InnoDB CHARSET utf8mb4;

View File

@ -272,28 +272,35 @@ def get_dice_rolls(quest_id=None, post_id=None):
return data return data
def insert_poll(post_id, multi_choice, allow_writein): def insert_poll(post_id, quest_id, multi_choice, allow_writein):
"""Inserts a new poll post.""" """Inserts a new poll post."""
_DB.execute( _DB.execute(
"INSERT INTO `polls` " \ "INSERT INTO `polls` " \
+ "(`post_id`, `multi_choice`, `allow_writein`) " \ + "(`post_id`, `quest_id`, `multi_choice`, `allow_writein`) " \
+ "VALUES (%s, %s, %s)", + "VALUES (%s, %s, %s, %s)",
(post_id, multi_choice, allow_writein)) (post_id, quest_id, multi_choice, allow_writein))
def get_polls(quest_id):
"""Gets poll information."""
data = _DB.execute(
"SELECT * FROM `polls` WHERE `quest_id` = %s", (quest_id,)).fetchall()
return data
def get_poll(post_id): def get_poll(post_id):
"""Gets poll information.""" """Gets poll information."""
data = _DB.excute( data = _DB.execute(
"SELECT * FROM `polls` WHERE `post_id` = %s", (post_id,)) "SELECT * FROM `polls` WHERE `post_id` = %s", (post_id,)).fetchall()
return data return data
def insert_poll_option(post_id, quest_id, option_text): def insert_poll_option(post_id, option_text):
"""Insert a new poll option. ips_voted will be NULL to start.""" """Insert a new poll option. ips_voted will be NULL to start."""
_DB.execute( _DB.execute(
"INSERT INTO `poll_options` " \ "INSERT INTO `poll_options` " \
+ "(`post_id`, `quest_id`, `option_text`) VALUES (%s, %s, %s)", + "(`post_id`, `option_text`) VALUES (%s, %s)",
(post_id, quest_id, option_text)) (post_id, option_text))
def get_poll_options(quest_id=None, post_id=None): def get_poll_options(quest_id=None, post_id=None):
@ -307,5 +314,28 @@ def get_poll_options(quest_id=None, post_id=None):
ins = (post_id,) ins = (post_id,)
else: else:
return return
data = _DB.execute(sql, ins) data = _DB.execute(sql, ins).fetchall()
return data
def insert_poll_vote(option_id, ip):
"""Inserts a new vote for a poll option."""
res = _DB.execute(
"INSERT ")
def insert_poll_vote(option_id, ip):
"""Removes a vote from a poll."""
pass
def get_poll_votes(quest_id):
pass
def get_num_votes(option_id):
"""Returns the number of votes for an option."""
data = _DB.execute(
"SELECT COUNT(`ip_address`) FROM `poll_votes` WHERE `option_id` = %s",
(option_id,)).fetchone()[0]
return data return data

View File

@ -9,7 +9,7 @@ import random
import functools import functools
import bleach import bleach
from flask import session from flask import session, request
from flask_socketio import SocketIO, emit, join_room from flask_socketio import SocketIO, emit, join_room
import tools import tools
@ -101,7 +101,7 @@ def message(data):
db.insert_quest_roll(message_id, room, dice_call_id, roll_data) db.insert_quest_roll(message_id, room, dice_call_id, roll_data)
if len(db.get_dice_rolls(post_id=dice_call_id)) == dice_call[4]: if len(db.get_dice_rolls(post_id=dice_call_id)) == dice_call[4]:
db.set_dice_call_closed(room) db.set_post_closed(room)
emit("close_dice_call", {"post_id": dice_call_id}, room=room) emit("close_dice_call", {"post_id": dice_call_id}, room=room)
room = data["room"] room = data["room"]
@ -277,7 +277,7 @@ def open_dice_call(data):
@qm_only() @qm_only()
def poll_post(data): def poll_post(data):
""" """
Called when the QM posts a new dice call. Called when the QM posts a new poll.
""" """
room = data.pop("room", None) room = data.pop("room", None)
multi_choice = bool(data.pop("pollAllowMultipleChoices", None)) multi_choice = bool(data.pop("pollAllowMultipleChoices", None))
@ -288,15 +288,16 @@ def poll_post(data):
continue continue
if len(value) >= 200: if len(value) >= 200:
return return
value = bleach.clean(value).replace("\n", "")
options.append(value) options.append(value)
post = "Poll" post = "Poll"
date = int(time.time()) date = int(time.time())
post_id = db.insert_quest_post(room, "poll", post, date) post_id = db.insert_quest_post(room, "poll", post, date)
db.insert_poll(post_id, multi_choice, allow_writein) db.insert_poll(post_id, room, multi_choice, allow_writein)
for option in options: for option in options:
db.insert_poll_option(post_id, room, option) db.insert_poll_option(post_id, option)
db.set_post_open(post_id, room) db.set_post_open(post_id, room)
data = {} data = {}
@ -306,3 +307,14 @@ def poll_post(data):
data["date"] = int(time.time()) data["date"] = int(time.time())
data["options"] = options data["options"] = options
emit("new_post", data, room=room) emit("new_post", data, room=room)
@socketio.on("vote")
def vote(data):
"""
Called when a user changes their vote on a poll.
"""
print(data)
room = data.get("room")
option_id = data.get("option_id")
polarity = data.get("polarity")

View File

@ -37,8 +37,8 @@ h3 {
} }
#questPane { #questPane {
padding-left: 10px; padding-left: 5%;
padding-right: 32%; padding-right: 35%;
min-width: 0; min-width: 0;
} }
@ -54,6 +54,7 @@ h3 {
.questPostData { .questPostData {
word-wrap: break-word; word-wrap: break-word;
min-width: 0; min-width: 0;
width: 100%;
} }
#QMPostPane { #QMPostPane {
@ -110,6 +111,58 @@ h3 {
margin: 0.1em; margin: 0.1em;
} }
.pollPost {
width: 100%;
}
.poll {
border-collapse: collapse;
width: 100%;
table-layout: fixed;
border: 1px solid #ddd;
}
.optionVotes {
width: 10%;
text-align: center;
}
.poll td {
padding: 0.5em;
word-wrap: break-word;
border-bottom: 1px solid #ddd;
}
.pollCheckBox input {
display: none;
}
.pollCheckBox {
width: 1.5em;
}
.pollCheckBox label {
cursor: pointer;
background: #eee;
border: 1px solid #ddd;
padding: 0.2em;
}
.pollCheckBox label:after {
content: "\2713";
color: #bfbfbf;
opacity: 0.3;
}
.pollCheckBox label:hover::after {
opacity: 0.5;
}
.pollCheckBox input[type=checkbox]:checked + label:after {
opacity: 1;
color: black;
}
#chatPane { #chatPane {
height: 100%; height: 100%;
width: 30%; width: 30%;

View File

@ -75,12 +75,20 @@
post.children[0].textContent = post.children[0].textContent.replace('Open', 'Closed'); post.children[0].textContent = post.children[0].textContent.replace('Open', 'Closed');
document.getElementById('close_post_id-' + data.post_id).style.display = 'none'; document.getElementById('close_post_id-' + data.post_id).style.display = 'none';
document.getElementById('open_post_id-' + data.post_id).style.display = 'initial'; document.getElementById('open_post_id-' + data.post_id).style.display = 'initial';
if (post.parentElement.classList.contains('pollPost')) {
table = document.getElementById('poll-' + data.post_id);
table.getElementsByTagName("col")[0].style.visibility = 'collapse';
}
}); });
socket.on('open_post', function(data) { socket.on('open_post', function(data) {
let post = document.getElementById('questPostData-' + data.post_id); let post = document.getElementById('questPostData-' + data.post_id);
post.firstElementChild.textContent = post.firstElementChild.textContent.replace('Closed', 'Open'); post.firstElementChild.textContent = post.firstElementChild.textContent.replace('Closed', 'Open');
document.getElementById('close_post_id-' + data.post_id).style.display = 'initial'; document.getElementById('close_post_id-' + data.post_id).style.display = 'initial';
document.getElementById('open_post_id-' + data.post_id).style.display = 'none'; document.getElementById('open_post_id-' + data.post_id).style.display = 'none';
if (post.parentElement.classList.contains('pollPost')) {
table = document.getElementById('poll-' + data.post_id);
table.getElementsByTagName("col")[0].style.visibility = '';
}
}); });
let mtarea = document.getElementById('messageTextArea'); let mtarea = document.getElementById('messageTextArea');
mtarea.addEventListener('keypress', function(event) { mtarea.addEventListener('keypress', function(event) {
@ -104,6 +112,9 @@
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 pollVote(option_id) {
socket.emit('vote', {option_id: option_id, polarity: document.getElementById('pollInput-' + option_id).checked, room: {{ room_id }}});
}
</script> </script>
{% if session.get("user_id") == owner_id %} {% if session.get("user_id") == owner_id %}
<script> <script>
@ -158,7 +169,7 @@
let opts = document.getElementById('pollOptions'); let opts = document.getElementById('pollOptions');
let num = opts.children.length+1; let num = opts.children.length+1;
let temp = document.createElement('template'); let temp = document.createElement('template');
temp.innerHTML = '<div><input type="text" name="pollOption-' + num + '" class="pollOption" placeholder="Option ' + num + '" /></div>'; temp.innerHTML = '<div><input type="text" name="pollOption-' + num + '" class="pollOption" placeholder="Option ' + num + '" maxlength="200" /></div>';
opts.appendChild(temp.content); opts.appendChild(temp.content);
} }
function removePollOption() { function removePollOption() {
@ -229,7 +240,7 @@
{% endautoescape %} {% endautoescape %}
{% elif quest_post[2] == "dice" %} {% elif quest_post[2] == "dice" %}
<h3>{{ quest_post[3] }} - {% if quest_post[0] == open_post_id %}Open{% else %}Closed{% endif %}</h3> <h3>{{ quest_post[3] }} - {% if quest_post[0] == open_post_id %}Open{% else %}Closed{% endif %}</h3>
{% for dice_roll in dice_rolls %} {% for dice_roll in quest_post[0]|get_rolls %}
{% if dice_roll[2] == quest_post[0] %} {% if dice_roll[2] == quest_post[0] %}
<div id="questRollId-{{ dice_roll[0] }}"> <div id="questRollId-{{ dice_roll[0] }}">
<b>Rolled {{ dice_roll[4] }} = {{ dice_roll[5] }} ({{ dice_roll[3] }}){% if quest_post[0]|dice_chal != 0 %} - {% if dice_roll[5] >= quest_post[0]|dice_chal %}Pass{% else %} Fail{% endif %}{% endif %}</b> <b>Rolled {{ dice_roll[4] }} = {{ dice_roll[5] }} ({{ dice_roll[3] }}){% if quest_post[0]|dice_chal != 0 %} - {% if dice_roll[5] >= quest_post[0]|dice_chal %}Pass{% else %} Fail{% endif %}{% endif %}</b>
@ -238,11 +249,19 @@
{% endfor %} {% endfor %}
{% elif quest_post[2] == "poll" %} {% elif quest_post[2] == "poll" %}
<h3>{{ quest_post[3] }} - {% if quest_post[0] == open_post_id %}Open{% else %}Closed{% endif %}</h3> <h3>{{ quest_post[3] }} - {% if quest_post[0] == open_post_id %}Open{% else %}Closed{% endif %}</h3>
<table id="poll-{{ quest_post[0] }}"> <table class="poll" id="poll-{{ quest_post[0] }}">
{% for option in poll_options %} <col{% if quest_post[0] != open_post_id %} style="visibility: collapse;"{% endif %}/>
{% if option[0] == quest_post[0] %} <col/>
<col/>
{% for option in options %}
{% if option[1] == quest_post[0] %}
<tr> <tr>
<td>{{ option[2] }}</td> <td class="pollCheckBox">
<input type="checkbox" id="pollInput-{{ option[0] }}" onchange="pollVote({{ option[0] }})"/>
<label for="pollInput-{{ option[0] }}"></label>
</td>
<td class="option_text">{{ option[2] }}</td>
<td class="optionVotes">0</td>
</tr> </tr>
{% endif %} {% endif %}
{% endfor %} {% endfor %}
@ -288,8 +307,8 @@
<a href="javascript:void(0);" id="pollInsertNewOption" onclick="insertPollOption()">[+]</a> <a href="javascript:void(0);" id="pollInsertNewOption" onclick="insertPollOption()">[+]</a>
<a href="javascript:void(0);" onclick="removePollOption()">[-]</a> <a href="javascript:void(0);" onclick="removePollOption()">[-]</a>
<div id="pollOptions"> <div id="pollOptions">
<div><input type="text" name="pollOption-1" class="pollOption" placeholder="Option 1" /></div> <div><input type="text" name="pollOption-1" class="pollOption" placeholder="Option 1" maxlength="200" /></div>
<div><input type="text" name="pollOption-2" class="pollOption" placeholder="Option 2" /></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="pollAllowMultipleChoices" />Allow multiple choices<br />

View File

@ -47,28 +47,25 @@ def quest(quest_title):
""" """
An arbituary quest page. An arbituary quest page.
""" """
then = time.time()
ident_title, _, extra = quest_title.partition("/") ident_title, _, extra = quest_title.partition("/")
data = db.get_quest_meta(ident_title=ident_title) data = db.get_quest_meta(ident_title=ident_title)
if not data: if not data:
abort(404) abort(404)
quest_id = data[0]
quest_id = room_id = data[0]
quest_title = data[1] quest_title = data[1]
owner_id = data[3] owner_id = data[3]
open_post_id = data[4] open_post_id = data[4]
quest_posts = db.get_quest_data(quest_id) quest_posts = db.get_quest_data(quest_id)
dice_rolls = db.get_dice_rolls(quest_id) dice_rolls = db.get_dice_rolls(quest_id)
poll_options = db.get_poll_options(quest_id) options = db.get_poll_options(quest_id=quest_id)
messages = db.get_chat_messages(quest_id) messages = db.get_chat_messages(quest_id)
return render_template('quest.html',
quest_title=quest_title, temp = render_template('quest.html', **locals())
ident_title=ident_title, print(time.time() - then)
quest_posts=quest_posts, return temp
dice_rolls=dice_rolls,
poll_options=poll_options,
owner_id=owner_id,
open_post_id=open_post_id,
room_id=quest_id,
messages=messages)
@views.route("/quest/<path:quest_title>/edit_quest", methods=["GET", "POST"]) @views.route("/quest/<path:quest_title>/edit_quest", methods=["GET", "POST"])
@ -200,4 +197,5 @@ def index():
""" """
The index page. The index page.
""" """
print(request.remote_addr)
return render_template("index.html") return render_template("index.html")