inline HTML, result viewer
This commit is contained in:
17
abot.py
17
abot.py
@@ -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)
|
||||||
|
|||||||
70
manager.py
70
manager.py
@@ -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(
|
|
||||||
"SELECT name FROM sqlite_master WHERE type='table' AND name=?;",
|
|
||||||
( 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:
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
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:
|
if options.tsv:
|
||||||
return summary_tsv(questions, answers, tokens)
|
out = summary_tsv(questions, answers, tokens)
|
||||||
else:
|
else:
|
||||||
return summary_list(questions, answers, tokens)
|
out = summary_list(questions, answers, tokens)
|
||||||
except AttributeError:
|
print(out)
|
||||||
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:
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
{% extends "layout.html" %}
|
{% extends "layout.html" %}
|
||||||
{% block body %}
|
{% block body %}
|
||||||
{{ message }}
|
<div class=message>{{ message }}</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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 }}" />
|
||||||
|
|||||||
@@ -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 %}
|
||||||
|
|||||||
72
utils.py
72
utils.py
@@ -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:
|
||||||
|
|||||||
Reference in New Issue
Block a user