Source code for election_day.forms.vote
from datetime import date
from onegov.core.utils import normalize_for_url
from onegov.election_day import _
from onegov.election_day.models import Ballot
from onegov.election_day.models import ComplexVote
from onegov.election_day.models import Vote
from onegov.form import Form
from onegov.form.fields import ChosenSelectField
from onegov.form.fields import PanelField
from onegov.form.fields import UploadField
from onegov.form.validators import FileSizeLimit
from onegov.form.validators import WhitelistedMimeType
from wtforms.fields import BooleanField
from wtforms.fields import DateField
from wtforms.fields import RadioField
from wtforms.fields import StringField
from wtforms.fields import URLField
from wtforms.validators import InputRequired
from wtforms.validators import Optional
from wtforms.validators import URL
from wtforms.validators import ValidationError
from typing import cast
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from onegov.election_day.request import ElectionDayRequest
[docs]
class VoteForm(Form):
[docs]
id_hint = PanelField(
label=_('Identifier'),
fieldset=_('Identifier'),
kind='callout',
text=_(
'The ID is used in the URL and might be used somewhere. '
'Changing the ID might break links on external sites!'
)
)
[docs]
id = StringField(
label=_('Identifier'),
fieldset=_('Identifier'),
validators=[
InputRequired()
],
)
[docs]
external_id = StringField(
label=_('External ID'),
fieldset=_('Identifier'),
render_kw={'long_description': _('Used for import if set.')},
)
[docs]
external_id_counter_proposal = StringField(
label=_('External ID of counter proposal'),
fieldset=_('Identifier'),
render_kw={'long_description': _('Used for import if set.')},
depends_on=('type', 'complex'),
)
[docs]
external_id_tie_breaker = StringField(
label=_('External ID of tie breaker'),
fieldset=_('Identifier'),
render_kw={'long_description': _('Used for import if set.')},
depends_on=('type', 'complex'),
)
[docs]
type = RadioField(
label=_('Type'),
fieldset=_('Properties'),
choices=[
('simple', _('Simple Vote')),
('complex', _('Vote with Counter-Proposal')),
],
validators=[
InputRequired()
],
default='simple'
)
[docs]
direct = RadioField(
label=_('Counter Proposal'),
fieldset=_('Properties'),
choices=[
('direct', _('Direct (Counter Proposal)')),
('indirect', _('Indirect (Counter Proposal)')),
],
validators=[
InputRequired()
],
default='direct',
depends_on=('type', 'complex')
)
[docs]
domain = RadioField(
label=_('Domain'),
fieldset=_('Properties'),
validators=[
InputRequired()
]
)
[docs]
municipality = ChosenSelectField(
label=_('Municipality'),
fieldset=_('Properties'),
validators=[
InputRequired()
],
depends_on=('domain', 'municipality'),
)
[docs]
has_expats = BooleanField(
label=_('Expats are listed separately'),
fieldset=_('Properties'),
description=_(
'Expats are uploaded and listed as a separate row in the results. '
'Changing this option requires a new upload of the data.'
),
render_kw={'force_simple': True}
)
[docs]
date = DateField(
label=_('Date'),
fieldset=_('Properties'),
validators=[InputRequired()],
default=date.today
)
[docs]
shortcode = StringField(
label=_('Shortcode'),
fieldset=_('Properties'),
render_kw={'long_description': _('Used for sorting.')}
)
[docs]
tie_breaker_vocabulary = BooleanField(
label=_('Display as tie-breaker'),
fieldset=_('View options'),
depends_on=('type', 'simple'),
render_kw={'force_simple': True},
)
[docs]
direct_vocabulary = RadioField(
label=_('Counter Proposal'),
fieldset=_('View options'),
choices=[
('direct', _('Direct (Counter Proposal)')),
('indirect', _('Indirect (Counter Proposal)')),
],
validators=[
InputRequired()
],
default='direct',
depends_on=('tie_breaker_vocabulary', 'y')
)
[docs]
title_de = StringField(
label=_('German'),
fieldset=_('Title of the the vote/proposal'),
render_kw={'lang': 'de'}
)
[docs]
title_fr = StringField(
label=_('French'),
fieldset=_('Title of the the vote/proposal'),
render_kw={'lang': 'fr'}
)
[docs]
title_it = StringField(
label=_('Italian'),
fieldset=_('Title of the the vote/proposal'),
render_kw={'lang': 'it'}
)
[docs]
title_rm = StringField(
label=_('Romansh'),
fieldset=_('Title of the the vote/proposal'),
render_kw={'lang': 'rm'}
)
[docs]
short_title_de = StringField(
label=_('German'),
fieldset=_('Short title of the the vote/proposal'),
render_kw={'lang': 'de'}
)
[docs]
short_title_fr = StringField(
label=_('French'),
fieldset=_('Short title of the the vote/proposal'),
render_kw={'lang': 'fr'}
)
[docs]
short_title_it = StringField(
label=_('Italian'),
fieldset=_('Short title of the the vote/proposal'),
render_kw={'lang': 'it'}
)
[docs]
short_title_rm = StringField(
label=_('Romansh'),
fieldset=_('Short title of the the vote/proposal'),
render_kw={'lang': 'rm'}
)
[docs]
counter_proposal_title_de = StringField(
label=_('German'),
fieldset=_('Title of the counter proposal'),
render_kw={'lang': 'de'},
depends_on=('type', 'complex')
)
[docs]
counter_proposal_title_fr = StringField(
label=_('French'),
fieldset=_('Title of the counter proposal'),
render_kw={'lang': 'fr'},
depends_on=('type', 'complex')
)
[docs]
counter_proposal_title_it = StringField(
label=_('Italian'),
fieldset=_('Title of the counter proposal'),
render_kw={'lang': 'it'},
depends_on=('type', 'complex')
)
[docs]
counter_proposal_title_rm = StringField(
label=_('Romansh'),
fieldset=_('Title of the counter proposal'),
render_kw={'lang': 'rm'},
depends_on=('type', 'complex')
)
[docs]
tie_breaker_title_de = StringField(
label=_('German'),
fieldset=_('Title of the tie breaker'),
render_kw={'lang': 'de'},
depends_on=('type', 'complex')
)
[docs]
tie_breaker_title_fr = StringField(
label=_('French'),
fieldset=_('Title of the tie breaker'),
render_kw={'lang': 'fr'},
depends_on=('type', 'complex'),
)
[docs]
tie_breaker_title_it = StringField(
label=_('Italian'),
fieldset=_('Title of the tie breaker'),
render_kw={'lang': 'it'},
depends_on=('type', 'complex')
)
[docs]
tie_breaker_title_rm = StringField(
label=_('Romansh'),
fieldset=_('Title of the tie breaker'),
render_kw={'lang': 'rm'},
depends_on=('type', 'complex')
)
[docs]
explanations_pdf = UploadField(
label=_('Explanations (PDF)'),
validators=[
WhitelistedMimeType({'application/pdf'}),
FileSizeLimit(100 * 1024 * 1024)
],
fieldset=_('Related link')
)
[docs]
def validate_id(self, field: StringField) -> None:
if normalize_for_url(field.data or '') != field.data:
raise ValidationError(_('Invalid ID'))
if self.model.id != field.data:
query = self.request.session.query(Vote.id)
query = query.filter_by(id=field.data)
if query.first():
raise ValidationError(_('ID already exists'))
[docs]
def validate_external_id(self, field: StringField) -> None:
if field.data:
if (
not hasattr(self.model, 'external_id')
or self.model.external_id != field.data
):
for cls in (Vote, Ballot):
query = self.request.session.query(cls)
query = query.filter_by(external_id=field.data)
if query.first():
raise ValidationError(_('ID already exists'))
[docs]
def validate_external_id_counter_proposal(
self,
field: StringField
) -> None:
if field.data:
if (
not hasattr(self.model, 'counter_proposal')
or self.model.counter_proposal.external_id != field.data
):
for cls in (Vote, Ballot):
query = self.request.session.query(cls)
query = query.filter_by(external_id=field.data)
if query.first():
raise ValidationError(_('ID already exists'))
if field.data == self.external_id.data:
raise ValidationError(_('ID already exists'))
[docs]
def validate_external_id_tie_breaker(self, field: StringField) -> None:
if field.data:
if (
not hasattr(self.model, 'tie_breaker')
or self.model.tie_breaker.external_id != field.data
):
for cls in (Vote, Ballot):
query = self.request.session.query(cls)
query = query.filter_by(external_id=field.data)
if query.first():
raise ValidationError(_('ID already exists'))
if field.data == self.external_id.data:
raise ValidationError(_('ID already exists'))
if field.data == self.external_id_counter_proposal.data:
raise ValidationError(_('ID already exists'))
[docs]
def on_request(self) -> None:
principal = self.request.app.principal
if principal.id != 'zg':
self.delete_field('tie_breaker_vocabulary')
self.delete_field('direct_vocabulary')
self.domain.choices = list(principal.domains_vote.items())
municipalities = {
municipality
for year in principal.entities.values()
for entity in year.values()
if (municipality := entity.get('name', None))
}
self.municipality.choices = [
(item, item) for item in sorted(municipalities)
]
if principal.domain == 'municipality':
assert principal.name is not None
self.municipality.choices = [(principal.name, principal.name)]
self.title_de.validators = []
self.title_fr.validators = []
self.title_it.validators = []
self.title_rm.validators = []
default_locale = self.request.default_locale or ''
if default_locale.startswith('de'):
self.title_de.validators.append(InputRequired())
if default_locale.startswith('fr'):
self.title_fr.validators.append(InputRequired())
if default_locale.startswith('it'):
self.title_it.validators.append(InputRequired())
if default_locale.startswith('rm'):
self.title_rm.validators.append(InputRequired())
[docs]
def update_model(self, model: Vote) -> None:
if self.id and self.id.data:
model.id = self.id.data
model.external_id = self.external_id.data
if isinstance(model, ComplexVote):
model.direct = self.direct.data == 'direct'
elif self.direct_vocabulary is not None:
model.direct = self.direct_vocabulary.data == 'direct'
assert self.date.data is not None
model.date = self.date.data
model.domain = self.domain.data
if model.domain == 'municipality':
model.domain_segment = self.municipality.data
model.has_expats = self.has_expats.data
model.shortcode = self.shortcode.data
model.related_link = self.related_link.data
if self.tie_breaker_vocabulary is not None:
model.tie_breaker_vocabulary = self.tie_breaker_vocabulary.data
titles = {}
if self.title_de.data:
titles['de_CH'] = self.title_de.data
if self.title_fr.data:
titles['fr_CH'] = self.title_fr.data
if self.title_it.data:
titles['it_CH'] = self.title_it.data
if self.title_rm.data:
titles['rm_CH'] = self.title_rm.data
model.title_translations = titles
titles = {}
if self.short_title_de.data:
titles['de_CH'] = self.short_title_de.data
if self.short_title_fr.data:
titles['fr_CH'] = self.short_title_fr.data
if self.short_title_it.data:
titles['it_CH'] = self.short_title_it.data
if self.short_title_rm.data:
titles['rm_CH'] = self.short_title_rm.data
model.short_title_translations = titles
link_labels = {}
if self.related_link_label_de.data:
link_labels['de_CH'] = self.related_link_label_de.data
if self.related_link_label_fr.data:
link_labels['fr_CH'] = self.related_link_label_fr.data
if self.related_link_label_it.data:
link_labels['it_CH'] = self.related_link_label_it.data
if self.related_link_label_rm.data:
link_labels['rm_CH'] = self.related_link_label_rm.data
model.related_link_label = link_labels
action = getattr(self.explanations_pdf, 'action', '')
if action == 'delete':
del model.explanations_pdf
if action == 'replace' and self.explanations_pdf.data:
assert self.explanations_pdf.file is not None
model.explanations_pdf = (
self.explanations_pdf.file,
self.explanations_pdf.filename,
)
if model.type == 'complex':
model = cast('ComplexVote', model)
model.counter_proposal.external_id = (
self.external_id_counter_proposal.data)
model.tie_breaker.external_id = self.external_id_tie_breaker.data
titles = {}
if self.counter_proposal_title_de.data:
titles['de_CH'] = self.counter_proposal_title_de.data
if self.counter_proposal_title_fr.data:
titles['fr_CH'] = self.counter_proposal_title_fr.data
if self.counter_proposal_title_it.data:
titles['it_CH'] = self.counter_proposal_title_it.data
if self.counter_proposal_title_rm.data:
titles['rm_CH'] = self.counter_proposal_title_rm.data
model.counter_proposal.title_translations = titles
titles = {}
if self.tie_breaker_title_de.data:
titles['de_CH'] = self.tie_breaker_title_de.data
if self.tie_breaker_title_fr.data:
titles['fr_CH'] = self.tie_breaker_title_fr.data
if self.tie_breaker_title_it.data:
titles['it_CH'] = self.tie_breaker_title_it.data
if self.tie_breaker_title_rm.data:
titles['rm_CH'] = self.tie_breaker_title_rm.data
model.tie_breaker.title_translations = titles
[docs]
def apply_model(self, model: Vote) -> None:
self.id.data = model.id
self.external_id.data = model.external_id
titles = model.title_translations or {}
self.title_de.data = titles.get('de_CH')
self.title_fr.data = titles.get('fr_CH')
self.title_it.data = titles.get('it_CH')
self.title_rm.data = titles.get('rm_CH')
titles = model.short_title_translations or {}
self.short_title_de.data = titles.get('de_CH')
self.short_title_fr.data = titles.get('fr_CH')
self.short_title_it.data = titles.get('it_CH')
self.short_title_rm.data = titles.get('rm_CH')
link_labels = model.related_link_label or {}
self.related_link_label_de.data = link_labels.get('de_CH', '')
self.related_link_label_fr.data = link_labels.get('fr_CH', '')
self.related_link_label_it.data = link_labels.get('it_CH', '')
self.related_link_label_rm.data = link_labels.get('rm_CH', '')
if isinstance(model, ComplexVote):
self.direct.data = 'direct' if model.direct else 'indirect'
elif self.direct_vocabulary is not None:
self.direct_vocabulary.data = (
'direct' if model.direct else 'indirect'
)
self.date.data = model.date
self.domain.data = model.domain
if model.domain == 'municipality':
self.municipality.data = model.domain_segment
self.has_expats.data = model.has_expats
self.shortcode.data = model.shortcode
self.related_link.data = model.related_link
if self.tie_breaker_vocabulary is not None:
self.tie_breaker_vocabulary.data = model.tie_breaker_vocabulary
file = model.explanations_pdf
if file:
self.explanations_pdf.data = {
'filename': file.reference.filename,
'size': file.reference.file.content_length,
'mimetype': file.reference.content_type
}
if model.type == 'complex':
model = cast('ComplexVote', model)
self.type.choices = [
('complex', _('Vote with Counter-Proposal'))
]
self.type.data = 'complex'
self.external_id_counter_proposal.data = (
model.counter_proposal.external_id)
self.external_id_tie_breaker.data = model.tie_breaker.external_id
titles = model.counter_proposal.title_translations or {}
self.counter_proposal_title_de.data = titles.get('de_CH')
self.counter_proposal_title_fr.data = titles.get('fr_CH')
self.counter_proposal_title_it.data = titles.get('it_CH')
self.counter_proposal_title_rm.data = titles.get('rm_CH')
titles = model.tie_breaker.title_translations or {}
self.tie_breaker_title_de.data = titles.get('de_CH')
self.tie_breaker_title_fr.data = titles.get('fr_CH')
self.tie_breaker_title_it.data = titles.get('it_CH')
self.tie_breaker_title_rm.data = titles.get('rm_CH')
else:
self.type.choices = [('simple', _('Simple Vote'))]
self.type.data = 'simple'
for fieldset in self.fieldsets:
if fieldset.label == 'Title of the counter proposal':
fieldset.label = None
if fieldset.label == 'Title of the tie breaker':
fieldset.label = None