class ElectionBuilder: def __init__(self, election_type, date): # init params if type(election_type) == str: election_type = ElectionType.objects.get( election_type=election_type) self.election_type = election_type if type(date) == str: date = datetime.strptime(date, "%Y-%m-%d") if type(date) == datetime: date = date.date() self.date = date # Initialise an IdBuiler object. # We'll build up an id string progressively # as we add properties to the election object self.id = IdBuilder(self.election_type.election_type, self.date) # core election data self.subtype = None self.organisation = None self.division = None self.contest_type = None # meta-data self._use_org = False self.notice = None self.source = '' self.snooped_election_id = None def with_subtype(self, subtype): valid_subtypes = ElectionSubType.objects.filter( election_type=self.election_type) if subtype not in valid_subtypes: raise ElectionSubType.ValidationError( "'%s' is not a valid subtype for election type '%s'" %\ (subtype, self.election_type) ) self.id = self.id.with_subtype(subtype.election_subtype) self.subtype = subtype return self def with_organisation(self, organisation): valid_election_types = organisation.election_types.all() if self.election_type not in valid_election_types: raise Organisation.ValidationError( "'%s' is not a valid organisation for election type '%s'" %\ (organisation, self.election_type) ) if organisation.start_date and organisation.start_date > self.date: raise Organisation.ValidationError( 'Organisation start date after election date') if organisation.end_date and organisation.end_date < self.date: raise Organisation.ValidationError( 'Organisation end date before election date') # if this is a top-level group id # we associate the election object with an organisation # but the organisation doesn't form part of the id if organisation.organisation_type == self.election_type.election_type: self._use_org = False self.organisation = Organisation.objects.get( organisation_type=self.election_type.election_type) else: self._use_org = True self.id = self.id.with_organisation(organisation.slug) self.organisation = organisation return self def with_division(self, division): if division.organisation != self.organisation: raise OrganisationDivision.ValidationError( "'%s' is not a child of '%s'" %\ (division, self.organisation) ) divisionset = division.divisionset if divisionset.start_date and divisionset.start_date > self.date: raise OrganisationDivisionSet.ValidationError( 'DivisionSet start date after election date') if divisionset.end_date and divisionset.end_date < self.date: raise OrganisationDivisionSet.ValidationError( 'DivisionSet end date before election date') self.id = self.id.with_division(division.slug) self.division = division return self def with_contest_type(self, contest_type): self.id = self.id.with_contest_type(contest_type) self.contest_type = contest_type return self def with_source(self, source): self.source = source return self def with_snooped_election(self, id): self.snooped_election_id = id return self def get_elected_role(self): if not self.organisation: return None try: return ElectedRole.objects.get(organisation=self.organisation, election_type=self.election_type) except ElectedRole.DoesNotExist: return None def get_voting_system(self): # Scottish council elections use Single Transferrable Vote if self._use_org: if self.organisation.territory_code == "SCT" and \ self.election_type.election_type == "local": return VotingSystem.objects.get(slug="STV") # The Constituency ballots in an Additional Member System # election are essentially FPTP if self.election_type.default_voting_system.slug == 'AMS' and\ self.subtype and self.subtype.election_subtype == 'c': return VotingSystem.objects.get(slug="FPTP") # otherwise we can rely on the election type return self.election_type.default_voting_system def get_seats_contested(self): if self.contest_type == "by": # Assume any by-election always elects one representative. # There may be edge cases where we need to edit this via /admin # but this is the best assumption we can make return 1 if self.election_type.election_type != "local": return 1 """ If this is an all-up local election, we can fairly safely return self.division.seats_total but at the moment we have no way to know if this is 'all-up' or not so doing this is likely to generate a lot of confusing wrong data TODO: Add an 'all-up' tickbox to the wizard for local elections Then we can either return self.division.seats_total or 1 here, which will mostly be right ..except for when it isn't ..which will be sometimes """ # otherwise don't attempt to guess return None def get_seats_total(self): if not self.division: return None return self.division.seats_total def __repr__(self): return self.id.__repr__() def to_title(self, id_type): if id_type == 'election': return self.election_type.name if id_type == 'subtype': return "{election} ({subtype})".format( election=self.election_type.name, subtype=self.subtype.name) parts = [] if self._use_org and self.organisation: parts.append(self.organisation.election_name) if self.division: parts.append("{}".format(self.division.name)) if self.subtype: parts.append("({})".format(self.subtype.name)) if self.contest_type == "by": parts.append('by-election') return " ".join(parts).strip() def __eq__(self, other): return self.id.__eq__(other.id) def _build(self, record): def merge_dicts(d1, d2): d3 = d1.copy() d3.update(d2) return d3 try: return Election.objects.get(election_id=record['election_id']) except Election.DoesNotExist: # return an instance of elections.models.Election # but don't persist it to the DB yet. # The calling code is responsible for calling .save() return Election(**merge_dicts( record, { 'poll_open_date': self.date, 'election_type': self.election_type, 'election_subtype': self.subtype, 'organisation': self.organisation, 'division': self.division, 'elected_role': self.get_elected_role(), 'voting_system': self.get_voting_system(), })) def build_election_group(self): return self._build({ 'election_id': self.id.election_group_id, 'election_title': self.to_title('election'), 'group': None, 'group_type': 'election', 'notice': None, 'source': '', 'snooped_election_id': None, }) def build_subtype_group(self, group): return self._build({ 'election_id': self.id.subtype_group_id, 'election_title': self.to_title('subtype'), 'group': group, 'group_type': 'subtype', 'notice': None, 'source': '', 'snooped_election_id': None, }) def build_organisation_group(self, group): return self._build({ 'election_id': self.id.organisation_group_id, 'election_title': self.to_title('organisation'), 'group': group, 'group_type': 'organisation', 'notice': None, 'source': '', 'snooped_election_id': None, }) def build_ballot(self, group): return self._build({ 'election_id': self.id.ballot_id, 'election_title': self.to_title('ballot'), 'group': group, 'group_type': None, 'notice': self.notice, 'source': self.source, 'snooped_election_id': self.snooped_election_id, 'seats_contested': self.get_seats_contested(), 'seats_total': self.get_seats_total(), })
class ElectionBuilder: def __init__(self, election_type, date): # init params if type(election_type) == str: election_type = get_cached_election_type(election_type) self.election_type = election_type if type(date) == str: date = datetime.strptime(date, "%Y-%m-%d") if type(date) == datetime: date = date.date() self.date = date # Initialise an IdBuiler object. # We'll build up an id string progressively # as we add properties to the election object self.id = IdBuilder(self.election_type.election_type, self.date) # core election data self.subtype = None self.organisation = None self.division = None self.contest_type = None # meta-data self._use_org = False self.notice = None self.source = "" self.snooped_election_id = None def with_subtype(self, subtype): valid_subtypes = get_cached_election_subtype(self.election_type) if subtype not in valid_subtypes: raise ElectionSubType.ValidationError( "'%s' is not a valid subtype for election type '%s'" % (subtype, self.election_type) ) self.id = self.id.with_subtype(subtype.election_subtype) self.subtype = subtype return self def with_organisation(self, organisation): valid_election_types = get_cached_valid_election_types(organisation) if self.election_type not in valid_election_types: raise Organisation.ValidationError( "'%s' is not a valid organisation for election type '%s'" % (organisation, self.election_type) ) if organisation.start_date and organisation.start_date > self.date: raise Organisation.ValidationError( "Organisation start date after election date" ) if organisation.end_date and organisation.end_date < self.date: raise Organisation.ValidationError( "Organisation end date before election date" ) # if this is a top-level group id # we associate the election object with an organisation # but the organisation doesn't form part of the id if organisation.organisation_type == self.election_type.election_type: self._use_org = False self.organisation = Organisation.objects.get( organisation_type=self.election_type.election_type ) else: self._use_org = True self.id = self.id.with_organisation(organisation.slug) self.organisation = organisation return self def with_division(self, division): if division.organisation != self.organisation: raise OrganisationDivision.ValidationError( "'%s' is not a child of '%s'" % (division, self.organisation) ) if ( self.subtype and self.subtype.election_subtype != division.division_election_sub_type ): raise OrganisationDivision.ValidationError( "election subtype is '%s' but division is of subtype '%s'" % (self.subtype.election_subtype, division.division_election_sub_type) ) divisionset = division.divisionset if divisionset.start_date and divisionset.start_date > self.date: raise OrganisationDivisionSet.ValidationError( "DivisionSet start date after election date" ) if divisionset.end_date and divisionset.end_date < self.date: raise OrganisationDivisionSet.ValidationError( "DivisionSet end date before election date" ) self.id = self.id.with_division(division.slug) self.division = division return self def with_contest_type(self, contest_type): self.id = self.id.with_contest_type(contest_type) self.contest_type = contest_type return self def with_source(self, source): self.source = source return self def with_snooped_election(self, id): self.snooped_election_id = id return self def get_elected_role(self): if not self.organisation: return None return get_cached_elected_role( self.organisation, election_type=self.election_type ) def get_voting_system(self): # Scottish and NI council elections use Single Transferrable Vote if self._use_org: if ( self.organisation.territory_code in ("SCT", "NIR") and self.election_type.election_type == "local" ): return get_cached_voting_system("STV") # The Constituency ballots in an Additional Member System # election are essentially FPTP if ( self.election_type.default_voting_system.slug == "AMS" and self.subtype and self.subtype.election_subtype == "c" ): return get_cached_voting_system("FPTP") # otherwise we can rely on the election type return self.election_type.default_voting_system def get_seats_contested(self): if self.contest_type == "by": # Assume any by-election always elects one representative. # There may be edge cases where we need to edit this via /admin # but this is the best assumption we can make return 1 if self.election_type.election_type != "local": if self.division and self.division.seats_total: return self.division.seats_total else: return 1 """ If this is an all-up local election, we can fairly safely return self.division.seats_total but at the moment we have no way to know if this is 'all-up' or not so doing this is likely to generate a lot of confusing wrong data TODO: Add an 'all-up' tickbox to the wizard for local elections Then we can either return self.division.seats_total or 1 here, which will mostly be right ..except for when it isn't ..which will be sometimes """ # otherwise don't attempt to guess return None def get_seats_total(self): if not self.division: return None return self.division.seats_total def __repr__(self): return self.id.__repr__() def to_title(self, id_type): if id_type == "election": return self.election_type.name parts = [] if self.subtype: subtype_title = "{election} ({subtype})".format( election=self.election_type.name, subtype=self.subtype.name ) if id_type == "subtype": return subtype_title else: parts.append(subtype_title) if self._use_org and self.organisation: if self.election_type.election_type == "mayor": parts.append(self.get_elected_role().elected_role_name) else: parts.append(self.organisation.election_name) if self.division: parts.append(self.division.name) if self.contest_type == "by": parts.append("by-election") return " ".join(parts).strip() def __eq__(self, other): return self.id.__eq__(other.id) def _build(self, record): def merge_dicts(d1, d2): d3 = d1.copy() d3.update(d2) return d3 existing_election = get_cached_private_elections( self.date, record["election_id"] ) if existing_election: return existing_election else: # return an instance of elections.models.Election # but don't persist it to the DB yet. # The calling code is responsible for calling .save() return Election( **merge_dicts( record, { "poll_open_date": self.date, "election_type": self.election_type, "election_subtype": self.subtype, "organisation": self.organisation, "division": self.division, "elected_role": self.get_elected_role(), "voting_system": self.get_voting_system(), }, ) ) def build_election_group(self): return self._build( { "election_id": self.id.election_group_id, "election_title": self.to_title("election"), "group": None, "group_type": "election", "notice": None, "source": "", "snooped_election_id": None, } ) def build_subtype_group(self, group, group_type="subtype"): return self._build( { "election_id": self.id.subtype_group_id, "election_title": self.to_title("subtype"), "group": group, "group_type": group_type, "notice": None, "source": "", "snooped_election_id": None, } ) def build_organisation_group(self, group): return self._build( { "election_id": self.id.organisation_group_id, "election_title": self.to_title("organisation"), "group": group, "group_type": "organisation", "notice": None, "source": "", "snooped_election_id": None, } ) def build_ballot(self, group): return self._build( { "election_id": self.id.ballot_id, "election_title": self.to_title("ballot"), "group": group, "group_type": None, "notice": self.notice, "source": self.source, "snooped_election_id": self.snooped_election_id, "seats_contested": self.get_seats_contested(), "seats_total": self.get_seats_total(), } )