inline HTML, result viewer

This commit is contained in:
Ville Rantanen
2018-12-04 20:58:24 +02:00
parent 1a8cc70914
commit 2910cc2186
10 changed files with 185 additions and 101 deletions

17
abot.py
View File

@@ -89,11 +89,22 @@ def save_vote():
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 = '' tokens = False
summary = False
questions = []
answers = []
if is_show_results(form): if is_show_results(form):
summary = get_html_summary(key) summary = True
questions, answers = get_summary(g.db, key)
tokens = get_token_counts(g.db, key)
return render_template('thank_you.html', summary = summary) return render_template(
'thank_you.html',
summary = summary,
tokens = tokens,
questions = questions,
answers = answers
)
if __name__ == "__main__": if __name__ == "__main__":
manager.main(DATABASE, QUESTIONS) manager.main(DATABASE, QUESTIONS)

View File

@@ -135,77 +135,17 @@ 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)
cur = db.cursor() questions, answers = get_summary(db, options.name)
tokens = get_token_counts(db, options.name)
cur.execute( if options.tsv:
"SELECT name FROM sqlite_master WHERE type='table' AND name=?;", out = summary_tsv(questions, answers, tokens)
( options.name, )
)
matching_tables = cur.fetchall()
if len(matching_tables) == 0:
return "No votes yet"
token_table = get_voter_table_name(options.name)
cur.execute(
"SELECT name FROM sqlite_master WHERE type='table' AND name=?;",
( token_table, )
)
matching_tables = cur.fetchall()
if len(matching_tables) == 0:
used_tokens = 0
unused_tokens = 0
else: else:
cur.execute( out = summary_list(questions, answers, tokens)
"SELECT count(*) FROM `%s` WHERE answered = 'true'"%( print(out)
token_table,
)
)
used_tokens = cur.fetchall()[0][0]
cur.execute(
"SELECT count(*) FROM `%s` WHERE answered = 'false'"%(
token_table,
)
)
unused_tokens = cur.fetchall()[0][0]
tokens = {
'unused': unused_tokens,
'used': used_tokens,
'total': used_tokens + unused_tokens
}
questions = []
answers = {}
cur.execute(
"SELECT question, answer, answer_type FROM `%s`"%(
options.name,
)
)
for row in cur:
if row[0] not in answers.keys():
questions.append(row[0])
answers[row[0]] = {
'answers': {},
'answer_type': row[2]
}
if row[1] not in answers[row[0]]['answers'].keys():
answers[row[0]]['answers'][row[1]] = 0
answers[row[0]]['answers'][row[1]] += 1
try:
if options.tsv:
return summary_tsv(questions, answers, tokens)
else:
return summary_list(questions, answers, tokens)
except AttributeError:
return summary_list(questions, answers, tokens)
def summary_list(questions, answers, tokens): def summary_list(questions, answers, tokens):
s = """# Tokens for this question set: s = """# Tokens for this question set:

View File

@@ -1,17 +1,30 @@
# expiry format: YYYY-MM-DD HH:MM +z # expiry format: YYYY-MM-DD HH:MM +z
# z is the difference to UTC in HHMM, +0000, -0700, etc.. # z is the difference to UTC in HHMM, +0000, -0700, etc..
expires: 2028-12-12 21:20 +0200 expires: 2028-12-12 21:20 +0200
# if "draft: true" voting is not possible. you can preview the form with address /preview/example
# if "draft: true" voting is not possible. you can preview the form with address "/preview/example"
draft: false draft: false
# "open" vote style is open for anyone without tokens. "closed" requires tokens to be generated
# Create tokens with `./manager token multi_question`
vote_style: closed
# By default voters can not see the results
show_results: false
# HTML can be added inline. The line needs to begin with "<"
# HTML added like this is technically a question without answer choices
<H1>Title of vote page</H1>
<img src="https://upload.wikimedia.org/wikipedia/en/thumb/7/7d/Lenna_%28test_image%29.png/220px-Lenna_%28test_image%29.png"/>
# - character is single choice question # - character is single choice question
It works? It works?
- yes - yes
- no - no
# If a question is in HTML tags, it's not formatted
<p>Fruits</p>
# + character is multi choice question # + character is multi choice question
Fruits
+ banana + banana
+ orange + orange
+ tomato + tomato

