from copy import deepcopy
from onegov.core.orm import Base
from onegov.core.orm import translation_hybrid
from onegov.core.orm.mixins import ContentMixin
from onegov.core.orm.mixins import dict_property
from onegov.core.orm.mixins import meta_property
from onegov.core.orm.mixins import TimestampMixin
from onegov.core.orm.mixins.content import dictionary_based_property_factory
from onegov.core.orm.types import HSTORE
from onegov.core.orm.types import UTCDateTime
from onegov.core.orm.types import UUID
from onegov.election_day.models.election import Election
from onegov.election_day.models.election_compound import ElectionCompound
from onegov.election_day.models.mixins import DomainOfInfluenceMixin
from onegov.election_day.models.mixins import TitleTranslationsMixin
from onegov.election_day.models.vote import Vote
from sqlalchemy import Boolean
from sqlalchemy import Column
from sqlalchemy import Date
from sqlalchemy import Enum
from sqlalchemy import Integer
from sqlalchemy import Text
from uuid import uuid4
from typing import Any
from typing import TYPE_CHECKING
if TYPE_CHECKING:
import datetime
import uuid
from builtins import type as _type
from collections.abc import Mapping
from onegov.election_day.request import ElectionDayRequest
from typing import Literal
from typing import Self
from typing import TypeAlias
[docs]
ResultType: TypeAlias = Literal['election', 'election_compound', 'vote']
[docs]
class ArchivedResult(Base, ContentMixin, TimestampMixin,
DomainOfInfluenceMixin, TitleTranslationsMixin):
""" Stores the result of an election or vote. """
[docs]
__tablename__ = 'archived_results'
#: Identifies the result
[docs]
id: 'Column[uuid.UUID]' = Column(
UUID, # type:ignore[arg-type]
primary_key=True,
default=uuid4
)
#: The date of the election/vote
[docs]
date: 'Column[datetime.date]' = Column(Date, nullable=False)
#: The last change of the results election/vote
[docs]
last_modified: 'Column[datetime.datetime | None]' = Column(
UTCDateTime,
nullable=True
)
#: The last change of election/vote
[docs]
last_result_change: 'Column[datetime.datetime | None]' = Column(
UTCDateTime,
nullable=True
)
#: Type of the result
[docs]
type: 'Column[ResultType]' = Column(
Enum( # type:ignore[arg-type]
'vote', 'election', 'election_compound',
name='type_of_result'
),
nullable=False
)
#: Origin of the result
[docs]
schema: 'Column[str]' = Column(Text, nullable=False)
#: The name of the principal
[docs]
name: 'Column[str]' = Column(Text, nullable=False)
#: Total number of political entities
[docs]
total_entities: 'Column[int | None]' = Column(Integer, nullable=True)
#: Number of already counted political entities
[docs]
counted_entities: 'Column[int | None]' = Column(Integer, nullable=True)
@property
[docs]
def progress(self) -> tuple[int, int]:
return self.counted_entities or 0, self.total_entities or 0
#: Number of already counted political entities
[docs]
has_results: 'Column[bool | None]' = Column(Boolean, nullable=True)
#: The link to the detailed results
[docs]
url: 'Column[str]' = Column(Text, nullable=False)
#: Title of the election/vote
[docs]
title_translations: 'Column[Mapping[str, str]]' = Column(
HSTORE,
nullable=False
)
[docs]
title = translation_hybrid(title_translations)
[docs]
def title_prefix(self, request: 'ElectionDayRequest') -> str:
if self.is_fetched(request) and self.domain == 'municipality':
return self.name or ''
return ''
#: Shortcode for cantons that use it
[docs]
shortcode: 'Column[str | None]' = Column(Text, nullable=True)
#: The id of the election/vote.
[docs]
external_id: dict_property[str | None] = meta_property('id')
#: The names of the elected candidates.
[docs]
elected_candidates: dict_property[list[tuple[str, str]]] = meta_property(
'elected_candidates',
default=list
)
#: The URLs of the elections (if it is a compound)
[docs]
elections: dict_property[list[str]] = meta_property(
'elections',
default=list
)
#: The answer of a vote (accepted, rejected, counter-proposal).
[docs]
answer: dict_property[str] = meta_property('answer', default='')
#: The nays rate of a vote.
[docs]
nays_percentage: dict_property[float] = meta_property(
'nays_percentage',
default=100.0
)
#: The yeas rate of a vote.
[docs]
yeas_percentage: dict_property[float] = meta_property(
'yeas_percentage',
default=0.0
)
#: True, if the vote or election has been counted.
[docs]
counted: dict_property[bool] = meta_property('counted', default=False)
#: True, if the vote or election has been completed.
[docs]
completed: dict_property[bool] = meta_property(
'completed',
default=False
)
#: Turnout (vote/elections)
[docs]
turnout: dict_property[float | None] = meta_property('turnout')
#: True, if this is direct complex vote
[docs]
direct: dict_property[bool] = meta_property(
'direct',
default=True
)
#: The local results (municipal results if fetched from cantonal instance)
[docs]
local: dict_property[dict[str, Any] | None] = meta_property('local')
#: The answer if this a fetched cantonal/federal result on a communal
#: instance.
[docs]
local_answer: dict_property[str] = meta_local_property('answer', '')
#: The nays rate if this a fetched cantonal/federal result on a communal
#: instance.
[docs]
local_nays_percentage: dict_property[float] = meta_local_property(
'nays_percentage',
100.0
)
#: The yeas rate if this a fetched cantonal/federal result on a communal
#: instance.
[docs]
local_yeas_percentage: dict_property[float] = meta_local_property(
'yeas_percentage',
0.0
)
@property
[docs]
def type_class(self) -> '_type[Election | ElectionCompound | Vote]':
if self.type == 'vote':
return Vote
elif self.type == 'election':
return Election
elif self.type == 'election_compound':
return ElectionCompound
raise NotImplementedError
[docs]
def is_fetched(self, request: 'ElectionDayRequest') -> bool:
""" Returns True, if this results has been fetched from another
instance.
"""
return self.schema != request.app.schema
[docs]
def is_fetched_by_municipality(
self,
request: 'ElectionDayRequest'
) -> bool:
""" Returns True, if this results has been fetched from another
instance by a communal instance.
"""
return (
self.is_fetched(request)
and request.app.principal.domain == 'municipality'
)
[docs]
def adjusted_url(self, request: 'ElectionDayRequest') -> str:
""" Returns the url adjusted to the current host. Needed if the
instance is accessible under different hosts at the same time.
"""
if self.is_fetched(request):
return self.url
return request.class_link(
self.type_class,
{'id': self.external_id}
)
[docs]
def display_answer(self, request: 'ElectionDayRequest') -> str:
""" Returns the answer (depending on the current instance). """
if self.is_fetched_by_municipality(request):
return self.local_answer
return self.answer
[docs]
def display_nays_percentage(self, request: 'ElectionDayRequest') -> float:
""" Returns the nays rate (depending on the current instance). """
if self.is_fetched_by_municipality(request):
return self.local_nays_percentage
return self.nays_percentage
[docs]
def display_yeas_percentage(self, request: 'ElectionDayRequest') -> float:
""" Returns the yeas rate (depending on the current instance). """
if self.is_fetched_by_municipality(request):
return self.local_yeas_percentage
return self.yeas_percentage
[docs]
def copy_from(self, source: 'Self') -> None:
self.date = source.date
self.last_modified = source.last_modified
self.last_result_change = source.last_result_change
self.type = source.type
self.schema = source.schema
self.name = source.name
self.total_entities = source.total_entities
self.counted_entities = source.counted_entities
self.has_results = source.has_results
self.url = source.url
self.title_translations = deepcopy(dict(source.title_translations))
self.shortcode = source.shortcode
self.domain = source.domain
self.meta = deepcopy(dict(source.meta))