def test_eq_not_equal(self): obj1 = (IdBuilder("local", date( 2018, 5, 3)).with_organisation("test-org").with_division("test-division")) obj2 = IdBuilder("local", date(2018, 5, 3)).with_organisation("test-org") self.assertNotEqual(obj1, obj2)
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 test_gla_without_subtype(self): id = IdBuilder("gla", date(2018, 5, 3)) election_id = id.election_group_id self.assertEqual("gla.2018-05-03", election_id) with self.assertRaises(ValueError): id.subtype_group_id with self.assertRaises(ValueError): id.organisation_group_id with self.assertRaises(ValueError): id.ballot_id
def test_gla_additional(self): id = IdBuilder("gla", date(2018, 5, 3)).with_subtype("a") election_id = id.election_group_id self.assertEqual("gla.2018-05-03", election_id) subtype_id = id.subtype_group_id self.assertEqual("gla.a.2018-05-03", subtype_id) with self.assertRaises(ValueError): id.organisation_group_id ballot_id = id.ballot_id self.assertEqual("gla.a.2018-05-03", ballot_id) self.assertEqual(["gla.2018-05-03", "gla.a.2018-05-03"], id.ids)
def test_local_no_org_no_div(self): id = IdBuilder("local", date(2018, 5, 3)) election_id = id.election_group_id self.assertEqual("local.2018-05-03", election_id) with self.assertRaises(ValueError): id.subtype_group_id with self.assertRaises(ValueError): id.organisation_group_id with self.assertRaises(ValueError): id.ballot_id self.assertEqual(["local.2018-05-03"], id.ids)
def test_local_no_org_with_div(self): id = IdBuilder("local", date(2018, 5, 3)).with_division("test-division") with self.assertRaises(ValueError): id.election_group_id with self.assertRaises(ValueError): id.subtype_group_id with self.assertRaises(ValueError): id.organisation_group_id with self.assertRaises(ValueError): id.ballot_id self.assertEqual([], id.ids)
def test_pcc_mayor_no_org(self): for election_type in ("pcc", "mayor"): id = IdBuilder(election_type, date(2018, 5, 3)) election_id = id.election_group_id self.assertEqual("%s.2018-05-03" % (election_type), election_id) with self.assertRaises(ValueError): id.subtype_group_id with self.assertRaises(ValueError): id.organisation_group_id with self.assertRaises(ValueError): id.ballot_id self.assertEqual(["%s.2018-05-03" % (election_type)], id.ids)
def test_nia_parl_europarl_no_div(self): for election_type in ("nia", "parl", "europarl"): id = IdBuilder(election_type, date(2018, 5, 3)) election_id = id.election_group_id self.assertEqual("%s.2018-05-03" % (election_type), election_id) with self.assertRaises(ValueError): id.subtype_group_id with self.assertRaises(ValueError): id.organisation_group_id with self.assertRaises(ValueError): id.ballot_id self.assertEqual(["%s.2018-05-03" % (election_type)], id.ids)
def test_naw_sp_without_subtype(self): for election_type in ("naw", "sp", "senedd"): id = IdBuilder(election_type, date(2018, 5, 3)).with_division("test-division") election_id = id.election_group_id self.assertEqual("%s.2018-05-03" % (election_type), election_id) with self.assertRaises(ValueError): id.subtype_group_id with self.assertRaises(ValueError): id.organisation_group_id with self.assertRaises(ValueError): id.ballot_id self.assertEqual(["%s.2018-05-03" % (election_type)], id.ids)
def test_gla_constituency_with_division(self): id = (IdBuilder("gla", date(2018, 5, 3)).with_subtype("c").with_division("test-div")) election_id = id.election_group_id self.assertEqual("gla.2018-05-03", election_id) subtype_id = id.subtype_group_id self.assertEqual("gla.c.2018-05-03", subtype_id) with self.assertRaises(ValueError): id.organisation_group_id ballot_id = id.ballot_id self.assertEqual("gla.c.test-div.2018-05-03", ballot_id) self.assertEqual([ "gla.2018-05-03", "gla.c.2018-05-03", "gla.c.test-div.2018-05-03" ], id.ids)
def test_explicit_contest_type(self): for contest_type in ("election", "ELECTION"): id = (IdBuilder("local", date( 2018, 5, 3)).with_organisation("test-org").with_division( "test-division").with_contest_type(contest_type)) election_id = id.election_group_id self.assertEqual("local.2018-05-03", election_id) organisation_id = id.organisation_group_id self.assertEqual("local.test-org.2018-05-03", organisation_id) ballot_id = id.ballot_id self.assertEqual("local.test-org.test-division.2018-05-03", ballot_id) self.assertEqual( [ "local.2018-05-03", "local.test-org.2018-05-03", "local.test-org.test-division.2018-05-03", ], id.ids, )
def test_local_with_org_with_div(self): id = (IdBuilder("local", date( 2018, 5, 3)).with_organisation("test-org").with_division("test-division")) election_id = id.election_group_id self.assertEqual("local.2018-05-03", election_id) with self.assertRaises(ValueError): id.subtype_group_id organisation_id = id.organisation_group_id self.assertEqual("local.test-org.2018-05-03", organisation_id) ballot_id = id.ballot_id self.assertEqual("local.test-org.test-division.2018-05-03", ballot_id) self.assertEqual( [ "local.2018-05-03", "local.test-org.2018-05-03", "local.test-org.test-division.2018-05-03", ], id.ids, )
def test_by_elections(self): for contest_type in ("by", "BY", "bY-elEction", "by eLECTion"): id = (IdBuilder("local", date( 2018, 5, 3)).with_organisation("test-org").with_division( "test-division").with_contest_type(contest_type)) election_id = id.election_group_id self.assertEqual("local.2018-05-03", election_id) organisation_id = id.organisation_group_id self.assertEqual("local.test-org.2018-05-03", organisation_id) ballot_id = id.ballot_id self.assertEqual("local.test-org.test-division.by.2018-05-03", ballot_id) self.assertEqual( [ "local.2018-05-03", "local.test-org.2018-05-03", "local.test-org.test-division.by.2018-05-03", ], id.ids, )
def test_naw_sp_with_org(self): for election_type in ("naw", "sp", "senedd"): with self.assertRaises(ValueError): IdBuilder(election_type, date(2018, 5, 3)).with_organisation("test-org")
def test_invalid_election_type(self): with self.assertRaises(ValueError): IdBuilder("foo", date(2018, 5, 3)) with self.assertRaises(ValueError): IdBuilder("eu", date(2018, 5, 3))
def test_slugger(self): id1 = (IdBuilder("local", date(2018, 5, 3)).with_organisation( "Test Org").with_division("Test Division").ballot_id) id2 = (IdBuilder("local", date(2018, 5, 3)).with_organisation( "test-org").with_division("test-division").ballot_id) self.assertEqual(id1, id2)
def test_empty(self): id = (IdBuilder("local", date( 2018, 5, 3)).with_organisation("test-org").with_division("")) self.assertEqual(["local.2018-05-03", "local.test-org.2018-05-03"], id.ids)
def test_ref(self): with self.assertRaises(NotImplementedError): IdBuilder("ref", date(2018, 5, 3))
def test_gla_with_org(self): for subtype in ("a", "c"): with self.assertRaises(ValueError): IdBuilder("gla", date( 2018, 5, 3)).with_subtype(subtype).with_organisation("test-org")
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(), })
def test_invalid_contest_type(self): with self.assertRaises(ValueError): IdBuilder("local", date(2018, 5, 3)).with_contest_type("foo")
def test_local_with_subtype(self): with self.assertRaises(ValueError): IdBuilder("local", date(2018, 5, 3)).with_subtype("x")
def test_string_date(self): id = IdBuilder("parl", "2018-05-03") election_id = id.election_group_id self.assertEqual("parl.2018-05-03", election_id)
def test_gla_unknown_subtype_with_division(self): with self.assertRaises(ValueError): IdBuilder("gla", date(2018, 5, 3)).with_division("test-div")
def test_gla_additional_with_division(self): with self.assertRaises(ValueError): IdBuilder("gla", date(2018, 5, 3)).with_subtype("a").with_division("test-div")
def test_nia_parl_europarl_with_subtype(self): for election_type in ("nia", "parl", "europarl"): with self.assertRaises(ValueError): IdBuilder(election_type, date(2018, 5, 3)).with_subtype("x")
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(), } )
def test_gla_with_invalid_subtype(self): with self.assertRaises(ValueError): IdBuilder("gla", date(2018, 5, 3)).with_subtype("x")
def test_naw_sp_invalid_subtype(self): for election_type in ("naw", "sp", "senedd"): with self.assertRaises(ValueError): IdBuilder(election_type, date(2018, 5, 3)).with_subtype("x")
def test_pcc_mayor_with_division(self): for election_type in ("pcc", "mayor"): with self.assertRaises(ValueError): IdBuilder(election_type, date(2018, 5, 3)).with_division("test-division")