Esempio n. 1
0
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(),
        })
Esempio n. 2
0
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(),
            }
        )