-
Notifications
You must be signed in to change notification settings - Fork 5
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #98 from TrustTheVote-Project/dev-make-flat
Merge development branch after last Friday's sample ballot, close relevant issues
- Loading branch information
Showing
49 changed files
with
1,453 additions
and
565 deletions.
There are no files selected for viewing
Binary file not shown.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
Binary file not shown.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
File renamed without changes
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
from dataclasses import dataclass, field | ||
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""" | ||
|
||
_can_con: dict = field(repr=False) | ||
# fields retrieved from the dict | ||
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.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"] | ||
for candidate_data in _candidates: | ||
self.candidates.append(CandidateData(candidate_data)) | ||
|
||
|
||
@dataclass | ||
class CandidateData: | ||
_can_data: dict = field(repr=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.name = self._can_data["name"] | ||
party_dict = self._can_data["party"] | ||
self.party = party_dict["name"] | ||
self.party_abbr = party_dict["abbreviation"] | ||
|
||
|
||
if __name__ == "__main__": # pragma: no cover | ||
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) | ||
b_measure_data_1 = BallotMeasureData(spacetown_data.ballot_measure_1) | ||
print(b_measure_data_1) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,230 @@ | ||
# format a ballot contest. | ||
|
||
from electos.ballotmaker.ballots.contest_data import ( | ||
BallotMeasureData, | ||
CandidateContestData, | ||
) | ||
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 | ||
|
||
# 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_candidate_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), | ||
], | ||
) | ||
|
||
|
||
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) | ||
|
||
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 = PageLayout.white | ||
self.oval.fillColor = white | ||
# self.oval.strokeColor = PageLayout.black | ||
self.oval.strokeColor = black | ||
self.oval.strokeWidth = 0.5 | ||
|
||
|
||
class CandidateContestLayout: | ||
""" | ||
Generate a candidate contest table flowable | ||
""" | ||
|
||
def __init__(self, contest_data: CandidateContestData): | ||
self.id = contest_data.id | ||
self.title = contest_data.title | ||
self.votes_allowed = contest_data.votes_allowed | ||
if self.votes_allowed > 1: | ||
self.instruct = f"Vote for up to {self.votes_allowed}" | ||
else: | ||
self.instruct = f"Vote for {self.votes_allowed}" | ||
self.candidates = contest_data.candidates | ||
_selections = [] | ||
|
||
oval = SelectionOval() | ||
for candidate in self.candidates: | ||
# add newlines around " and " | ||
# if candidate.find(" and "): | ||
# candidate = candidate.replace(" and ", "<br />and<br />") | ||
contest_line = f"<b>{candidate.name}</b>" | ||
if candidate.party_abbr != "": | ||
contest_line += f"<br />{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 | ||
) | ||
self.contest_table = build_candidate_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"<b>{choice}</b>" | ||
contest_row = [oval, Paragraph(contest_line, normal)] | ||
_selections.append(contest_row) | ||
|
||
self.contest_list = build_contest_list( | ||
self.title, self.instruct, _selections, self.text | ||
) | ||
self.contest_table = build_ballot_measure_table(self.contest_list) | ||
|
||
|
||
if __name__ == "__main__": # pragma: no cover | ||
from electos.ballotmaker.demo_data import spacetown_data | ||
|
||
contest_1 = CandidateContestData(spacetown_data.can_con_1) | ||
print(contest_1.candidates) | ||
layout_1 = CandidateContestLayout(contest_1) | ||
print(layout_1.contest_list) |
Oops, something went wrong.