Skip to content

Commit

Permalink
Merge pull request #1686 from DemocracyClub/feature/cancellation-reason
Browse files Browse the repository at this point in the history
Feature/cancellation reason
  • Loading branch information
Bekabyx authored Oct 5, 2023
2 parents 2fa7b7c + 3347248 commit 580de80
Show file tree
Hide file tree
Showing 6 changed files with 204 additions and 105 deletions.
14 changes: 14 additions & 0 deletions wcivf/apps/elections/import_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,11 @@ def import_metadata_from_ee(self, election):
election.requires_voter_id = requires_voter_id
updated = True

cancellation_reason = ee_data["cancellation_reason"]
if cancellation_reason:
election.cancellation_reason = cancellation_reason
updated = True

voting_system = ee_data["voting_system"]
if voting_system:
election.voting_system = VotingSystem.objects.update_or_create(
Expand Down Expand Up @@ -447,6 +452,7 @@ def import_metadata_from_ee(self, ballot):
self.set_voting_system(ballot)
self.set_metadata(ballot)
self.set_requires_voter_id(ballot)
self.set_cancellation_reason(ballot)
self.set_organisation_type(ballot)
self.set_division_type(ballot)
ballot.save()
Expand Down Expand Up @@ -498,6 +504,14 @@ def set_requires_voter_id(self, ballot):
ballot.requires_voter_id = ee_data["requires_voter_id"]
ballot.save()

def set_cancellation_reason(self, ballot):
if ballot.cancellation_reason and not self.force_update:
return
ee_data = self.ee_helper.get_data(ballot.ballot_paper_id)
if ee_data:
ballot.cancellation_reason = ee_data["cancellation_reason"]
ballot.save()

def set_organisation_type(self, ballot):
if ballot.post.organization_type and not self.force_update:
return
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# Generated by Django 4.2.3 on 2023-09-26 11:59

from django.db import migrations, models


class Migration(migrations.Migration):
dependencies = [
(
"elections",
"0041_election_ballot_papers_issued_election_electorate_and_more",
),
]

operations = [
migrations.AddField(
model_name="postelection",
name="cancellation_reason",
field=models.CharField(
blank=True,
choices=[
("NO_CANDIDATES", "No candidates"),
("EQUAL_CANDIDATES", "Equal candidates to seats"),
("UNDER_CONTESTED", "Fewer candidates than seats"),
("CANDIDATE_DEATH", "Death of a candidate"),
],
default=None,
max_length=16,
null=True,
),
),
]
52 changes: 35 additions & 17 deletions wcivf/apps/elections/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,13 @@ class InvalidPostcodeError(Exception):
pass


class ElectionCancellationReason(models.TextChoices):
NO_CANDIDATES = "NO_CANDIDATES", "No candidates"
EQUAL_CANDIDATES = "EQUAL_CANDIDATES", "Equal candidates to seats"
UNDER_CONTESTED = "UNDER_CONTESTED", "Fewer candidates than seats"
CANDIDATE_DEATH = "CANDIDATE_DEATH", "Death of a candidate"


def utc_to_local(utc_dt):
return utc_dt.replace(tzinfo=pytz.utc).astimezone(LOCAL_TZ)

Expand Down Expand Up @@ -403,6 +410,13 @@ class PostElection(TimeStampedModel):
help_text="Timestamp of when this ballot was updated in the YNR",
)
requires_voter_id = models.CharField(blank=True, null=True, max_length=50)
cancellation_reason = models.CharField(
max_length=16,
null=True,
blank=True,
choices=ElectionCancellationReason.choices,
default=None,
)

objects = PostElectionQuerySet.as_manager()

Expand Down Expand Up @@ -525,19 +539,30 @@ def short_cancelled_message_html(self):
if not self.cancelled:
return ""
message = None
if self.metadata and self.metadata.get("cancelled_election"):
title = self.metadata["cancelled_election"].get("title")
url = self.metadata["cancelled_election"].get("url")
message = title
if url:
message = """<strong> ❌ <a href="{}">{}</a></strong>""".format(
url, title
)

if self.cancellation_reason:
if self.cancellation_reason == "CANDIDATE_DEATH":
message = """<strong> ❌ This election has been cancelled due to the death of a candidate.</strong>"""
else:
message = """<strong> ❌ The poll for this election will not take place because it is uncontested.</strong>"""
else:
# Leaving this in for now as we transition away from metadata
if self.metadata and self.metadata.get("cancelled_election"):
title = self.metadata["cancelled_election"].get("title")
url = self.metadata["cancelled_election"].get("url")
message = title
if url:
message = (
"""<strong> ❌ <a href="{}">{}</a></strong>""".format(
url, title
)
)
if not message:
if self.election.in_past:
message = "(The poll for this election was cancelled)"
else:
message = "<strong>(The poll for this election has been cancelled)</strong>"

return mark_safe(message)

