• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

DemocracyClub / UK-Polling-Stations / 82ca43a4-4730-4f55-9cc1-847b31abfd78

06 Jun 2024 10:10AM UTC coverage: 71.051% (-0.01%) from 71.062%
82ca43a4-4730-4f55-9cc1-847b31abfd78

Pull #7346

circleci

awdem
Import script for Waltham Forest (2024-07-04) (closes #7345)
Pull Request #7346: Import script for Waltham Forest (2024-07-04) (closes #7345)

3765 of 5299 relevant lines covered (71.05%)

0.71 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

95.65
/polling_stations/apps/data_finder/helpers/routing.py
1
from urllib.parse import urlencode
1✔
2

3
from addressbase.models import Address
1✔
4

5
# use a postcode to decide which endpoint the user should be directed to
6
from councils.models import CouncilGeography
1✔
7
from data_finder.helpers.baked_data_helper import (
1✔
8
    LocalParquetElectionsHelper,
9
    NoOpElectionsHelper,
10
)
11
from django.conf import settings
1✔
12
from django.urls import reverse
1✔
13
from django.utils.functional import cached_property
1✔
14
from uk_geo_utils.helpers import Postcode
1✔
15

16

17
class RoutingHelper:
1✔
18
    _query_params_to_preserve = {
1✔
19
        "utm_content",
20
        "utm_medium",
21
        "utm_source",
22
        "utm_campaign",
23
    }
24

25
    def __init__(self, postcode):
1✔
26
        self.postcode = Postcode(postcode)
1✔
27
        self.addresses = self.get_addresses()
1✔
28
        self.elections_backend = self.get_elections_backend()
1✔
29
        self._elections_response = None
1✔
30

31
    def get_elections_backend(self):
1✔
32
        if getattr(settings, "USE_LOCAL_PARQUET_ELECTIONS", False):
1✔
33
            return LocalParquetElectionsHelper
×
34
        return NoOpElectionsHelper
1✔
35

36
    def get_addresses(self):
1✔
37
        return Address.objects.filter(postcode=self.postcode.with_space).select_related(
1✔
38
            "uprntocouncil"
39
        )
40

41
    @property
1✔
42
    def councils(self):
1✔
43
        """
44
        This method returns None when all the addresses in the postcode are in the same council, and otherwise returns
45
        a list of council_ids that the postcode overlaps.
46

47
        It is also being pressed into service to set a `_council_name` property on all the address objects with the
48
        postcode. This means we want to call this before serializing the postcode response object in the API.
49
        This is not a nice way of doing it, but means we only hit the db once, and can figure out how to do it nicer
50
        after May 6th.
51
        """
52
        gss_codes = {
1✔
53
            a: a.uprntocouncil.lad for a in self.addresses if a.uprntocouncil.lad
54
        }
55

56
        council_map = {
1✔
57
            v[0]: v
58
            for v in CouncilGeography.objects.filter(
59
                gss__in=set(gss_codes.values())
60
            ).values_list("gss", "council_id", "council__name")
61
        }
62
        """
1✔
63
        This is a bit of a hack because it adds a property to each address objects in self.addresses, so that
64
        it is already set when the address serializer populates the 'council' field with the council_name property
65
        from each address.
66
        """
67
        for address, gss in gss_codes.items():
1✔
68
            address._council_name = council_map.get(gss)[2]
1✔
69

70
        if len(council_map) == 1:
1✔
71
            return None
1✔
72
        return [v[1] for v in council_map.values()]
1✔
73

74
    @property
1✔
75
    def polling_stations(self):
1✔
76
        return {address.polling_station_id for address in self.addresses}
1✔
77

78
    @property
1✔
79
    def has_addresses(self):
1✔
80
        return bool(self.addresses)
1✔
81

82
    @property
1✔
83
    def no_stations(self):
1✔
84
        """Return true if there are addresses, but no polling station information"""
85
        return self.polling_stations == {""}
1✔
86

87
    @property
1✔
88
    def addresses_have_single_station(self):
1✔
89
        """Check if all addresses have the same polling station id and
90
        that it is not an empty string"""
91
        if len(self.polling_stations) == 1:
1✔
92
            return bool(list(self.polling_stations)[0])
1✔
93
        return False
1✔
94

95
    @property
1✔
96
    def elections_response(self):
1✔
97
        if not self._elections_response:
1✔
98
            self.lookup_elections()
1✔
99
        return self._elections_response
1✔
100

101
    def lookup_elections(self):
1✔
102
        self._elections_response = self.elections_backend().get_response_for_postcode(
1✔
103
            self.postcode
104
        )
105
        return self._elections_response
1✔
106

107
    @property
1✔
108
    def split_elections(self):
1✔
109
        if self.elections_response.get("address_picker"):
1✔
110
            return True
×
111
        return False
1✔
112

113
    @cached_property
1✔
114
    def route_type(self):
1✔
115
        if not self.has_addresses:
1✔
116
            # Postcode is not in addressbase
117
            return "postcode"
1✔
118
        if self.councils and self.no_stations:
1✔
119
            # multiple councils and no stations for any address in postcode
120
            return "multiple_addresses"
1✔
121

122
        self.lookup_elections()
1✔
123

124
        if self.split_elections:
1✔
125
            return "multiple_addresses"
×
126
        if self.no_stations:
1✔
127
            # We don't have any station information for this address
128
            return "postcode"
1✔
129
        if self.addresses_have_single_station:
1✔
130
            # all the addresses in this postcode
131
            # map to one polling station
132
            return "single_address"
1✔
133
        # addresses in this postcode map to
134
        # multiple polling stations
135
        return "multiple_addresses"
1✔
136

137
    @cached_property
1✔
138
    def view(self):
1✔
139
        if self.route_type == "single_address":
1✔
140
            # all the addresses in this postcode
141
            # map to one polling station
142
            return "address_view"
1✔
143
        if self.route_type == "multiple_addresses":
1✔
144
            # addresses in this postcode map to
145
            # multiple polling stations
146
            return "address_select_view"
1✔
147
        if self.route_type == "postcode":
1✔
148
            # postcode is not in addressbase table or we
149
            # don't have any polling station information for it.
150
            return "postcode_view"
1✔
151
        return None
×
152

153
    @cached_property
1✔
154
    def kwargs(self):
1✔
155
        if self.route_type == "single_address":
1✔
156
            return {"uprn": self.addresses[0].uprn}
1✔
157
        return {"postcode": self.postcode.without_space}
1✔
158

159
    def get_canonical_url(self, request, preserve_query=True):
1✔
160
        """Returns a canonical URL to route to, preserving any important query parameters"""
161
        url = reverse(self.view, kwargs=self.kwargs)
1✔
162
        query = urlencode(
1✔
163
            [
164
                (k, request.GET.getlist(k))
165
                for k in request.GET
166
                if k in self._query_params_to_preserve
167
            ],
168
            doseq=True,
169
        )
170
        if query and preserve_query:
1✔
171
            url += "?" + query
1✔
172
        return url
1✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc