From c2d3b4ab9339d23ba5711ed43d1d62ce053d562a Mon Sep 17 00:00:00 2001 From: RunasSudo Date: Mon, 27 Nov 2017 22:56:43 +1100 Subject: [PATCH] Add preferential voting support. Closes #4 --- eos/base/election.py | 11 +++ eosweb/core/bower.json | 3 +- eosweb/core/main.py | 2 +- eosweb/core/modelview.py | 6 ++ eosweb/core/static/css/main.css | 57 +++++++++++++ .../question/approval/selections_make.html | 8 +- .../question/approval/selections_review.html | 4 +- .../preferential/selections_make.html | 80 +++++++++++++++++++ .../preferential/selections_review.html | 40 ++++++++++ eosweb/core/templates/election/booth.html | 9 +++ .../templates/question/approval/view.html | 2 + .../question/preferential/result_raw.html | 26 ++++++ .../templates/question/preferential/view.html | 25 ++++++ 13 files changed, 265 insertions(+), 8 deletions(-) create mode 100644 eosweb/core/static/nunjucks/question/preferential/selections_make.html create mode 100644 eosweb/core/static/nunjucks/question/preferential/selections_review.html create mode 100644 eosweb/core/templates/question/preferential/result_raw.html create mode 100644 eosweb/core/templates/question/preferential/view.html diff --git a/eos/base/election.py b/eos/base/election.py index 054d946..a530632 100644 --- a/eos/base/election.py +++ b/eos/base/election.py @@ -101,6 +101,17 @@ class ApprovalQuestion(Question): class ApprovalAnswer(Answer): choices = ListField(IntField()) +class PreferentialQuestion(Question): + choices = ListField(StringField()) + min_choices = IntField() + max_choices = IntField() + + def pretty_answer(self, answer): + return ', '.join([self.choices[choice] for choice in answer.choices]) + +class PreferentialAnswer(Answer): + choices = ListField(IntField()) + class RawResult(Result): answers = EmbeddedObjectListField() diff --git a/eosweb/core/bower.json b/eosweb/core/bower.json index 61a6d2a..3587672 100644 --- a/eosweb/core/bower.json +++ b/eosweb/core/bower.json @@ -17,6 +17,7 @@ ], "dependencies": { "semantic": "semantic-ui#^2.2.13", - "nunjucks": "^3.0.1" + "nunjucks": "^3.0.1", + "dragula.js": "dragula#^3.7.2" } } diff --git a/eosweb/core/main.py b/eosweb/core/main.py index 16a4e08..4ab0cb9 100644 --- a/eosweb/core/main.py +++ b/eosweb/core/main.py @@ -109,7 +109,7 @@ def setup_test_election(): election.sk = EGPrivateKey.generate() election.public_key = election.sk.public_key - question = ApprovalQuestion(prompt='President', choices=['John Smith', 'Joe Bloggs', 'John Q. Public'], min_choices=0, max_choices=2) + question = PreferentialQuestion(prompt='President', choices=['John Smith', 'Joe Bloggs', 'John Q. Public'], min_choices=0, max_choices=3) election.questions.append(question) question = ApprovalQuestion(prompt='Chairman', choices=['John Doe', 'Andrew Citizen'], min_choices=0, max_choices=1) diff --git a/eosweb/core/modelview.py b/eosweb/core/modelview.py index d33d6f6..9616f6f 100644 --- a/eosweb/core/modelview.py +++ b/eosweb/core/modelview.py @@ -27,6 +27,12 @@ model_view_map = { Election: { 'tabs': 'election/core/tabs.html' }, + PreferentialQuestion: { + 'view': 'question/preferential/view.html', + 'result_raw': 'question/preferential/result_raw.html', + 'selections_make': 'question/preferential/selections_make.html', + 'selections_review': 'question/preferential/selections_review.html' + }, PSRElection: { 'tabs': 'election/psr/tabs.html' } diff --git a/eosweb/core/static/css/main.css b/eosweb/core/static/css/main.css index f1d10f0..bafefd8 100644 --- a/eosweb/core/static/css/main.css +++ b/eosweb/core/static/css/main.css @@ -36,3 +36,60 @@ margin-top: 4em; } } + +/* Preferential voting */ + +.preferential-choices { + padding: 0.5em; + position: relative; +} + +.preferential-choices .dragarea { + min-height: 3em; + margin-top: 0.5em; +} + +.preferential-choices .dragarea-hint:first-child:last-child { + content: ""; + width: calc(100% - 1em); + height: 3em; + box-sizing: border-box; + z-index: -100; + border: 1px dashed #555; + position: absolute; +} + +#question-choices-selected { + border: 1px solid #3465a4; + margin-bottom: 0.5em; +} + +.preferential-choice { + background-color: #eee; + font-size: 1.1rem; + margin-top: 0.5em; + display: flex; + align-items: center; + min-height: 3em; +} + +.preferential-choice:first-child { + margin-top: 0; +} + +#question-choices-selected .preferential-choice { + background-color: #e6f1fc; +} + +#question-choices-remaining { + border: 1px solid #555; +} + +.preferential-choice .number, .preferential-choice .name { + padding: 0.5em 0 0.5em 0.5em; +} + +.preferential-choice .number { + width: 2em; + text-align: center; +} diff --git a/eosweb/core/static/nunjucks/question/approval/selections_make.html b/eosweb/core/static/nunjucks/question/approval/selections_make.html index cdced20..1526e6f 100644 --- a/eosweb/core/static/nunjucks/question/approval/selections_make.html +++ b/eosweb/core/static/nunjucks/question/approval/selections_make.html @@ -18,7 +18,7 @@

