Skip to content

Commit

Permalink
Merge pull request #108 from DemocracyClub/col-registration20241030
Browse files Browse the repository at this point in the history
City of London registration info
  • Loading branch information
chris48s authored Nov 4, 2024
2 parents 338928b + 6d8956b commit 6fdc644
Show file tree
Hide file tree
Showing 8 changed files with 316 additions and 71 deletions.
7 changes: 4 additions & 3 deletions Pipfile.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

57 changes: 57 additions & 0 deletions postcode_lookup/mock_responses.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
from response_builder.v1.builders.ballots import (
LocalBallotBuilder,
ParlBallotBuilder,
)
from response_builder.v1.builders.base import RootBuilder
from response_builder.v1.generated_responses import candidates
from response_builder.v1.generated_responses.root_responses import (
CANCELLED_BALLOT_CANDIDATE_DEATH,
CANCELLED_BALLOT_EQUAL_CANDIDATES,
Expand All @@ -11,6 +17,49 @@
SINGLE_LOCAL_FUTURE_BALLOT_WITHOUT_POLLING_STATION,
)


class CityOfLondonParlBallot(ParlBallotBuilder):
def __init__(self, poll_open_date, **kwargs):
super().__init__(**kwargs)
self.with_ballot_paper_id(
f"parl.cities-of-london-and-westminster.{poll_open_date}"
)
self.with_ballot_title(
"UK Parliamentary general election Cities of London and Westminster"
)
self.with_date(poll_open_date)
self.with_post_name("Cities of London and Westminster")
self.with_election_name("UK Parliamentary general election")
self.with_election_id(f"parl.{poll_open_date}")
self.with_candidates(candidates.all_candidates)


class CityOfLondonLocalBallot(LocalBallotBuilder):
def __init__(self, poll_open_date, **kwargs):
super().__init__(**kwargs)
self.with_ballot_paper_id(
f"local.city-of-london.aldersgate.{poll_open_date}"
)
self.with_ballot_title("City of London local election Aldersgate")
self.with_date(poll_open_date)
self.with_post_name("Aldersgate")
self.with_election_name("City of London local election")
self.with_election_id(f"local.city-of-london.{poll_open_date}")
self.with_candidates(candidates.all_candidates)


CITY_OF_LONDON_COUNCIL_AND_PARL_DIFFERENT_DAYS = (
RootBuilder()
.with_ballot(CityOfLondonLocalBallot("2025-03-20").build())
.with_ballot(CityOfLondonParlBallot("2025-05-01").build())
)
CITY_OF_LONDON_COUNCIL_AND_PARL_SAME_DAY = (
RootBuilder()
.with_ballot(CityOfLondonLocalBallot("2025-03-20").build())
.with_ballot(CityOfLondonParlBallot("2025-03-20").build())
)


