from __future__ import annotations
from datetime import date
from sqlalchemy import and_, or_, exists
from sqlalchemy.ext.hybrid import hybrid_property
from onegov.core.orm import Base
from onegov.core.orm.mixins import ContentMixin
from onegov.core.orm.mixins import dict_property, content_property
from onegov.core.orm.mixins import TimestampMixin
from onegov.core.orm.types import UUID
from onegov.file import AssociatedFiles
from onegov.file import NamedFile
from onegov.parliament import _
from sqlalchemy import Column
from sqlalchemy import Date
from sqlalchemy import Enum
from sqlalchemy import Text
from sqlalchemy.orm import relationship
from uuid import uuid4
from typing import Literal, TypeAlias, TYPE_CHECKING, Any
if TYPE_CHECKING:
import uuid
from onegov.parliament.models import (
CommissionMembership,
ParliamentarianRole,
)
[docs]
Gender: TypeAlias = Literal[
'male',
'female',
]
ShippingMethod: TypeAlias = Literal[
'a',
'plus',
'registered',
'confidential',
'personal',
]
[docs]
GENDERS: dict[Gender, str] = {
'male': _('male'),
'female': _('female'),
}
[docs]
SHIPPING_METHODS: dict[ShippingMethod, str] = {
'a': _('A mail'),
'plus': _('A mail plus'),
'registered': _('registered'),
'confidential': _('confidential'),
'personal': _('personal / confidential')
}
[docs]
class Parliamentarian(Base, ContentMixin, TimestampMixin, AssociatedFiles):
[docs]
__tablename__ = 'par_parliamentarians'
[docs]
type: Column[str] = Column(
Text,
nullable=False,
default=lambda: 'generic'
)
[docs]
__mapper_args__ = {
'polymorphic_on': type,
'polymorphic_identity': 'generic',
}
@property
[docs]
def title(self) -> str:
return f'{self.first_name} {self.last_name}'
#: Internal ID
[docs]
id: Column[uuid.UUID] = Column(
UUID, # type:ignore[arg-type]
primary_key=True,
default=uuid4
)
#: External ID
#
# Note: Value can only be None if the data is imported from an Excel file.
# Fixme: The excel data import will not be used in the future so we will be
# able to make this Non-Nullable soon.
[docs]
external_kub_id: Column[uuid.UUID | None] = Column(
UUID, # type:ignore[arg-type]
nullable=True,
default=uuid4,
unique=True
)
#: The first name
[docs]
first_name: Column[str] = Column(
Text,
nullable=False
)
#: The last name
[docs]
last_name: Column[str] = Column(
Text,
nullable=False
)
#: The personnel number
[docs]
personnel_number: Column[str | None] = Column(
Text,
nullable=True
)
#: The contract number
[docs]
contract_number: Column[str | None] = Column(
Text,
nullable=True
)
#: The gender value
[docs]
gender: Column[Gender] = Column(
Enum(
*GENDERS.keys(), # type:ignore[arg-type]
name='pas_gender'
),
nullable=False,
default='male'
)
#: The gender as translated text
@property
[docs]
def gender_label(self) -> str:
return GENDERS.get(self.gender, '')
@property
#: The shipping method value
[docs]
shipping_method: Column[ShippingMethod] = Column(
Enum(
*SHIPPING_METHODS.keys(), # type:ignore[arg-type]
name='pas_shipping_methods'
),
nullable=False,
default='a'
)
#: The shipping method as translated text
@property
[docs]
def shipping_method_label(self) -> str:
return SHIPPING_METHODS.get(self.shipping_method, '')
#: The shipping address
[docs]
shipping_address: Column[str | None] = Column(
Text,
nullable=True
)
#: The shipping address addition
[docs]
shipping_address_addition: Column[str | None] = Column(
Text,
nullable=True
)
#: The shipping address zip code
[docs]
shipping_address_zip_code: Column[str | None] = Column(
Text,
nullable=True
)
#: The shipping address city
[docs]
shipping_address_city: Column[str | None] = Column(
Text,
nullable=True
)
#: The private address
[docs]
private_address: Column[str | None] = Column(
Text,
nullable=True
)
#: The private address addition
[docs]
private_address_addition: Column[str | None] = Column(
Text,
nullable=True
)
#: The private address zip code
[docs]
private_address_zip_code: Column[str | None] = Column(
Text,
nullable=True
)
#: The private address city
[docs]
private_address_city: Column[str | None] = Column(
Text,
nullable=True
)
#: The date of birth
[docs]
date_of_birth: Column[date | None] = Column(
Date,
nullable=True
)
#: The date of death
[docs]
date_of_death: Column[date | None] = Column(
Date,
nullable=True
)
#: The place of origin
[docs]
place_of_origin: Column[str | None] = Column(
Text,
nullable=True
)
[docs]
party: Column[str | None] = Column(
Text,
nullable=True
)
#: The occupation
[docs]
occupation: Column[str | None] = Column(
Text,
nullable=True
)
#: The academic title
[docs]
academic_title: Column[str | None] = Column(
Text,
nullable=True
)
#: The salutation
[docs]
salutation: Column[str | None] = Column(
Text,
nullable=True
)
#: The salutation used in the address
[docs]
salutation_for_address: Column[str | None] = Column(
Text,
nullable=True
)
#: The salutation used for letters
[docs]
salutation_for_letter: Column[str | None] = Column(
Text,
nullable=True
)
#: How bills should be delivered
[docs]
forwarding_of_bills: Column[str | None] = Column(
Text,
nullable=True
)
#: The private phone number
[docs]
phone_private: Column[str | None] = Column(
Text,
nullable=True
)
#: The mobile phone number
[docs]
phone_mobile: Column[str | None] = Column(
Text,
nullable=True
)
#: The business phone number
[docs]
phone_business: Column[str | None] = Column(
Text,
nullable=True
)
#: The primary email address
[docs]
email_primary: Column[str | None] = Column(
Text,
nullable=True
)
#: The secondary email address
[docs]
email_secondary: Column[str | None] = Column(
Text,
nullable=True
)
#: The website
[docs]
website: Column[str | None] = Column(
Text,
nullable=True
)
#: The remarks
#: A picture
#: A parliamentarian may have n roles
[docs]
roles: relationship[list[ParliamentarianRole]]
roles = relationship(
'ParliamentarianRole',
cascade='all, delete-orphan',
back_populates='parliamentarian',
order_by='desc(ParliamentarianRole.start)'
)
#: A parliamentarian's interest ties
[docs]
interests: dict_property[dict[str, Any]] = content_property(default=dict)
@hybrid_property
[docs]
def active(self) -> bool:
if not self.roles and not self.commission_memberships:
return True
for role in self.roles:
if role.end is None or role.end >= date.today():
return True
for membership in self.commission_memberships:
if membership.end is None or membership.end >= date.today():
return True
return False
@active.expression # type:ignore[no-redef]
def active(cls):
from onegov.parliament.models import (
CommissionMembership,
ParliamentarianRole,
)
return or_(
and_(
~exists().where(
ParliamentarianRole.parliamentarian_id == cls.id),
~exists().where(
CommissionMembership.parliamentarian_id == cls.id)
),
exists().where(
and_(
ParliamentarianRole.parliamentarian_id == cls.id,
or_(
ParliamentarianRole.end.is_(None),
ParliamentarianRole.end >= date.today()
)
)
),
exists().where(
and_(
CommissionMembership.parliamentarian_id == cls.id,
or_(
CommissionMembership.end.is_(None),
CommissionMembership.end >= date.today()
)
)
)
)
[docs]
def active_during(self, start: date, end: date) -> bool:
if not self.roles:
return True
for role in self.roles:
role_start = role.start if role.start is not None else date.min
role_end = role.end if role.end is not None else date.max
if role_end >= start and role_start <= end:
return True
for membership in self.commission_memberships:
membership_start = (
membership.start) if membership.start is not None else date.min
membership_end = (
membership.end) if membership.end is not None else date.max
if membership_end >= start and membership_start <= end:
return True
return False
#: A parliamentarian may be part of n commissions
[docs]
commission_memberships: relationship[list[CommissionMembership]]
commission_memberships = relationship(
'CommissionMembership',
cascade='all, delete-orphan',
back_populates='parliamentarian'
)
[docs]
def __repr__(self) -> str:
info = [
f'id={self.id}',
f"last_name='{self.last_name}'",
f"first_name='{self.first_name}'",
]
if self.academic_title:
info.append(f"title='{self.academic_title}'")
if self.salutation:
info.append(f"salutation='{self.salutation}'")
if self.email_primary:
info.append(
f"email='{self.email_primary}'")
return f"<Parliamentarian {', '.join(info)}>"