{{ questionNum + 1 }}. {{ election.questions.__getitem__(questionNum).prompt }}

-

Vote for between {{ election.questions.__getitem__(questionNum).min_choices }} and {{ election.questions.__getitem__(questionNum).max_choices }} candidates. Click the check-boxes to the left of the candidates' names to make your selection, then click the ‘Continue’ button. If you make a mistake, click the check-boxes again to clear your selection.

+

Vote for between {{ election.questions.__getitem__(questionNum).min_choices }} and {{ election.questions.__getitem__(questionNum).max_choices }} choices. Click the check-boxes to the left of the choices to make your selection, then click the ‘Continue’ button. If you make a mistake, click the check-boxes again to clear your selection.

@@ -34,7 +34,7 @@
diff --git a/eosweb/core/static/nunjucks/question/preferential/selections_review.html b/eosweb/core/static/nunjucks/question/preferential/selections_review.html new file mode 100644 index 0000000..49b98fe --- /dev/null +++ b/eosweb/core/static/nunjucks/question/preferential/selections_review.html @@ -0,0 +1,40 @@ +{# + Eos - Verifiable elections + Copyright © 2017 RunasSudo (Yingtong Li) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . +#} + +{% if booth.answers[loop.index0].value.choices.length > 0 %} +
+ {% for choice in booth.answers[loop.index0].value.choices %} +
+
{{ question.choices.__getitem__(choice) }}
+
+ {% endfor %} +
+{% else %} +
+
+ +
No choices selected
+
+
+{% endif %} + +{% if booth.answers[loop.index0].value.choices.length < question.max_choices %} +
+

You have selected fewer than the maximum allowed number of choices. If this was not your intention, please click the ‘Back’ button below now, and correct your selections.

+
+{% endif %} diff --git a/eosweb/core/templates/election/booth.html b/eosweb/core/templates/election/booth.html index 76f5f69..d729060 100644 --- a/eosweb/core/templates/election/booth.html +++ b/eosweb/core/templates/election/booth.html @@ -20,6 +20,11 @@ {% block title %}{{ election.name }} – Voting booth{% endblock %} +{% block head %} + {{ super() }} + +{% endblock %} + {% block content %}
Loading voting booth. Please wait.
@@ -28,8 +33,12 @@ {% block basecontent %} {{ super() }} + + + +