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

DemocracyClub / UK-Polling-Stations / 2a5e4348-521a-4792-836b-97464834abef

13 Mar 2025 01:20PM UTC coverage: 71.881% (+0.04%) from 71.837%
2a5e4348-521a-4792-836b-97464834abef

push

circleci

GeoWill
Update import_councils to use 2024 boundaries

3998 of 5562 relevant lines covered (71.88%)

0.72 hits per line

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

85.87
/polling_stations/apps/api/postcode.py
1
from data_finder.helpers import (
1✔
2
    PostcodeError,
3
    RoutingHelper,
4
    geocode,
5
    get_council,
6
)
7
from data_finder.helpers.every_election import (
1✔
8
    EmptyEveryElectionWrapper,
9
)
10
from data_finder.views import LogLookUpMixin, polling_station_current
1✔
11
from django.core.exceptions import ObjectDoesNotExist
1✔
12
from drf_spectacular.types import OpenApiTypes
1✔
13
from drf_spectacular.utils import OpenApiExample, OpenApiParameter, extend_schema
1✔
14
from rest_framework.permissions import IsAuthenticatedOrReadOnly
1✔
15
from rest_framework.response import Response
1✔
16
from rest_framework.viewsets import ViewSet
1✔
17
from uk_geo_utils.helpers import AddressSorter, Postcode
1✔
18

19
from .address import PostcodeResponseSerializer, get_bug_report_url
1✔
20
from .councils import tmp_fix_parl_24_scotland_details
1✔
21
from .mixins import parse_qs_to_python
1✔
22

23

24
class PostcodeViewSet(ViewSet, LogLookUpMixin):
1✔
25
    permission_classes = [IsAuthenticatedOrReadOnly]
1✔
26
    http_method_names = ["get", "post", "head", "options"]
1✔
27
    lookup_field = "postcode"
1✔
28
    serializer_class = PostcodeResponseSerializer
1✔
29

30
    def generate_addresses(self, routing_helper):
1✔
31
        if routing_helper.route_type == "multiple_addresses":
1✔
32
            sorter = AddressSorter(routing_helper.addresses)
1✔
33
            return sorter.natural_sort()
1✔
34
        return []
1✔
35

36
    def generate_polling_station(self, routing_helper):
1✔
37
        if routing_helper.route_type == "single_address":
1✔
38
            return routing_helper.addresses[0].polling_station_with_elections()
1✔
39
        return None
1✔
40

41
    def generate_advance_voting_station(self, routing_helper):
1✔
42
        if routing_helper.route_type == "single_address":
1✔
43
            return routing_helper.addresses[0].uprntocouncil.advance_voting_station
1✔
44
        return None
1✔
45

46
    def get_ee_wrapper(self, postcode, rh, query_params):
1✔
47
        if rh.route_type == "multiple_addresses":
×
48
            return EmptyEveryElectionWrapper()
×
49
        if rh.elections_response:
×
50
            return rh.elections_backend.ee_wrapper(rh.elections_response)
×
51
        kwargs = {}
×
52
        query_params = parse_qs_to_python(query_params)
×
53
        if include_current := query_params.get("include_current", False):
×
54
            kwargs["include_current"] = any(include_current)
×
55
        return rh.elections_backend.ee_wrapper(postcode, **kwargs)
×
56

57
    @extend_schema(
1✔
58
        parameters=[
59
            OpenApiParameter(
60
                name="postcode",
61
                type=OpenApiTypes.STR,
62
                location=OpenApiParameter.PATH,
63
                description="A Valid UK postcode",
64
                examples=[
65
                    OpenApiExample(
66
                        "Example 1", summary="Buckingham Palace", value="SW1A 1AA"
67
                    ),
68
                ],
69
            )
70
        ]
71
    )
72
    def retrieve(self, request, postcode=None, format=None, geocoder=geocode, log=True):
1✔
73
        postcode = Postcode(postcode)
1✔
74
        ret = {}
1✔
75

76
        rh = RoutingHelper(postcode)
1✔
77

78
        # attempt to attach point and gss_codes
79
        try:
1✔
80
            loc = geocoder(postcode)
1✔
81
            location = loc.centroid
1✔
82
        except PostcodeError as e:
1✔
83
            return Response({"detail": e.args[0]}, status=400)
1✔
84

85
        ret["postcode_location"] = location
1✔
86

87
        # council object
88
        if rh.councils or not loc:
1✔
89
            # We can't assign this postcode to exactly one council
90
            council = None
×
91
        else:
92
            try:
1✔
93
                council = get_council(loc)
1✔
94
            except ObjectDoesNotExist:
1✔
95
                return Response({"detail": "Internal server error"}, 500)
1✔
96
        ret["council"] = council
1✔
97

98
        ret["addresses"] = self.generate_addresses(rh)
1✔
99

100
        ret["polling_station_known"] = False
1✔
101
        ret["polling_station"] = None
1✔
102

103
        ee = self.get_ee_wrapper(postcode, rh, request.query_params)
1✔
104
        has_election = ee.has_election()
1✔
105
        if has_election:
1✔
106
            ret["council"] = tmp_fix_parl_24_scotland_details(ret["council"], ee)
1✔
107

108
            # get polling station if there is an election in this area
109
            ret["polling_station_known"] = False
1✔
110
            ret["polling_station"] = self.generate_polling_station(rh)
1✔
111
            if ret["polling_station"]:
1✔
112
                if polling_station_current(ret["polling_station"]):
1✔
113
                    ret["polling_station_known"] = True
1✔
114
                else:
115
                    ret["polling_station"] = None
1✔
116
            if ret["polling_station"] and not ret["council"]:
1✔
117
                ret["council"] = ret["polling_station"].council
×
118

119
        # get advance voting station
120
        ret["advance_voting_station"] = self.generate_advance_voting_station(rh)
1✔
121

122
        ret["metadata"] = ee.get_metadata()
1✔
123

124
        if request.query_params.get("all_future_ballots", None):
1✔
125
            ret["ballots"] = ee.get_all_ballots()
×
126
        else:
127
            ret["ballots"] = ee.get_ballots_for_next_date()
1✔
128

129
        # create log entry
130
        log_data = {}
1✔
131
        log_data["we_know_where_you_should_vote"] = ret["polling_station_known"]
1✔
132
        log_data["location"] = location
1✔
133
        log_data["council"] = council
1✔
134
        log_data["brand"] = "api"
1✔
135
        log_data["language"] = ""
1✔
136
        log_data["api_user"] = request.user
1✔
137
        log_data["has_election"] = has_election
1✔
138
        if log and not ret["addresses"]:
1✔
139
            self.log_postcode(postcode, log_data, "api")
×
140
            # don't log 'address select' hits
141

142
        ret["report_problem_url"] = get_bug_report_url(
1✔
143
            request, ret["polling_station_known"]
144
        )
145

146
        serializer = PostcodeResponseSerializer(
1✔
147
            ret, read_only=True, context={"request": request}
148
        )
149
        return Response(serializer.data)
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

© 2026 Coveralls, Inc