From 3958f12e7ee82ed7f04cb74086f157cb10279c16 Mon Sep 17 00:00:00 2001 From: Neil Johnson Date: Wed, 17 Aug 2022 12:02:35 -0400 Subject: [PATCH 01/28] Extract GPUnit names based on ID --- src/electos/ballotmaker/election_data.py | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/src/electos/ballotmaker/election_data.py b/src/electos/ballotmaker/election_data.py index 0a1b740..5d090fa 100644 --- a/src/electos/ballotmaker/election_data.py +++ b/src/electos/ballotmaker/election_data.py @@ -16,6 +16,7 @@ class ElectionData: # properties retrieved from the EDF edf_error: int = field(init=False) election_report: ElectionReport = field(init=False) + index: ElementIndex = field(init=False) ballot_styles: ElementIndex = field(init=False) ballot_count: int = field(init=False) election_name: str = field(init=False) @@ -23,6 +24,15 @@ class ElectionData: end_date: str = field(init=False) election_type: str = field(init=False) + def get_gp_unit_list(self, gp_unit_ids: list) -> list: + gp_unit_list = [] + for gp_unit_id in gp_unit_ids: + gp_unit = self.index.by_id(gp_unit_id) + gp_unit_name = gp_unit.name.text[0].content + gp_unit_list.append(gp_unit_name) + log.debug(f"GP Unit IDs: {gp_unit_ids}; GP Units: {gp_unit_list}") + return gp_unit_list + def __post_init__(self): # let's assume there are no errors self.edf_error = NO_ERRORS @@ -56,13 +66,15 @@ def __post_init__(self): log.info(f"{self.election_type}") # index the election report to retrieve lists - index = ElementIndex(self.election_report, "ElectionResults") + self.index = ElementIndex(self.election_report, "ElectionResults") # how many ballots? - self.ballot_styles = index.by_type("ElectionResults.BallotStyle") + self.ballot_styles = self.index.by_type("ElectionResults.BallotStyle") # list and count ballots for count, ballot in enumerate(self.ballot_styles, start=1): ballot_value = ballot.external_identifier[0].value - log.info(f"Ballot: {ballot_value}") + ballot_gp_unit_ids = ballot.gp_unit_ids + ballot_gp_units = self.get_gp_unit_list(ballot_gp_unit_ids) + log.info(f"Ballot: {ballot_value}; GP Units: {ballot_gp_units}") self.ballot_count = count log.info(f"Found {self.ballot_count} ballot styles in {self.edf}") From 9aac1a563fd62d99ae3ee4faa0f4aee7d643f39c Mon Sep 17 00:00:00 2001 From: Neil Johnson Date: Thu, 18 Aug 2022 22:53:15 -0400 Subject: [PATCH 02/28] Create dictionaires of lookup tables --- .../ballotmaker/demo_data/dict_maker.py | 23 +++++++++++++++++++ src/electos/ballotmaker/demo_data/gp_units.py | 12 ++++++++++ src/electos/ballotmaker/demo_data/offices.py | 7 ++++++ src/electos/ballotmaker/demo_data/party.py | 4 ++++ 4 files changed, 46 insertions(+) create mode 100644 src/electos/ballotmaker/demo_data/dict_maker.py create mode 100644 src/electos/ballotmaker/demo_data/gp_units.py create mode 100644 src/electos/ballotmaker/demo_data/offices.py create mode 100644 src/electos/ballotmaker/demo_data/party.py diff --git a/src/electos/ballotmaker/demo_data/dict_maker.py b/src/electos/ballotmaker/demo_data/dict_maker.py new file mode 100644 index 0000000..aed47a8 --- /dev/null +++ b/src/electos/ballotmaker/demo_data/dict_maker.py @@ -0,0 +1,23 @@ +import json +from pathlib import Path + +from electos.datamodels.nist.indexes.element_index import ElementIndex +from electos.datamodels.nist.models.edf import ElectionReport + +edf = Path("/Users/neil/repos/BallotLabFork/tests/june_test_case.json") +edf_data = json.loads(edf.read_text()) +election_report = ElectionReport(**edf_data) +index = ElementIndex(election_report, "ElectionResults") + +for party in index.by_type("ElectionResults.Party"): + party_id = party.model__id + print(f"'{party_id}'") + +# generate the list of Persons +print("people = {") +for person in index.by_type("ElectionResults.Person"): + person_id = person.model__id + first_name = person.first_name + last_name = person.last_name + print(f" '{party_id}': ('{first_name}','{last_name}'),") +print("}") diff --git a/src/electos/ballotmaker/demo_data/gp_units.py b/src/electos/ballotmaker/demo_data/gp_units.py new file mode 100644 index 0000000..2925d6f --- /dev/null +++ b/src/electos/ballotmaker/demo_data/gp_units.py @@ -0,0 +1,12 @@ +gp_units = { + "rec7dCergEa3mzqxy": ("precinct", "Port Precinct"), + "rec93s713Yh6ZJT31": ("state", "The State of Farallon"), + "recFIehh5Aj0zGTn6": ("precinct", "Downtown Precinct"), + "recOVSnILnPJ7Dahl": ("county", "Gadget County"), + "recSQ3ZpvJlTll1Ve": ("precinct", "Bedrock Precinct"), + "recTXCMIfa5VQJju2": ("country", "United States of America"), + "recUuJTc3tUIUvgF1": ("precinct", "Spacetown Precinct"), + "recVAsRw7BvEIBnTe": ("school", "Gadget County Unified School District"), + "recVN5dRsq4j6QZn3": ("municipality", "Aldrin Space Transport District"), + "recfK8xOapcRIeZ2k": ("city", "Orbit City"), +} diff --git a/src/electos/ballotmaker/demo_data/offices.py b/src/electos/ballotmaker/demo_data/offices.py new file mode 100644 index 0000000..d729268 --- /dev/null +++ b/src/electos/ballotmaker/demo_data/offices.py @@ -0,0 +1,7 @@ +offices = { + "rec7N0cboW3L1Mv0I": ("Mayor of Orbit City", True), + "recBAG7iuOZ1MER6i": ("Spaceport Control Board Member", False), + "rec1zWmGWlgKKmUO4": ("Gadget County School Board", False), + "recFr8nr6uAZsD2r8": ("President of the United States", True), + "recIR57LPmJ0VvtEo": ("Vice-President of the United States", True), +} diff --git a/src/electos/ballotmaker/demo_data/party.py b/src/electos/ballotmaker/demo_data/party.py new file mode 100644 index 0000000..d3c2d79 --- /dev/null +++ b/src/electos/ballotmaker/demo_data/party.py @@ -0,0 +1,4 @@ +parties = { + "reckpEKRtLuDdt03n": ("The Hadron Party of Farallon", "HAD"), + "recBiK9LZXeZmmFEg": ("The Lepton Party", "LEP"), +} From b29199c6fabccedb7ce7a022795ceae4dd60db08 Mon Sep 17 00:00:00 2001 From: Neil Johnson Date: Fri, 19 Aug 2022 05:35:49 -0400 Subject: [PATCH 03/28] Create and test demo subcommand --- src/electos/ballotmaker/cli.py | 6 +++++ src/electos/ballotmaker/demo.py | 12 ++++++++++ src/electos/ballotmaker/demo_election_data.py | 23 +++++++++++++++++++ tests/test_cli.py | 5 ++++ tests/test_demo.py | 6 +++++ 5 files changed, 52 insertions(+) create mode 100644 src/electos/ballotmaker/demo.py create mode 100644 src/electos/ballotmaker/demo_election_data.py create mode 100644 tests/test_demo.py diff --git a/src/electos/ballotmaker/cli.py b/src/electos/ballotmaker/cli.py index ec9a832..25ce040 100644 --- a/src/electos/ballotmaker/cli.py +++ b/src/electos/ballotmaker/cli.py @@ -39,6 +39,12 @@ def main( return NO_ERRORS +@app.command() +def demo(): + """Make ballots from previously extracted EDF data""" + return NO_ERRORS + + @app.command() def make( edf: Path = typer.Option( diff --git a/src/electos/ballotmaker/demo.py b/src/electos/ballotmaker/demo.py new file mode 100644 index 0000000..f39408e --- /dev/null +++ b/src/electos/ballotmaker/demo.py @@ -0,0 +1,12 @@ +import logging + +from electos.ballotmaker.constants import NO_ERRORS +from electos.ballotmaker.demo_election_data import DemoElectionData + +log = logging.getLogger(__name__) + + +def demo(): + log.debug("Starting ballotlab demo ...") + demo_data = DemoElectionData() + return NO_ERRORS diff --git a/src/electos/ballotmaker/demo_election_data.py b/src/electos/ballotmaker/demo_election_data.py new file mode 100644 index 0000000..1c4ed5d --- /dev/null +++ b/src/electos/ballotmaker/demo_election_data.py @@ -0,0 +1,23 @@ +import logging +from dataclasses import dataclass, field + +log = logging.getLogger(__name__) + + +def get_election_header() -> dict: + return { + "Name": "General Election", + "StartDate": "2024-11-05", + "EndDate": "2024-11-05", + "Type": "general", + "ElectionScope": "United States of America", + } + + +@dataclass +class DemoElectionData: + election_header: dict = field(init=False) + + def __post_init__(self): + self.election_header = get_election_header() + log.debug(f"Election Name: {self.election_header.get('Name')}") diff --git a/tests/test_cli.py b/tests/test_cli.py index df69232..4002197 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -58,3 +58,8 @@ def test_validate(): result = runner.invoke(cli.app, ["validate", "--edf"]) assert result.exit_code == NO_FILE assert "Error: Option" in result.stdout + + +def test_demo(): + result = runner.invoke(cli.app, ["demo"]) + assert result.exit_code == NO_ERRORS diff --git a/tests/test_demo.py b/tests/test_demo.py new file mode 100644 index 0000000..81ee974 --- /dev/null +++ b/tests/test_demo.py @@ -0,0 +1,6 @@ +from electos.ballotmaker.constants import NO_ERRORS +from electos.ballotmaker.demo import demo + + +def test_demo(): + assert demo() == NO_ERRORS From 918446eccb251f6e7b56a2e0e533ef6768e76143 Mon Sep 17 00:00:00 2001 From: Neil Johnson Date: Fri, 19 Aug 2022 11:57:17 -0400 Subject: [PATCH 04/28] Build more lookup dictionaries --- .../ballotmaker/demo_data/dict_maker.py | 74 ++++++++++++++++++- src/electos/ballotmaker/demo_data/gp_units.py | 20 ++--- src/electos/ballotmaker/demo_election_data.py | 2 +- 3 files changed, 81 insertions(+), 15 deletions(-) diff --git a/src/electos/ballotmaker/demo_data/dict_maker.py b/src/electos/ballotmaker/demo_data/dict_maker.py index aed47a8..20217b8 100644 --- a/src/electos/ballotmaker/demo_data/dict_maker.py +++ b/src/electos/ballotmaker/demo_data/dict_maker.py @@ -1,5 +1,6 @@ import json from pathlib import Path +from pprint import pprint from electos.datamodels.nist.indexes.element_index import ElementIndex from electos.datamodels.nist.models.edf import ElectionReport @@ -9,15 +10,80 @@ election_report = ElectionReport(**edf_data) index = ElementIndex(election_report, "ElectionResults") -for party in index.by_type("ElectionResults.Party"): - party_id = party.model__id - print(f"'{party_id}'") +gp_units = {} +offices = {} +parties = {} + + +def get_gp_units_dict(gp_units: dict) -> int: + for count, gp_unit in enumerate( + index.by_type("ElectionResults.ReportingUnit"), start=1 + ): + gp_unit_id = gp_unit.model__id + gp_unit_name = gp_unit.name.text[0].content + # gp_unit_type = gp_unit.type + print(f" '{gp_unit_id}': '{gp_unit_name}'") + gp_units[gp_unit_id] = gp_unit_name + return count + + +def get_offices_dict(offices: dict) -> int: + for count, office in enumerate( + index.by_type("ElectionResults.Office"), start=1 + ): + office_id = office.model__id + office_name = office.name.text[0].content + print(f" '{office_id}': '{office_name}'") + offices[office_id] = office_name + return count + + +def get_parties_dict(parties: dict) -> int: + for count, party in enumerate( + index.by_type("ElectionResults.Party"), start=1 + ): + party_id = party.model__id + party_name = party.name.text[0].content + party_abbr = party.abbreviation.text[0].content + party_value = (party_name, party_abbr) + print(f" '{party_id}': ('{party_name}, {party_abbr})'") + parties[party_id] = party_value + return count + + +print("# Dictionary of GPUnit = id: name") +print(f"Found {get_gp_units_dict(gp_units)} GPUnits:") +pprint(gp_units) + +print("# Dictionary of Office = id: name") +print(f"Found {get_offices_dict(offices)} offices:") +pprint(offices) + +print("# Dictionary of Party = id: (name, abbreviation)") +print(f"Found {get_parties_dict(parties)} parties:") +pprint(parties) # generate the list of Persons +print("# Dictionary of People = id: (first name, last name)") print("people = {") for person in index.by_type("ElectionResults.Person"): person_id = person.model__id first_name = person.first_name last_name = person.last_name - print(f" '{party_id}': ('{first_name}','{last_name}'),") + print(f" '{person_id}': ('{first_name}','{last_name}'),") +print("}") + +print("# Dictionary of CandidateContest") +for candidate_contest in index.by_type("ElectionResults.CandidateContest"): + can_contest_id = candidate_contest.model__id + can_contest_name = candidate_contest.name + # office_ids could contain multiple items + office_ids = candidate_contest.office_ids + contest_offices = [offices[id] for id in office_ids] + vote_variation = candidate_contest.vote_variation + votes_allowed = candidate_contest.votes_allowed + election_district = gp_units[candidate_contest.election_district_id] + print( + f" '{can_contest_id}': ('{can_contest_name}','{contest_offices}', '{vote_variation}', {votes_allowed}, '{election_district}')," + ) print("}") diff --git a/src/electos/ballotmaker/demo_data/gp_units.py b/src/electos/ballotmaker/demo_data/gp_units.py index 2925d6f..e52b019 100644 --- a/src/electos/ballotmaker/demo_data/gp_units.py +++ b/src/electos/ballotmaker/demo_data/gp_units.py @@ -1,12 +1,12 @@ gp_units = { - "rec7dCergEa3mzqxy": ("precinct", "Port Precinct"), - "rec93s713Yh6ZJT31": ("state", "The State of Farallon"), - "recFIehh5Aj0zGTn6": ("precinct", "Downtown Precinct"), - "recOVSnILnPJ7Dahl": ("county", "Gadget County"), - "recSQ3ZpvJlTll1Ve": ("precinct", "Bedrock Precinct"), - "recTXCMIfa5VQJju2": ("country", "United States of America"), - "recUuJTc3tUIUvgF1": ("precinct", "Spacetown Precinct"), - "recVAsRw7BvEIBnTe": ("school", "Gadget County Unified School District"), - "recVN5dRsq4j6QZn3": ("municipality", "Aldrin Space Transport District"), - "recfK8xOapcRIeZ2k": ("city", "Orbit City"), + "rec7dCergEa3mzqxy": "Port Precinct", + "rec93s713Yh6ZJT31": "The State of Farallon", + "recFIehh5Aj0zGTn6": "Downtown Precinct", + "recOVSnILnPJ7Dahl": "Gadget County", + "recSQ3ZpvJlTll1Ve": "Bedrock Precinct", + "recTXCMIfa5VQJju2": "United States of America", + "recUuJTc3tUIUvgF1": "Spacetown Precinct", + "recVAsRw7BvEIBnTe": "Gadget County Unified School District", + "recVN5dRsq4j6QZn3": "Aldrin Space Transport District", + "recfK8xOapcRIeZ2k": "Orbit City", } diff --git a/src/electos/ballotmaker/demo_election_data.py b/src/electos/ballotmaker/demo_election_data.py index 1c4ed5d..de3dc23 100644 --- a/src/electos/ballotmaker/demo_election_data.py +++ b/src/electos/ballotmaker/demo_election_data.py @@ -20,4 +20,4 @@ class DemoElectionData: def __post_init__(self): self.election_header = get_election_header() - log.debug(f"Election Name: {self.election_header.get('Name')}") + log.debug(f"Election Name: {self.election_header['Name']}") From b461e301ccb5dcd4f36981ffb630c69c18b9c8e2 Mon Sep 17 00:00:00 2001 From: Neil Johnson Date: Fri, 19 Aug 2022 18:37:02 -0400 Subject: [PATCH 05/28] Retrieve more data --- .../ballotmaker/demo_data/dict_maker.py | 59 +++++++++++++++---- 1 file changed, 47 insertions(+), 12 deletions(-) diff --git a/src/electos/ballotmaker/demo_data/dict_maker.py b/src/electos/ballotmaker/demo_data/dict_maker.py index 20217b8..0c95e78 100644 --- a/src/electos/ballotmaker/demo_data/dict_maker.py +++ b/src/electos/ballotmaker/demo_data/dict_maker.py @@ -10,9 +10,11 @@ election_report = ElectionReport(**edf_data) index = ElementIndex(election_report, "ElectionResults") +candidates = {} gp_units = {} offices = {} parties = {} +people = {} def get_gp_units_dict(gp_units: dict) -> int: @@ -22,7 +24,7 @@ def get_gp_units_dict(gp_units: dict) -> int: gp_unit_id = gp_unit.model__id gp_unit_name = gp_unit.name.text[0].content # gp_unit_type = gp_unit.type - print(f" '{gp_unit_id}': '{gp_unit_name}'") + # print(f" '{gp_unit_id}': '{gp_unit_name}'") gp_units[gp_unit_id] = gp_unit_name return count @@ -51,6 +53,29 @@ def get_parties_dict(parties: dict) -> int: return count +def get_people_dict(people: dict) -> int: + for count, person in enumerate( + index.by_type("ElectionResults.Person"), start=1 + ): + person_id = person.model__id + first_name = person.first_name + last_name = person.last_name + print(f" '{person_id}': {first_name} {last_name},") + people[person_id] = f"{first_name} {last_name}" + return count + + +def get_candidate_dict(candidates: dict) -> int: + for count, candidate in enumerate( + index.by_type("ElectionResults.Candidate"), start=1 + ): + candidate_id = candidate.model__id + candidate_ballot_name = candidate.ballot_name.text[0].content + print(f" '{candidate_id}': {candidate_ballot_name},") + candidates[candidate_id] = candidate_ballot_name + return count + + print("# Dictionary of GPUnit = id: name") print(f"Found {get_gp_units_dict(gp_units)} GPUnits:") pprint(gp_units) @@ -63,27 +88,37 @@ def get_parties_dict(parties: dict) -> int: print(f"Found {get_parties_dict(parties)} parties:") pprint(parties) -# generate the list of Persons -print("# Dictionary of People = id: (first name, last name)") -print("people = {") -for person in index.by_type("ElectionResults.Person"): - person_id = person.model__id - first_name = person.first_name - last_name = person.last_name - print(f" '{person_id}': ('{first_name}','{last_name}'),") -print("}") +print("# Dictionary of People = id: firstname lastname") +print(f"Found {get_people_dict(people)} people:") +pprint(people) + +print("# Dictionary of Candidate") +print(f"Found {get_candidate_dict(candidates)} candidate:") +pprint(candidates) print("# Dictionary of CandidateContest") for candidate_contest in index.by_type("ElectionResults.CandidateContest"): + vote_variation = candidate_contest.vote_variation.value + if vote_variation == "n-of-m": + continue can_contest_id = candidate_contest.model__id can_contest_name = candidate_contest.name # office_ids could contain multiple items office_ids = candidate_contest.office_ids contest_offices = [offices[id] for id in office_ids] - vote_variation = candidate_contest.vote_variation votes_allowed = candidate_contest.votes_allowed election_district = gp_units[candidate_contest.election_district_id] + contest_index = ElementIndex(candidate_contest, "ElectionResults") print( f" '{can_contest_id}': ('{can_contest_name}','{contest_offices}', '{vote_variation}', {votes_allowed}, '{election_district}')," ) -print("}") + print(contest_index) + for content_selection in contest_index.by_type( + "ElectionResults.CandidateSelection" + ): + print(content_selection.name) + contest_id = content_selection.model__id + + candidate_ids = content_selection.candidate_ids + candidate_names = [candidates[c_id] for c_id in candidate_ids] + print(f"{contest_id} {candidate_names}") From bcf848df9846fcd4d88120fb0f044b14b0e03b25 Mon Sep 17 00:00:00 2001 From: Neil Johnson Date: Sun, 21 Aug 2022 13:42:57 -0400 Subject: [PATCH 06/28] Select useable code for ballots, rebuild imports --- .../ballots/contest.py | 15 +++-- .../ballots/demo_ballot.py} | 22 ++++--- src/electos/ballotmaker/ballots/files.py | 60 +++++++++++++++++++ .../ballots/images.py | 4 +- .../ballots/instructions.py | 25 +++++--- .../ballots/page_layout.py | 48 ++++++++------- src/electos/ballotmaker/cli.py | 3 +- src/electos/ballotmaker/demo.py | 6 +- .../versadm/{ballots => }/ballot_demo.py | 16 +++-- src/electos/versadm/{ballots => }/data.py | 0 tests/test_demo.py | 4 +- 11 files changed, 141 insertions(+), 62 deletions(-) rename src/electos/{versadm => ballotmaker}/ballots/contest.py (93%) rename src/electos/{versadm/ballots/ballot.py => ballotmaker/ballots/demo_ballot.py} (83%) create mode 100644 src/electos/ballotmaker/ballots/files.py rename src/electos/{versadm => ballotmaker}/ballots/images.py (92%) rename src/electos/{versadm => ballotmaker}/ballots/instructions.py (92%) rename src/electos/{versadm => ballotmaker}/ballots/page_layout.py (55%) rename src/electos/versadm/{ballots => }/ballot_demo.py (86%) rename src/electos/versadm/{ballots => }/data.py (100%) diff --git a/src/electos/versadm/ballots/contest.py b/src/electos/ballotmaker/ballots/contest.py similarity index 93% rename from src/electos/versadm/ballots/contest.py rename to src/electos/ballotmaker/ballots/contest.py index 15d5a67..32726f0 100644 --- a/src/electos/versadm/ballots/contest.py +++ b/src/electos/ballotmaker/ballots/contest.py @@ -1,14 +1,11 @@ # contest.py # format a ballot contest. -from page_layout import PageLayout -from images import EmbeddedImage -from reportlab.platypus import Table -from reportlab.platypus import Paragraph +from electos.ballotmaker.ballots.page_layout import PageLayout +from reportlab.graphics.shapes import Drawing, Ellipse, _DrawingEditorMixin +from reportlab.lib.colors import black, white from reportlab.lib.styles import LineStyle, getSampleStyleSheet -from reportlab.graphics.shapes import Drawing, _DrawingEditorMixin -from reportlab.graphics.shapes import Ellipse -from reportlab.lib.colors import white, black +from reportlab.platypus import Paragraph, Table oval_width = 10 oval_height = 4 @@ -49,7 +46,9 @@ def __init__(self): self.contest_instruct = "" def get_contest_data(): - self.contest_title = "President and Vice-President of the United States" + self.contest_title = ( + "President and Vice-President of the United States" + ) self.contest_instruct = "Vote for 1 pair" self.contestants = [ ("Joseph Barchi and Joseph Hallaren", "Blue"), diff --git a/src/electos/versadm/ballots/ballot.py b/src/electos/ballotmaker/ballots/demo_ballot.py similarity index 83% rename from src/electos/versadm/ballots/ballot.py rename to src/electos/ballotmaker/ballots/demo_ballot.py index 6773e35..66b3390 100644 --- a/src/electos/versadm/ballots/ballot.py +++ b/src/electos/ballotmaker/ballots/demo_ballot.py @@ -4,20 +4,18 @@ page templates, and specific pages """ from datetime import datetime -from reportlab.platypus import ( +from pathlib import Path + +# from reportlab.lib.styles import getSampleStyleSheet +from electos.ballotmaker.ballots.contest import Contest +from electos.ballotmaker.ballots.instructions import Instructions +from electos.ballotmaker.ballots.page_layout import PageLayout +from reportlab.lib.units import inch +from reportlab.platypus import ( # Paragraph,; NextPageTemplate,; PageBreak, BaseDocTemplate, Frame, - # Paragraph, - # NextPageTemplate, - # PageBreak, PageTemplate, ) -from reportlab.lib.units import inch - -# from reportlab.lib.styles import getSampleStyleSheet -from versadm.ballots.contest import Contest -from versadm.ballots.instructions import Instructions -from versadm.ballots.page_layout import PageLayout def build_ballot(): @@ -70,8 +68,8 @@ def build_ballot(): # create datestamp string for PDF now = datetime.now() date_time = now.strftime("%Y_%m_%dT%H%M%S") - # TODO: Fix path with project_files.py - ballot_name = "pdfs/ballot_{0}.pdf".format(date_time) + home_dir = Path.home() + ballot_name = f"{home_dir}/ballot_demo_{date_time}.pdf" ballot_doc = BaseDocTemplate(ballot_name) ballot_doc.addPageTemplates( diff --git a/src/electos/ballotmaker/ballots/files.py b/src/electos/ballotmaker/ballots/files.py new file mode 100644 index 0000000..0e08fca --- /dev/null +++ b/src/electos/ballotmaker/ballots/files.py @@ -0,0 +1,60 @@ +# Get properties of the file system +# TODO: Revactor with pathlib to normalize paths across operating systems +# [PEP 428 -- The pathlib module -- object-oriented filesystem paths | Python.org](https://www.python.org/dev/peps/pep-0428/) + +import os +from inspect import getsourcefile + +from genericpath import isfile + + +class FileTools: + def __init__(self, file_name="", rel_path=""): + self.file_name = file_name + self.rel_path = rel_path + self.ext = "" + self.file_found = False + self.package_name = "BallotLabFork" + # get the absolute path to this module + self.code_dir = os.path.dirname(getsourcefile(lambda: 0)) + base_len = int(self.code_dir.find(self.package_name)) + if base_len == -1: + raise Exception("Not executing in the expected package.") + # if this module is part of the specified package, build the absolute path + self.package_root = os.path.join( + self.code_dir[:base_len], self.package_name + ) + self.full_path = self.package_root + # add a relative path to the target directory + if self.rel_path: + self.full_path = os.path.join(self.full_path, self.rel_path) + if self.file_name: + self.abs_path_to_file = os.path.join( + self.full_path, self.file_name + ) + # if the file exists, get the extension and set the file found flag to True + if isfile(self.abs_path_to_file): + self.ext = os.path.splitext(self.file_name)[1] + self.file_found = True + + +if __name__ == "__main__": + print("Default settings:") + file_defaults = FileTools() + print(file_defaults.code_dir) + print(file_defaults.package_root) + print(file_defaults.package_root) + print(file_defaults.full_path) + print(file_defaults.file_found) + + target_file = "writein.png" + target_dir = "assets/img" + print(f"Check for file {target_file} in directory {target_dir}") + file_check = FileTools(target_file, target_dir) + print(file_check.code_dir) + print(file_check.package_root) + print(file_check.file_name) + print(file_check.rel_path) + print(file_check.full_path) + print(file_check.abs_path_to_file) + print(file_check.file_found) diff --git a/src/electos/versadm/ballots/images.py b/src/electos/ballotmaker/ballots/images.py similarity index 92% rename from src/electos/versadm/ballots/images.py rename to src/electos/ballotmaker/ballots/images.py index 24a381e..c56a6e0 100644 --- a/src/electos/versadm/ballots/images.py +++ b/src/electos/ballotmaker/ballots/images.py @@ -1,7 +1,7 @@ # images.py # work with images, including embedding images into # Paragraph flowables -from versadm.utils.project_files import ProjectFiles +from electos.ballotmaker.ballots.files import FileTools # from reportlab.platypus import Image from reportlab.lib import utils @@ -21,7 +21,7 @@ def __init__(self, image_name, new_width=240) -> None: self.rel_img_path = "assets/img" self.embed_text = "" # find the image - image_file = ProjectFiles(self.image_name, self.rel_img_path, PROJECT_NAME) + image_file = FileTools(self.image_name, self.rel_img_path) self.file_check(image_file) # retrieve the image and measure it self.image_full_path = image_file.abs_path_to_file diff --git a/src/electos/versadm/ballots/instructions.py b/src/electos/ballotmaker/ballots/instructions.py similarity index 92% rename from src/electos/versadm/ballots/instructions.py rename to src/electos/ballotmaker/ballots/instructions.py index 3a2dcf0..294cbca 100644 --- a/src/electos/versadm/ballots/instructions.py +++ b/src/electos/ballotmaker/ballots/instructions.py @@ -1,13 +1,12 @@ # instructions.py # Build the ballot instructions - -from page_layout import PageLayout -from images import EmbeddedImage -from reportlab.platypus.flowables import CondPageBreak, PageBreak, Spacer -from reportlab.platypus import Paragraph +from electos.ballotmaker.ballots.images import EmbeddedImage +from electos.ballotmaker.ballots.page_layout import PageLayout from reportlab.lib.styles import getSampleStyleSheet from reportlab.lib.units import inch +from reportlab.platypus import Paragraph +from reportlab.platypus.flowables import CondPageBreak, PageBreak, Spacer class Instructions: @@ -139,10 +138,22 @@ def build_instruction_list(): head_lead, ) PageLayout.define_custom_style( - normal, light, border_pad, font_size, black, font_normal, normal_lead + normal, + light, + border_pad, + font_size, + black, + font_normal, + normal_lead, ) PageLayout.define_custom_style( - warn_text, light, border_pad, font_size, dark, font_bold, normal_lead + warn_text, + light, + border_pad, + font_size, + dark, + font_bold, + normal_lead, ) PageLayout.define_custom_style( img_graf, diff --git a/src/electos/versadm/ballots/page_layout.py b/src/electos/ballotmaker/ballots/page_layout.py similarity index 55% rename from src/electos/versadm/ballots/page_layout.py rename to src/electos/ballotmaker/ballots/page_layout.py index 944d4d0..7cbc556 100644 --- a/src/electos/versadm/ballots/page_layout.py +++ b/src/electos/ballotmaker/ballots/page_layout.py @@ -1,41 +1,48 @@ # page_layout.py -# Stores page layout settings in a dictionary +# Stores page layout settings in a class +# TODO: refactor as a dict or dataclass # customize only what's different from the samples +from dataclasses import dataclass + +@dataclass class PageLayout: # use floats for these values - font_family = "Helvetica" - margin = 0.5 - col_width = 2.25 - col_height = 9 - col_space = 0.25 + font_family: str = "Helvetica" + margin: float = 0.5 + col_width: float = 2.25 + col_height: float = 9 + col_space: float = 0.25 # font family info - font_normal = "Helvetica" - font_bold = "Helvetica-Bold" - font_size = 12 - normal_lead = 15 - head_lead = 20 - border_pad = 8 - space_before = 12 - space_after = 6 + font_normal: str = "Helvetica" + font_bold: str = "Helvetica-Bold" + font_size: int = 12 + normal_lead: int = 15 + head_lead: int = 20 + border_pad: int = 8 + space_before: int = 12 + space_after: int = 6 # define CMYKColor values # Use floats! (0 - 1) Didn't work with values 0 - 100 # 100% cyan - dark = (1, 0, 0, 0) + dark: tuple = (1, 0, 0, 0) # light cyan - light = (0.1, 0, 0, 0) - white = (0, 0, 0, 0) - black = (0, 0, 0, 1) - grey = (0, 0, 0, 0.15) + light: tuple = (0.1, 0, 0, 0) + white: tuple = (0, 0, 0, 0) + black: tuple = (0, 0, 0, 1) + grey: tuple = (0, 0, 0, 0.15) + + bg_color: tuple = white + border_color: tuple = black # TODO: Rewrite with *args, **kwargs? def define_custom_style( style, - bg_color, + bg_color=bg_color, border_pd=border_pad, font_sz=font_size, txt_color=black, @@ -45,7 +52,6 @@ def define_custom_style( sp_after=space_after, ): style.backColor = bg_color - style.borderColor = bg_color style.borderPadding = border_pd style.fontSize = font_sz style.textColor = txt_color diff --git a/src/electos/ballotmaker/cli.py b/src/electos/ballotmaker/cli.py index 25ce040..d5feff3 100644 --- a/src/electos/ballotmaker/cli.py +++ b/src/electos/ballotmaker/cli.py @@ -6,7 +6,7 @@ from typing import Optional import typer -from electos.ballotmaker import make_ballots, validate_edf +from electos.ballotmaker import demo, make_ballots, validate_edf from electos.ballotmaker.constants import NO_ERRORS, PROGRAM_NAME, VERSION EDF_HELP = "EDF file with ballot data (JSON format)" @@ -42,6 +42,7 @@ def main( @app.command() def demo(): """Make ballots from previously extracted EDF data""" + # demo.make_demo_ballot() return NO_ERRORS diff --git a/src/electos/ballotmaker/demo.py b/src/electos/ballotmaker/demo.py index f39408e..e5fde29 100644 --- a/src/electos/ballotmaker/demo.py +++ b/src/electos/ballotmaker/demo.py @@ -1,12 +1,12 @@ import logging +# from electos.ballotmaker.ballots.demo_ballot import build_ballot from electos.ballotmaker.constants import NO_ERRORS -from electos.ballotmaker.demo_election_data import DemoElectionData log = logging.getLogger(__name__) -def demo(): +def make_demo_ballot(): log.debug("Starting ballotlab demo ...") - demo_data = DemoElectionData() + # build_ballot() return NO_ERRORS diff --git a/src/electos/versadm/ballots/ballot_demo.py b/src/electos/versadm/ballot_demo.py similarity index 86% rename from src/electos/versadm/ballots/ballot_demo.py rename to src/electos/versadm/ballot_demo.py index 1811d7d..2d7d5c6 100644 --- a/src/electos/versadm/ballots/ballot_demo.py +++ b/src/electos/versadm/ballot_demo.py @@ -1,15 +1,17 @@ # ballot_demo.py # create a demo object with BallotLab classes -from versadm.ballots.contest import Contest -from versadm.ballots.instructions import Instructions -from versadm.ballots.page_layout import PageLayout +from datetime import datetime +from pathlib import Path + +from electos.ballotmaker.ballots.contest import Contest +from electos.ballotmaker.ballots.instructions import Instructions +from electos.ballotmaker.ballots.page_layout import PageLayout from reportlab.lib.pagesizes import letter from reportlab.lib.styles import getSampleStyleSheet from reportlab.lib.units import inch from reportlab.pdfgen.canvas import Canvas -from reportlab.platypus import Paragraph, Frame -from datetime import datetime +from reportlab.platypus import Frame, Paragraph def ballot_demo(): @@ -26,8 +28,10 @@ def ballot_demo(): now = datetime.now() date_time = now.strftime("%Y_%m_%dT%H%M%S") # create the PDF document canvas + + home_dir = Path.home() ballot_canvas = Canvas( - "pdfs/ballot_demo_{0}.pdf".format(date_time), + f"{home_dir}/ballot_demo_{date_time}.pdf", pagesize=letter, enforceColorSpace="CMYK", ) diff --git a/src/electos/versadm/ballots/data.py b/src/electos/versadm/data.py similarity index 100% rename from src/electos/versadm/ballots/data.py rename to src/electos/versadm/data.py diff --git a/tests/test_demo.py b/tests/test_demo.py index 81ee974..eee9ff6 100644 --- a/tests/test_demo.py +++ b/tests/test_demo.py @@ -1,6 +1,6 @@ from electos.ballotmaker.constants import NO_ERRORS -from electos.ballotmaker.demo import demo +from electos.ballotmaker.demo import make_demo_ballot def test_demo(): - assert demo() == NO_ERRORS + assert make_demo_ballot() == NO_ERRORS From e4ba4fe084bcaf56a10f3e20309eaf71dd885364 Mon Sep 17 00:00:00 2001 From: Neil Johnson Date: Sun, 21 Aug 2022 21:02:59 -0400 Subject: [PATCH 07/28] Add header --- .../ballotmaker/ballots/demo_ballot.py | 86 +++++++++++++------ src/electos/ballotmaker/ballots/header.py | 32 +++++++ .../ballotmaker/ballots/page_layout.py | 2 +- 3 files changed, 95 insertions(+), 25 deletions(-) create mode 100644 src/electos/ballotmaker/ballots/header.py diff --git a/src/electos/ballotmaker/ballots/demo_ballot.py b/src/electos/ballotmaker/ballots/demo_ballot.py index 66b3390..a558b29 100644 --- a/src/electos/ballotmaker/ballots/demo_ballot.py +++ b/src/electos/ballotmaker/ballots/demo_ballot.py @@ -4,32 +4,60 @@ page templates, and specific pages """ from datetime import datetime +from functools import partial from pathlib import Path # from reportlab.lib.styles import getSampleStyleSheet from electos.ballotmaker.ballots.contest import Contest +from electos.ballotmaker.ballots.header import header from electos.ballotmaker.ballots.instructions import Instructions from electos.ballotmaker.ballots.page_layout import PageLayout +from reportlab.lib.styles import getSampleStyleSheet from reportlab.lib.units import inch -from reportlab.platypus import ( # Paragraph,; NextPageTemplate,; PageBreak, +from reportlab.platypus import ( BaseDocTemplate, Frame, + NextPageTemplate, PageTemplate, + Paragraph, ) +# set up frames +# 1 = True, 0 = FALSE +SHOW_BOUNDARY = 1 +# get page layout settings +margin = PageLayout.margin +c_width = PageLayout.col_width +c_height = PageLayout.col_height +c_space = PageLayout.col_space -def build_ballot(): - # set up frames - # 1 = True, 0 = FALSE - SHOW_BOUNDARY = 0 - # get page layout settings - margin = PageLayout.margin - c_width = PageLayout.col_width - c_height = PageLayout.col_height - c_space = PageLayout.col_space +def get_election_header() -> dict: + return { + "Name": "General Election", + "StartDate": "2024-11-05", + "EndDate": "2024-11-05", + "ElectionScope": "Port Precinct, The State of Farallon", + } + + +def build_header_text(): + elect_dict = get_election_header() + font_size = 14 + + return f"Sample Ballot for {elect_dict['Name']}" + + +def header(canvas, doc, content): + canvas.saveState() + width, height = content.wrap(doc.width, doc.topMargin) + content.drawOn(canvas, 0.5 * inch, 11 * inch) + canvas.restoreState() + - # define 3-column layout +def build_ballot(): + + # define 3-column layout with header left_frame = Frame( margin * inch, margin * inch, @@ -55,15 +83,6 @@ def build_ballot(): showBoundary=SHOW_BOUNDARY, ) - # styles = getSampleStyleSheet() - - # add voting instructions - inst = Instructions() - elements = inst.instruction_list - # add a ballot contest to the second frame (colomn) - contest_1 = Contest() - elements.append(contest_1.contest_table) - # create PDF filename # create datestamp string for PDF now = datetime.now() @@ -71,11 +90,30 @@ def build_ballot(): home_dir = Path.home() ballot_name = f"{home_dir}/ballot_demo_{date_time}.pdf" - ballot_doc = BaseDocTemplate(ballot_name) - ballot_doc.addPageTemplates( - PageTemplate(id="3col", frames=[left_frame, mid_frame, right_frame]) + doc = BaseDocTemplate(ballot_name) + + styles = getSampleStyleSheet() + normal = styles["Normal"] + head_text = build_header_text() + header_content = Paragraph(head_text, normal) + pg_template = PageTemplate( + id="3col", + frames=[left_frame, mid_frame, right_frame], + onPage=partial(header, content=header_content), ) - ballot_doc.build(elements) + doc.addPageTemplates(pg_template) + + elements = [] + # add voting instructions + inst = Instructions() + elements = inst.instruction_list + elements.append(NextPageTemplate("3col")) + # add a ballot contest to the second frame (colomn) + contest_1 = Contest() + elements.append(contest_1.contest_table) + + doc.build(elements) + # doc.build(elements) if __name__ == "__main__": diff --git a/src/electos/ballotmaker/ballots/header.py b/src/electos/ballotmaker/ballots/header.py new file mode 100644 index 0000000..1a54e1a --- /dev/null +++ b/src/electos/ballotmaker/ballots/header.py @@ -0,0 +1,32 @@ +from reportlab.lib.styles import getSampleStyleSheet +from reportlab.platypus import Paragraph + + +def get_election_header() -> dict: + return { + "Name": "General Election", + "StartDate": "2024-11-05", + "EndDate": "2024-11-05", + "ElectionScope": "Port Precinct, The State of Farallon", + } + + +def build_header_text(): + elect_dict = get_election_header() + font_size = 14 + + return f"Sample Ballot for {elect_dict['Name']}" + + +def header(canvas, doc): + width, height = doc.pagesize + styles = getSampleStyleSheet + ptext = build_header_text() + p = Paragraph(ptext, styles["Normal"]) + p.wrapOn(canvas, width, height) + p.drawOn(canvas, 400, 730) + + +if __name__ == "__main__": + header_text = build_header_text() + print(header_text) diff --git a/src/electos/ballotmaker/ballots/page_layout.py b/src/electos/ballotmaker/ballots/page_layout.py index 7cbc556..0c44fdd 100644 --- a/src/electos/ballotmaker/ballots/page_layout.py +++ b/src/electos/ballotmaker/ballots/page_layout.py @@ -13,7 +13,7 @@ class PageLayout: font_family: str = "Helvetica" margin: float = 0.5 col_width: float = 2.25 - col_height: float = 9 + col_height: float = 9.5 col_space: float = 0.25 # font family info From b5ad780846a1eacfa6af851a98a2173b5985b213 Mon Sep 17 00:00:00 2001 From: Neil Johnson Date: Sun, 21 Aug 2022 23:27:25 -0400 Subject: [PATCH 08/28] Render demo ballot --- src/electos/ballotmaker/ballots/contest.py | 47 ++-- .../ballotmaker/ballots/demo_ballot.py | 41 ++- .../ballotmaker/ballots/instructions.py | 17 +- .../ballotmaker/ballots/page_layout.py | 7 +- .../ballotmaker/demo_data/ballot_lab_data.py | 253 ++++++++++++++++++ .../ballotmaker/demo_data/spacetown_data.py | 46 ++++ 6 files changed, 372 insertions(+), 39 deletions(-) create mode 100644 src/electos/ballotmaker/demo_data/ballot_lab_data.py create mode 100644 src/electos/ballotmaker/demo_data/spacetown_data.py diff --git a/src/electos/ballotmaker/ballots/contest.py b/src/electos/ballotmaker/ballots/contest.py index 32726f0..3d10a07 100644 --- a/src/electos/ballotmaker/ballots/contest.py +++ b/src/electos/ballotmaker/ballots/contest.py @@ -31,45 +31,35 @@ def __init__(self, width=400, height=200, *args, **kw): self.oval.strokeWidth = 0.5 -class Contest: +class CandidateContest: """ Ballot Contest class encapsulates the generation of a ballot contest table """ - def __init__(self): + def __init__(self, contest_data: dict): # set up the page layout settings self.contest_list = [] - self.contestants = [] + self.candidates = [] self.contest_title = "" self.contest_instruct = "" + self.contest_data = contest_data def get_contest_data(): - self.contest_title = ( - "President and Vice-President of the United States" - ) - self.contest_instruct = "Vote for 1 pair" - self.contestants = [ - ("Joseph Barchi and Joseph Hallaren", "Blue"), - ("Adam Cramer and Greg Vuocolo", "Yellow"), - ("Daniel Court and Amy Blumhard", "Purple"), - ("Alvin Boone and James Lian", "Orange"), - ("Austin Hildebrand and James Garritty", "Pink"), - ("Martin Patterson and Clay Lariviere", "Gold"), - ("Elizabeth Harp and Antoine Jefferson", "Gray"), - ("Marzena Pazgier and Welton Phelps", "Brown"), - ] - - def build_contest_list(contestants, contestant_party_list): + self.contest_title = self.contest_data["title"] + self.contest_instruct = "Vote for 1" + self.candidates = self.contest_data["candidates"] + + def build_contest_list(candidates, contest_list): oval = SelectionOval() - for contestant, party in contestants: + for candidate in candidates: # add newlines around " and " - if contestant.find(" and "): - contestant = contestant.replace(" and ", "
and
") - contest_line = "{}
{}".format(contestant, party) + if candidate.find(" and "): + candidate = candidate.replace(" and ", "
and
") + contest_line = f"{candidate}" contest_row = [oval, Paragraph(contest_line, normal)] - contestant_party_list.append(contest_row) + contest_list.append(contest_row) def build_contest_table(): """ @@ -83,7 +73,7 @@ def build_contest_table(): row_2 = [Paragraph(self.contest_instruct, h2), ""] self.contest_list = [row_1] self.contest_list.append(row_2) - build_contest_list(self.contestants, self.contest_list) + build_contest_list(self.candidates, self.contest_list) # construct and format the contest table self.contest_table = Table( @@ -146,6 +136,7 @@ def build_contest_table(): normal_lead, sp_before=12, sp_after=48, + keep_w_next=1, ) PageLayout.define_custom_style( h2, @@ -157,6 +148,7 @@ def build_contest_table(): normal_lead, sp_before=12, sp_after=48, + keep_w_next=1, ) PageLayout.define_custom_style( normal, @@ -172,5 +164,8 @@ def build_contest_table(): if __name__ == "__main__": - contest_1 = Contest() + from electos.ballotmaker.demo_data import spacetown_data + + contest_1 = CandidateContest(spacetown_data.can_con_1) + print(contest_1.candidates) print(contest_1.contest_list) diff --git a/src/electos/ballotmaker/ballots/demo_ballot.py b/src/electos/ballotmaker/ballots/demo_ballot.py index a558b29..fb32316 100644 --- a/src/electos/ballotmaker/ballots/demo_ballot.py +++ b/src/electos/ballotmaker/ballots/demo_ballot.py @@ -8,10 +8,11 @@ from pathlib import Path # from reportlab.lib.styles import getSampleStyleSheet -from electos.ballotmaker.ballots.contest import Contest +from electos.ballotmaker.ballots.contest import CandidateContest from electos.ballotmaker.ballots.header import header from electos.ballotmaker.ballots.instructions import Instructions from electos.ballotmaker.ballots.page_layout import PageLayout +from electos.ballotmaker.demo_data import spacetown_data from reportlab.lib.styles import getSampleStyleSheet from reportlab.lib.units import inch from reportlab.platypus import ( @@ -21,10 +22,11 @@ PageTemplate, Paragraph, ) +from reportlab.platypus.flowables import CondPageBreak # set up frames # 1 = True, 0 = FALSE -SHOW_BOUNDARY = 1 +SHOW_BOUNDARY = 0 # get page layout settings margin = PageLayout.margin c_width = PageLayout.col_width @@ -37,21 +39,37 @@ def get_election_header() -> dict: "Name": "General Election", "StartDate": "2024-11-05", "EndDate": "2024-11-05", - "ElectionScope": "Port Precinct, The State of Farallon", + "ElectionScope": "Spacetown Precinct, Orbit City", } +def add_header_line(font_size, line_text, new_line=False): + line_end = "
" if new_line else "" + header_line = f"{line_text}{line_end}" + return header_line + + def build_header_text(): elect_dict = get_election_header() - font_size = 14 + font_size = 12 + formatted_header = add_header_line( + font_size + 2, f"Sample Ballot for {elect_dict['Name']}", new_line=True + ) + formatted_header += "
" + formatted_header += add_header_line( + font_size, elect_dict["ElectionScope"], new_line=True + ) + end_date = datetime.fromisoformat(elect_dict["EndDate"]) + formatted_date = end_date.strftime("%B %m, %Y") + formatted_header += add_header_line(font_size, formatted_date) - return f"Sample Ballot for {elect_dict['Name']}" + return formatted_header def header(canvas, doc, content): canvas.saveState() width, height = content.wrap(doc.width, doc.topMargin) - content.drawOn(canvas, 0.5 * inch, 11 * inch) + content.drawOn(canvas, 0.5 * inch, 10.6 * inch) canvas.restoreState() @@ -109,11 +127,16 @@ def build_ballot(): elements = inst.instruction_list elements.append(NextPageTemplate("3col")) # add a ballot contest to the second frame (colomn) - contest_1 = Contest() + contest_1 = CandidateContest(spacetown_data.can_con_1) elements.append(contest_1.contest_table) - + contest_2 = CandidateContest(spacetown_data.can_con_2) + elements.append(contest_2.contest_table) + contest_3 = CandidateContest(spacetown_data.can_con_3) + elements.append(contest_3.contest_table) + elements.append(CondPageBreak(c_height * inch)) + contest_4 = CandidateContest(spacetown_data.can_con_4) + elements.append(contest_4.contest_table) doc.build(elements) - # doc.build(elements) if __name__ == "__main__": diff --git a/src/electos/ballotmaker/ballots/instructions.py b/src/electos/ballotmaker/ballots/instructions.py index 294cbca..47cb79f 100644 --- a/src/electos/ballotmaker/ballots/instructions.py +++ b/src/electos/ballotmaker/ballots/instructions.py @@ -70,7 +70,12 @@ def build_instruction_list(): spacing = border_pad / 3 self.instruction_list = [ - (Paragraph(instruct_head, h1)), + ( + Paragraph( + instruct_head, + h1, + ) + ), (Spacer(0, spacing)), (Paragraph(fill_head, h2)), (Paragraph(image1_graf, img_graf)), @@ -126,7 +131,14 @@ def build_instruction_list(): # define our custom styles PageLayout.define_custom_style( - h1, dark, border_pad, font_size + 2, white, font_bold, head_lead + h1, + dark, + border_pad, + font_size + 2, + white, + font_bold, + head_lead, + keep_w_next=True, ) PageLayout.define_custom_style( h2, @@ -136,6 +148,7 @@ def build_instruction_list(): black, font_bold, head_lead, + keep_w_next=True, ) PageLayout.define_custom_style( normal, diff --git a/src/electos/ballotmaker/ballots/page_layout.py b/src/electos/ballotmaker/ballots/page_layout.py index 0c44fdd..e24ca9e 100644 --- a/src/electos/ballotmaker/ballots/page_layout.py +++ b/src/electos/ballotmaker/ballots/page_layout.py @@ -38,27 +38,30 @@ class PageLayout: bg_color: tuple = white border_color: tuple = black + keep_w_next = False # TODO: Rewrite with *args, **kwargs? def define_custom_style( style, bg_color=bg_color, - border_pd=border_pad, + border_pad=border_pad, font_sz=font_size, txt_color=black, font_n=font_normal, line_space=font_size + 1, sp_before=space_before, sp_after=space_after, + keep_w_next=keep_w_next, ): style.backColor = bg_color - style.borderPadding = border_pd + style.borderPadding = border_pad style.fontSize = font_sz style.textColor = txt_color style.fontName = font_n style.leading = line_space style.spaceBefore = sp_before style.spaceAfter = sp_after + style.keepWithNext = keep_w_next if __name__ == "__main__": diff --git a/src/electos/ballotmaker/demo_data/ballot_lab_data.py b/src/electos/ballotmaker/demo_data/ballot_lab_data.py new file mode 100644 index 0000000..cff33bf --- /dev/null +++ b/src/electos/ballotmaker/demo_data/ballot_lab_data.py @@ -0,0 +1,253 @@ +from typing import List, Union + +from electos.datamodels.nist.indexes import ElementIndex +from electos.datamodels.nist.models.edf import * + +# --- Base Types +# +# Schema expresses these as union types not subclasses + +Contest = Union[BallotMeasureContest, CandidateContest] + +OrderedContent = Union[OrderedContest, OrderedHeader] + + +# --- Utilities + + +def text_content(item): + """Return joined lines from internationalized text.""" + assert isinstance(item, InternationalizedText) + text = "\n".join(_.content for _ in item.text) + return text + + +def walk_ordered_contests(content: List[OrderedContent]): + """Walk ordered content yielding contests.""" + for item in content: + if isinstance(item, OrderedContest): + yield item + elif isinstance(item, OrderedHeader): + yield from walk_ordered_contests(item.ordered_content) + else: + raise TypeError(f"Unexpected type: {type(item).__name__}") + + +def walk_ordered_headers(content: List[OrderedContent]): + """Walk ordered content yielding headers.""" + for item in content: + if isinstance(item, OrderedHeader): + yield item + yield from walk_ordered_headers(item.ordered_content) + else: + raise TypeError(f"Unexpected type: {type(item).__name__}") + + +# --- Properties + + +def all_ballot_styles(election_report: ElectionReport, index): + """Yield all ballot styles.""" + for ballot_style in index.by_type("BallotStyle"): + yield ballot_style + + +def ballot_style_id(ballot_style: BallotStyle): + """Get the text of a ballot style's external identifier if any.""" + if ballot_style.external_identifier: + assert ( + len(ballot_style.external_identifier) == 1 + ), "Not ready to handle multiple BallotStyle external IDs" + name = ballot_style.external_identifier[0].value + else: + name = "" + return name + + +def ballot_style_gp_units(ballot_style: BallotStyle, index): + for id_ in ballot_style.gp_unit_ids: + gp_unit = index.by_id(id_) + yield gp_unit + + +def ballot_style_contests(ballot_style: BallotStyle, index): + """Yield the contests of a ballot style.""" + for item in walk_ordered_contests(ballot_style.ordered_content): + contest = index.by_id(item.contest_id) + yield contest + + +def candidate_name(candidate: Candidate): + """Get the name of a candidate as it appears on a ballot.""" + name = text_content(candidate.ballot_name) + return name + + +def candidate_contest_offices(contest: CandidateContest, index): + """Get any offices associated with a candidate contest.""" + offices = [] + if contest.office_ids: + for id_ in contest.office_ids: + office = index.by_id(id_) + name = text_content(office.name) + offices.append(name) + return offices + + +def candidate_contest_parties(contest: CandidateContest, index): + """Get any parties associated with a candidate contest.""" + parties = [] + if contest.primary_party_ids: + for id_ in contest.primary_party_ids: + party = index.by_id(id_) + name = text_content(party.name) + parties.append(name) + return parties + + +def contest_election_district(contest: Contest, index): + """Get the district name of a contest.""" + district = index.by_id(contest.election_district_id) + district = text_content(district.name) + return district + + +# Gather & Extract +# +# Results are data needed for ballot generation. + + +def extract_candidate_contest(contest: CandidateContest, index): + """Extract candidate contest information needed for ballots.""" + district = contest_election_district(contest, index) + candidates = [] + offices = candidate_contest_offices(contest, index) + parties = candidate_contest_parties(contest, index) + write_ins = 0 + for selection in contest.contest_selection: + assert isinstance( + selection, CandidateSelection + ), f"Unexpected non-candidate selection: {type(selection).__name__}" + # Write-ins have no candidate IDs + if selection.candidate_ids: + for id_ in selection.candidate_ids: + candidate = index.by_id(id_) + candidates.append(candidate) + if selection.is_write_in: + write_ins += 1 + result = { + "title": contest.name, + "type": "candidate", + "vote_type": contest.vote_variation.value, + "district": district, + "candidates": [candidate_name(_) for _ in candidates], + # Leave out offices and parties for now + # "offices": offices, + # "parties": parties, + "write_ins": write_ins, + } + return result + + +def extract_ballot_measure_contest(contest: BallotMeasureContest, index): + """Extract ballot measure contest information needed for ballots.""" + choices = [] + for selection in contest.contest_selection: + assert isinstance( + selection, BallotMeasureSelection + ), f"Unexpected non-ballot measure selection: {type(selection).__name__}" + choice = text_content(selection.selection) + choices.append(choice) + district = contest_election_district(contest, index) + full_text = text_content(contest.full_text) + result = { + "title": contest.name, + "type": "ballot measure", + "district": district, + "choices": choices, + "text": full_text, + } + return result + + +def gather_contests(ballot_style: BallotStyle, index): + """Extract all contest information needed for ballots.""" + contests = {kind: [] for kind in ("candidate", "ballot_measure")} + for contest in ballot_style_contests(ballot_style, index): + if isinstance(contest, CandidateContest): + entry = extract_candidate_contest(contest, index) + contests["candidate"].append(entry) + elif isinstance(contest, BallotMeasureContest): + entry = extract_ballot_measure_contest(contest, index) + contests["ballot_measure"].append(entry) + else: + # Ignore other contest types + print(f"Skipping contest of type {contest.model__type}") + return contests + + +# --- Main + +import argparse +import json +from pathlib import Path + + +def report(root, index, nth, **opts): + """Generate data needed by BallotLab""" + ballot_styles = list(all_ballot_styles(root, index)) + if not (1 <= nth <= len(ballot_styles)): + print(f"Ballot styles: {nth} is out of range [1-{len(ballot_styles)}]") + return + ballot_style = ballot_styles[nth - 1] + data = {} + id_ = ballot_style_id(ballot_style) + data["ballot_style"] = id_ + # gp_units = ballot_style_gp_units(ballot_style, index) + # data["locations"] = [text_content(_.name) for _ in gp_units] + contests = gather_contests(ballot_style, index) + if not contests: + print(f"No contests found for ballot style: {id_}\n") + data["contests"] = contests + print(json.dumps(data, indent=4)) + + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument( + "file", + nargs="?", + type=Path, + default="june-test-case.json", + help="Test case data (JSON)", + ) + parser.add_argument( + "nth", + nargs="?", + type=int, + default=1, + help="Index of the ballot style to extract (default: 1 (1st))", + ) + parser.add_argument( + "--debug", + action="store_true", + help="Enable debugging output and stack traces", + ) + opts = parser.parse_args() + file = opts.file + opts = vars(opts) + try: + with file.open() as input: + text = input.read() + data = json.loads(text) + edf = ElectionReport(**data) + index = ElementIndex(edf, "ElectionResults") + report(edf, index, **opts) + except Exception as ex: + if opts["debug"]: + raise ex + print("error:", ex) + + +if __name__ == "__main__": + main() diff --git a/src/electos/ballotmaker/demo_data/spacetown_data.py b/src/electos/ballotmaker/demo_data/spacetown_data.py new file mode 100644 index 0000000..cd016d8 --- /dev/null +++ b/src/electos/ballotmaker/demo_data/spacetown_data.py @@ -0,0 +1,46 @@ +can_con_4 = { + "title": "Contest for Mayor of Orbit City", + "type": "candidate", + "vote_type": "plurality", + "district": "Orbit City", + "candidates": ["Cosmo Spacely", "Spencer Cogswell"], + "write_ins": 1, +} + +can_con_3 = { + "title": "Spaceport Control Board", + "type": "candidate", + "vote_type": "n-of-m", + "district": "Aldrin Space Transport District", + "candidates": ["Jane Jetson", "Harlan Ellis", "Rudy Indexer"], + "write_ins": 2, +} + +can_con_2 = { + "title": "Gadget County School Board", + "type": "candidate", + "vote_type": "n-of-m", + "district": "Gadget County", + "candidates": [ + "Sally Smith", + "Hector Gomez", + "Rosashawn Davis", + "Oliver Tsi", + "Glavin Orotund", + ], + "write_ins": 3, +} + +can_con_1 = { + "title": "President of the United States", + "type": "candidate", + "vote_type": "plurality", + "district": "United States of America", + "candidates": [ + "Anthony Alpha", + "Betty Beta", + "Gloria Gamma", + "David Delta", + ], + "write_ins": 1, +} From c8ca76990c66060b68f1a5c0016bb0454461affa Mon Sep 17 00:00:00 2001 From: Neil Johnson Date: Sun, 21 Aug 2022 23:37:15 -0400 Subject: [PATCH 09/28] Add demo PDF --- pdfs/ballot_demo_2022_08_21T232623.pdf | Bin 0 -> 165067 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 pdfs/ballot_demo_2022_08_21T232623.pdf diff --git a/pdfs/ballot_demo_2022_08_21T232623.pdf b/pdfs/ballot_demo_2022_08_21T232623.pdf new file mode 100644 index 0000000000000000000000000000000000000000..a4469f4df232e7ea5ac215205e5df74113fe7b21 GIT binary patch literal 165067 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 literal 0 HcmV?d00001 From f70c16bfec1204fdb3eab81616ce2dcb88e2cea5 Mon Sep 17 00:00:00 2001 From: Neil Johnson Date: Mon, 22 Aug 2022 14:44:00 -0400 Subject: [PATCH 10/28] Create demo ballot from the CLI --- src/electos/ballotmaker/demo.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/electos/ballotmaker/demo.py b/src/electos/ballotmaker/demo.py index e5fde29..ae82496 100644 --- a/src/electos/ballotmaker/demo.py +++ b/src/electos/ballotmaker/demo.py @@ -1,6 +1,6 @@ import logging -# from electos.ballotmaker.ballots.demo_ballot import build_ballot +from electos.ballotmaker.ballots.demo_ballot import build_ballot from electos.ballotmaker.constants import NO_ERRORS log = logging.getLogger(__name__) @@ -8,5 +8,5 @@ def make_demo_ballot(): log.debug("Starting ballotlab demo ...") - # build_ballot() + build_ballot() return NO_ERRORS From bde72b47d7d994ab13d6d505689b3ca1d574b39e Mon Sep 17 00:00:00 2001 From: Neil Johnson Date: Tue, 23 Aug 2022 18:30:18 -0400 Subject: [PATCH 11/28] Minor updates, 100% test coverage --- src/electos/ballotmaker/ballots/demo_ballot.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/electos/ballotmaker/ballots/demo_ballot.py b/src/electos/ballotmaker/ballots/demo_ballot.py index fb32316..43a5f37 100644 --- a/src/electos/ballotmaker/ballots/demo_ballot.py +++ b/src/electos/ballotmaker/ballots/demo_ballot.py @@ -1,6 +1,6 @@ """ ballot.py -The Ballot Class contains the document specifications, +The Demo Ballot module contains the document specifications, page templates, and specific pages """ from datetime import datetime @@ -39,6 +39,7 @@ def get_election_header() -> dict: "Name": "General Election", "StartDate": "2024-11-05", "EndDate": "2024-11-05", + "Type": "general" "ElectionScope": "Spacetown Precinct, Orbit City", } From 86819a09eafcc3d840b32cba185a70a057545086 Mon Sep 17 00:00:00 2001 From: Neil Johnson Date: Tue, 23 Aug 2022 18:31:34 -0400 Subject: [PATCH 12/28] Fixed election_header dict --- src/electos/ballotmaker/ballots/demo_ballot.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/electos/ballotmaker/ballots/demo_ballot.py b/src/electos/ballotmaker/ballots/demo_ballot.py index 43a5f37..3193e2b 100644 --- a/src/electos/ballotmaker/ballots/demo_ballot.py +++ b/src/electos/ballotmaker/ballots/demo_ballot.py @@ -39,7 +39,7 @@ def get_election_header() -> dict: "Name": "General Election", "StartDate": "2024-11-05", "EndDate": "2024-11-05", - "Type": "general" + "Type": "general", "ElectionScope": "Spacetown Precinct, Orbit City", } From 2d0e4a1adb91d5bdd682e7a38e5c40cc6b0de324 Mon Sep 17 00:00:00 2001 From: Neil Johnson Date: Wed, 24 Aug 2022 16:15:12 -0400 Subject: [PATCH 13/28] This week's version of the json data generator --- .../ballotmaker/demo_data/ballot_lab_data.py | 43 +++++++++++++------ 1 file changed, 29 insertions(+), 14 deletions(-) diff --git a/src/electos/ballotmaker/demo_data/ballot_lab_data.py b/src/electos/ballotmaker/demo_data/ballot_lab_data.py index cff33bf..e86dc1c 100644 --- a/src/electos/ballotmaker/demo_data/ballot_lab_data.py +++ b/src/electos/ballotmaker/demo_data/ballot_lab_data.py @@ -43,7 +43,7 @@ def walk_ordered_headers(content: List[OrderedContent]): raise TypeError(f"Unexpected type: {type(item).__name__}") -# --- Properties +# --- Ballot Properties def all_ballot_styles(election_report: ElectionReport, index): @@ -83,6 +83,22 @@ def candidate_name(candidate: Candidate): return name +def candidate_party(candidate: Candidate, index): + """Get the name and abbreviation of the party of a candidate as it appears on a ballot.""" + party = index.by_id(candidate.party_id) + name = text_content(party.name) if party else "" + abbreviation = ( + text_content(party.abbreviation) + if party and party.abbreviation + else "" + ) + result = { + "name": name, + "abbreviation": abbreviation, + } + return result + + def candidate_contest_offices(contest: CandidateContest, index): """Get any offices associated with a candidate contest.""" offices = [] @@ -123,7 +139,7 @@ def extract_candidate_contest(contest: CandidateContest, index): candidates = [] offices = candidate_contest_offices(contest, index) parties = candidate_contest_parties(contest, index) - write_ins = 0 + write_ins = [] for selection in contest.contest_selection: assert isinstance( selection, CandidateSelection @@ -134,13 +150,19 @@ def extract_candidate_contest(contest: CandidateContest, index): candidate = index.by_id(id_) candidates.append(candidate) if selection.is_write_in: - write_ins += 1 + write_ins.append(selection.model__id) result = { + "id": contest.model__id, "title": contest.name, "type": "candidate", "vote_type": contest.vote_variation.value, + # Include even when default is 1: don't require caller to track that. + "votes_allowed": contest.votes_allowed, "district": district, - "candidates": [candidate_name(_) for _ in candidates], + "candidates": [ + {"name": candidate_name(_), "party": candidate_party(_, index)} + for _ in candidates + ], # Leave out offices and parties for now # "offices": offices, # "parties": parties, @@ -203,8 +225,6 @@ def report(root, index, nth, **opts): data = {} id_ = ballot_style_id(ballot_style) data["ballot_style"] = id_ - # gp_units = ballot_style_gp_units(ballot_style, index) - # data["locations"] = [text_content(_.name) for _ in gp_units] contests = gather_contests(ballot_style, index) if not contests: print(f"No contests found for ballot style: {id_}\n") @@ -214,19 +234,13 @@ def report(root, index, nth, **opts): def main(): parser = argparse.ArgumentParser() - parser.add_argument( - "file", - nargs="?", - type=Path, - default="june-test-case.json", - help="Test case data (JSON)", - ) + parser.add_argument("file", type=Path, help="Test case data (JSON)") parser.add_argument( "nth", nargs="?", type=int, default=1, - help="Index of the ballot style to extract (default: 1 (1st))", + help="Index of the ballot style, starting from 1 (default: 1)", ) parser.add_argument( "--debug", @@ -236,6 +250,7 @@ def main(): opts = parser.parse_args() file = opts.file opts = vars(opts) + try: with file.open() as input: text = input.read() From 0e7a2cc6598cc326f3630609cdc83a752cfd31fc Mon Sep 17 00:00:00 2001 From: Neil Johnson Date: Wed, 24 Aug 2022 18:56:10 -0400 Subject: [PATCH 14/28] Update Spacetown data from the new spacetown JSON --- .../ballotmaker/demo_data/spacetown_data.json | 182 ++++++++++++++++++ .../ballotmaker/demo_data/spacetown_data.py | 92 +++++++-- 2 files changed, 259 insertions(+), 15 deletions(-) create mode 100644 src/electos/ballotmaker/demo_data/spacetown_data.json diff --git a/src/electos/ballotmaker/demo_data/spacetown_data.json b/src/electos/ballotmaker/demo_data/spacetown_data.json new file mode 100644 index 0000000..5d3f652 --- /dev/null +++ b/src/electos/ballotmaker/demo_data/spacetown_data.json @@ -0,0 +1,182 @@ +{ + "ballot_style": "precinct_2_spacetown", + "contests": { + "candidate": [ + { + "id": "recIj8OmzqzzvnDbM", + "title": "Contest for Mayor of Orbit City", + "type": "candidate", + "vote_type": "plurality", + "votes_allowed": 1, + "district": "Orbit City", + "candidates": [ + { + "name": "Cosmo Spacely", + "party": { + "name": "The Lepton Party", + "abbreviation": "LEP" + } + }, + { + "name": "Spencer Cogswell", + "party": { + "name": "The Hadron Party of Farallon", + "abbreviation": "HAD" + } + } + ], + "write_ins": [ + "recqq21kO6HWgpJZV" + ] + }, + { + "id": "recXNb4zPrvC1m6Fr", + "title": "Spaceport Control Board", + "type": "candidate", + "vote_type": "n-of-m", + "votes_allowed": 2, + "district": "Aldrin Space Transport District", + "candidates": [ + { + "name": "Jane Jetson", + "party": { + "name": "", + "abbreviation": "" + } + }, + { + "name": "Harlan Ellis", + "party": { + "name": "", + "abbreviation": "" + } + }, + { + "name": "Rudy Indexer", + "party": { + "name": "", + "abbreviation": "" + } + } + ], + "write_ins": [ + "rec9Eev970VhohqKi", + "recFiGYjGCIyk5LBe" + ] + }, + { + "id": "recthF6jdx5ybBNkC", + "title": "Gadget County School Board", + "type": "candidate", + "vote_type": "n-of-m", + "votes_allowed": 4, + "district": "Gadget County", + "candidates": [ + { + "name": "Sally Smith", + "party": { + "name": "", + "abbreviation": "" + } + }, + { + "name": "Hector Gomez", + "party": { + "name": "", + "abbreviation": "" + } + }, + { + "name": "Rosashawn Davis", + "party": { + "name": "", + "abbreviation": "" + } + }, + { + "name": "Oliver Tsi", + "party": { + "name": "", + "abbreviation": "" + } + }, + { + "name": "Glavin Orotund", + "party": { + "name": "", + "abbreviation": "" + } + } + ], + "write_ins": [ + "recYurH2CLY3SlYS8", + "recI5jfcXIsbAKytC", + "recn9m0o1em7gLahj" + ] + }, + { + "id": "recsoZy7vYhS3lbcK", + "title": "President of the United States", + "type": "candidate", + "vote_type": "plurality", + "votes_allowed": 1, + "district": "United States of America", + "candidates": [ + { + "name": "Anthony Alpha", + "party": { + "name": "The Lepton Party", + "abbreviation": "LEP" + } + }, + { + "name": "Betty Beta", + "party": { + "name": "The Lepton Party", + "abbreviation": "LEP" + } + }, + { + "name": "Gloria Gamma", + "party": { + "name": "The Hadron Party of Farallon", + "abbreviation": "HAD" + } + }, + { + "name": "David Delta", + "party": { + "name": "The Hadron Party of Farallon", + "abbreviation": "HAD" + } + } + ], + "write_ins": [ + "recPod2L8VhwagiDl" + ] + } + ], + "ballot_measure": [ + { + "title": "Air Traffic Control Tax Increase", + "type": "ballot measure", + "district": "Gadget County", + "choices": [ + "Yes", + "No" + ], + "text": "Shall Gadget County increase its sales tax from 1% to 1.1% for the purpose of raising additional revenue to fund expanded air traffic control operations?" + }, + { + "title": "Constitutional Amendment", + "type": "ballot measure", + "district": "The State of Farallon", + "choices": [ + "Yes", + "No" + ], + "text": "Do you approve amending the Constitution to legalize the controlled use of helium balloons? Only adults at least 21 years of age could use helium. The State commission created to oversee the State's medical helium program would also oversee the new, personal use helium market.Helium balloons would be subject to the State sales tax. If authorized by the Legislature, a municipality may pass a local ordinance to charge a local tax on helium balloons." + } + ] + } +} diff --git a/src/electos/ballotmaker/demo_data/spacetown_data.py b/src/electos/ballotmaker/demo_data/spacetown_data.py index cd016d8..75b2dd4 100644 --- a/src/electos/ballotmaker/demo_data/spacetown_data.py +++ b/src/electos/ballotmaker/demo_data/spacetown_data.py @@ -1,46 +1,108 @@ can_con_4 = { + "id": "recIj8OmzqzzvnDbM", "title": "Contest for Mayor of Orbit City", "type": "candidate", "vote_type": "plurality", + "votes_allowed": 1, "district": "Orbit City", - "candidates": ["Cosmo Spacely", "Spencer Cogswell"], - "write_ins": 1, + "candidates": [ + { + "name": "Cosmo Spacely", + "party": {"name": "The Lepton Party", "abbreviation": "LEP"}, + }, + { + "name": "Spencer Cogswell", + "party": { + "name": "The Hadron Party of Farallon", + "abbreviation": "HAD", + }, + }, + ], + "write_ins": ["recqq21kO6HWgpJZV"], } can_con_3 = { + "id": "recXNb4zPrvC1m6Fr", "title": "Spaceport Control Board", "type": "candidate", "vote_type": "n-of-m", + "votes_allowed": 2, "district": "Aldrin Space Transport District", - "candidates": ["Jane Jetson", "Harlan Ellis", "Rudy Indexer"], - "write_ins": 2, + "candidates": [ + {"name": "Jane Jetson", "party": {"name": "", "abbreviation": ""}}, + {"name": "Harlan Ellis", "party": {"name": "", "abbreviation": ""}}, + {"name": "Rudy Indexer", "party": {"name": "", "abbreviation": ""}}, + ], + "write_ins": ["rec9Eev970VhohqKi", "recFiGYjGCIyk5LBe"], } can_con_2 = { + "id": "recthF6jdx5ybBNkC", "title": "Gadget County School Board", "type": "candidate", "vote_type": "n-of-m", + "votes_allowed": 4, "district": "Gadget County", "candidates": [ - "Sally Smith", - "Hector Gomez", - "Rosashawn Davis", - "Oliver Tsi", - "Glavin Orotund", + {"name": "Sally Smith", "party": {"name": "", "abbreviation": ""}}, + {"name": "Hector Gomez", "party": {"name": "", "abbreviation": ""}}, + {"name": "Rosashawn Davis", "party": {"name": "", "abbreviation": ""}}, + {"name": "Oliver Tsi", "party": {"name": "", "abbreviation": ""}}, + {"name": "Glavin Orotund", "party": {"name": "", "abbreviation": ""}}, + ], + "write_ins": [ + "recYurH2CLY3SlYS8", + "recI5jfcXIsbAKytC", + "recn9m0o1em7gLahj", ], - "write_ins": 3, } can_con_1 = { + "id": "recsoZy7vYhS3lbcK", "title": "President of the United States", "type": "candidate", "vote_type": "plurality", + "votes_allowed": 1, "district": "United States of America", "candidates": [ - "Anthony Alpha", - "Betty Beta", - "Gloria Gamma", - "David Delta", + { + "name": "Anthony Alpha", + "party": {"name": "The Lepton Party", "abbreviation": "LEP"}, + }, + { + "name": "Betty Beta", + "party": {"name": "The Lepton Party", "abbreviation": "LEP"}, + }, + { + "name": "Gloria Gamma", + "party": { + "name": "The Hadron Party of Farallon", + "abbreviation": "HAD", + }, + }, + { + "name": "David Delta", + "party": { + "name": "The Hadron Party of Farallon", + "abbreviation": "HAD", + }, + }, ], - "write_ins": 1, + "write_ins": ["recPod2L8VhwagiDl"], +} + +ballot_measure_1 = { + "title": "Constitutional Amendment", + "type": "ballot measure", + "district": "The State of Farallon", + "choices": ["Yes", "No"], + "text": "Do you approve amending the Constitution to legalize the controlled use of helium balloons? Only adults at least 21 years of age could use helium. The State commission created to oversee the State's medical helium program would also oversee the new, personal use helium market.Helium balloons would be subject to the State sales tax. If authorized by the Legislature, a municipality may pass a local ordinance to charge a local tax on helium balloons.", +} + +ballot_measure_2 = { + "title": "Air Traffic Control Tax Increase", + "type": "ballot measure", + "district": "Gadget County", + "choices": ["Yes", "No"], + "text": "Shall Gadget County increase its sales tax from 1% to 1.1% for the purpose of raising additional revenue to fund expanded air traffic control operations?", } From c3ec1f892d94935b20cceb5ffe1c7edf17f2b623 Mon Sep 17 00:00:00 2001 From: Neil Johnson Date: Thu, 25 Aug 2022 10:08:13 -0400 Subject: [PATCH 15/28] Comment out ticket code --- src/electos/ballotmaker/ballots/contest.py | 4 ++-- src/electos/ballotmaker/ballots/demo_ballot.py | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/electos/ballotmaker/ballots/contest.py b/src/electos/ballotmaker/ballots/contest.py index 3d10a07..58d3f29 100644 --- a/src/electos/ballotmaker/ballots/contest.py +++ b/src/electos/ballotmaker/ballots/contest.py @@ -55,8 +55,8 @@ def build_contest_list(candidates, contest_list): oval = SelectionOval() for candidate in candidates: # add newlines around " and " - if candidate.find(" and "): - candidate = candidate.replace(" and ", "
and
") + # if candidate.find(" and "): + # candidate = candidate.replace(" and ", "
and
") contest_line = f"{candidate}" contest_row = [oval, Paragraph(contest_line, normal)] contest_list.append(contest_row) diff --git a/src/electos/ballotmaker/ballots/demo_ballot.py b/src/electos/ballotmaker/ballots/demo_ballot.py index 3193e2b..d56ee03 100644 --- a/src/electos/ballotmaker/ballots/demo_ballot.py +++ b/src/electos/ballotmaker/ballots/demo_ballot.py @@ -1,5 +1,4 @@ """ -ballot.py The Demo Ballot module contains the document specifications, page templates, and specific pages """ From 276ceb7c5a84f90c0cc86a7025a6ccd887c32efb Mon Sep 17 00:00:00 2001 From: Neil Johnson Date: Thu, 25 Aug 2022 10:23:16 -0400 Subject: [PATCH 16/28] Move img files into src tree --- src/electos/ballotmaker/ballots/files.py | 7 +++--- src/electos/ballotmaker/ballots/images.py | 20 +++++------------- .../ballotmaker}/img/filled_bubble.png | Bin .../electos/ballotmaker}/img/warn_cyan.png | Bin .../electos/ballotmaker}/img/writein.png | Bin 5 files changed, 9 insertions(+), 18 deletions(-) rename {assets => src/electos/ballotmaker}/img/filled_bubble.png (100%) rename {assets => src/electos/ballotmaker}/img/warn_cyan.png (100%) rename {assets => src/electos/ballotmaker}/img/writein.png (100%) diff --git a/src/electos/ballotmaker/ballots/files.py b/src/electos/ballotmaker/ballots/files.py index 0e08fca..d469700 100644 --- a/src/electos/ballotmaker/ballots/files.py +++ b/src/electos/ballotmaker/ballots/files.py @@ -19,7 +19,9 @@ def __init__(self, file_name="", rel_path=""): self.code_dir = os.path.dirname(getsourcefile(lambda: 0)) base_len = int(self.code_dir.find(self.package_name)) if base_len == -1: - raise Exception("Not executing in the expected package.") + raise Exception( + "Not executing in the expected package.", ValueError + ) # if this module is part of the specified package, build the absolute path self.package_root = os.path.join( self.code_dir[:base_len], self.package_name @@ -43,12 +45,11 @@ def __init__(self, file_name="", rel_path=""): file_defaults = FileTools() print(file_defaults.code_dir) print(file_defaults.package_root) - print(file_defaults.package_root) print(file_defaults.full_path) print(file_defaults.file_found) target_file = "writein.png" - target_dir = "assets/img" + target_dir = "src/electos/ballotmaker/img" print(f"Check for file {target_file} in directory {target_dir}") file_check = FileTools(target_file, target_dir) print(file_check.code_dir) diff --git a/src/electos/ballotmaker/ballots/images.py b/src/electos/ballotmaker/ballots/images.py index c56a6e0..a1934dc 100644 --- a/src/electos/ballotmaker/ballots/images.py +++ b/src/electos/ballotmaker/ballots/images.py @@ -18,9 +18,9 @@ class EmbeddedImage: def __init__(self, image_name, new_width=240) -> None: self.image_name = image_name self.new_width = new_width - self.rel_img_path = "assets/img" + self.rel_img_path = "src/electos/ballotmaker/img" self.embed_text = "" - # find the image + image_file = FileTools(self.image_name, self.rel_img_path) self.file_check(image_file) # retrieve the image and measure it @@ -30,22 +30,12 @@ def __init__(self, image_name, new_width=240) -> None: aspect = img_height / float(img_width) # resize based on the new width self.new_height = round(new_width * aspect) - self.embed_text = ( - '
' - '
'.format( - round(self.new_height / 2), - self.image_full_path, - new_width, - self.new_height, - ) - ) + self.embed_text = f'
' def file_check(self, image_file): if image_file.file_found is False: - file_error = "File {} not found in {}".format( - self.image_name, self.rel_img_path + file_error = ( + f"File {self.image_name} not found in {self.rel_img_path}" ) raise FileNotFoundError(file_error) diff --git a/assets/img/filled_bubble.png b/src/electos/ballotmaker/img/filled_bubble.png similarity index 100% rename from assets/img/filled_bubble.png rename to src/electos/ballotmaker/img/filled_bubble.png diff --git a/assets/img/warn_cyan.png b/src/electos/ballotmaker/img/warn_cyan.png similarity index 100% rename from assets/img/warn_cyan.png rename to src/electos/ballotmaker/img/warn_cyan.png diff --git a/assets/img/writein.png b/src/electos/ballotmaker/img/writein.png similarity index 100% rename from assets/img/writein.png rename to src/electos/ballotmaker/img/writein.png From 400efad0e64347fb83c5c917573e0e36c8c4b3bd Mon Sep 17 00:00:00 2001 From: Neil Johnson Date: Thu, 25 Aug 2022 11:50:57 -0400 Subject: [PATCH 17/28] Rewrite files to look in source dir using pathlib --- src/electos/ballotmaker/ballots/files.py | 83 ++++++++++++----------- src/electos/ballotmaker/ballots/images.py | 2 +- 2 files changed, 43 insertions(+), 42 deletions(-) diff --git a/src/electos/ballotmaker/ballots/files.py b/src/electos/ballotmaker/ballots/files.py index d469700..8a91fc0 100644 --- a/src/electos/ballotmaker/ballots/files.py +++ b/src/electos/ballotmaker/ballots/files.py @@ -1,61 +1,62 @@ -# Get properties of the file system -# TODO: Revactor with pathlib to normalize paths across operating systems -# [PEP 428 -- The pathlib module -- object-oriented filesystem paths | Python.org](https://www.python.org/dev/peps/pep-0428/) +# Finds the source directory for the specified package, +# builds paths and confirms the existence of files -import os -from inspect import getsourcefile - -from genericpath import isfile +from pathlib import Path class FileTools: - def __init__(self, file_name="", rel_path=""): - self.file_name = file_name - self.rel_path = rel_path + def __init__( + self, file_name="", rel_path="", source_dir="BallotLabFork/src" + ): + self.file_name: str = file_name + self.rel_path: str = rel_path self.ext = "" - self.file_found = False - self.package_name = "BallotLabFork" + self.file_found: bool = False + self.source_dir: str = source_dir + # get the absolute path to this module - self.code_dir = os.path.dirname(getsourcefile(lambda: 0)) - base_len = int(self.code_dir.find(self.package_name)) + self.code_dir = Path(__file__).parent.resolve() + + base_len = str(self.code_dir).find(self.source_dir) if base_len == -1: - raise Exception( - "Not executing in the expected package.", ValueError + raise ValueError( + f"Not executing in the expected package, {self.source_dir}.", ) - # if this module is part of the specified package, build the absolute path - self.package_root = os.path.join( - self.code_dir[:base_len], self.package_name - ) - self.full_path = self.package_root - # add a relative path to the target directory - if self.rel_path: - self.full_path = os.path.join(self.full_path, self.rel_path) + # if this module is part of the specified package's source tree, + # build the absolute path + self.full_source_path = Path(str(self.code_dir)[:base_len], source_dir) + # add a relative path to the source directory, if provided + self.full_path = Path(self.full_source_path, self.rel_path) + + if not self.full_path.is_dir(): + raise FileExistsError(f"Directory doesn't exist: {self.full_path}") + # look for the file, if provided if self.file_name: - self.abs_path_to_file = os.path.join( - self.full_path, self.file_name - ) + self.abs_path_to_file = Path(self.full_path, self.file_name) # if the file exists, get the extension and set the file found flag to True - if isfile(self.abs_path_to_file): - self.ext = os.path.splitext(self.file_name)[1] + if self.abs_path_to_file.is_file(): + self.ext = self.abs_path_to_file.suffix self.file_found = True if __name__ == "__main__": print("Default settings:") file_defaults = FileTools() - print(file_defaults.code_dir) - print(file_defaults.package_root) - print(file_defaults.full_path) - print(file_defaults.file_found) + print(f"Default code dir = {file_defaults.code_dir}") + print(f"Default package dir = {file_defaults.full_source_path}") + print(f"Default relative path = {file_defaults.rel_path}") + print(f"Default full path = {file_defaults.full_path}") + print(f"Default file found = {file_defaults.file_found}") target_file = "writein.png" - target_dir = "src/electos/ballotmaker/img" + target_dir = "electos/ballotmaker/img" print(f"Check for file {target_file} in directory {target_dir}") file_check = FileTools(target_file, target_dir) - print(file_check.code_dir) - print(file_check.package_root) - print(file_check.file_name) - print(file_check.rel_path) - print(file_check.full_path) - print(file_check.abs_path_to_file) - print(file_check.file_found) + print(f"File check code dir = {file_check.code_dir}") + print(f"File check full source path = {file_check.full_source_path}") + print(f"File check file name = {file_check.file_name}") + print(f"File check relative path = {file_check.rel_path}") + print(f"File check full path = {file_check.full_path}") + print(f"File check absolute path to file = {file_check.abs_path_to_file}") + print(f"File check file ext = {file_check.ext}") + print(f"File check file found = {file_check.file_found}") diff --git a/src/electos/ballotmaker/ballots/images.py b/src/electos/ballotmaker/ballots/images.py index a1934dc..70902b5 100644 --- a/src/electos/ballotmaker/ballots/images.py +++ b/src/electos/ballotmaker/ballots/images.py @@ -18,7 +18,7 @@ class EmbeddedImage: def __init__(self, image_name, new_width=240) -> None: self.image_name = image_name self.new_width = new_width - self.rel_img_path = "src/electos/ballotmaker/img" + self.rel_img_path = "electos/ballotmaker/img" self.embed_text = "" image_file = FileTools(self.image_name, self.rel_img_path) From 34a854e7eef1295e40c5c582ab41b82a2eda5176 Mon Sep 17 00:00:00 2001 From: Neil Johnson Date: Thu, 25 Aug 2022 15:14:09 -0400 Subject: [PATCH 18/28] Fix paths for Windows, include project (not src) --- .../img/filled_bubble.png | Bin .../ballotmaker => assets}/img/warn_cyan.png | Bin .../ballotmaker => assets}/img/writein.png | Bin src/electos/ballotmaker/ballots/files.py | 33 +++++++++--------- src/electos/ballotmaker/ballots/images.py | 2 +- src/electos/ballotmaker/cli.py | 5 +-- 6 files changed, 21 insertions(+), 19 deletions(-) rename {src/electos/ballotmaker => assets}/img/filled_bubble.png (100%) rename {src/electos/ballotmaker => assets}/img/warn_cyan.png (100%) rename {src/electos/ballotmaker => assets}/img/writein.png (100%) diff --git a/src/electos/ballotmaker/img/filled_bubble.png b/assets/img/filled_bubble.png similarity index 100% rename from src/electos/ballotmaker/img/filled_bubble.png rename to assets/img/filled_bubble.png diff --git a/src/electos/ballotmaker/img/warn_cyan.png b/assets/img/warn_cyan.png similarity index 100% rename from src/electos/ballotmaker/img/warn_cyan.png rename to assets/img/warn_cyan.png diff --git a/src/electos/ballotmaker/img/writein.png b/assets/img/writein.png similarity index 100% rename from src/electos/ballotmaker/img/writein.png rename to assets/img/writein.png diff --git a/src/electos/ballotmaker/ballots/files.py b/src/electos/ballotmaker/ballots/files.py index 8a91fc0..b9bab97 100644 --- a/src/electos/ballotmaker/ballots/files.py +++ b/src/electos/ballotmaker/ballots/files.py @@ -6,27 +6,28 @@ class FileTools: def __init__( - self, file_name="", rel_path="", source_dir="BallotLabFork/src" + self, file_name="", rel_path="assets/img", package_name="BallotLabFork" ): self.file_name: str = file_name - self.rel_path: str = rel_path - self.ext = "" + self.rel_path: Path = Path(rel_path) + self.ext: str = "" self.file_found: bool = False - self.source_dir: str = source_dir + self.package_name: str = package_name + self.package_path: Path = "" # get the absolute path to this module self.code_dir = Path(__file__).parent.resolve() - base_len = str(self.code_dir).find(self.source_dir) - if base_len == -1: - raise ValueError( - f"Not executing in the expected package, {self.source_dir}.", - ) - # if this module is part of the specified package's source tree, - # build the absolute path - self.full_source_path = Path(str(self.code_dir)[:base_len], source_dir) + # does the package name appear in the code path? + # if self.code_dir.match(self.package_name): + # walk the path upward until the source dir + path_finder = self.code_dir + while path_finder.name != self.package_name: + path_finder = path_finder.parent + self.package_path = path_finder + # add a relative path to the source directory, if provided - self.full_path = Path(self.full_source_path, self.rel_path) + self.full_path = Path(self.package_path, self.rel_path) if not self.full_path.is_dir(): raise FileExistsError(f"Directory doesn't exist: {self.full_path}") @@ -43,17 +44,17 @@ def __init__( print("Default settings:") file_defaults = FileTools() print(f"Default code dir = {file_defaults.code_dir}") - print(f"Default package dir = {file_defaults.full_source_path}") + print(f"Default package dir = {file_defaults.package_path}") print(f"Default relative path = {file_defaults.rel_path}") print(f"Default full path = {file_defaults.full_path}") print(f"Default file found = {file_defaults.file_found}") target_file = "writein.png" - target_dir = "electos/ballotmaker/img" + target_dir = "assets/img" print(f"Check for file {target_file} in directory {target_dir}") file_check = FileTools(target_file, target_dir) print(f"File check code dir = {file_check.code_dir}") - print(f"File check full source path = {file_check.full_source_path}") + print(f"File check full source path = {file_check.package_path}") print(f"File check file name = {file_check.file_name}") print(f"File check relative path = {file_check.rel_path}") print(f"File check full path = {file_check.full_path}") diff --git a/src/electos/ballotmaker/ballots/images.py b/src/electos/ballotmaker/ballots/images.py index 70902b5..c93aaa0 100644 --- a/src/electos/ballotmaker/ballots/images.py +++ b/src/electos/ballotmaker/ballots/images.py @@ -18,7 +18,7 @@ class EmbeddedImage: def __init__(self, image_name, new_width=240) -> None: self.image_name = image_name self.new_width = new_width - self.rel_img_path = "electos/ballotmaker/img" + self.rel_img_path = "assets/img" self.embed_text = "" image_file = FileTools(self.image_name, self.rel_img_path) diff --git a/src/electos/ballotmaker/cli.py b/src/electos/ballotmaker/cli.py index d5feff3..e2a8dc5 100644 --- a/src/electos/ballotmaker/cli.py +++ b/src/electos/ballotmaker/cli.py @@ -6,7 +6,8 @@ from typing import Optional import typer -from electos.ballotmaker import demo, make_ballots, validate_edf +from electos.ballotmaker import make_ballots, validate_edf +from electos.ballotmaker.ballots.demo_ballot import build_ballot from electos.ballotmaker.constants import NO_ERRORS, PROGRAM_NAME, VERSION EDF_HELP = "EDF file with ballot data (JSON format)" @@ -42,7 +43,7 @@ def main( @app.command() def demo(): """Make ballots from previously extracted EDF data""" - # demo.make_demo_ballot() + build_ballot() return NO_ERRORS From 08f9b432abb4f998edaad8334da57fc4dcabe0a4 Mon Sep 17 00:00:00 2001 From: Neil Johnson Date: Thu, 25 Aug 2022 21:54:08 -0400 Subject: [PATCH 19/28] Create contest data and layout classes --- .../ballotmaker/ballots/contest_data.py | 48 +++++ .../ballotmaker/ballots/contest_layout.py | 170 ++++++++++++++++++ .../ballotmaker/ballots/demo_ballot.py | 3 +- 3 files changed, 219 insertions(+), 2 deletions(-) create mode 100644 src/electos/ballotmaker/ballots/contest_data.py create mode 100644 src/electos/ballotmaker/ballots/contest_layout.py diff --git a/src/electos/ballotmaker/ballots/contest_data.py b/src/electos/ballotmaker/ballots/contest_data.py new file mode 100644 index 0000000..d53df2e --- /dev/null +++ b/src/electos/ballotmaker/ballots/contest_data.py @@ -0,0 +1,48 @@ +from dataclasses import dataclass, field +from typing import List + + +@dataclass +class CandidateContestData: + """Retrieve candidate contest data from a dict""" + + _can_con: dict = field(repr=False) + # fields retrieved from the dict + contest_id: str = field(init=False) + contest_title: str = field(init=False) + votes_allowed: int = field(init=False) + district: str = field(init=False) + candidates: list = field(default_factory=list, init=False, repr=True) + + def __post_init__(self): + self.contest_id = self._can_con["id"] + self.contest_title = self._can_con["title"] + self.votes_allowed = self._can_con["votes_allowed"] + self.district = self._can_con["district"] + _candidates = self._can_con["candidates"] + for candidate_data in _candidates: + self.candidates.append(CandidateData(candidate_data)) + + +@dataclass +class CandidateData: + _can_data: dict = field(repr=False) + candidate_id: str = "no_id_provided" + candidate_name: str = field(init=False) + candidate_party: str = field(init=False) + candidate_party_abbr: str = field(init=False) + + def __post_init__(self): + self.candidate_name = self._can_data["name"] + party_dict = self._can_data["party"] + self.candidate_party = party_dict["name"] + self.candidate_party_abbr = party_dict["abbreviation"] + + +if __name__ == "__main__": + from electos.ballotmaker.demo_data import spacetown_data + + can_con_data_1 = CandidateContestData(spacetown_data.can_con_1) + print(can_con_data_1) + can_con_data_2 = CandidateContestData(spacetown_data.can_con_2) + print(can_con_data_2) diff --git a/src/electos/ballotmaker/ballots/contest_layout.py b/src/electos/ballotmaker/ballots/contest_layout.py new file mode 100644 index 0000000..201773a --- /dev/null +++ b/src/electos/ballotmaker/ballots/contest_layout.py @@ -0,0 +1,170 @@ +# format a ballot contest. + +from electos.ballotmaker.ballots.page_layout import PageLayout +from reportlab.graphics.shapes import Drawing, Ellipse, _DrawingEditorMixin +from reportlab.lib.colors import black, white +from reportlab.lib.styles import LineStyle, getSampleStyleSheet +from reportlab.platypus import Paragraph, Table + +oval_width = 10 +oval_height = 4 + + +class SelectionOval(_DrawingEditorMixin, Drawing): + def __init__(self, width=400, height=200, *args, **kw): + Drawing.__init__(self, width, height, *args, **kw) + + self.width = oval_width + PageLayout.border_pad + self.height = oval_height + PageLayout.border_pad + oval_cx = self.width / 2 + oval_cy = self.height / 2 + self._add( + self, + Ellipse(oval_cx, oval_cy, oval_width, oval_height), + name="oval", + validate=None, + desc=None, + ) + self.oval.fillColor = white + self.oval.strokeColor = black + self.oval.strokeWidth = 0.5 + + +class CandidateContestLayout: + """ + Ballot Contest Laout class encapsulates + the generation of a ballot contest + table flowable + """ + + def __init__(self, contest_data: dict): + # set up the page layout settings + self.contest_list = [] + self.candidates = [] + self.contest_title = "" + self.contest_instruct = "" + self.contest_data = contest_data + + def get_contest_data(): + self.contest_title = self.contest_data["title"] + self.contest_instruct = "Vote for 1" + self.candidates = self.contest_data["candidates"] + + def build_contest_list(candidates, contest_list): + oval = SelectionOval() + for candidate in candidates: + # add newlines around " and " + # if candidate.find(" and "): + # candidate = candidate.replace(" and ", "
and
") + contest_line = f"{candidate}" + contest_row = [oval, Paragraph(contest_line, normal)] + contest_list.append(contest_row) + + def build_contest_table(): + """ + Builds a table with contest header, instructions + and choices + """ + # get the contest data from the data source + get_contest_data() + # build the contest header + row_1 = [Paragraph(self.contest_title, h1), ""] + row_2 = [Paragraph(self.contest_instruct, h2), ""] + self.contest_list = [row_1] + self.contest_list.append(row_2) + build_contest_list(self.candidates, self.contest_list) + + # construct and format the contest table + self.contest_table = Table( + data=self.contest_list, + colWidths=(oval_width * 3, None), + style=[ + # draw lines below each contestant + ("LINEBELOW", (1, 2), (1, -1), 1, grey), + # format the header + ("BACKGROUND", (0, 0), (1, 0), grey), + ("BACKGROUND", (0, 1), (1, 1), light), + # draw the outer border on top + ("LINEABOVE", (0, 0), (1, 0), 3, black), + ("LINEBEFORE", (0, 0), (0, -1), 1, black), + ("LINEBELOW", (0, -1), (-1, -1), 1, black), + ("VALIGN", (0, 0), (-1, -1), "TOP"), + ("SPAN", (0, 0), (1, 0)), + ("SPAN", (0, 1), (1, 1)), + # ("FONTSIZE", (1, 2), (-1, -1), 48), + ("TOPPADDING", (0, 2), (-1, -1), 4), + # pad the first cell + ("BOTTOMPADDING", (0, 0), (0, 1), 8), + # pad below each contestant + ("BOTTOMPADDING", (0, 2), (-1, -1), 16), + ], + ) + # self.contest_table._linecmds(["ROUNDEDCORNERS", 1]) + + # define styles + # fill colors + light = PageLayout.light + white = PageLayout.white + black = PageLayout.black + grey = PageLayout.grey + + # font family info + font_normal = PageLayout.font_normal + font_bold = PageLayout.font_bold + font_size = PageLayout.font_size + normal_lead = PageLayout.normal_lead + border_pad = PageLayout.border_pad / 2 + + # image dimensions + col_width = PageLayout.col_width + + # start with the sample styles + styles = getSampleStyleSheet() + normal = styles["Normal"] + h1 = styles["Heading1"] + h2 = styles["Heading2"] + + # define custom styles for contest tables + PageLayout.define_custom_style( + h1, + grey, + border_pad, + font_size + 2, + black, + font_bold, + normal_lead, + sp_before=12, + sp_after=48, + keep_w_next=1, + ) + PageLayout.define_custom_style( + h2, + light, + border_pad, + font_size, + black, + font_bold, + normal_lead, + sp_before=12, + sp_after=48, + keep_w_next=1, + ) + PageLayout.define_custom_style( + normal, + white, + border_pad, + font_size, + black, + font_normal, + normal_lead, + ) + # build the contest table, an attribute of the Contest object + build_contest_table() + + +if __name__ == "__main__": + from electos.ballotmaker.demo_data import spacetown_data + + contest_1 = CandidateContest(spacetown_data.can_con_1) + print(contest_1.candidates) + print(contest_1.contest_list) diff --git a/src/electos/ballotmaker/ballots/demo_ballot.py b/src/electos/ballotmaker/ballots/demo_ballot.py index d56ee03..eb781c7 100644 --- a/src/electos/ballotmaker/ballots/demo_ballot.py +++ b/src/electos/ballotmaker/ballots/demo_ballot.py @@ -45,8 +45,7 @@ def get_election_header() -> dict: def add_header_line(font_size, line_text, new_line=False): line_end = "
" if new_line else "" - header_line = f"{line_text}{line_end}" - return header_line + return f"{line_text}{line_end}" def build_header_text(): From 86311d229ed1a9633b41a985920643ceddbb9e61 Mon Sep 17 00:00:00 2001 From: Neil Johnson Date: Fri, 26 Aug 2022 05:48:46 -0400 Subject: [PATCH 20/28] Change loggin level to INFO --- src/electos/ballotmaker/cli.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/electos/ballotmaker/cli.py b/src/electos/ballotmaker/cli.py index e2a8dc5..2fcce07 100644 --- a/src/electos/ballotmaker/cli.py +++ b/src/electos/ballotmaker/cli.py @@ -16,7 +16,7 @@ VERSION_HELP = "Print the version number." # configure logging -logging.basicConfig(stream=sys.stdout, level=logging.DEBUG) +logging.basicConfig(stream=sys.stdout, level=logging.INFO) log = logging.getLogger(__name__) From b507496b33a5b617981b882300526736afd30b3b Mon Sep 17 00:00:00 2001 From: Neil Johnson Date: Fri, 26 Aug 2022 06:14:21 -0400 Subject: [PATCH 21/28] Add data to contest layout --- .../ballotmaker/ballots/contest_data.py | 22 ++++++------- .../ballotmaker/ballots/contest_layout.py | 31 ++++++++++--------- 2 files changed, 27 insertions(+), 26 deletions(-) diff --git a/src/electos/ballotmaker/ballots/contest_data.py b/src/electos/ballotmaker/ballots/contest_data.py index d53df2e..aa22cc5 100644 --- a/src/electos/ballotmaker/ballots/contest_data.py +++ b/src/electos/ballotmaker/ballots/contest_data.py @@ -8,15 +8,15 @@ class CandidateContestData: _can_con: dict = field(repr=False) # fields retrieved from the dict - contest_id: str = field(init=False) - contest_title: str = field(init=False) + id: str = field(init=False) + title: str = field(init=False) votes_allowed: int = field(init=False) district: str = field(init=False) candidates: list = field(default_factory=list, init=False, repr=True) def __post_init__(self): - self.contest_id = self._can_con["id"] - self.contest_title = self._can_con["title"] + self.id = self._can_con["id"] + self.title = self._can_con["title"] self.votes_allowed = self._can_con["votes_allowed"] self.district = self._can_con["district"] _candidates = self._can_con["candidates"] @@ -27,16 +27,16 @@ def __post_init__(self): @dataclass class CandidateData: _can_data: dict = field(repr=False) - candidate_id: str = "no_id_provided" - candidate_name: str = field(init=False) - candidate_party: str = field(init=False) - candidate_party_abbr: str = field(init=False) + id: str = "no_id_provided" + name: str = field(init=False) + party: str = field(init=False) + party_abbr: str = field(init=False) def __post_init__(self): - self.candidate_name = self._can_data["name"] + self.name = self._can_data["name"] party_dict = self._can_data["party"] - self.candidate_party = party_dict["name"] - self.candidate_party_abbr = party_dict["abbreviation"] + self.party = party_dict["name"] + self.party_abbr = party_dict["abbreviation"] if __name__ == "__main__": diff --git a/src/electos/ballotmaker/ballots/contest_layout.py b/src/electos/ballotmaker/ballots/contest_layout.py index 201773a..0ff6fbc 100644 --- a/src/electos/ballotmaker/ballots/contest_layout.py +++ b/src/electos/ballotmaker/ballots/contest_layout.py @@ -1,5 +1,9 @@ # format a ballot contest. +from electos.ballotmaker.ballots.contest_data import ( + CandidateContestData, + CandidateData, +) from electos.ballotmaker.ballots.page_layout import PageLayout from reportlab.graphics.shapes import Drawing, Ellipse, _DrawingEditorMixin from reportlab.lib.colors import black, white @@ -37,18 +41,16 @@ class CandidateContestLayout: table flowable """ - def __init__(self, contest_data: dict): - # set up the page layout settings + def __init__(self, contest_data: CandidateContestData): self.contest_list = [] - self.candidates = [] - self.contest_title = "" - self.contest_instruct = "" - self.contest_data = contest_data - - def get_contest_data(): - self.contest_title = self.contest_data["title"] - self.contest_instruct = "Vote for 1" - self.candidates = self.contest_data["candidates"] + self.contest_id = contest_data.id + self.contest_title = contest_data.title + self.votes_allowed = contest_data.votes_allowed + if self.votes_allowed > 1: + self.contest_instruct = f"Vote for up to {self.votes_allowed}" + else: + self.contest_instruct = f"Vote for {self.votes_allowed}" + self.candidates = contest_data.candidates def build_contest_list(candidates, contest_list): oval = SelectionOval() @@ -65,8 +67,6 @@ def build_contest_table(): Builds a table with contest header, instructions and choices """ - # get the contest data from the data source - get_contest_data() # build the contest header row_1 = [Paragraph(self.contest_title, h1), ""] row_2 = [Paragraph(self.contest_instruct, h2), ""] @@ -165,6 +165,7 @@ def build_contest_table(): if __name__ == "__main__": from electos.ballotmaker.demo_data import spacetown_data - contest_1 = CandidateContest(spacetown_data.can_con_1) + contest_1 = CandidateContestData(spacetown_data.can_con_1) print(contest_1.candidates) - print(contest_1.contest_list) + layout_1 = CandidateContestLayout(contest_1) + print(layout_1.contest_list) From e3d074b2eb15d195721d321542f56dd3ccd60fa4 Mon Sep 17 00:00:00 2001 From: Neil Johnson Date: Fri, 26 Aug 2022 06:32:18 -0400 Subject: [PATCH 22/28] Add party to layout --- .../ballotmaker/ballots/contest_layout.py | 4 +++- src/electos/ballotmaker/ballots/demo_ballot.py | 18 +++++++----------- 2 files changed, 10 insertions(+), 12 deletions(-) diff --git a/src/electos/ballotmaker/ballots/contest_layout.py b/src/electos/ballotmaker/ballots/contest_layout.py index 0ff6fbc..ef2cc5e 100644 --- a/src/electos/ballotmaker/ballots/contest_layout.py +++ b/src/electos/ballotmaker/ballots/contest_layout.py @@ -58,7 +58,9 @@ def build_contest_list(candidates, contest_list): # add newlines around " and " # if candidate.find(" and "): # candidate = candidate.replace(" and ", "
and
") - contest_line = f"{candidate}" + contest_line = f"{candidate.name}" + if candidate.party_abbr != "": + contest_line += f"
{candidate.party_abbr}" contest_row = [oval, Paragraph(contest_line, normal)] contest_list.append(contest_row) diff --git a/src/electos/ballotmaker/ballots/demo_ballot.py b/src/electos/ballotmaker/ballots/demo_ballot.py index eb781c7..410a58d 100644 --- a/src/electos/ballotmaker/ballots/demo_ballot.py +++ b/src/electos/ballotmaker/ballots/demo_ballot.py @@ -6,8 +6,10 @@ from functools import partial from pathlib import Path -# from reportlab.lib.styles import getSampleStyleSheet -from electos.ballotmaker.ballots.contest import CandidateContest +from electos.ballotmaker.ballots.contest_layout import ( + CandidateContestData, + CandidateContestLayout, +) from electos.ballotmaker.ballots.header import header from electos.ballotmaker.ballots.instructions import Instructions from electos.ballotmaker.ballots.page_layout import PageLayout @@ -126,15 +128,9 @@ def build_ballot(): elements = inst.instruction_list elements.append(NextPageTemplate("3col")) # add a ballot contest to the second frame (colomn) - contest_1 = CandidateContest(spacetown_data.can_con_1) - elements.append(contest_1.contest_table) - contest_2 = CandidateContest(spacetown_data.can_con_2) - elements.append(contest_2.contest_table) - contest_3 = CandidateContest(spacetown_data.can_con_3) - elements.append(contest_3.contest_table) - elements.append(CondPageBreak(c_height * inch)) - contest_4 = CandidateContest(spacetown_data.can_con_4) - elements.append(contest_4.contest_table) + contest_1 = CandidateContestData(spacetown_data.can_con_1) + layout_1 = CandidateContestLayout(contest_1) + elements.append(layout_1.contest_table) doc.build(elements) From 50822dfafb61f9609115877dddcd14eada43ed33 Mon Sep 17 00:00:00 2001 From: Neil Johnson Date: Fri, 26 Aug 2022 06:41:30 -0400 Subject: [PATCH 23/28] Add all 4 candidate contests --- src/electos/ballotmaker/ballots/demo_ballot.py | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/src/electos/ballotmaker/ballots/demo_ballot.py b/src/electos/ballotmaker/ballots/demo_ballot.py index 410a58d..8f4d476 100644 --- a/src/electos/ballotmaker/ballots/demo_ballot.py +++ b/src/electos/ballotmaker/ballots/demo_ballot.py @@ -128,9 +128,23 @@ def build_ballot(): elements = inst.instruction_list elements.append(NextPageTemplate("3col")) # add a ballot contest to the second frame (colomn) - contest_1 = CandidateContestData(spacetown_data.can_con_1) - layout_1 = CandidateContestLayout(contest_1) + layout_1 = CandidateContestLayout( + CandidateContestData(spacetown_data.can_con_1) + ) + layout_2 = CandidateContestLayout( + CandidateContestData(spacetown_data.can_con_2) + ) + layout_3 = CandidateContestLayout( + CandidateContestData(spacetown_data.can_con_3) + ) + layout_4 = CandidateContestLayout( + CandidateContestData(spacetown_data.can_con_4) + ) elements.append(layout_1.contest_table) + elements.append(layout_2.contest_table) + elements.append(CondPageBreak(c_height * inch)) + elements.append(layout_3.contest_table) + elements.append(layout_4.contest_table) doc.build(elements) From 4ca6cfe334d242261878a37f3a6c2da60b785b07 Mon Sep 17 00:00:00 2001 From: Neil Johnson Date: Fri, 26 Aug 2022 07:09:21 -0400 Subject: [PATCH 24/28] Remove redundant code, fix test coverage --- src/electos/ballotmaker/ballots/contest.py | 171 ------------------ .../ballotmaker/ballots/contest_data.py | 2 +- .../ballotmaker/ballots/contest_layout.py | 2 +- .../ballotmaker/ballots/demo_ballot.py | 61 +++---- src/electos/ballotmaker/ballots/files.py | 2 +- src/electos/ballotmaker/cli.py | 3 +- src/electos/ballotmaker/demo.py | 12 -- tests/test_demo.py | 6 - 8 files changed, 35 insertions(+), 224 deletions(-) delete mode 100644 src/electos/ballotmaker/ballots/contest.py delete mode 100644 src/electos/ballotmaker/demo.py delete mode 100644 tests/test_demo.py diff --git a/src/electos/ballotmaker/ballots/contest.py b/src/electos/ballotmaker/ballots/contest.py deleted file mode 100644 index 58d3f29..0000000 --- a/src/electos/ballotmaker/ballots/contest.py +++ /dev/null @@ -1,171 +0,0 @@ -# contest.py -# format a ballot contest. - -from electos.ballotmaker.ballots.page_layout import PageLayout -from reportlab.graphics.shapes import Drawing, Ellipse, _DrawingEditorMixin -from reportlab.lib.colors import black, white -from reportlab.lib.styles import LineStyle, getSampleStyleSheet -from reportlab.platypus import Paragraph, Table - -oval_width = 10 -oval_height = 4 - - -class SelectionOval(_DrawingEditorMixin, Drawing): - def __init__(self, width=400, height=200, *args, **kw): - Drawing.__init__(self, width, height, *args, **kw) - - self.width = oval_width + PageLayout.border_pad - self.height = oval_height + PageLayout.border_pad - oval_cx = self.width / 2 - oval_cy = self.height / 2 - self._add( - self, - Ellipse(oval_cx, oval_cy, oval_width, oval_height), - name="oval", - validate=None, - desc=None, - ) - self.oval.fillColor = white - self.oval.strokeColor = black - self.oval.strokeWidth = 0.5 - - -class CandidateContest: - """ - Ballot Contest class encapsulates - the generation of a ballot contest - table - """ - - def __init__(self, contest_data: dict): - # set up the page layout settings - self.contest_list = [] - self.candidates = [] - self.contest_title = "" - self.contest_instruct = "" - self.contest_data = contest_data - - def get_contest_data(): - self.contest_title = self.contest_data["title"] - self.contest_instruct = "Vote for 1" - self.candidates = self.contest_data["candidates"] - - def build_contest_list(candidates, contest_list): - oval = SelectionOval() - for candidate in candidates: - # add newlines around " and " - # if candidate.find(" and "): - # candidate = candidate.replace(" and ", "
and
") - contest_line = f"{candidate}" - contest_row = [oval, Paragraph(contest_line, normal)] - contest_list.append(contest_row) - - def build_contest_table(): - """ - Builds a table with contest header, instructions - and choices - """ - # get the contest data from the data source - get_contest_data() - # build the contest header - row_1 = [Paragraph(self.contest_title, h1), ""] - row_2 = [Paragraph(self.contest_instruct, h2), ""] - self.contest_list = [row_1] - self.contest_list.append(row_2) - build_contest_list(self.candidates, self.contest_list) - - # construct and format the contest table - self.contest_table = Table( - data=self.contest_list, - colWidths=(oval_width * 3, None), - style=[ - # draw lines below each contestant - ("LINEBELOW", (1, 2), (1, -1), 1, grey), - # format the header - ("BACKGROUND", (0, 0), (1, 0), grey), - ("BACKGROUND", (0, 1), (1, 1), light), - # draw the outer border on top - ("LINEABOVE", (0, 0), (1, 0), 3, black), - ("LINEBEFORE", (0, 0), (0, -1), 1, black), - ("LINEBELOW", (0, -1), (-1, -1), 1, black), - ("VALIGN", (0, 0), (-1, -1), "TOP"), - ("SPAN", (0, 0), (1, 0)), - ("SPAN", (0, 1), (1, 1)), - # ("FONTSIZE", (1, 2), (-1, -1), 48), - ("TOPPADDING", (0, 2), (-1, -1), 4), - # pad the first cell - ("BOTTOMPADDING", (0, 0), (0, 1), 8), - # pad below each contestant - ("BOTTOMPADDING", (0, 2), (-1, -1), 16), - ], - ) - # self.contest_table._linecmds(["ROUNDEDCORNERS", 1]) - - # define styles - # fill colors - light = PageLayout.light - white = PageLayout.white - black = PageLayout.black - grey = PageLayout.grey - - # font family info - font_normal = PageLayout.font_normal - font_bold = PageLayout.font_bold - font_size = PageLayout.font_size - normal_lead = PageLayout.normal_lead - border_pad = PageLayout.border_pad / 2 - - # image dimensions - col_width = PageLayout.col_width - - # start with the sample styles - styles = getSampleStyleSheet() - normal = styles["Normal"] - h1 = styles["Heading1"] - h2 = styles["Heading2"] - - # define custom styles for contest tables - PageLayout.define_custom_style( - h1, - grey, - border_pad, - font_size + 2, - black, - font_bold, - normal_lead, - sp_before=12, - sp_after=48, - keep_w_next=1, - ) - PageLayout.define_custom_style( - h2, - light, - border_pad, - font_size, - black, - font_bold, - normal_lead, - sp_before=12, - sp_after=48, - keep_w_next=1, - ) - PageLayout.define_custom_style( - normal, - white, - border_pad, - font_size, - black, - font_normal, - normal_lead, - ) - # build the contest table, an attribute of the Contest object - build_contest_table() - - -if __name__ == "__main__": - from electos.ballotmaker.demo_data import spacetown_data - - contest_1 = CandidateContest(spacetown_data.can_con_1) - print(contest_1.candidates) - print(contest_1.contest_list) diff --git a/src/electos/ballotmaker/ballots/contest_data.py b/src/electos/ballotmaker/ballots/contest_data.py index aa22cc5..71a4e49 100644 --- a/src/electos/ballotmaker/ballots/contest_data.py +++ b/src/electos/ballotmaker/ballots/contest_data.py @@ -39,7 +39,7 @@ def __post_init__(self): self.party_abbr = party_dict["abbreviation"] -if __name__ == "__main__": +if __name__ == "__main__": # pragma: no cover from electos.ballotmaker.demo_data import spacetown_data can_con_data_1 = CandidateContestData(spacetown_data.can_con_1) diff --git a/src/electos/ballotmaker/ballots/contest_layout.py b/src/electos/ballotmaker/ballots/contest_layout.py index ef2cc5e..738be0f 100644 --- a/src/electos/ballotmaker/ballots/contest_layout.py +++ b/src/electos/ballotmaker/ballots/contest_layout.py @@ -164,7 +164,7 @@ def build_contest_table(): build_contest_table() -if __name__ == "__main__": +if __name__ == "__main__": # pragma: no cover from electos.ballotmaker.demo_data import spacetown_data contest_1 = CandidateContestData(spacetown_data.can_con_1) diff --git a/src/electos/ballotmaker/ballots/demo_ballot.py b/src/electos/ballotmaker/ballots/demo_ballot.py index 8f4d476..e03a5b7 100644 --- a/src/electos/ballotmaker/ballots/demo_ballot.py +++ b/src/electos/ballotmaker/ballots/demo_ballot.py @@ -33,6 +33,31 @@ c_width = PageLayout.col_width c_height = PageLayout.col_height c_space = PageLayout.col_space +# define 3-column layout with header +left_frame = Frame( + margin * inch, + margin * inch, + width=c_width * inch, + height=c_height * inch, + topPadding=0, + showBoundary=SHOW_BOUNDARY, +) +mid_frame = Frame( + (margin + c_width + c_space) * inch, + margin * inch, + width=c_width * inch, + height=c_height * inch, + topPadding=0, + showBoundary=SHOW_BOUNDARY, +) +right_frame = Frame( + (margin + (2 * (c_width + c_space))) * inch, + margin * inch, + width=c_width * inch, + height=c_height * inch, + topPadding=0, + showBoundary=SHOW_BOUNDARY, +) def get_election_header() -> dict: @@ -74,36 +99,9 @@ def header(canvas, doc, content): canvas.restoreState() -def build_ballot(): - - # define 3-column layout with header - left_frame = Frame( - margin * inch, - margin * inch, - width=c_width * inch, - height=c_height * inch, - topPadding=0, - showBoundary=SHOW_BOUNDARY, - ) - mid_frame = Frame( - (margin + c_width + c_space) * inch, - margin * inch, - width=c_width * inch, - height=c_height * inch, - topPadding=0, - showBoundary=SHOW_BOUNDARY, - ) - right_frame = Frame( - (margin + (2 * (c_width + c_space))) * inch, - margin * inch, - width=c_width * inch, - height=c_height * inch, - topPadding=0, - showBoundary=SHOW_BOUNDARY, - ) - - # create PDF filename - # create datestamp string for PDF +def build_ballot() -> str: + # create PDF filename; include + # datestamp string for PDF now = datetime.now() date_time = now.strftime("%Y_%m_%dT%H%M%S") home_dir = Path.home() @@ -146,7 +144,8 @@ def build_ballot(): elements.append(layout_3.contest_table) elements.append(layout_4.contest_table) doc.build(elements) + return str(ballot_name) -if __name__ == "__main__": +if __name__ == "__main__": # pragma: no cover build_ballot() diff --git a/src/electos/ballotmaker/ballots/files.py b/src/electos/ballotmaker/ballots/files.py index b9bab97..dfa0921 100644 --- a/src/electos/ballotmaker/ballots/files.py +++ b/src/electos/ballotmaker/ballots/files.py @@ -40,7 +40,7 @@ def __init__( self.file_found = True -if __name__ == "__main__": +if __name__ == "__main__": # pragma: no cover print("Default settings:") file_defaults = FileTools() print(f"Default code dir = {file_defaults.code_dir}") diff --git a/src/electos/ballotmaker/cli.py b/src/electos/ballotmaker/cli.py index 2fcce07..a1973e6 100644 --- a/src/electos/ballotmaker/cli.py +++ b/src/electos/ballotmaker/cli.py @@ -43,7 +43,8 @@ def main( @app.command() def demo(): """Make ballots from previously extracted EDF data""" - build_ballot() + new_ballot_name = build_ballot() + typer.echo(f"Ballot created: {new_ballot_name}") return NO_ERRORS diff --git a/src/electos/ballotmaker/demo.py b/src/electos/ballotmaker/demo.py deleted file mode 100644 index ae82496..0000000 --- a/src/electos/ballotmaker/demo.py +++ /dev/null @@ -1,12 +0,0 @@ -import logging - -from electos.ballotmaker.ballots.demo_ballot import build_ballot -from electos.ballotmaker.constants import NO_ERRORS - -log = logging.getLogger(__name__) - - -def make_demo_ballot(): - log.debug("Starting ballotlab demo ...") - build_ballot() - return NO_ERRORS diff --git a/tests/test_demo.py b/tests/test_demo.py deleted file mode 100644 index eee9ff6..0000000 --- a/tests/test_demo.py +++ /dev/null @@ -1,6 +0,0 @@ -from electos.ballotmaker.constants import NO_ERRORS -from electos.ballotmaker.demo import make_demo_ballot - - -def test_demo(): - assert make_demo_ballot() == NO_ERRORS From 05c6609f1b2852a30ab45c67e269b9b177862d8c Mon Sep 17 00:00:00 2001 From: Neil Johnson Date: Fri, 26 Aug 2022 08:09:33 -0400 Subject: [PATCH 25/28] The end of the Versadm directory --- src/electos/versadm/ballot_demo.py | 83 ----------------- src/electos/versadm/data.py | 137 ----------------------------- 2 files changed, 220 deletions(-) delete mode 100644 src/electos/versadm/ballot_demo.py delete mode 100644 src/electos/versadm/data.py diff --git a/src/electos/versadm/ballot_demo.py b/src/electos/versadm/ballot_demo.py deleted file mode 100644 index 2d7d5c6..0000000 --- a/src/electos/versadm/ballot_demo.py +++ /dev/null @@ -1,83 +0,0 @@ -# ballot_demo.py -# create a demo object with BallotLab classes - -from datetime import datetime -from pathlib import Path - -from electos.ballotmaker.ballots.contest import Contest -from electos.ballotmaker.ballots.instructions import Instructions -from electos.ballotmaker.ballots.page_layout import PageLayout -from reportlab.lib.pagesizes import letter -from reportlab.lib.styles import getSampleStyleSheet -from reportlab.lib.units import inch -from reportlab.pdfgen.canvas import Canvas -from reportlab.platypus import Frame, Paragraph - - -def ballot_demo(): - # 1 = True, 0 = FALSE - SHOW_BOUNDARY = 0 - margin = PageLayout.margin - c_width = PageLayout.col_width - c_height = PageLayout.col_height - c_space = PageLayout.col_space - styles = getSampleStyleSheet() - normal = styles["Normal"] - h1 = styles["Heading1"] - # create datestamp string for PDF - now = datetime.now() - date_time = now.strftime("%Y_%m_%dT%H%M%S") - # create the PDF document canvas - - home_dir = Path.home() - ballot_canvas = Canvas( - f"{home_dir}/ballot_demo_{date_time}.pdf", - pagesize=letter, - enforceColorSpace="CMYK", - ) - # ballot_canvas.setLineCap(0) - # add voting instructions to the first frame (column) - inst = Instructions() - left_column = inst.instruction_list - - # add a ballot contest to the second frame (colomn) - contest_1 = Contest() - contest_table = contest_1.contest_table - mid_column = [contest_table] - # mid_column.append(Paragraph(contest_instruct, normal)) - - right_column = [Paragraph("Contest #2", h1)] - right_column.append(Paragraph("ipsum lorem", normal)) - - left_frame = Frame( - margin * inch, - margin * inch, - width=c_width * inch, - height=c_height * inch, - showBoundary=SHOW_BOUNDARY, - ) - mid_frame = Frame( - (margin + c_width + c_space) * inch, - margin * inch, - width=c_width * inch, - height=c_height * inch, - topPadding=0, - showBoundary=SHOW_BOUNDARY, - ) - right_frame = Frame( - (margin + (2 * (c_width + c_space))) * inch, - margin * inch, - width=c_width * inch, - height=c_height * inch, - showBoundary=SHOW_BOUNDARY, - ) - - left_frame.addFromList(left_column, ballot_canvas) - mid_frame.addFromList(mid_column, ballot_canvas) - right_frame.addFromList(right_column, ballot_canvas) - - ballot_canvas.save() - - -if __name__ == "__main__": - ballot_demo() diff --git a/src/electos/versadm/data.py b/src/electos/versadm/data.py deleted file mode 100644 index be3464e..0000000 --- a/src/electos/versadm/data.py +++ /dev/null @@ -1,137 +0,0 @@ -# data.py -# read and write structured data - -# from posixpath import relpath -from versadm.utils.project_files import ProjectFiles - -# import xmltodict -import json -import jsonschema - -# import pprint -PROJECT_NAME = "ballotlab" - -# error codes -## JSON errors -### JSON file isn't formatted correctly (can't be parsed) -ERR_JSON_FORMAT = 200 -### JSON data didn't validate against the schema -ERR_JSON_SCHEMA = 201 - -# supported_ext_types = [".xml", ".XML"] -# supported_ext_types = [".json", ".JSON", ".xml", ".XML"] -supported_ext_types = [".json", ".JSON"] -# create a string of supported extensions from list -ext_types_str = " ".join(str(item) for item in supported_ext_types) - - -class ElectionData: - """ - Open the specified Election Data File (EDF) - Read data into Python objects. - Read well-formatted json and xml only - Raises RuntimeError for bad data - """ - - def __init__(self, data_file, data_dir, print_rpt=False): - - election_file = ProjectFiles(data_file, data_dir, PROJECT_NAME) - if not election_file.file_found: - msg = "Election data file {} not found in directory {}" - raise RuntimeError(msg.format(str(data_file, data_dir))) - - self.data_file = data_file - self.data_dir = data_dir - self.abs_path_to_data = election_file.abs_path_to_file - self.ext = election_file.ext - - if self.ext not in supported_ext_types: - msg = "Election data must be one of the following file types: " "{}. Got {}" - raise RuntimeError(msg.format(ext_types_str, self.ext)) - - # read data file into Python objects, based on type. - if self.ext in [".xml", ".XML"]: - self.election_rpt = self.parse_xml(self.abs_path_to_data) - elif self.ext in [".json", ".JSON"]: - # let's try to read the file - self.election_rpt = self.parse_json(self.abs_path_to_data) - if self.election_rpt != ERR_JSON_FORMAT: - self.elect_name = self.election_rpt["Election"][0]["Name"] - self.start_date = self.election_rpt["Election"][0]["StartDate"] - self.end_date = self.election_rpt["Election"][0]["EndDate"] - self.elect_type = self.election_rpt["Election"][0]["Type"] - self.gpunits = self.election_rpt["GpUnit"] - ## Disable schema validation code - # if self.validate_json(self.election_rpt): - # # Read Election data from JSON dict, which is - # self.elect_name = self.election_rpt["Election"][0]["Name"] - # self.start_date = self.election_rpt["Election"][0]["StartDate"] - # self.end_date = self.election_rpt["Election"][0]["EndDate"] - # self.elect_type = self.election_rpt["Election"][0]["Type"] - # else: - # print("JSON Schema Error!") - - if self.election_rpt != ERR_JSON_FORMAT: - rpt_title = "Election Report" - self.text_rpt = "{}\n".format(rpt_title) - self.text_rpt += ("=" * len(rpt_title)) + "\n" - - # EDF file info - self.text_rpt += "EDF name: {}\n".format(self.data_file) - self.text_rpt += "Location:\n {}\n".format(self.abs_path_to_data) - # Election contains BallotStyle, Candidate and Contest. - self.text_rpt += "Election name: {}\n".format(self.elect_name) - self.text_rpt += "Election type: {}\n".format(self.elect_type) - self.text_rpt += "Start date: {}\n".format(self.start_date) - self.text_rpt += "End date: {}\n".format(self.end_date) - self.text_rpt += "GPUnits: \n{}\n".format(self.gpunits) - else: - self.text_rpt = "Report can't be generated. Error code:" - - if print_rpt: - print(self.text_rpt) - # pprint.pprint(self.election_rpt) - - # def parse_xml(self, xml_file): - # """ - # parse xml file into JSON-style dict - # """ - # with open(xml_file) as xmlf: - # xml = xmlf.read() - # return xmltodict.parse(xml, dict_constructor=dict) - - def parse_json(self, json_file): - """ - parse json file into dictionary - """ - # read JSON file and perform basic JSON validation - try: - with open(json_file, "r") as jsf: - json_data = json.load(jsf) - return json_data - except json.decoder.JSONDecodeError: - print("JSON file is not well-formed: {}".format(json_file)) - return ERR_JSON_FORMAT - - def validate_json(self, json_data): - json_schema_file = ProjectFiles( - "NIST_V2_election_results_reporting.json", "assets/schema", PROJECT_NAME - ) - json_schema = self.parse_json(json_schema_file.abs_path_to_file) - jsonschema.validate(instance=json_data, schema=json_schema) - # try: - # jsonschema.validate(instance=json_data, schema=json_schema) - # except jsonschema.exceptions.ValidationError as err: - # return ERR_JSON_SCHEMA - # return True - - -if __name__ == "__main__": - # xml_election = ElectionData("nist_sample_election_report.xml", "assets/data") - # json_election = ElectionData("NIST_sample.json", "assets/data/") - json_election = ElectionData( - "BallotStudio_16_Edits.JSON", "assets/data/", print_rpt=True - ) - json_election = ElectionData( - "JESTONS_PAPARDEV_&_AUG_2021.json", "assets/data/", print_rpt=False - ) From 1bdf76b73d43625fcdd30afbaa8135037309dde7 Mon Sep 17 00:00:00 2001 From: Neil Johnson Date: Fri, 26 Aug 2022 10:22:36 -0400 Subject: [PATCH 26/28] Layout updates, add ballot measures --- .../ballotmaker/ballots/contest_data.py | 33 +++ .../ballotmaker/ballots/contest_layout.py | 263 ++++++++++-------- .../ballotmaker/ballots/demo_ballot.py | 11 + .../ballotmaker/ballots/page_layout.py | 4 +- 4 files changed, 191 insertions(+), 120 deletions(-) diff --git a/src/electos/ballotmaker/ballots/contest_data.py b/src/electos/ballotmaker/ballots/contest_data.py index 71a4e49..e964d27 100644 --- a/src/electos/ballotmaker/ballots/contest_data.py +++ b/src/electos/ballotmaker/ballots/contest_data.py @@ -2,6 +2,37 @@ from typing import List +@dataclass +class BallotMeasureData: + """Retrieve ballot measure contest data from a dict""" + + _b_measure_con: dict = field(repr=False) + id: str = field(init=False) + title: str = field(init=False) + district: str = field(init=False) + text: str = field(init=False) + choices: list = field(default_factory=list, init=False, repr=True) + + def __post_init__(self): + self.id = "no_id_provided" + self.title = self._b_measure_con["title"] + self.district = self._b_measure_con["district"] + self.text = self._b_measure_con["text"] + self.choices = self._b_measure_con["choices"] + # for choice_data in _choices: + # self.choices.append(ChoiceData(choice_data)) + + +@dataclass +class ChoiceData: + _choice_data: dict = field(repr=False) + id: str = "no_id_provided" + label: str = field(init=False) + + def __post_init__(self): + self.label = "no label provided" + + @dataclass class CandidateContestData: """Retrieve candidate contest data from a dict""" @@ -46,3 +77,5 @@ def __post_init__(self): print(can_con_data_1) can_con_data_2 = CandidateContestData(spacetown_data.can_con_2) print(can_con_data_2) + b_measure_data_1 = BallotMeasureData(spacetown_data.ballot_measure_1) + print(b_measure_data_1) diff --git a/src/electos/ballotmaker/ballots/contest_layout.py b/src/electos/ballotmaker/ballots/contest_layout.py index 738be0f..638ced8 100644 --- a/src/electos/ballotmaker/ballots/contest_layout.py +++ b/src/electos/ballotmaker/ballots/contest_layout.py @@ -1,8 +1,8 @@ # format a ballot contest. from electos.ballotmaker.ballots.contest_data import ( + BallotMeasureData, CandidateContestData, - CandidateData, ) from electos.ballotmaker.ballots.page_layout import PageLayout from reportlab.graphics.shapes import Drawing, Ellipse, _DrawingEditorMixin @@ -13,6 +13,102 @@ oval_width = 10 oval_height = 4 +# define styles +# fill colors +light = PageLayout.light +grey = PageLayout.grey + +# font family info +font_normal = PageLayout.font_normal +font_bold = PageLayout.font_bold +font_size = PageLayout.font_size +normal_lead = PageLayout.normal_lead +border_pad = PageLayout.border_pad / 2 + +# start with the sample styles +styles = getSampleStyleSheet() +normal = styles["Normal"] +h1 = styles["Heading1"] +h2 = styles["Heading2"] + +# define custom styles for contest tables +PageLayout.define_custom_style( + h1, + grey, + border_pad, + font_size + 2, + black, + font_bold, + normal_lead, + sp_before=12, + sp_after=48, + keep_w_next=1, +) +PageLayout.define_custom_style( + h2, + light, + border_pad, + font_size, + black, + font_bold, + normal_lead, + sp_before=12, + sp_after=48, + keep_w_next=1, +) +PageLayout.define_custom_style( + normal, + white, + border_pad, + font_size, + black, + font_normal, + normal_lead, +) + + +def build_contest_list( + title: str, instruction: str, selections: list, text: str = "" +) -> list: + """ + Builds a table with contest header, instructions + and choices + """ + row_1 = [Paragraph(title, h1), ""] + row_2 = [Paragraph(instruction, h2), ""] + contest_list = [row_1, row_2] + if text != "": + contest_list.append(Paragraph(text, normal)) + contest_list.extend(iter(selections)) + return contest_list + + +def build_contest_table(contest_list): + return Table( + data=contest_list, + colWidths=(oval_width * 3, None), + style=[ + # draw lines below each contestant + ("LINEBELOW", (1, 2), (1, -1), 1, grey), + # format the header + ("BACKGROUND", (0, 0), (1, 0), grey), + ("BACKGROUND", (0, 1), (1, 1), light), + # draw the outer border on top + ("LINEABOVE", (0, 0), (1, 0), 3, black), + ("LINEBEFORE", (0, 0), (0, -1), 1, black), + ("LINEBELOW", (0, -1), (-1, -1), 1, black), + ("VALIGN", (0, 0), (-1, -1), "TOP"), + ("SPAN", (0, 0), (1, 0)), + ("SPAN", (0, 1), (1, 1)), + # ("FONTSIZE", (1, 2), (-1, -1), 48), + ("TOPPADDING", (0, 2), (-1, -1), 4), + # pad the first cell + ("BOTTOMPADDING", (0, 0), (0, 1), 8), + # pad below each contestant + ("BOTTOMPADDING", (0, 2), (-1, -1), 16), + ], + ) + class SelectionOval(_DrawingEditorMixin, Drawing): def __init__(self, width=400, height=200, *args, **kw): @@ -29,139 +125,70 @@ def __init__(self, width=400, height=200, *args, **kw): validate=None, desc=None, ) + # self.oval.fillColor = PageLayout.white self.oval.fillColor = white + # self.oval.strokeColor = PageLayout.black self.oval.strokeColor = black self.oval.strokeWidth = 0.5 class CandidateContestLayout: """ - Ballot Contest Laout class encapsulates - the generation of a ballot contest - table flowable + Generate a candidate contest table flowable """ def __init__(self, contest_data: CandidateContestData): - self.contest_list = [] - self.contest_id = contest_data.id - self.contest_title = contest_data.title + self.id = contest_data.id + self.title = contest_data.title self.votes_allowed = contest_data.votes_allowed if self.votes_allowed > 1: - self.contest_instruct = f"Vote for up to {self.votes_allowed}" + self.instruct = f"Vote for up to {self.votes_allowed}" else: - self.contest_instruct = f"Vote for {self.votes_allowed}" + self.instruct = f"Vote for {self.votes_allowed}" self.candidates = contest_data.candidates - - def build_contest_list(candidates, contest_list): - oval = SelectionOval() - for candidate in candidates: - # add newlines around " and " - # if candidate.find(" and "): - # candidate = candidate.replace(" and ", "
and
") - contest_line = f"{candidate.name}" - if candidate.party_abbr != "": - contest_line += f"
{candidate.party_abbr}" - contest_row = [oval, Paragraph(contest_line, normal)] - contest_list.append(contest_row) - - def build_contest_table(): - """ - Builds a table with contest header, instructions - and choices - """ - # build the contest header - row_1 = [Paragraph(self.contest_title, h1), ""] - row_2 = [Paragraph(self.contest_instruct, h2), ""] - self.contest_list = [row_1] - self.contest_list.append(row_2) - build_contest_list(self.candidates, self.contest_list) - - # construct and format the contest table - self.contest_table = Table( - data=self.contest_list, - colWidths=(oval_width * 3, None), - style=[ - # draw lines below each contestant - ("LINEBELOW", (1, 2), (1, -1), 1, grey), - # format the header - ("BACKGROUND", (0, 0), (1, 0), grey), - ("BACKGROUND", (0, 1), (1, 1), light), - # draw the outer border on top - ("LINEABOVE", (0, 0), (1, 0), 3, black), - ("LINEBEFORE", (0, 0), (0, -1), 1, black), - ("LINEBELOW", (0, -1), (-1, -1), 1, black), - ("VALIGN", (0, 0), (-1, -1), "TOP"), - ("SPAN", (0, 0), (1, 0)), - ("SPAN", (0, 1), (1, 1)), - # ("FONTSIZE", (1, 2), (-1, -1), 48), - ("TOPPADDING", (0, 2), (-1, -1), 4), - # pad the first cell - ("BOTTOMPADDING", (0, 0), (0, 1), 8), - # pad below each contestant - ("BOTTOMPADDING", (0, 2), (-1, -1), 16), - ], - ) - # self.contest_table._linecmds(["ROUNDEDCORNERS", 1]) - - # define styles - # fill colors - light = PageLayout.light - white = PageLayout.white - black = PageLayout.black - grey = PageLayout.grey - - # font family info - font_normal = PageLayout.font_normal - font_bold = PageLayout.font_bold - font_size = PageLayout.font_size - normal_lead = PageLayout.normal_lead - border_pad = PageLayout.border_pad / 2 - - # image dimensions - col_width = PageLayout.col_width - - # start with the sample styles - styles = getSampleStyleSheet() - normal = styles["Normal"] - h1 = styles["Heading1"] - h2 = styles["Heading2"] - - # define custom styles for contest tables - PageLayout.define_custom_style( - h1, - grey, - border_pad, - font_size + 2, - black, - font_bold, - normal_lead, - sp_before=12, - sp_after=48, - keep_w_next=1, - ) - PageLayout.define_custom_style( - h2, - light, - border_pad, - font_size, - black, - font_bold, - normal_lead, - sp_before=12, - sp_after=48, - keep_w_next=1, + _selections = [] + + oval = SelectionOval() + for candidate in self.candidates: + # add newlines around " and " + # if candidate.find(" and "): + # candidate = candidate.replace(" and ", "
and
") + contest_line = f"{candidate.name}" + if candidate.party_abbr != "": + contest_line += f"
{candidate.party_abbr}" + contest_row = [oval, Paragraph(contest_line, normal)] + _selections.append(contest_row) + # build the contest table, an attribute of the Contest object + + self.contest_list = build_contest_list( + self.title, self.instruct, _selections ) - PageLayout.define_custom_style( - normal, - white, - border_pad, - font_size, - black, - font_normal, - normal_lead, + self.contest_table = build_contest_table(self.contest_list) + + +class BallotMeasureLayout: + """ + Generate a candidate contest table flowable + """ + + def __init__(self, contest_data: BallotMeasureData): + self.id = contest_data.id + self.title = contest_data.title + self.instruct = "Vote yes or no" + self.text = contest_data.text + self.choices = contest_data.choices + + oval = SelectionOval() + _selections = [] + for choice in self.choices: + contest_line = f"{choice}" + contest_row = [oval, Paragraph(contest_line, normal)] + _selections.append(contest_row) + + self.contest_list = build_contest_list( + self.title, self.instruct, _selections, self.text ) - # build the contest table, an attribute of the Contest object - build_contest_table() + self.contest_table = build_contest_table(self.contest_list) if __name__ == "__main__": # pragma: no cover diff --git a/src/electos/ballotmaker/ballots/demo_ballot.py b/src/electos/ballotmaker/ballots/demo_ballot.py index e03a5b7..24cae3b 100644 --- a/src/electos/ballotmaker/ballots/demo_ballot.py +++ b/src/electos/ballotmaker/ballots/demo_ballot.py @@ -7,6 +7,8 @@ from pathlib import Path from electos.ballotmaker.ballots.contest_layout import ( + BallotMeasureData, + BallotMeasureLayout, CandidateContestData, CandidateContestLayout, ) @@ -138,11 +140,20 @@ def build_ballot() -> str: layout_4 = CandidateContestLayout( CandidateContestData(spacetown_data.can_con_4) ) + # layout_5 = BallotMeasureLayout( + # BallotMeasureData(spacetown_data.ballot_measure_1) + # ) + # layout_6 = BallotMeasureLayout( + # BallotMeasureData(spacetown_data.ballot_measure_2) + # ) elements.append(layout_1.contest_table) elements.append(layout_2.contest_table) elements.append(CondPageBreak(c_height * inch)) elements.append(layout_3.contest_table) elements.append(layout_4.contest_table) + elements.append(CondPageBreak(c_height * inch)) + # elements.append(layout_5.contest_table) + # elements.append(layout_6.contest_table) doc.build(elements) return str(ballot_name) diff --git a/src/electos/ballotmaker/ballots/page_layout.py b/src/electos/ballotmaker/ballots/page_layout.py index e24ca9e..e23624c 100644 --- a/src/electos/ballotmaker/ballots/page_layout.py +++ b/src/electos/ballotmaker/ballots/page_layout.py @@ -11,10 +11,10 @@ class PageLayout: # use floats for these values font_family: str = "Helvetica" - margin: float = 0.5 + margin: float = 0.6 col_width: float = 2.25 col_height: float = 9.5 - col_space: float = 0.25 + col_space: float = 0.15 # font family info font_normal: str = "Helvetica" From c60a4fcd748af92779283c36e8a51f552f8c6e75 Mon Sep 17 00:00:00 2001 From: Neil Johnson Date: Fri, 26 Aug 2022 11:52:38 -0400 Subject: [PATCH 27/28] Add ballot measures --- .../ballotmaker/ballots/contest_layout.py | 38 ++++++++++++-- .../ballotmaker/ballots/demo_ballot.py | 52 +++++++++++++------ 2 files changed, 70 insertions(+), 20 deletions(-) diff --git a/src/electos/ballotmaker/ballots/contest_layout.py b/src/electos/ballotmaker/ballots/contest_layout.py index 638ced8..ebe3d3e 100644 --- a/src/electos/ballotmaker/ballots/contest_layout.py +++ b/src/electos/ballotmaker/ballots/contest_layout.py @@ -78,12 +78,12 @@ def build_contest_list( row_2 = [Paragraph(instruction, h2), ""] contest_list = [row_1, row_2] if text != "": - contest_list.append(Paragraph(text, normal)) + contest_list.append([Paragraph(text, normal), ""]) contest_list.extend(iter(selections)) return contest_list -def build_contest_table(contest_list): +def build_candidate_table(contest_list): return Table( data=contest_list, colWidths=(oval_width * 3, None), @@ -110,6 +110,36 @@ def build_contest_table(contest_list): ) +def build_ballot_measure_table(contest_list): + return Table( + data=contest_list, + colWidths=(oval_width * 3, None), + style=[ + # draw lines below each selection + ("LINEBELOW", (1, 2), (1, -1), 1, grey), + # format the header + ("BACKGROUND", (0, 0), (1, 0), grey), + ("BACKGROUND", (0, 1), (1, 1), light), + # draw the outer border on top + ("LINEABOVE", (0, 0), (1, 0), 3, black), + ("LINEBEFORE", (0, 0), (0, -1), 1, black), + ("LINEBELOW", (0, -1), (-1, -1), 1, black), + ("VALIGN", (0, 0), (-1, -1), "TOP"), + ("SPAN", (0, 0), (-1, 0)), + ("SPAN", (0, 1), (-1, 1)), + ("SPAN", (0, 2), (-1, 2)), + # ("SPAN", (0, 3), (-1, 3)), + # ("SPAN", (0, 4), (1, 1)), + # ("FONTSIZE", (1, 2), (-1, -1), 48), + ("TOPPADDING", (0, 2), (-1, -1), 4), + # pad the first cell + ("BOTTOMPADDING", (0, 0), (0, 1), 8), + # pad below each contestant + ("BOTTOMPADDING", (0, 2), (-1, -1), 16), + ], + ) + + class SelectionOval(_DrawingEditorMixin, Drawing): def __init__(self, width=400, height=200, *args, **kw): Drawing.__init__(self, width, height, *args, **kw) @@ -163,7 +193,7 @@ def __init__(self, contest_data: CandidateContestData): self.contest_list = build_contest_list( self.title, self.instruct, _selections ) - self.contest_table = build_contest_table(self.contest_list) + self.contest_table = build_candidate_table(self.contest_list) class BallotMeasureLayout: @@ -188,7 +218,7 @@ def __init__(self, contest_data: BallotMeasureData): self.contest_list = build_contest_list( self.title, self.instruct, _selections, self.text ) - self.contest_table = build_contest_table(self.contest_list) + self.contest_table = build_ballot_measure_table(self.contest_list) if __name__ == "__main__": # pragma: no cover diff --git a/src/electos/ballotmaker/ballots/demo_ballot.py b/src/electos/ballotmaker/ballots/demo_ballot.py index 24cae3b..ce0fed3 100644 --- a/src/electos/ballotmaker/ballots/demo_ballot.py +++ b/src/electos/ballotmaker/ballots/demo_ballot.py @@ -22,6 +22,7 @@ BaseDocTemplate, Frame, NextPageTemplate, + PageBreak, PageTemplate, Paragraph, ) @@ -61,6 +62,15 @@ showBoundary=SHOW_BOUNDARY, ) +one_frame = Frame( + margin * inch, + margin * inch, + width=7 * inch, + height=c_height * inch, + topPadding=0, + showBoundary=SHOW_BOUNDARY, +) + def get_election_header() -> dict: return { @@ -115,18 +125,21 @@ def build_ballot() -> str: normal = styles["Normal"] head_text = build_header_text() header_content = Paragraph(head_text, normal) - pg_template = PageTemplate( + three_column_template = PageTemplate( id="3col", frames=[left_frame, mid_frame, right_frame], onPage=partial(header, content=header_content), ) - doc.addPageTemplates(pg_template) - - elements = [] - # add voting instructions - inst = Instructions() - elements = inst.instruction_list - elements.append(NextPageTemplate("3col")) + one_column_template = PageTemplate( + id="1col", + frames=[one_frame], + onPage=partial( + header, + content=header_content, + ), + ) + doc.addPageTemplates(three_column_template) + doc.addPageTemplates(one_column_template) # add a ballot contest to the second frame (colomn) layout_1 = CandidateContestLayout( CandidateContestData(spacetown_data.can_con_1) @@ -140,20 +153,27 @@ def build_ballot() -> str: layout_4 = CandidateContestLayout( CandidateContestData(spacetown_data.can_con_4) ) - # layout_5 = BallotMeasureLayout( - # BallotMeasureData(spacetown_data.ballot_measure_1) - # ) - # layout_6 = BallotMeasureLayout( - # BallotMeasureData(spacetown_data.ballot_measure_2) - # ) + layout_5 = BallotMeasureLayout( + BallotMeasureData(spacetown_data.ballot_measure_1) + ) + layout_6 = BallotMeasureLayout( + BallotMeasureData(spacetown_data.ballot_measure_2) + ) + elements = [] + # add voting instructions + inst = Instructions() + elements = inst.instruction_list + elements.append(NextPageTemplate("3col")) elements.append(layout_1.contest_table) elements.append(layout_2.contest_table) elements.append(CondPageBreak(c_height * inch)) elements.append(layout_3.contest_table) elements.append(layout_4.contest_table) + elements.append(NextPageTemplate("1col")) + elements.append(PageBreak()) + elements.append(layout_5.contest_table) elements.append(CondPageBreak(c_height * inch)) - # elements.append(layout_5.contest_table) - # elements.append(layout_6.contest_table) + elements.append(layout_6.contest_table) doc.build(elements) return str(ballot_name) From 891011f6258b7c9d0518dff88e3c88ac1d1b90e3 Mon Sep 17 00:00:00 2001 From: Neil Johnson Date: Fri, 26 Aug 2022 15:59:35 -0400 Subject: [PATCH 28/28] Rearrange files to support GitHub Actions file access --- assets/img/warn_cyan.png | Bin 1367068 -> 0 bytes {assets/fonts => fonts}/Roboto/LICENSE.txt | 0 .../fonts => fonts}/Roboto/Roboto-Black.ttf | Bin .../Roboto/Roboto-BlackItalic.ttf | Bin .../fonts => fonts}/Roboto/Roboto-Bold.ttf | Bin .../Roboto/Roboto-BoldItalic.ttf | Bin .../fonts => fonts}/Roboto/Roboto-Italic.ttf | Bin .../fonts => fonts}/Roboto/Roboto-Light.ttf | Bin .../Roboto/Roboto-LightItalic.ttf | Bin .../fonts => fonts}/Roboto/Roboto-Medium.ttf | Bin .../Roboto/Roboto-MediumItalic.ttf | Bin .../fonts => fonts}/Roboto/Roboto-Regular.ttf | Bin .../fonts => fonts}/Roboto/Roboto-Thin.ttf | Bin .../Roboto/Roboto-ThinItalic.ttf | Bin .../assets}/data/BallotStudio_16_Edits.JSON | 0 .../data/BallotStudio_7_Formatted.JSON | 0 .../data/JESTONS_PAPARDEV_&_AUG_2021.json | 0 .../ballotmaker/assets}/data/NIST_sample.json | 0 .../assets}/data/june_test_case.json | 0 .../data/nist_sample_election_report.xml | 0 .../ballotmaker/assets}/img/filled_bubble.png | Bin .../ballotmaker/assets/img/warn_cyan.png | Bin 0 -> 4302 bytes .../ballotmaker/assets}/img/writein.png | Bin .../NIST_V2_election_results_reporting.json | 0 src/electos/ballotmaker/ballots/files.py | 4 ++-- 25 files changed, 2 insertions(+), 2 deletions(-) delete mode 100644 assets/img/warn_cyan.png rename {assets/fonts => fonts}/Roboto/LICENSE.txt (100%) rename {assets/fonts => fonts}/Roboto/Roboto-Black.ttf (100%) rename {assets/fonts => fonts}/Roboto/Roboto-BlackItalic.ttf (100%) rename {assets/fonts => fonts}/Roboto/Roboto-Bold.ttf (100%) rename {assets/fonts => fonts}/Roboto/Roboto-BoldItalic.ttf (100%) rename {assets/fonts => fonts}/Roboto/Roboto-Italic.ttf (100%) rename {assets/fonts => fonts}/Roboto/Roboto-Light.ttf (100%) rename {assets/fonts => fonts}/Roboto/Roboto-LightItalic.ttf (100%) rename {assets/fonts => fonts}/Roboto/Roboto-Medium.ttf (100%) rename {assets/fonts => fonts}/Roboto/Roboto-MediumItalic.ttf (100%) rename {assets/fonts => fonts}/Roboto/Roboto-Regular.ttf (100%) rename {assets/fonts => fonts}/Roboto/Roboto-Thin.ttf (100%) rename {assets/fonts => fonts}/Roboto/Roboto-ThinItalic.ttf (100%) rename {assets => src/electos/ballotmaker/assets}/data/BallotStudio_16_Edits.JSON (100%) rename {assets => src/electos/ballotmaker/assets}/data/BallotStudio_7_Formatted.JSON (100%) rename {assets => src/electos/ballotmaker/assets}/data/JESTONS_PAPARDEV_&_AUG_2021.json (100%) rename {assets => src/electos/ballotmaker/assets}/data/NIST_sample.json (100%) rename {assets => src/electos/ballotmaker/assets}/data/june_test_case.json (100%) rename {assets => src/electos/ballotmaker/assets}/data/nist_sample_election_report.xml (100%) rename {assets => src/electos/ballotmaker/assets}/img/filled_bubble.png (100%) create mode 100644 src/electos/ballotmaker/assets/img/warn_cyan.png rename {assets => src/electos/ballotmaker/assets}/img/writein.png (100%) rename {assets => src/electos/ballotmaker/assets}/schema/NIST_V2_election_results_reporting.json (100%) diff --git a/assets/img/warn_cyan.png b/assets/img/warn_cyan.png deleted file mode 100644 index 3ff1f4b52796475066b70c087153e86d360c009f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1367068 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 F`U5+RXxjh) diff --git a/assets/fonts/Roboto/LICENSE.txt b/fonts/Roboto/LICENSE.txt similarity index 100% rename from assets/fonts/Roboto/LICENSE.txt rename to fonts/Roboto/LICENSE.txt diff --git a/assets/fonts/Roboto/Roboto-Black.ttf b/fonts/Roboto/Roboto-Black.ttf similarity index 100% rename from assets/fonts/Roboto/Roboto-Black.ttf rename to fonts/Roboto/Roboto-Black.ttf diff --git a/assets/fonts/Roboto/Roboto-BlackItalic.ttf b/fonts/Roboto/Roboto-BlackItalic.ttf similarity index 100% rename from assets/fonts/Roboto/Roboto-BlackItalic.ttf rename to fonts/Roboto/Roboto-BlackItalic.ttf diff --git a/assets/fonts/Roboto/Roboto-Bold.ttf b/fonts/Roboto/Roboto-Bold.ttf similarity index 100% rename from assets/fonts/Roboto/Roboto-Bold.ttf rename to fonts/Roboto/Roboto-Bold.ttf diff --git a/assets/fonts/Roboto/Roboto-BoldItalic.ttf b/fonts/Roboto/Roboto-BoldItalic.ttf similarity index 100% rename from assets/fonts/Roboto/Roboto-BoldItalic.ttf rename to fonts/Roboto/Roboto-BoldItalic.ttf diff --git a/assets/fonts/Roboto/Roboto-Italic.ttf b/fonts/Roboto/Roboto-Italic.ttf similarity index 100% rename from assets/fonts/Roboto/Roboto-Italic.ttf rename to fonts/Roboto/Roboto-Italic.ttf diff --git a/assets/fonts/Roboto/Roboto-Light.ttf b/fonts/Roboto/Roboto-Light.ttf similarity index 100% rename from assets/fonts/Roboto/Roboto-Light.ttf rename to fonts/Roboto/Roboto-Light.ttf diff --git a/assets/fonts/Roboto/Roboto-LightItalic.ttf b/fonts/Roboto/Roboto-LightItalic.ttf similarity index 100% rename from assets/fonts/Roboto/Roboto-LightItalic.ttf rename to fonts/Roboto/Roboto-LightItalic.ttf diff --git a/assets/fonts/Roboto/Roboto-Medium.ttf b/fonts/Roboto/Roboto-Medium.ttf similarity index 100% rename from assets/fonts/Roboto/Roboto-Medium.ttf rename to fonts/Roboto/Roboto-Medium.ttf diff --git a/assets/fonts/Roboto/Roboto-MediumItalic.ttf b/fonts/Roboto/Roboto-MediumItalic.ttf similarity index 100% rename from assets/fonts/Roboto/Roboto-MediumItalic.ttf rename to fonts/Roboto/Roboto-MediumItalic.ttf diff --git a/assets/fonts/Roboto/Roboto-Regular.ttf b/fonts/Roboto/Roboto-Regular.ttf similarity index 100% rename from assets/fonts/Roboto/Roboto-Regular.ttf rename to fonts/Roboto/Roboto-Regular.ttf diff --git a/assets/fonts/Roboto/Roboto-Thin.ttf b/fonts/Roboto/Roboto-Thin.ttf similarity index 100% rename from assets/fonts/Roboto/Roboto-Thin.ttf rename to fonts/Roboto/Roboto-Thin.ttf diff --git a/assets/fonts/Roboto/Roboto-ThinItalic.ttf b/fonts/Roboto/Roboto-ThinItalic.ttf similarity index 100% rename from assets/fonts/Roboto/Roboto-ThinItalic.ttf rename to fonts/Roboto/Roboto-ThinItalic.ttf diff --git a/assets/data/BallotStudio_16_Edits.JSON b/src/electos/ballotmaker/assets/data/BallotStudio_16_Edits.JSON similarity index 100% rename from assets/data/BallotStudio_16_Edits.JSON rename to src/electos/ballotmaker/assets/data/BallotStudio_16_Edits.JSON diff --git a/assets/data/BallotStudio_7_Formatted.JSON b/src/electos/ballotmaker/assets/data/BallotStudio_7_Formatted.JSON similarity index 100% rename from assets/data/BallotStudio_7_Formatted.JSON rename to src/electos/ballotmaker/assets/data/BallotStudio_7_Formatted.JSON diff --git a/assets/data/JESTONS_PAPARDEV_&_AUG_2021.json b/src/electos/ballotmaker/assets/data/JESTONS_PAPARDEV_&_AUG_2021.json similarity index 100% rename from assets/data/JESTONS_PAPARDEV_&_AUG_2021.json rename to src/electos/ballotmaker/assets/data/JESTONS_PAPARDEV_&_AUG_2021.json diff --git a/assets/data/NIST_sample.json b/src/electos/ballotmaker/assets/data/NIST_sample.json similarity index 100% rename from assets/data/NIST_sample.json rename to src/electos/ballotmaker/assets/data/NIST_sample.json diff --git a/assets/data/june_test_case.json b/src/electos/ballotmaker/assets/data/june_test_case.json similarity index 100% rename from assets/data/june_test_case.json rename to src/electos/ballotmaker/assets/data/june_test_case.json diff --git a/assets/data/nist_sample_election_report.xml b/src/electos/ballotmaker/assets/data/nist_sample_election_report.xml similarity index 100% rename from assets/data/nist_sample_election_report.xml rename to src/electos/ballotmaker/assets/data/nist_sample_election_report.xml diff --git a/assets/img/filled_bubble.png b/src/electos/ballotmaker/assets/img/filled_bubble.png similarity index 100% rename from assets/img/filled_bubble.png rename to src/electos/ballotmaker/assets/img/filled_bubble.png diff --git a/src/electos/ballotmaker/assets/img/warn_cyan.png b/src/electos/ballotmaker/assets/img/warn_cyan.png new file mode 100644 index 0000000000000000000000000000000000000000..522f02e0a7f53ffd5855fb96f00c1568ef3c632d GIT binary patch literal 4302 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