__ALL__ = ("example_responses",)
example_responses = {
"AA1 1AA": {
Expand Down Expand Up @@ -69,4 +118,12 @@
# "description": "Police and Crime Commissioner ballot",
# "response": PCC_BALLOT,
# },
"AA1 1AL": {
"description": "City of London (Common Councilman) and UK Parl ballots on different upcoming dates",
"response": CITY_OF_LONDON_COUNCIL_AND_PARL_DIFFERENT_DAYS,
},
"AA1 1AM": {
"description": "City of London (Common Councilman) and UK Parl ballots on the same date",
"response": CITY_OF_LONDON_COUNCIL_AND_PARL_SAME_DAY,
},
}
88 changes: 75 additions & 13 deletions postcode_lookup/template_sorter.py
Original file line number Diff line number Diff line change
Expand Up @@ -151,9 +151,12 @@ def context(self):
context["can_register_vac"] = self.timetable.is_before(
TimetableEvent.VAC_APPLICATION_DEADLINE
)
context["htag"] = "h2"
context["htag_primary"] = "h2"
context["htag_secondary"] = "h3"
if self.response_type == ResponseTypes.MULTIPLE_DATES:
context["htag"] = "h3"
context["htag_primary"] = "h3"
context["htag_secondary"] = "h4"
context["toc_id"] = self.toc_id
return context

@property
Expand All @@ -162,7 +165,34 @@ def toc_label(self):

@property
def toc_id(self):
return "voter-registration"
return f"voter-registration-{self.timetable.poll_date}-{self.timetable.registration_deadline}"


class CityOfLondonRegistrationDateSection(RegistrationDateSection):
template_name = "includes/registration_timetable_city_of_london.html"

def __init__(self, *args, **kwargs) -> None:
self.with_headers = kwargs.pop("with_headers")
super().__init__(*args, **kwargs)

@property
def weight(self):
parent_weight = super().weight
return 0 if parent_weight == 0 else parent_weight + 1

@property
def context(self):
context = super().context
context["with_headers"] = self.with_headers
return context

@property
def toc_label(self):
return _("Voter registration")

@property
def toc_id(self):
return f"voter-registration-col-{self.timetable.poll_date}-{self.timetable.registration_deadline}"


class ElectionDateTemplateSorter:
Expand Down Expand Up @@ -199,7 +229,7 @@ def __init__(

self.polling_station_opening_times_str = _("7am – 10pm")
if any(
"city-of-london" in ballot.ballot_paper_id
ballot.ballot_paper_id.startswith("local.city-of-london.")
for ballot in self.date_data.ballots
):
self.polling_station_opening_times_str = _("8am – 8pm")
Expand All @@ -217,18 +247,50 @@ def __init__(
"timetable": self.timetable,
}

enabled_sections = [BallotSection]
if not self.all_cancelled:
enabled_sections.append(RegistrationDateSection)
enabled_sections = [BallotSection(**section_kwargs)]

city_of_london_ballots = [
b
for b in self.date_data.ballots
if not b.cancelled
and b.ballot_paper_id.startswith("local.city-of-london.")
]
other_ballots = [
b
for b in self.date_data.ballots
if not b.cancelled
and not b.ballot_paper_id.startswith("local.city-of-london.")
]
if len(other_ballots) > 0:
enabled_sections.append(
RegistrationDateSection(
data=self.date_data,
mode=self.current_mode,
response_type=self.response_type,
current_date=self.current_date,
timetable=from_election_id(
other_ballots[0].election_id, country=country
),
)
)
if len(city_of_london_ballots) > 0:
enabled_sections.append(
CityOfLondonRegistrationDateSection(
data=self.date_data,
mode=self.current_mode,
response_type=self.response_type,
current_date=self.current_date,
timetable=from_election_id(
city_of_london_ballots[0].election_id, country=country
),
with_headers=len(other_ballots) == 0,
)
)

if self.first_upcoming_date:
enabled_sections.append(PollingStationSection)
enabled_sections.append(PollingStationSection(**section_kwargs))

self.sections = sorted(
[section(**section_kwargs) for section in enabled_sections],
key=lambda sec: sec.weight,
# reverse=True,
)
self.sections = sorted(enabled_sections, key=lambda sec: sec.weight)


class TemplateSorter:
Expand Down
23 changes: 17 additions & 6 deletions postcode_lookup/templates/includes/registration_timetable.html
Original file line number Diff line number Diff line change
@@ -1,11 +1,22 @@
<{{ section.context.htag }} id="voter-registration">{% trans %}Voter registration{% endtrans %}</{{ section.context.htag }}>
<{{ section.context.htag_primary }} id="{{toc_id}}">{% trans %}Voter registration{% endtrans %}</{{ section.context.htag_primary }}>
{% if section.context.can_register %}
<h3>Register to vote</h3>
<p>You have until {{ section.timetable.registration_deadline|date_filter }}</p>
<p><a href="https://gov.uk/register-to-vote/" class="o-button o-button--primary o-button--dark o-external-link">
Register to vote</a></p>
<{{ section.context.htag_secondary }}>{% trans %}Register to vote{% endtrans %}</{{ section.context.htag_secondary }}>
<p>
{% trans date=section.timetable.registration_deadline|date_filter %}
You have until {{ date }}
{% endtrans %}
</p>
<p>
<a href="https://gov.uk/register-to-vote/" class="o-button o-button--primary o-button--dark o-external-link">
{% trans %}Register to vote{% endtrans %}
</a>
</p>
{% else %}
{% trans date=section.data.date|date_filter, registration_deadline=section.timetable.registration_deadline|date_filter %}
{%
trans
date=section.data.date|date_filter,
registration_deadline=section.timetable.registration_deadline|date_filter
%}
<p>The deadline to register to vote on {{ date }}
was {{ registration_deadline }}.</p>
<p>If you registered to vote before the deadline, then you will be able to vote in this election. <a
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
{% if section.context.with_headers %}
<{{ section.context.htag_primary }} id="{{toc_id}}">{% trans %}Voter registration{% endtrans %}</{{ section.context.htag_primary }}>
{% endif %}
{% if section.context.can_register %}
{% if section.context.with_headers %}
<{{ section.context.htag_secondary }}>{% trans %}Register to vote{% endtrans %}</{{ section.context.htag_secondary }}>
{% endif %}
<p>{% trans %}City of London council elections do not use the same electoral register as other elections.{% endtrans %}</p>
<p>
{% trans %}
Both residents and city workers are eligible to vote. There is one register
published annually, and the deadline to apply is 30 November each year.
The new register comes into force on 16 February each year,
and cannot be modified after that date.
{% endtrans %}
</p>
<p>
<a href="https://www.speakforthecity.com/" class="o-button o-button--primary o-button--dark o-external-link">
{% trans %}Register to vote in the City of London{% endtrans %}
</a>
</p>
{% else %}
<{{ section.context.htag_secondary }} id="{{toc_id}}">{% trans %}Registration in the City of London{% endtrans %}</{{ section.context.htag_secondary }}>
<p>{% trans %}City of London council elections do not use the same electoral register as other elections.{% endtrans %}</p>
{%
trans
date=section.data.date|date_filter,
registration_deadline=section.timetable.registration_deadline|date_filter
%}
<p>The deadline to register to vote in City of London local elections on {{ date }}
was {{ registration_deadline }}.</p>
<p>If you registered to vote before the deadline, then you will be able to vote in this election. <a
href="#electoral_services">Contact City of London</a> to check if you are on the register.</p>
<p>You can <a href="https://www.speakforthecity.com/">register to vote in the City of London</a> in future elections.</p>
{% endtrans %}
{% endif %}
50 changes: 50 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,13 @@
import uvicorn
from app import app
from starlette.testclient import TestClient
from template_sorter import (
ApiModes,
ElectionDateTemplateSorter,
TemplateSorter,
)
from uk_election_timetables.calendars import Country
from uk_election_timetables.election_ids import from_election_id


@pytest.fixture(scope="function")
Expand Down Expand Up @@ -33,3 +40,46 @@ def uvicorn_server():
time.sleep(0.3)
yield f"http://localhost:{port}"
proc.kill()


@pytest.fixture
def template_sorter():
def get_template_sorter(mock_response, date):
api_response = mock_response
# The dates exist in the api_response, but not
# in the format that matches the RootBuilder:
# dates = api_response._values["dates"] vs
# dates = api_response.dates so I've set it here.
api_response.dates = api_response._values["dates"]
mode = ApiModes.UPCOMING_ELECTIONS

sorter = TemplateSorter(
api_response=api_response, mode=mode, current_date=date
)
sorter.country = Country.ENGLAND
sorter.dates = api_response.dates

return sorter

return get_template_sorter


@pytest.fixture
def election_date_template_sorter():
def get_election_date_template_sorter(template_sorter, date):
election_date_sorter = ElectionDateTemplateSorter(
date_data=date,
country=template_sorter.country,
current_date=template_sorter.current_date,
response_type=template_sorter.response_type,
)
election_date_sorter.current_date = template_sorter.current_date

election_date_sorter.timetable = from_election_id(
template_sorter.dates[0].ballots[0].election_id,
country=template_sorter.country,
)

return election_date_sorter

return get_election_date_template_sorter
Loading

0 comments on commit 6fdc644

Please sign in to comment.