Implement randomisation of choices

This commit is contained in:
RunasSudo 2017-12-11 12:30:41 +10:30
parent db1955d628
commit d264159237
Signed by: RunasSudo
GPG Key ID: 7234E476BF21C61A
7 changed files with 27 additions and 9 deletions

View File

@ -15,6 +15,7 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
from eos.core.objects import * from eos.core.objects import *
from eos.core.bigint import *
from eos.base.workflow import * from eos.base.workflow import *
class Answer(EmbeddedObject): class Answer(EmbeddedObject):
@ -110,11 +111,12 @@ class Result(EmbeddedObject):
pass pass
class ListChoiceQuestion(Question): class ListChoiceQuestion(Question):
_ver = StringField(default='0.4') _ver = StringField(default='0.5')
choices = EmbeddedObjectListField() choices = EmbeddedObjectListField()
min_choices = IntField() min_choices = IntField()
max_choices = IntField() max_choices = IntField()
randomise_choices = BooleanField(default=False)
def pretty_answer(self, answer): def pretty_answer(self, answer):
if len(answer.choices) == 0: if len(answer.choices) == 0:
@ -136,6 +138,21 @@ class ListChoiceQuestion(Question):
else: else:
flat_choices.append(choice) flat_choices.append(choice)
return flat_choices return flat_choices
def randomised_choices(self):
if not self.randomise_choices:
return self.choices
else:
# Clone list
output = EosList([x for x in self.choices])
# Fisher-Yates shuffle
i = len(output)
while i != 0:
rnd = BigInt.noncrypto_random(0, i - 1)
rnd = rnd.__int__()
i -= 1
output[rnd], output[i] = output[i], output[rnd]
return output
class ApprovalAnswer(Answer): class ApprovalAnswer(Answer):
choices = ListField(IntField()) choices = ListField(IntField())

View File

@ -83,6 +83,7 @@ class PrimitiveField(Field):
DictField = PrimitiveField DictField = PrimitiveField
IntField = PrimitiveField IntField = PrimitiveField
StringField = PrimitiveField StringField = PrimitiveField
BooleanField = PrimitiveField
class EmbeddedObjectField(Field): class EmbeddedObjectField(Field):
def __init__(self, object_type=None, *args, **kwargs): def __init__(self, object_type=None, *args, **kwargs):

View File

@ -129,7 +129,7 @@ def setup_test_election():
Choice(name='Joe Bloggs', party='Independent ACME') Choice(name='Joe Bloggs', party='Independent ACME')
]), ]),
Choice(name='John Q. Public') Choice(name='John Q. Public')
], min_choices=0, max_choices=3) ], min_choices=0, max_choices=3, randomise_choices=True)
election.questions.append(question) election.questions.append(question)
question = ApprovalQuestion(prompt='Chairman', choices=[Choice(name='John Doe'), Choice(name='Andrew Citizen')], min_choices=0, max_choices=1) question = ApprovalQuestion(prompt='Chairman', choices=[Choice(name='John Doe'), Choice(name='Andrew Citizen')], min_choices=0, max_choices=1)

View File

@ -22,11 +22,11 @@
<div id="question-choices" class="ui form" style="margin-bottom: 1em;"> <div id="question-choices" class="ui form" style="margin-bottom: 1em;">
<div class="grouped fields"> <div class="grouped fields">
{% for choice in election.questions.__getitem__(questionNum).choices.impl %} {% for choice in election.questions.__getitem__(questionNum).randomised_choices().impl %}
<div class="field"> <div class="field">
<div class="ui checkbox"> <div class="ui checkbox">
<input type="checkbox" id="question-choice-{{ loop.index0 }}" onchange="choicesChanged();"> <input type="checkbox" id="question-choice-{{ election.questions.__getitem__(questionNum).choices.impl.indexOf(choice) }}" onchange="choicesChanged();">
<label for="question-choice-{{ loop.index0 }}">{{ choice.name }}{% if choice.party %} – {{ choice.party }}{% endif %}</label> <label for="question-choice-{{ election.questions.__getitem__(questionNum).choices.impl.indexOf(choice) }}">{{ choice.name }}{% if choice.party %} – {{ choice.party }}{% endif %}</label>
</div> </div>
</div> </div>
{% endfor %} {% endfor %}

View File

@ -58,10 +58,10 @@
<div>Options not yet voted for:</div> <div>Options not yet voted for:</div>
<div class="dragarea"> <div class="dragarea">
<div class="dragarea-hint"></div> <div class="dragarea-hint"></div>
{% for choice in election.questions.__getitem__(questionNum).choices.impl %} {% for choice in election.questions.__getitem__(questionNum).randomised_choices().impl %}
{% if choice.choices %} {% if choice.choices %}
{# Ticket #} {# Ticket #}
<div class="preferential-choice ticket" data-ticketno="{{ loop.index0 }}"> <div class="preferential-choice ticket">
<div class="number"></div> <div class="number"></div>
<div class="content"> <div class="content">
<div class="party-name">{{ choice.name }}</div> <div class="party-name">{{ choice.name }}</div>

View File

@ -16,7 +16,7 @@
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
#} #}
<p><small>Approval voting. Vote for between {{ question.min_choices }} and {{ question.max_choices }} choices.</small></p> <p><small>Approval voting. Vote for between {{ question.min_choices }} and {{ question.max_choices }} choices.{% if question.randomise_choices %} Order of choices is randomised.{% endif %}</small></p>
<ul class="ui list"> <ul class="ui list">
{% for choice in question.choices %} {% for choice in question.choices %}

View File

@ -16,7 +16,7 @@
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
#} #}
<p><small>Preferential voting. Vote for between {{ question.min_choices }} and {{ question.max_choices }} choices.</small></p> <p><small>Preferential voting. Vote for between {{ question.min_choices }} and {{ question.max_choices }} choices.{% if question.randomise_choices %} Order of choices is randomised.{% endif %}</small></p>
<ul class="ui list"> <ul class="ui list">
{% for choice in question.choices %} {% for choice in question.choices %}