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

DemocracyClub / UK-Polling-Stations / ce17da77-6533-4c16-84f2-9af86882bdd1

20 Mar 2025 04:22PM UTC coverage: 73.575% (+0.2%) from 73.378%
ce17da77-6533-4c16-84f2-9af86882bdd1

Pull #8487

circleci

chris48s
 add unit test for get_full_ballots function
Pull Request #8487: WIP log/handle errors 2

27 of 34 new or added lines in 1 file covered. (79.41%)

18 existing lines in 3 files now uncovered.

4065 of 5525 relevant lines covered (73.57%)

0.74 hits per line

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

87.78
/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 EEFetcher, EEWrapper, EmptyEEWrapper
1✔
8
from data_finder.views import LogLookUpMixin, polling_station_current
1✔
9
from django.core.exceptions import ObjectDoesNotExist
1✔
10
from drf_spectacular.types import OpenApiTypes
1✔
11
from drf_spectacular.utils import OpenApiExample, OpenApiParameter, extend_schema
1✔
12
from rest_framework.permissions import IsAuthenticatedOrReadOnly
1✔
13
from rest_framework.response import Response
1✔
14
from rest_framework.viewsets import ViewSet
1✔
15
from uk_geo_utils.helpers import AddressSorter, Postcode
1✔
16

17
from .address import PostcodeResponseSerializer, get_bug_report_url
1✔
18
from .councils import tmp_fix_parl_24_scotland_details
1✔
19
from .mixins import parse_qs_to_python
1✔
20

21

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

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

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

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

44
    def get_ee_wrapper(self, postcode, rh, query_params):
1✔
UNCOV
45
        query_params = parse_qs_to_python(query_params)
×
46
        include_current = any(query_params.get("include_current", []))
×
47

UNCOV
48
        if rh.route_type == "multiple_addresses":
×
49
            return EmptyEEWrapper()
×
50

UNCOV
51
        if rh.elections_response:
×
52
            return EEWrapper(
×
53
                rh.elections_response["ballots"],
54
                request_success=rh.elections_response["request_success"],
55
                include_current=include_current,
56
            )
57

UNCOV
58
        return EEWrapper(
×
59
            **EEFetcher(postcode=postcode).fetch(), include_current=include_current
60
        )
61

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

81
        rh = RoutingHelper(postcode)
1✔
82

83
        # attempt to attach point and gss_codes
84
        try:
1✔
85
            loc = geocoder(postcode)
1✔
86
            location = loc.centroid
1✔
87
        except PostcodeError as e:
1✔
88
            return Response({"detail": e.args[0]}, status=400)
1✔
89

90
        ret["postcode_location"] = location
1✔
91

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

103
        ret["addresses"] = self.generate_addresses(rh)
1✔
104

105
        ret["polling_station_known"] = False
1✔
106
        ret["polling_station"] = None
1✔
107

108
        ee = self.get_ee_wrapper(postcode, rh, request.query_params)
1✔
109
        has_election = ee.has_election()
1✔
110
        if has_election:
1✔
111
            ret["council"] = tmp_fix_parl_24_scotland_details(ret["council"], ee)
1✔
112

113
            # get polling station if there is an election in this area
114
            ret["polling_station_known"] = False
1✔
115
            ret["polling_station"] = self.generate_polling_station(rh)
1✔
116
            if ret["polling_station"]:
1✔
117
                if polling_station_current(ret["polling_station"]):
1✔
118
                    ret["polling_station_known"] = True
1✔
119
                else:
120
                    ret["polling_station"] = None
1✔
121
            if ret["polling_station"] and not ret["council"]:
1✔
UNCOV
122
                ret["council"] = ret["polling_station"].council
×
123

124
        # get advance voting station
125
        ret["advance_voting_station"] = self.generate_advance_voting_station(rh)
1✔
126

127
        ret["metadata"] = ee.get_metadata()
1✔
128

129
        if request.query_params.get("all_future_ballots", None):
1✔
UNCOV
130
            ret["ballots"] = ee.get_all_ballots()
×
131
        else:
132
            ret["ballots"] = ee.get_ballots_for_next_date()
1✔
133

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

147
        ret["report_problem_url"] = get_bug_report_url(
1✔
148
            request, ret["polling_station_known"]
149
        )
150

151
        serializer = PostcodeResponseSerializer(
1✔
152
            ret, read_only=True, context={"request": request}
153
        )
154
        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