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] request: 'ElectionDayRequest'
[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