diff --git a/management/forms.py b/management/forms.py index 8fd739f85868384959ba86617dcdda4264ff05a6..6f22792351f3d594ed26927515affe5987d029d3 100644 --- a/management/forms.py +++ b/management/forms.py @@ -208,7 +208,7 @@ class AddElectionForm(forms.ModelForm, TemplateStringForm): model = Election fields = ( 'title', 'start_date', 'end_date', 'session', 'max_votes_yes', 'voters_self_apply', 'send_emails_on_start', - 'remind_text', 'disable_abstention', 'result_unpublished') + 'remind_text', 'enable_abstention', 'result_published') labels = { 'title': 'Election Name', @@ -217,10 +217,10 @@ class AddElectionForm(forms.ModelForm, TemplateStringForm): 'voters_self_apply': 'Voters can apply for the election', 'send_emails_on_start': 'Voters receive an e-mail when the election starts<br>' '(useful for elections that last several days)', - 'disable_abstention': 'Disable the option to abstain in this election<br>' - '(only YES and NO votes will be allowed)', + 'enable_abstention': 'Enable the option to abstain in this election<br>' + '(YES, NO and ABSTENTION votes will be allowed)', 'remind_text': '', - 'result_unpublished': 'Disable auto publish of the election results', + 'result_published': 'Automatically publish the election results', } def clean_remind_text(self): diff --git a/management/templates/management/add_election.html b/management/templates/management/add_election.html index 17873c02ad45da25c5813fd0412eea4e2636c169..47df2a622b868889f5de3b7c03f85f1aaf4d0666 100644 --- a/management/templates/management/add_election.html +++ b/management/templates/management/add_election.html @@ -17,7 +17,7 @@ {{ form|as_crispy_errors }} {% for field in form %} - {% if field.html_name != "remind_text" and field.html_name != "send_emails_on_start" and field.html_name != "voters_self_apply" and field.html_name != "email" and field.html_name != 'disable_abstention' and field.html_name != 'result_unpublished' %} + {% if field.html_name != "remind_text" and field.html_name != "send_emails_on_start" and field.html_name != "voters_self_apply" and field.html_name != "email" and field.html_name != 'enable_abstention' and field.html_name != 'result_published' %} {{ field|as_crispy_field }} {% endif %} {% endfor %} @@ -32,8 +32,8 @@ </span> </div> <div id="collapseOne" class="card-body collapse{% if form.remind_text.value %} show{% endif %}"> - {{ form.result_unpublished|as_crispy_field }} - {{ form.disable_abstention|as_crispy_field }} + {{ form.result_published|as_crispy_field }} + {{ form.enable_abstention|as_crispy_field }} {{ form.voters_self_apply|as_crispy_field }} {{ form.send_emails_on_start|as_crispy_field }} <h5>Remind email template text</h5> diff --git a/management/templates/management/election.html b/management/templates/management/election.html index aba5e8ceac651de29089543f9e8b20f05f945a47..471f5c780fd57f764e5b67544eafec2706ab3717 100644 --- a/management/templates/management/election.html +++ b/management/templates/management/election.html @@ -120,7 +120,7 @@ <h4>Result</h4> {% include 'management/results.html' %} - {% if election.result_unpublished %} + {% if not election.result_published %} <hr> <form action="{% url 'management:election' election.pk %}" method="post"> {% csrf_token %} diff --git a/management/views.py b/management/views.py index cf0f04014d74a73625a5a68d2295064d5324abcb..475dede475d58c9537580ccbe130ab36f2e64c9a 100644 --- a/management/views.py +++ b/management/views.py @@ -243,7 +243,7 @@ def election_detail(request, pk): context['start_election_form'] = form if request.POST and request.POST.get('action') == 'publish': - election.result_unpublished = False + election.result_published = True election.save() return render(request, template_name='management/election.html', context=context) diff --git a/requirements_dev.txt b/requirements_dev.txt index 7d536fbc518a5b4aaded10235b82170a8613e891..34bfbce35a15d20f72f07b8d0d5533d82f39863f 100644 --- a/requirements_dev.txt +++ b/requirements_dev.txt @@ -3,3 +3,4 @@ pylint~=2.8 pylint-django~=2.4 django-stubs build +freezegun \ No newline at end of file diff --git a/vote/forms.py b/vote/forms.py index 7e0b986ae837d2c822f51ffcde78e7fcafe98fab..0ec5006f42fd431c34731a9af31e399796c8a45e 100644 --- a/vote/forms.py +++ b/vote/forms.py @@ -61,12 +61,12 @@ class VoteBoundField(forms.BoundField): class VoteField(forms.ChoiceField): - def __init__(self, *, application, disable_abstention=False, **kwargs): + def __init__(self, *, application, enable_abstention=True, **kwargs): super().__init__( label=application.get_display_name(), - choices=VOTE_CHOICES_NO_ABSTENTION if disable_abstention else VOTE_CHOICES, + choices=VOTE_CHOICES if enable_abstention else VOTE_CHOICES_NO_ABSTENTION, widget=forms.RadioSelect(), - initial=None if disable_abstention else VOTE_ABSTENTION, + initial=VOTE_ABSTENTION if enable_abstention else None, **kwargs ) self.application = application @@ -89,7 +89,7 @@ class VoteForm(forms.Form): # dynamically construct form fields for application in self.election.applications.all(): self.fields[f'{application.pk}'] = VoteField(application=application, - disable_abstention=self.election.disable_abstention) + enable_abstention=self.election.enable_abstention) self.num_applications = self.election.applications.all().count() diff --git a/vote/migrations/0029_rename_unpublished_and_disable_abstention.py b/vote/migrations/0029_rename_unpublished_and_disable_abstention.py new file mode 100644 index 0000000000000000000000000000000000000000..dffbda68eb954e0c5fdf6bdb33460b0f68b3bc60 --- /dev/null +++ b/vote/migrations/0029_rename_unpublished_and_disable_abstention.py @@ -0,0 +1,30 @@ +# Generated by Django 3.1.13 on 2021-08-06 08:40 + +from django.db import migrations +from django.db.models import Q + + +def update_values(apps, schema_editor): + election = apps.get_model('vote', 'election') + election.objects.update(result_published=Q(result_published=False)) + election.objects.update(enable_abstention=Q(enable_abstention=False)) + + +class Migration(migrations.Migration): + dependencies = [ + ('vote', '0028_auto_20210804_2335'), + ] + + operations = [ + migrations.RenameField( + model_name='election', + old_name='result_unpublished', + new_name='result_published', + ), + migrations.RenameField( + model_name='election', + old_name='disable_abstention', + new_name='enable_abstention', + ), + migrations.RunPython(update_values, reverse_code=update_values), + ] diff --git a/vote/models.py b/vote/models.py index db30e085ef601182f77ddebbdbdfa08cac946d3f..e1e00924ee09e7f48f64e57935d0e966e7014836 100644 --- a/vote/models.py +++ b/vote/models.py @@ -97,8 +97,8 @@ class Election(models.Model): end_date = models.DateTimeField(blank=True, null=True) max_votes_yes = models.IntegerField(blank=True, null=True) session = models.ForeignKey(Session, related_name='elections', on_delete=CASCADE) - result_unpublished = models.BooleanField(null=False, default=True) - disable_abstention = models.BooleanField(default=False) + result_published = models.BooleanField(null=False, default=False) + enable_abstention = models.BooleanField(default=True) voters_self_apply = models.BooleanField(default=False) send_emails_on_start = models.BooleanField(default=False) remind_text = models.TextField(max_length=8000, blank=True, null=True) diff --git a/vote/selectors.py b/vote/selectors.py index e40ccc93d6d83631f048bdb9f74a2ba38d2c4b18..f08a82310ea6ea672b80c2a9a40946f2d7c1a0a7 100644 --- a/vote/selectors.py +++ b/vote/selectors.py @@ -24,8 +24,8 @@ def _closed_elections(session: Session): def published_elections(session: Session): - return _closed_elections(session).filter(result_unpublished=False) + return _closed_elections(session).filter(result_published=True) def closed_elections(session: Session): - return _closed_elections(session).filter(result_unpublished=True) + return _closed_elections(session).filter(result_published=False) diff --git a/vote/templates/vote/index_election_item.html b/vote/templates/vote/index_election_item.html index 7a82c6d497115e6b0bd1871be6592592df0f5c4f..34ad03b6182688c0789dfcfe9e041002ef498fa8 100644 --- a/vote/templates/vote/index_election_item.html +++ b/vote/templates/vote/index_election_item.html @@ -24,7 +24,7 @@ <div class="list-group mt-3"> {% if can_vote %} <a class="btn btn-primary" role="button" href="{% url 'vote:vote' election.pk %}">Vote Now!</a> - {% elif election.closed and not election.result_unpublished %} + {% elif election.closed and election.result_published %} <div class="alert alert-info" role="alert"> <h4 class="alert-heading">Voting Ended:</h4> <hr> diff --git a/vote/templates/vote/spectator_election_item.html b/vote/templates/vote/spectator_election_item.html index ce68ec184c4ebe7c066fa5a6349372c4ae05aeb1..e2130ff57c00640efb529fc531d444da6cf75614 100644 --- a/vote/templates/vote/spectator_election_item.html +++ b/vote/templates/vote/spectator_election_item.html @@ -10,7 +10,7 @@ {% endif %} <hr> <div class="list-group mt-3"> - {% if election.closed and not election.result_unpublished %} + {% if election.closed and election.result_published %} <div class="alert alert-info" role="alert"> <h4 class="alert-heading">Election result:</h4> <hr> diff --git a/vote/templates/vote/vote.html b/vote/templates/vote/vote.html index f055095442c7e8fbf6f59c568413c2de77578498..4d8ac08c7baa3263751b2b6da4737377731c1bf7 100644 --- a/vote/templates/vote/vote.html +++ b/vote/templates/vote/vote.html @@ -49,7 +49,7 @@ <thead class="thead-light"> <tr> <th>{% if election.voters_self_apply %}Applicant{% else %}Option{% endif %}</th> - {% if not election.disable_abstention %} + {% if election.enable_abstention %} <th class="choice text-center">Abstention</th> {% endif %} <th class="choice text-center text-success">YES</th> diff --git a/vote/tests.py b/vote/tests.py index 272439f950bb2369ac5fe9cb8d4b128ecc30e043..48d990e09635903acd70012f403e262b0ddeaa7d 100644 --- a/vote/tests.py +++ b/vote/tests.py @@ -1,7 +1,8 @@ -from datetime import timedelta +from datetime import timedelta, datetime from django.test import TestCase from django.utils import timezone +from freezegun import freeze_time from vote.models import Election, Enc32, Voter, Session from vote.selectors import closed_elections, open_elections, published_elections, upcoming_elections @@ -27,10 +28,11 @@ class VoterTestCase(TestCase): class ElectionSelectorsTest(TestCase): def test_election_selectors(self) -> None: - now = timezone.now() # but why does freezegun not work in the query sets - before = now - timedelta(minutes=50) - bbefore = now - timedelta(minutes=100) - after = now + timedelta(minutes=50) + now = datetime(year=2021, month=4, day=1, tzinfo=timezone.get_fixed_timezone(5)) + before = now - timedelta(seconds=5) + bbefore = now - timedelta(seconds=10) + after = now + timedelta(seconds=5) + freeze_time(now).start() session = Session.objects.create(title="TEST") # upcoming elections @@ -44,9 +46,9 @@ class ElectionSelectorsTest(TestCase): # published elections all_published = set() all_published.add(Election.objects.create(session=session, start_date=bbefore, end_date=before, - result_unpublished=False)) + result_published=True)) all_published.add(Election.objects.create(session=session, start_date=before, end_date=now, - result_unpublished=False)) + result_published=True)) # closed (not published) elections all_closed = set() all_closed.add(Election.objects.create(session=session, start_date=bbefore, end_date=before)) @@ -68,13 +70,13 @@ class ElectionSelectorsTest(TestCase): published = published_elections(session) self.assertEqual(all_published, set(published)) for e in published: - self.assertTrue(e.started and e.closed and not e.is_open and not e.result_unpublished) + self.assertTrue(e.started and e.closed and not e.is_open and e.result_published) # test closed closed = closed_elections(session) self.assertEqual(all_closed, set(closed)) for e in closed: - self.assertTrue(e.started and e.closed and not e.is_open and e.result_unpublished) + self.assertTrue(e.started and e.closed and not e.is_open and not e.result_published) def gen_data():