open voting, without tokens

This commit is contained in:
Ville Rantanen
2018-12-03 21:31:09 +02:00
parent 083915e69a
commit 1a8cc70914
6 changed files with 108 additions and 39 deletions

12
abot.py
View File

@@ -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,6 +62,7 @@ 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 is_closed_vote(form):
if has_voted(key, token): if has_voted(key, token):
return render_template('blank.html', message = "Token already used") return render_template('blank.html', message = "Token already used")
valid_for = time_to_expiry(form) valid_for = time_to_expiry(form)
@@ -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 is_closed_vote(form):
if has_voted(key, token): if has_voted(key, token):
return render_template('blank.html', message = "Token already used") 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)

View File

@@ -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):

View File

@@ -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;
}

View File

@@ -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>

View File

@@ -4,5 +4,6 @@
Thank you for the vote! Thank you for the vote!
{{ summary|safe }}
{% endblock %} {% endblock %}

View File

@@ -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,7 +201,7 @@ 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),