-
Notifications
You must be signed in to change notification settings - Fork 16
Elections and recounts
(This page is current as of September 2017)
- Users can announce their candidacy up to a certain date/time
- At another date/time, voting begins and users submit sorted lists of candidates
- At the final date/time, voting ends
- Someone (an admin or a cron job) invokes
manage.py processelections
:-
election.models.Elections.process
is invoked - An instance of
election.utils.BallotCounter
counts the votes - The same
election.utils.BallotCounter
writes a copy of raw (but anonymized) ballots to a JSON file in the filesystem, the name/path of which are determined bysettings.BALLOT_SAVEFILE_FORMAT
. - The original ballot data will be deleted from the database
- A set of
election.models.ElectionResultRow
objects will be created, representing the results
-
If the cron jobs are disabled, an admin will need to manually trigger the initial count. This is done like so:
$ manage.py processelections
...
If not all elections are to be processed, giving the election ID as the first argument will limit the count to only that election and no others:
$ manage.py processelections 123
...
Hint: The election ID is clearly visible in the URL of the election itself on the web. Easy peasy!
Currently the wasa2il code-base does not directly support hiding election results. However, this can be easily "hacked in" by editing wasa2il/templates/election/election_view.html
and adding requirements to the various if
statements that control what is visible and what is not.
An ugly git diff
example limiting the output to only members of staff:
diff --git a/wasa2il/templates/election/election_view.html b/wasa2il/templates/election/election_view.html
index a14e966..f3adf12 100644
--- a/wasa2il/templates/election/election_view.html
+++ b/wasa2il/templates/election/election_view.html
@@ -52,7 +52,7 @@
{% if election.is_closed %}
<div class="alert alert-danger">
{% trans "This election is closed." %}
- {% if user_result and election.results_are_ordered %}
+ {% if user.is_staff and user_result and election.results_are_ordered %}
<b>{% blocktrans %}You ended up in {{user_result}}. place.{% endblocktrans %}</b>
{% endif %}
</div>
@@ -193,7 +193,7 @@
{% if not election.results_are_ordered %}
<div class="alert alert-info">{% trans 'The voting system used in this election generates a non-ordered list. All winners are in the same place.' %}</div>
{% endif %}
- {% if ordered_candidates %}
+ {% if user.is_staff and ordered_candidates %}
<div id="election_candidates_winners" style="margin-top: 1em;">
<ol class="candidates" id="candidates_winners" {% if not election.results_are_ordered %}style="list-style: none; padding-left: 0px;"{% endif %}>
{% for candidate in ordered_candidates %}
@@ -236,7 +236,7 @@
</div>
</div>
-{% if statistics and election.stats_publish_files and election.is_closed %}
+{% if user.is_staff and statistics and election.stats_publish_files and election.is_closed %}
<div class="row">
<p class="stats_downloads" style="text-align: right;">
{% trans "Download detailed statistics as:" %}
The file election/utils.py
can be invoked as a stand-alone tool. It can be used to recount ballots from a JSON file using different algorithms, and/or omitting certain candidates.
An example of the common case, recounting after one or more candidates have withdrawn from the election:
$ python election/utils.py \
-e quitter1 -e quitter2 \
count schulze /path/to/ballot/data.json
...
The list of usernames can be converted into a more human-readable list of real names, by using the manage.py lookup_usernames
command:
$ manage.py lookup_usernames user1, user2, user3
...
(Commas will be ignored, to facilitate copy-pasting directly from the election/utils.py
output into the lookup tool)
Note that if two users are tied, the libraries used by election/utils.py
will perform a virtual coin toss to order those candidates. As a result, recounts may randomly change the order of candidates for no other reason than pure chance. It may be worth recounting multiple times to detect when this happens, and manually preserve the order chosen by the first election in such cases.
At the time of writing, election/utils.py
supports the following voting systems: schulze
, stcom
, condorcet
, stv1
, ..., stv5
, stv10
Most of these are standard counting methods that you can read about on Wikipedia or elsewhere. The exception is the stcom
method: it implements the Icelandic pirate party steering committee election system; currently a combination of schulze and plain condorcet (to verify whether the chairperson is unambiguously elected).
The built-in help is likely to be more current than this document:
$ python election/utils.py --help
usage: utils.py [-h] [-e EXCLUDE] [--keep-gaps]
operation system filenames [filenames ...]
positional arguments:
operation Operation to perform (count)
system Counting system to use (schulze, stv5, ...)
filenames Ballot files to read
optional arguments:
-h, --help show this help message and exit
-e EXCLUDE, --exclude EXCLUDE
Candidate(s) to exclude when counting
--keep-gaps Preserve gaps if ballots are not sequential
We can use the included test-data to verify that Schulze will arbitrarily break Condorcet ties/cycles; if you run this a few times you'll get different results each time.
$ python election/utils.py count schulze test_data/condorcet_cycle.json
Voting system:
Schulze, Ordered list (schulze)
Loaded 3 ballots from:
test_data/condorcet_cycle.json
Schulze old and new match, hooray.
Results:
Bjarni, Smari, Bjorn
Counting using the condorcet method should however correctly return no results:
$ python election/utils.py count condorcet test_data/condorcet_cycle.json
Voting system:
Condorcet (condorcet)
Loaded 3 ballots from:
test_data/condorcet_cycle.json
Results:
To break the cycle, we can try omitting one candidate:
$ python election/utils.py -e Bjorn count condorcet test_data/condorcet_cycle.json
Voting system:
Condorcet (condorcet)
Loaded 3 ballots from:
test_data/condorcet_cycle.json
Results:
Bjarni
Hooray!
-
If you're not sure what cron-jobs exist, try this:
crontab -l
-
Since during elections, the wasa2il database contains non-anonymized ballots, we recommend using a backup system which prevents admins from easily accessing old data. This prevents after-the-fact attacks on voter privacy. This is what the Icelandic pirates use.
-
The schulze method should tolerate candidates withdrawing from an election right in the middle. It's unclear whether the same can be said about other counting methods supported by wasa2il, further research is needed before implementing a user-friendly "instant withdraw" feature.