@property
Expand Down Expand Up @@ -627,15 +652,8 @@ def should_show_candidates(self):
return True
if not self.metadata:
return True
if reason := self.metadata.get("cancelled_election", {}).get(
"cancellation_reason", ""
):
if reason == "CANDIDATE_DEATH":
return False
if reason == "EQUAL_CANDIDATES":
return True
if reason == "UNDER_CONTESTED":
return True
if self.cancellation_reason in ["CANDIDATE_DEATH"]:
return False
return True


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,12 @@
{% load static %}
{% load i18n %}

{% if not object.contested and not object.metadata.cancelled_election %}
{% comment %} Case 1: Election cancelled, uncontested, number of candidates equal seats, no metadata{% endcomment %}
{% if object.winner_count == object.people.count %}
{% if object.cancellation_reason %}
{% if object.cancellation_reason == "EQUAL_CANDIDATES" %}
<h4>{% trans "Uncontested Election" %}</h4>

<p>
{% blocktrans trimmed with is_or_are=object.winner_count|pluralize:"is,are" winner_count=object.winner_count|apnumber post=object.post.full_label num_people=object.people.count|apnumber pluralise_candidates=object.people|pluralize pluralise_seat=object.winner_count|pluralize %}
This election was uncontested because the number of candidates who stood was equal to the number of available seats.
This election was cancelled because the number of candidates who stood was equal to the number of available seats.
There {{ is_or_are }} {{ winner_count }} seat{{ pluralise_seat }} in {{ post }}, and only {{ num_people }} candidate{{ pluralise_candidates }}.
{% endblocktrans %}
</p>
Expand All @@ -20,34 +18,46 @@ <h4>{% trans "Uncontested Election" %}</h4>
the winner{{ pluralise_candidates }}.
{% endblocktrans %}
</p>
{% comment %} Case 2: Election cancelled, uncontested and there are fewer candidates than seats, no metadata {% endcomment %}
{% elif object.winner_count > object.people.count %}
{% elif object.cancellation_reason == "UNDER_CONTESTED" %}
<h4>{% trans "Uncontested and Rescheduled Election" %}</h4>

<p>
{% blocktrans trimmed with winner_count=object.winner_count|apnumber post_label=object.post.full_label num_people=object.people.count|apnumber count counter=object.people.count %}
This election was uncontested because the number of candidates who stood was fewer than the number of available seats.
This election was cancelled because the number of candidates who stood was fewer than the number of available seats.
There is {{ winner_count }} seat in {{ post_label }}, and {{ num_people }} candidate.
{% plural %}
This election was uncontested because the number of candidates who stood was fewer than the number of available seats.
This election was cancelled because the number of candidates who stood was fewer than the number of available seats.
There are {{ winner_count }} seats in {{ post_label }}, and {{ num_people }} candidates.
{% endblocktrans %}
{% blocktrans trimmed with has_or_have=object.people|pluralize:"s,ve" plural=object.people|pluralize %}
No votes will be cast, and the candidate{{ plural }} below
ha{{ has_or_have }} been automatically declared the
winner{{ plural }}.

A new election to fill the unclaimed seat{{ plural }}
will be held within 35 working days of the original election date.
{% endblocktrans %}
</p>
{% comment %} Case 3: Election cancelled, uncontested and zero candidates, no metadata {% endcomment %}
{% if object.people %}
<p>
{% blocktrans trimmed with has_or_have=object.people|pluralize:"s,ve" plural=object.people|pluralize %}
No votes will be cast, and the candidate{{ plural }} below
ha{{ has_or_have }} been automatically declared the
winner{{ plural }}.
{% elif object.cancellation_reason == "NO_CANDIDATES" %}
<h4>{% trans "Uncontested and Rescheduled Election" %}</h4>
<p>
{% blocktrans %}
This election was cancelled because no candidates stood for the available seats.

A new election to fill the unclaimed seat{{ plural }}
will be held within 35 working days of the original election date.
{% endblocktrans %}
</p>
{% endif %}
A new election to fill the unclaimed seat{{ plural }}
will be held within 35 working days of the original election date.
{% endblocktrans %}
</p>
{% elif object.cancellation_reason == "CANDIDATE_DEATH" %}
<h4>{% trans "Cancelled and Rescheduled Election" %}</h4>
<p>
{% blocktrans %}
This election was cancelled due to the death of a candidate.

A new election to fill the unclaimed seat{{ plural }}
will be held within 35 working days of the original election date.
{% endblocktrans %}
</p>
{% endif %}
{% comment %} Case 4: Contested but cancelled for other reasons {% endcomment %}
{% else %}
{% if object.metadata.cancelled_election %}
<h4>{{ object.metadata.cancelled_election.title }}</h4>
Expand Down Expand Up @@ -75,4 +85,3 @@ <h4>{% trans "Cancelled Election" %}</h4>
<a href="{{ object.metadata.cancelled_election.url }}">{% trans "Read more" %}</a>
{% endif %}
</p>

Loading

0 comments on commit 580de80

Please sign in to comment.