open voting, without tokens
This commit is contained in:
20
abot.py
20
abot.py
@@ -7,7 +7,7 @@ from utils import *
|
|||||||
import manager
|
import manager
|
||||||
|
|
||||||
DATABASE = 'abot.sqlite'
|
DATABASE = 'abot.sqlite'
|
||||||
DEBUG = False
|
DEBUG = True
|
||||||
SECRET_KEY = 'otwet6oi539iosf'
|
SECRET_KEY = 'otwet6oi539iosf'
|
||||||
QUESTIONS = 'questions' # path to questions
|
QUESTIONS = 'questions' # path to questions
|
||||||
|
|
||||||
@@ -51,7 +51,8 @@ def preview(key):
|
|||||||
)
|
)
|
||||||
|
|
||||||
@app.route('/vote/<key>/<token>')
|
@app.route('/vote/<key>/<token>')
|
||||||
def vote(key, token):
|
@app.route('/vote/<key>')
|
||||||
|
def vote(key, token = None):
|
||||||
if not is_key(key):
|
if not is_key(key):
|
||||||
return render_template('blank.html', message = "Unknown key")
|
return render_template('blank.html', message = "Unknown key")
|
||||||
form = parse_form(key)
|
form = parse_form(key)
|
||||||
@@ -61,8 +62,9 @@ def vote(key, token):
|
|||||||
return render_template('blank.html', message = "Not published")
|
return render_template('blank.html', message = "Not published")
|
||||||
if is_expired(form):
|
if is_expired(form):
|
||||||
return render_template('blank.html', message = "Voting has closed")
|
return render_template('blank.html', message = "Voting has closed")
|
||||||
if has_voted(key, token):
|
if is_closed_vote(form):
|
||||||
return render_template('blank.html', message = "Token already used")
|
if has_voted(key, token):
|
||||||
|
return render_template('blank.html', message = "Token already used")
|
||||||
valid_for = time_to_expiry(form)
|
valid_for = time_to_expiry(form)
|
||||||
|
|
||||||
return render_template('vote.html', form = form, key = key, token = token, valid_for = valid_for)
|
return render_template('vote.html', form = form, key = key, token = token, valid_for = valid_for)
|
||||||
@@ -81,13 +83,17 @@ def save_vote():
|
|||||||
return render_template('blank.html', message = "Not published")
|
return render_template('blank.html', message = "Not published")
|
||||||
if is_expired(form):
|
if is_expired(form):
|
||||||
return render_template('blank.html', message = "Voting has closed")
|
return render_template('blank.html', message = "Voting has closed")
|
||||||
if has_voted(key, token):
|
if is_closed_vote(form):
|
||||||
return render_template('blank.html', message = "Token already used")
|
if has_voted(key, token):
|
||||||
|
return render_template('blank.html', message = "Token already used")
|
||||||
create_result_table(key)
|
create_result_table(key)
|
||||||
|
|
||||||
write_vote(key, token, request.form, form) # using request.
|
write_vote(key, token, request.form, form) # using request.
|
||||||
|
summary = ''
|
||||||
|
if is_show_results(form):
|
||||||
|
summary = get_html_summary(key)
|
||||||
|
|
||||||
return render_template('thank_you.html')
|
return render_template('thank_you.html', summary = summary)
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
manager.main(DATABASE, QUESTIONS)
|
manager.main(DATABASE, QUESTIONS)
|
||||||
|
|||||||
44
manager.py
44
manager.py
@@ -135,6 +135,10 @@ def list_question_sets(options):
|
|||||||
|
|
||||||
|
|
||||||
def summary(options):
|
def summary(options):
|
||||||
|
print(get_summary(options))
|
||||||
|
|
||||||
|
|
||||||
|
def get_summary(options):
|
||||||
if not is_key(options.name, options):
|
if not is_key(options.name, options):
|
||||||
raise Exception("%s does not exist, or is not a valid question set name"%( options.name, ))
|
raise Exception("%s does not exist, or is not a valid question set name"%( options.name, ))
|
||||||
db = open_db(options.db)
|
db = open_db(options.db)
|
||||||
@@ -146,8 +150,8 @@ def summary(options):
|
|||||||
)
|
)
|
||||||
matching_tables = cur.fetchall()
|
matching_tables = cur.fetchall()
|
||||||
if len(matching_tables) == 0:
|
if len(matching_tables) == 0:
|
||||||
print("No votes yet")
|
return "No votes yet"
|
||||||
return
|
|
||||||
token_table = get_voter_table_name(options.name)
|
token_table = get_voter_table_name(options.name)
|
||||||
cur.execute(
|
cur.execute(
|
||||||
"SELECT name FROM sqlite_master WHERE type='table' AND name=?;",
|
"SELECT name FROM sqlite_master WHERE type='table' AND name=?;",
|
||||||
@@ -197,21 +201,20 @@ def summary(options):
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
if options.tsv:
|
if options.tsv:
|
||||||
summary_tsv(questions, answers, tokens)
|
return summary_tsv(questions, answers, tokens)
|
||||||
else:
|
else:
|
||||||
summary_list(questions, answers, tokens)
|
return summary_list(questions, answers, tokens)
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
summary_list(questions, answers, tokens)
|
return summary_list(questions, answers, tokens)
|
||||||
|
|
||||||
def summary_list(questions, answers, tokens):
|
def summary_list(questions, answers, tokens):
|
||||||
print(
|
s = """# Tokens for this question set:
|
||||||
"""# Tokens for this question set:
|
# used: {used}, unused: {unused}, total: {total}
|
||||||
# used: {used}, unused: {unused}, total: {total}""".format_map(tokens)
|
""".format_map(tokens)
|
||||||
)
|
|
||||||
|
|
||||||
for q,a in zip(questions, answers):
|
for q,a in zip(questions, answers):
|
||||||
sum_answers = sum([answers[q]['answers'][x] for x in answers[q]['answers']])
|
sum_answers = sum([answers[q]['answers'][x] for x in answers[q]['answers']])
|
||||||
print("\n%s\n# Answers total: %d"%( q, sum_answers, ))
|
s += "\n%s\n# Answers total: %d\n"%( q, sum_answers, )
|
||||||
sorted_answers = sorted(
|
sorted_answers = sorted(
|
||||||
[(x, answers[q]['answers'][x]) for x in answers[q]['answers']],
|
[(x, answers[q]['answers'][x]) for x in answers[q]['answers']],
|
||||||
key = lambda i: -i[1]
|
key = lambda i: -i[1]
|
||||||
@@ -229,33 +232,34 @@ def summary_list(questions, answers, tokens):
|
|||||||
prefix = "----\n> "
|
prefix = "----\n> "
|
||||||
postfix = ""
|
postfix = ""
|
||||||
|
|
||||||
print("%s%s%s"%(
|
s += "%s%s%s\n"%(
|
||||||
prefix,
|
prefix,
|
||||||
answer[0],
|
answer[0],
|
||||||
postfix
|
postfix
|
||||||
))
|
)
|
||||||
|
return s
|
||||||
|
|
||||||
|
|
||||||
def summary_tsv(questions, answers, tokens):
|
def summary_tsv(questions, answers, tokens):
|
||||||
print(
|
s = '''Tokens\tUsed\tUnused\tTotal
|
||||||
'''Tokens\tUsed\tUnused\tTotal
|
""\t"{used}"\t"{unused}"\t"{total}"
|
||||||
""\t"{used}"\t"{unused}"\t"{total}"'''.format_map(tokens)
|
'''.format_map(tokens)
|
||||||
)
|
s += '"Question"\t"Question type"\t"Answer"\t"Count"\n'
|
||||||
print('"Question"\t"Question type"\t"Answer"\t"Count"')
|
|
||||||
good_characters = dict.fromkeys(range(32))
|
good_characters = dict.fromkeys(range(32))
|
||||||
for q,a in zip(questions, answers):
|
for q,a in zip(questions, answers):
|
||||||
sum_answers = sum([answers[q]['answers'][x] for x in answers[q]['answers']])
|
sum_answers = sum([answers[q]['answers'][x] for x in answers[q]['answers']])
|
||||||
print('"%s"\t"%s"\t""\t"%d"'%( q, answers[q]['answer_type'], sum_answers, ))
|
s += '"%s"\t"%s"\t""\t"%d"\n'%( q, answers[q]['answer_type'], sum_answers, )
|
||||||
sorted_answers = sorted(
|
sorted_answers = sorted(
|
||||||
[(x, answers[q]['answers'][x]) for x in answers[q]['answers']],
|
[(x, answers[q]['answers'][x]) for x in answers[q]['answers']],
|
||||||
key = lambda i: -i[1]
|
key = lambda i: -i[1]
|
||||||
)
|
)
|
||||||
for answer in sorted_answers:
|
for answer in sorted_answers:
|
||||||
|
|
||||||
print('""\t""\t""%s\t"%d"'%(
|
s += '""\t""\t""%s\t"%d"\n'%(
|
||||||
answer[0].translate(good_characters),
|
answer[0].translate(good_characters),
|
||||||
answer[1],
|
answer[1],
|
||||||
))
|
)
|
||||||
|
return s
|
||||||
|
|
||||||
|
|
||||||
def clear_votes(options):
|
def clear_votes(options):
|
||||||
|
|||||||
@@ -32,3 +32,13 @@ table.entriesall { border-collapse: collapse; }
|
|||||||
textarea {
|
textarea {
|
||||||
width: 90%;
|
width: 90%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.header {
|
||||||
|
text-align: right;
|
||||||
|
font-size: small;
|
||||||
|
}
|
||||||
|
.footer {
|
||||||
|
margin-top: 2em;
|
||||||
|
text-align: left;
|
||||||
|
font-size: small;
|
||||||
|
}
|
||||||
|
|||||||
@@ -6,7 +6,22 @@
|
|||||||
<script src="{{ url_for('static', filename='script.js') }}" type="text/javascript"></script>
|
<script src="{{ url_for('static', filename='script.js') }}" type="text/javascript"></script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|
||||||
|
|
||||||
<div class=page>
|
<div class=page>
|
||||||
|
<div class=header>
|
||||||
|
Read more about me at the <a href="#bottom">bottom!</a>
|
||||||
|
</div>
|
||||||
{% block body %}{% endblock %}
|
{% block body %}{% endblock %}
|
||||||
|
<hr>
|
||||||
|
<div class=footer>
|
||||||
|
<ul>
|
||||||
|
<li>This voting machine does not store information about you.</li>
|
||||||
|
<li>The token given to you is stored separately to the answers you give.</li>
|
||||||
|
<li>If you were given the token via email or any other such means, this voting machine does not know the connection between your contact information and the vote token.</li>
|
||||||
|
<li>Source code at <a href="https://bitbucket.org/MoonQ/aBot/">Bitbucket</a></li>
|
||||||
|
</ul>
|
||||||
|
<a id="bottom"></a>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
@@ -4,5 +4,6 @@
|
|||||||
|
|
||||||
Thank you for the vote!
|
Thank you for the vote!
|
||||||
|
|
||||||
|
{{ summary|safe }}
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
57
utils.py
57
utils.py
@@ -16,6 +16,7 @@ def create_result_table(key):
|
|||||||
)
|
)
|
||||||
g.db.commit()
|
g.db.commit()
|
||||||
|
|
||||||
|
|
||||||
def create_voter_table(db, name):
|
def create_voter_table(db, name):
|
||||||
table_name = get_voter_table_name(name)
|
table_name = get_voter_table_name(name)
|
||||||
cur = db.cursor()
|
cur = db.cursor()
|
||||||
@@ -29,6 +30,7 @@ def create_voter_table(db, name):
|
|||||||
)
|
)
|
||||||
db.commit()
|
db.commit()
|
||||||
|
|
||||||
|
|
||||||
def get_voter_table_name(key):
|
def get_voter_table_name(key):
|
||||||
return key + "__voters"
|
return key + "__voters"
|
||||||
|
|
||||||
@@ -37,7 +39,14 @@ def get_result_table_name(key):
|
|||||||
return key
|
return key
|
||||||
|
|
||||||
|
|
||||||
|
def get_html_summary(key):
|
||||||
|
# TODO
|
||||||
|
return ''
|
||||||
|
|
||||||
|
|
||||||
def has_voted(key, token):
|
def has_voted(key, token):
|
||||||
|
if token == None:
|
||||||
|
return True
|
||||||
cur = g.db.cursor()
|
cur = g.db.cursor()
|
||||||
cur.execute(
|
cur.execute(
|
||||||
"SELECT token FROM %s WHERE token = ? AND answered = 'true'"%(
|
"SELECT token FROM %s WHERE token = ? AND answered = 'true'"%(
|
||||||
@@ -50,15 +59,21 @@ def has_voted(key, token):
|
|||||||
return len(cur.fetchall()) > 0
|
return len(cur.fetchall()) > 0
|
||||||
|
|
||||||
|
|
||||||
|
def is_closed_vote(form):
|
||||||
|
return form['vote_style'] == 'closed'
|
||||||
|
|
||||||
|
|
||||||
def is_draft(form):
|
def is_draft(form):
|
||||||
return form['draft']
|
return form['draft']
|
||||||
|
|
||||||
|
|
||||||
def is_expired(form):
|
def is_expired(form):
|
||||||
if form['expires'] == None:
|
if form['expires'] == None:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
return datetime.now(timezone.utc) > form['expires']
|
return datetime.now(timezone.utc) > form['expires']
|
||||||
|
|
||||||
|
|
||||||
def is_key(key, cli_opts = False):
|
def is_key(key, cli_opts = False):
|
||||||
key = secure_filename(key)
|
key = secure_filename(key)
|
||||||
|
|
||||||
@@ -74,10 +89,17 @@ def is_key(key, cli_opts = False):
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def is_show_results(form):
|
||||||
|
return form['show_results']
|
||||||
|
|
||||||
|
|
||||||
def parse_form(key):
|
def parse_form(key):
|
||||||
form = {
|
form = {
|
||||||
'expires': None,
|
'expires': None,
|
||||||
'draft': False,
|
'draft': False,
|
||||||
|
'vote_style': "closed",
|
||||||
|
'show_results': False,
|
||||||
'questions': []
|
'questions': []
|
||||||
}
|
}
|
||||||
key = secure_filename(key)
|
key = secure_filename(key)
|
||||||
@@ -87,15 +109,24 @@ def parse_form(key):
|
|||||||
for row in fp:
|
for row in fp:
|
||||||
if row.strip() == "":
|
if row.strip() == "":
|
||||||
continue
|
continue
|
||||||
|
rowsl = row.lower().rstrip()
|
||||||
if row.startswith("#"):
|
if row.startswith("#"):
|
||||||
continue
|
continue
|
||||||
if row.lower().startswith("expires: "):
|
if rowsl.startswith("expires: "):
|
||||||
form['expires'] = parse_row_date(row)
|
form['expires'] = parse_row_date(row)
|
||||||
continue
|
continue
|
||||||
if row.lower().startswith("draft: "):
|
if rowsl.startswith("draft: "):
|
||||||
if row.lower().rstrip() == "draft: true":
|
if rowsl == "draft: true":
|
||||||
form['draft'] = True
|
form['draft'] = True
|
||||||
continue
|
continue
|
||||||
|
if rowsl.startswith("vote_style: "):
|
||||||
|
if rowsl == "vote_style: open":
|
||||||
|
form['vote_style'] = "open"
|
||||||
|
continue
|
||||||
|
if rowsl.startswith("show_results: "):
|
||||||
|
if rowsl == "show_results: true":
|
||||||
|
form['show_results'] = True
|
||||||
|
continue
|
||||||
if row.startswith("- "):
|
if row.startswith("- "):
|
||||||
if current_question == None:
|
if current_question == None:
|
||||||
continue
|
continue
|
||||||
@@ -157,9 +188,11 @@ def write_vote(key, token, answers, form):
|
|||||||
answer_type = "open"
|
answer_type = "open"
|
||||||
if answer == None:
|
if answer == None:
|
||||||
continue
|
continue
|
||||||
|
if answer_type == None:
|
||||||
|
continue
|
||||||
for single in answer:
|
for single in answer:
|
||||||
cur.execute(
|
cur.execute(
|
||||||
"INSERT INTO %s VALUES (?, ?, ?)"%(
|
"INSERT INTO `%s` VALUES (?, ?, ?)"%(
|
||||||
key,
|
key,
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
@@ -168,15 +201,15 @@ def write_vote(key, token, answers, form):
|
|||||||
answer_type
|
answer_type
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
if is_closed_vote(form):
|
||||||
cur.execute(
|
cur.execute(
|
||||||
"UPDATE %s SET answered = 'true' WHERE token = ?"%(
|
"UPDATE %s SET answered = 'true' WHERE token = ?"%(
|
||||||
get_voter_table_name(key),
|
get_voter_table_name(key),
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
token,
|
token,
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
|
||||||
g.db.commit()
|
g.db.commit()
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user