Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
"""Add email template model

Revision ID: 86873b143da2
Revises: 932389d22b1f
Create Date: 2025-09-12 21:42:26.537009
"""

import sqlalchemy as sa
from alembic import op
from sqlalchemy.dialects import postgresql


# revision identifiers, used by Alembic.
revision = '86873b143da2'
down_revision = '932389d22b1f'
branch_labels = None
depends_on = None


def upgrade():
op.create_table('email_templates',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('title', sa.String(), nullable=False),
sa.Column('type', sa.String(), nullable=False),
sa.Column('status', sa.String(), nullable=False),
sa.Column('subject', sa.String(), nullable=False),
sa.Column('body', sa.Text(), nullable=False),
sa.Column('event_id', sa.Integer(), nullable=True, index=True),
sa.Column('category_id', sa.Integer(), nullable=True, index=True),
sa.Column('rules', postgresql.JSONB(astext_type=sa.Text()), nullable=False),
sa.Column('is_active', sa.Boolean(), nullable=False),
sa.Column('is_system_template', sa.Boolean(), nullable=False),
sa.CheckConstraint('(event_id IS NULL) != (category_id IS NULL)',
name='event_xor_category_id_null'),
sa.ForeignKeyConstraint(['category_id'], ['categories.categories.id']),
sa.ForeignKeyConstraint(['event_id'], ['events.events.id']),
sa.PrimaryKeyConstraint('id'),
schema='indico')


def downgrade():
op.drop_table('email_templates', schema='indico')
4 changes: 4 additions & 0 deletions indico/modules/designer/controllers.py
Original file line number Diff line number Diff line change
Expand Up @@ -197,9 +197,13 @@ class CloneTemplateMixin(TargetFromURLMixin):
def _check_access(self):
if not self.target.can_manage(session.user):
raise Forbidden
elif isinstance(self.target, Event):
check_event_locked(self, self.target)

def _process_args(self):
self.template = DesignerTemplate.get_or_404(request.view_args['template_id'])
if self.target.is_deleted:
raise NotFound

def clone_template(self, target=None):
title = f'{self.template.title} (copy)'
Expand Down
29 changes: 29 additions & 0 deletions indico/modules/email_templates/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# This file is part of Indico.
# Copyright (C) 2002 - 2025 CERN
#
# Indico is free software; you can redistribute it and/or
# modify it under the terms of the MIT License; see the
# LICENSE file for more details.

from flask import session

from indico.core import signals
from indico.util.i18n import _
from indico.web.flask.util import url_for
from indico.web.menu import SideMenuItem


@signals.menu.items.connect_via('event-management-sidemenu')
def _event_sidemenu_items(sender, event, **kwargs):
if event.can_manage(session.user):
return SideMenuItem('email_templates', _('Email Templates'),
url_for('email_templates.email_template_list', event),
section='customization')


@signals.menu.items.connect_via('category-management-sidemenu')
def _category_sidemenu_items(sender, category, **kwargs):
if category.can_manage(session.user):
return SideMenuItem('email_templates', _('Email Templates'),
url_for('email_templates.email_template_list', category),
weight=50, icon='mail')
55 changes: 55 additions & 0 deletions indico/modules/email_templates/blueprint.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# This file is part of Indico.
# Copyright (C) 2002 - 2025 CERN
#
# Indico is free software; you can redistribute it and/or
# modify it under the terms of the MIT License; see the
# LICENSE file for more details.

from indico.modules.email_templates.controllers import (RHAddCategoryEmailTemplate, RHAddEventEmailTemplate,
RHCloneCategoryEmailTemplate,
RHCloneCategorySystemEmailTemplate, RHCloneEventEmailTemplate,
RHCloneEventSystemEmailTemplate, RHDeleteEmailTemplate,
RHEditEmailTemplate, RHListCategoryEmailTemplates,
RHListEventEmailTemplates, RHViewCategoryEmailTemplate,
RHViewEventEmailTemplate)
from indico.util.caching import memoize
from indico.web.flask.util import make_view_func
from indico.web.flask.wrappers import IndicoBlueprint


_bp = IndicoBlueprint('email_templates', __name__, template_folder='templates',
virtual_template_folder='email_templates')


@memoize
def _dispatch(event_rh, category_rh):
event_view = make_view_func(event_rh)
categ_view = make_view_func(category_rh)

def view_func(**kwargs):
return categ_view(**kwargs) if kwargs['object_type'] == 'category' else event_view(**kwargs)

return view_func


for object_type in ('event', 'category'):
prefix = f'/{object_type}/<int:{object_type}_id>/manage/email_templates'
_bp.add_url_rule(f'{prefix}/', 'email_template_list',
_dispatch(RHListEventEmailTemplates, RHListCategoryEmailTemplates),
defaults={'object_type': object_type})
_bp.add_url_rule(f'{prefix}/add', 'add_email_template',
_dispatch(RHAddEventEmailTemplate, RHAddCategoryEmailTemplate),
defaults={'object_type': object_type}, methods=('GET', 'POST'))
_bp.add_url_rule(f'{prefix}/<int:email_template_id>/', 'edit_email_template',
RHEditEmailTemplate, defaults={'object_type': object_type}, methods=('GET', 'POST'))
_bp.add_url_rule(f'{prefix}/<int:email_template_id>', 'delete_email_template',
RHDeleteEmailTemplate, defaults={'object_type': object_type}, methods=('DELETE',))
_bp.add_url_rule(f'{prefix}/<int:email_template_id>/clone', 'clone_email_template',
_dispatch(RHCloneEventEmailTemplate, RHCloneCategoryEmailTemplate),
defaults={'object_type': object_type}, methods=('GET', 'POST'))
_bp.add_url_rule(f'{prefix}/<email_template_name>/view-system-template', 'view_system_email_template',
_dispatch(RHViewEventEmailTemplate, RHViewCategoryEmailTemplate),
defaults={'object_type': object_type}, methods=('GET', 'POST'))
_bp.add_url_rule(f'{prefix}/<email_template_name>/system-template/clone', 'clone_system_email_template',
_dispatch(RHCloneEventSystemEmailTemplate, RHCloneCategorySystemEmailTemplate),
defaults={'object_type': object_type}, methods=('GET', 'POST'))
Loading