View File

@@ -1,8 +1,14 @@
# if "true" voting is not possible. you can preview the form in address /preview/example # if "true" voting is not possible. you can preview the form in address /preview/example
draft: false draft: false
# "open" vote style is open for anyone without tokens. "closed" requires tokens to be generated
vote_style: open
# By default voters can not see the results
show_results: true
# Questions are any line that doesnt match configuration commands
It works? It works?
# single choice answers are denoted with dashes
- yes - yes
- no - no

View File

@@ -1,18 +1,12 @@
body { font-family: sans-serif; background: #eee; } body { font-family: sans-serif; background: #eee; }
a, h1, h2 { color: #377ba8; } a, h1, h2 { color: #377ba8; }
h1, h2 { font-family: 'Georgia', serif; margin: 0; } h1, h2 { font-family: 'Georgia', serif; margin: 0; margin-top: 0.2em; }
h1 { border-bottom: 2px solid #eee; } h1 { border-bottom: 2px solid #eee; }
h2 { font-size: 1.2em; } h2 { font-size: 1.2em; }
input { margin-top: 0.5em; border: 1px solid gray;} input { margin-top: 0.5em; border: 1px solid gray;}
.page { margin: 2em auto; width: 90%; border: 5px solid #ccc; .page { margin: 2em auto; width: 90%; border: 5px solid #ccc;
padding: 0.8em; background: white; } padding: 0.8em; background: white; }
.entries { list-style: none; margin: 0; padding: 0; }
.entries li { margin: 0.8em 1.2em; }
.entries li h2 { margin-left: -1em; }
table.entriesall { border-collapse: collapse; }
.entriesall td, .entriesall th { border: 1px solid black;
padding: 0.5em; }
.index { .index {
margin-top: 1em; margin-top: 1em;
} }
@@ -21,16 +15,27 @@ table.entriesall { border-collapse: collapse; }
margin-top: 1em; margin-top: 1em;
} }
.question { .question {
border-bottom: 1px solid gray;
padding-bottom: 0.5em; padding-bottom: 0.5em;
} }
.autoformat {
border-bottom: 1px solid gray;
}
.warning { .warning {
font-size: small; font-size: small;
color: red; color: red;
} }
.message {
font-size: large;
color: red;
margin-top: 1em;
margin-bottom: 1em;
}
textarea { textarea {
width: 90%; width: 100%;
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
} }
.header { .header {
@@ -38,7 +43,23 @@ textarea {
font-size: small; font-size: small;
} }
.footer { .footer {
margin-top: 2em;
text-align: left; text-align: left;
font-size: small; font-size: small;
} }
.summary_single {
list-style-type: disk;
}
.summary_multiple {
list-style-type: square;
}
.summary_open {
list-style-type: circle;
font-style: italic;
}
.thankyou {
font-size: large;
margin-top: 1em;
margin-bottom: 1em;
}

View File

@@ -1,4 +1,4 @@
{% extends "layout.html" %} {% extends "layout.html" %}
{% block body %} {% block body %}
{{ message }} <div class=message>{{ message }}</div>
{% endblock %} {% endblock %}

View File

@@ -9,17 +9,19 @@
<div class=page> <div class=page>
<div class=header> <div class=header>
Read more about me at the <a href="#bottom">bottom!</a> <a href="#bottom">About</a>
</div> </div>
{% block body %}{% endblock %} {% block body %}{% endblock %}
<hr> </div>
<div class=page>
<div class=footer> <div class=footer>
About aBot
<ul> <ul>
<li>This voting machine does not store information about you.</li> <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>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>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> <li>Source code at <a href="https://bitbucket.org/MoonQ/aBot/">Bitbucket</a></li>
</ul> </ul>
<a id="bottom"></a> <a id="bottom"></a>
</div> </div>

View File

@@ -1,8 +1,13 @@
<div id="questions"> <div id="questions">
{% for question in form.questions %} {% for question in form.questions %}
<div class = "question">
<h2>{{ question.name|safe }}</h2>
{% if question.autoformat %}
<div class = "question autoformat">
<h2>{{ question.name|safe }}</h2>
{% else %}
<div class = "question">
{{ question.name|safe }}
{% endif %}
{% for choice in question.choices %} {% for choice in question.choices %}
<div> <div>
<input type="radio" name="QC{{ question.index }}" value="{{ choice|safe }}" /> <input type="radio" name="QC{{ question.index }}" value="{{ choice|safe }}" />

View File

@@ -1,9 +1,35 @@
{% extends "layout.html" %} {% extends "layout.html" %}
{% block body %} {% block body %}
<h1>aBot!</h1> <h1>aBot!</h1>
<div class=thankyou>
Thank you for the vote! Thank you for the vote!
</div>
{% if summary %}
<h3>Current report</h3>
{{ summary|safe }} <div id="questions">
{% for question in questions %}
<div class = "question autoformat">
<h2>{{ question|safe }}</h2>
<ul>
{% for choice in answers[question].answers %}
{% if answers[question].answer_type == "single" %}
<li class = "summary_single">{{ choice }}: {{ answers[question].answers[choice] }}
{% endif %}
{% if answers[question].answer_type == "multiple" %}
<li class = "summary_multiple">{{ choice }}: {{ answers[question].answers[choice] }}
{% endif %}
{% if answers[question].answer_type == "open" %}
<li class = "summary_open">"{{ choice }}"
{% endif %}
{% endfor %}
</ul>
</div>
{% endfor %}
</div>
{% endif %}
{% endblock %} {% endblock %}

View File

@@ -38,10 +38,69 @@ def get_voter_table_name(key):
def get_result_table_name(key): def get_result_table_name(key):
return key return key
def get_token_counts(db, key):
cur = db.cursor()
token_table = get_voter_table_name(key)
cur.execute(
"SELECT name FROM sqlite_master WHERE type='table' AND name=?;",
( token_table, )
)
matching_tables = cur.fetchall()
if len(matching_tables) == 0:
used_tokens = 0
unused_tokens = 0
else:
cur.execute(
"SELECT count(*) FROM `%s` WHERE answered = 'true'"%(
token_table,
)
)
used_tokens = cur.fetchall()[0][0]
cur.execute(
"SELECT count(*) FROM `%s` WHERE answered = 'false'"%(
token_table,
)
)
unused_tokens = cur.fetchall()[0][0]
tokens = {
'unused': unused_tokens,
'used': used_tokens,
'total': used_tokens + unused_tokens
}
return tokens
def get_html_summary(key): def get_summary(db, key):
# TODO """ returns summary for a vote event """
return '' questions = []
answers = {}
cur = db.cursor()
cur.execute(
"SELECT name FROM sqlite_master WHERE type='table' AND name=?;",
( key, )
)
matching_tables = cur.fetchall()
if len(matching_tables) == 0:
return questions, answers
cur.execute(
"SELECT question, answer, answer_type FROM `%s`"%(
key,
)
)
for row in cur:
if row[0] not in answers.keys():
questions.append(row[0])
answers[row[0]] = {
'answers': {},
'answer_type': row[2]
}
if row[1] not in answers[row[0]]['answers'].keys():
answers[row[0]]['answers'][row[1]] = 0
answers[row[0]]['answers'][row[1]] += 1
return questions, answers
def has_voted(key, token): def has_voted(key, token):
@@ -104,7 +163,7 @@ def parse_form(key):
} }
key = secure_filename(key) key = secure_filename(key)
try: try:
current_question = None current_question = 0
with open(os.path.join(app.config['QUESTIONS'], key + ".txt"), "rt") as fp: with open(os.path.join(app.config['QUESTIONS'], key + ".txt"), "rt") as fp:
for row in fp: for row in fp:
if row.strip() == "": if row.strip() == "":
@@ -145,9 +204,10 @@ def parse_form(key):
form['questions'].append({ form['questions'].append({
'choices': [], 'choices': [],
'multichoices': [], 'multichoices': [],
'index': len(form['questions']) + 1, 'index': current_question + 1,
'name': row.strip().rstrip("_:").rstrip(), 'name': row.strip().rstrip("_:").rstrip(),
'open_question': row.strip().endswith("___") 'open_question': row.strip().endswith("___"),
'autoformat': not rowsl.startswith("<")
}) })
return form return form
except Exception as err: except Exception as err: