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

DemocracyClub / WhoCanIVoteFor / e1a02576-fed3-4822-b3d7-83768ed61fb1

09 Oct 2025 04:45PM UTC coverage: 58.669% (+0.1%) from 58.523%
e1a02576-fed3-4822-b3d7-83768ed61fb1

Pull #2299

circleci

chris48s
register markdown_subset filter, use it for candidate statements
Pull Request #2299: WIP render good markdown, but not bad markdown

11 of 11 new or added lines in 1 file covered. (100.0%)

268 existing lines in 11 files now uncovered.

2927 of 4989 relevant lines covered (58.67%)

0.59 hits per line

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

0.0
/wcivf/apps/ppc_2024/management/commands/import_2024_ppcs.py
1
"""
2
Importer for all the corporate overlords
3
"""
4

5
import contextlib
×
6
import csv
×
7
from dataclasses import dataclass
×
8
from typing import Dict, List, Optional
×
UNCOV
9
from urllib.parse import urljoin
×
10

11
import requests
×
12
from django.conf import settings
×
13
from django.core.management.base import BaseCommand
×
14
from django.db import transaction
×
15
from parties.models import Party
×
UNCOV
16
from people.models import Person
×
UNCOV
17
from ppc_2024.models import PPCPerson
×
18

19

UNCOV
20
class BlankRowException(ValueError):
×
UNCOV
21
    ...
×
22

23

24
def clean_party_id(party_id):
×
25
    if not party_id:
×
UNCOV
26
        return None
×
UNCOV
27
    if party_id.startswith("ynmp-party"):
×
28
        # special case, just return this ID
29
        # (independents or speaker)
30
        return party_id
×
31

32
    if "-" in party_id:
×
33
        if party_id.startswith("joint-party:"):
×
UNCOV
34
            return party_id
×
35
        return f"joint-party:{party_id}"
×
36

UNCOV
37
    return f"PP{party_id}"
×
38

39

40
@dataclass
×
41
class CSVRow:
×
42
    person_name: str
×
43
    party_id: str
×
44
    person_id: str
×
45
    constituency_name: str
×
UNCOV
46
    region_name: str
×
47
    sheet_row: dict
×
48

49
    @classmethod
×
50
    def from_csv_row(cls, row: dict):
×
51
        party_id = clean_party_id(row.pop("Party ID", None))
×
UNCOV
52
        if not party_id:
×
53
            raise BlankRowException("No party ID")
×
54

55
        person_name = row.pop("Candidate Name")
×
56
        person_id = row.pop("DC Candidate ID")
×
UNCOV
57
        constituency_name = row.pop("Constituency")
×
58
        region_name = row.pop("Nation / Region")
×
59

UNCOV
60
        sheet_row = row
×
UNCOV
61
        return cls(
×
62
            party_id=party_id,
63
            person_name=person_name,
64
            person_id=person_id,
65
            constituency_name=constituency_name,
66
            region_name=region_name,
67
            sheet_row=sheet_row,
68
        )
69

70

71
class Command(BaseCommand):
×
UNCOV
72
    def delete_all_ppcs(self):
×
73
        PPCPerson.objects.all().delete()
×
74

75
    def get_person(self, person_id):
×
76
        if not person_id:
×
UNCOV
77
            return None
×
78
        try:
×
79
            return Person.objects.get(ynr_id=person_id)
×
80
        except Person.DoesNotExist:
×
81
            # if this person doesn't exist in WCIVF
82
            # this could be due to a merge.
83
            # See if we can get an alternative person id from YNR
84
            url = urljoin(
×
85
                settings.YNR_BASE, f"/api/next/person_redirects/{person_id}"
86
            )
UNCOV
87
            req = requests.get(url)
×
UNCOV
88
            if req.status_code != 200:
×
UNCOV
89
                raise
×
90

UNCOV
91
            result = req.json()
×
92

UNCOV
93
            if "new_person_id" not in result:
×
94
                # we couldn't find an alt person id, re-raise the exception
95
                raise
×
96

97
            try:
×
98
                # see if the alt person id exists
99
                return Person.objects.get(ynr_id=result["new_person_id"])
×
100
            except Person.DoesNotExist:
×
101
                # this person still doesn't exist in WCIVF
102
                # re-raise the exception
103
                raise
×
104

105
    def create_ppc(self, data: CSVRow):
×
106
        print(data.party_id)
×
107
        party: Party = Party.objects.get(ec_id=data.party_id)
×
108

109
        person: Optional[Person] = None
×
UNCOV
110
        with contextlib.suppress(Person.DoesNotExist):
×
111
            person = self.get_person(data.person_id)
×
112

UNCOV
113
        return PPCPerson.objects.create(
×
114
            person_name=data.person_name,
115
            party=party,
116
            person=person,
117
            constituency_name=data.constituency_name,
118
            region_name=data.region_name,
119
            sheet_row=data.sheet_row,
120
        )
121

UNCOV
122
    @transaction.atomic
×
UNCOV
123
    def handle(self, **options):
×
UNCOV
124
        self.delete_all_ppcs()
×
UNCOV
125
        counter = 0
×
UNCOV
126
        req = requests.get(PPCPerson.CSV_URL)
×
UNCOV
127
        reader: List[Dict] = csv.DictReader(
×
128
            req.content.decode("utf8").splitlines()
129
        )
UNCOV
130
        for row in reader:
×
UNCOV
131
            try:
×
UNCOV
132
                data = CSVRow.from_csv_row(row)
×
UNCOV
133
                self.create_ppc(data)
×
UNCOV
134
            except (BlankRowException, ValueError):
×
UNCOV
135
                self.stderr.write(f"Error importing row: {row}")
×
UNCOV
136
                continue
×
137

UNCOV
138
            counter += 1
×
UNCOV
139
        print(counter)
×
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