Source code for fsi.models.course_notification_template

from uuid import uuid4

from markupsafe import Markup
from sqlalchemy import Column, Text, ForeignKey, Enum, UniqueConstraint
from sqlalchemy.orm import relationship

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


from typing import Any, Literal, TYPE_CHECKING
if TYPE_CHECKING:
    import uuid
    from collections.abc import Iterable
    from datetime import datetime
    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: 'Column[NotificationType]' = Column( Enum(*NOTIFICATION_TYPES, name='notification_types'), # type:ignore nullable=False, )
[docs] __mapper_args__ = { 'polymorphic_on': type, 'polymorphic_identity': 'fsi_notification_templates' }
# One-To-Many relationship with course
[docs] course_event_id: 'Column[uuid.UUID]' = Column( UUID, # type:ignore[arg-type] ForeignKey('fsi_course_events.id'), nullable=False )
[docs] course_event: 'relationship[CourseEvent]' = relationship( 'CourseEvent', back_populates='notification_templates' )
#: The public id of the notification template
[docs] id: 'Column[uuid.UUID]' = Column( UUID, # type:ignore[arg-type] primary_key=True, default=uuid4 )
#: The subject of the notification would be according to template type
[docs] subject: 'Column[str | None]' = Column(Text, default=get_template_default)
#: The body text injected in plaintext (not html)
[docs] text: 'Column[str | None]' = Column(Text)
# when email based on template was sent last time
[docs] last_sent: 'Column[datetime | None]' = Column(UTCDateTime)
[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'}