Source code for fsi.models.course_notification_template

from __future__ import annotations

from datetime import datetime
from markupsafe import Markup
from sqlalchemy import ForeignKey, Enum, UniqueConstraint
from sqlalchemy.orm import mapped_column, relationship, Mapped
from uuid import uuid4, UUID

from onegov.core.orm import Base
from onegov.core.orm.mixins import ContentMixin, TimestampMixin
from onegov.fsi import _


from typing import Any, Literal, TYPE_CHECKING
if TYPE_CHECKING:
    from collections.abc import Iterable
    from onegov.fsi.request import FsiRequest
    from typing import Self, TypeAlias
    from .course_event import CourseEvent


[docs] NotificationType: TypeAlias = Literal[ 'info', 'reservation', 'reminder', 'cancellation', ]
[docs] NOTIFICATION_TYPES: tuple[NotificationType, ...] = ( 'info', 'reservation', 'reminder', 'cancellation')
[docs] NOTIFICATION_TYPE_TRANSLATIONS = ( _('Info Mail'), _('Subscription Confirmation'), _('Event Reminder'), _('Cancellation Confirmation') )
[docs] GERMAN_TYPE_TRANSLATIONS = { 'info': 'Info E-Mail Kursveranstaltung', 'reservation': 'Anmeldungsbestätigung', 'reminder': 'Erinnerung Kursdurchführung', 'cancellation': 'Absage Kursveranstaltung', 'invitation': 'Einladung für Kursanmeldung' }
# for forms...
[docs] def template_type_choices( request: FsiRequest | None = None ) -> tuple[tuple[str, str], ...]: if request: translations: Iterable[str] = ( request.translate(t) for t in NOTIFICATION_TYPE_TRANSLATIONS) else: translations = NOTIFICATION_TYPE_TRANSLATIONS return tuple( (val, key) for val, key in zip(NOTIFICATION_TYPES, translations))
[docs] def get_template_default( # FIXME: We can improve the type of context in SQLAlchemy 2.0 context: Any, type: str | None = None ) -> str: t = type or context.current_parameters.get('type') return GERMAN_TYPE_TRANSLATIONS[t]
[docs] def template_name( type: NotificationType | Literal['invitation'], request: FsiRequest | None = None ) -> str: try: if type == 'invitation': text = _('Course Subscription Invitation') else: # FIXME: Why not just collapse these into a dictionary? text = NOTIFICATION_TYPE_TRANSLATIONS[ NOTIFICATION_TYPES.index(type) ] except ValueError as exception: raise AssertionError( 'There are 5 notifications types allowed' ) from exception return request.translate(text) if request else text
[docs] class CourseInvitationTemplate: """ This is kind of a dummy db model for using as the template for CourseInviteMailLayout. If needed, this can be replaced with a real model without changing too much code. """
[docs] text = None
[docs] text_html = None
[docs] type: Literal['invitation'] = 'invitation'
[docs] subject = get_template_default(None, type)
[docs] class CourseNotificationTemplate(Base, ContentMixin, TimestampMixin):
[docs] __tablename__ = 'fsi_notification_templates'
[docs] __table_args__ = (UniqueConstraint('course_event_id', 'type', 'subject', name='_course_type_subject_uc'), )
# the notification type used to choose the correct chameleon template
[docs] type: Mapped[NotificationType] = mapped_column( Enum(*NOTIFICATION_TYPES, name='notification_types'), )
[docs] __mapper_args__ = { 'polymorphic_on': type, 'polymorphic_identity': 'fsi_notification_templates' }
# One-To-Many relationship with course
[docs] course_event_id: Mapped[UUID] = mapped_column( ForeignKey('fsi_course_events.id') )
[docs] course_event: Mapped[CourseEvent] = relationship( back_populates='notification_templates', overlaps='info_template,reservation_template,' 'cancellation_template,reminder_template' )
#: The public id of the notification template
[docs] id: Mapped[UUID] = mapped_column( primary_key=True, default=uuid4 )
#: The subject of the notification would be according to template type
[docs] subject: Mapped[str | None] = mapped_column(default=get_template_default)
#: The body text injected in plaintext (not html)
[docs] text: Mapped[str | None]
# when email based on template was sent last time
[docs] last_sent: Mapped[datetime | None]
[docs] def duplicate(self) -> Self: return self.__class__( type=self.type, id=uuid4(), subject=self.subject, text=self.text )
@property
[docs] def text_html(self) -> Markup | None: if not self.text: return None return Markup(' ').join( Markup('<p>{}</p>').format(part) for part in self.text.split('\n') if part )
[docs] class InfoTemplate(CourseNotificationTemplate):
[docs] __mapper_args__ = {'polymorphic_identity': 'info'}
[docs] class SubscriptionTemplate(CourseNotificationTemplate):
[docs] __mapper_args__ = {'polymorphic_identity': 'reservation'}
[docs] class ReminderTemplate(CourseNotificationTemplate):
[docs] __mapper_args__ = {'polymorphic_identity': 'reminder'}
[docs] class CancellationTemplate(CourseNotificationTemplate):
[docs] __mapper_args__ = {'polymorphic_identity': 'cancellation'}