Skip to content

Commit ab63ddf

Browse files
committed
fixup! ✨(backend) add EmailTemplate model and API
1 parent cce8d69 commit ab63ddf

10 files changed

+204
-260
lines changed

src/backend/core/admin.py

Lines changed: 29 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -526,35 +526,35 @@ class DKIMKeyAdmin(admin.ModelAdmin):
526526
)
527527

528528

529-
class EmailTemplateMailDomainInline(admin.TabularInline):
530-
"""Inline class for the EmailTemplateMailDomain model"""
529+
class MessageTemplateMailDomainInline(admin.TabularInline):
530+
"""Inline class for the MessageTemplateMailDomain model"""
531531

532-
model = models.EmailTemplateMailDomain
532+
model = models.MessageTemplateMailDomain
533533
extra = 1
534534

535535

536-
class EmailTemplateMailboxInline(admin.TabularInline):
537-
"""Inline class for the EmailTemplateMailbox model"""
536+
class MessageTemplateMailboxInline(admin.TabularInline):
537+
"""Inline class for the MessageTemplateMailbox model"""
538538

539-
model = models.EmailTemplateMailbox
539+
model = models.MessageTemplateMailbox
540540
extra = 1
541541

542542

543-
@admin.register(models.EmailTemplate)
544-
class EmailTemplateAdmin(admin.ModelAdmin):
545-
"""Admin class for the EmailTemplate model"""
543+
@admin.register(models.MessageTemplate)
544+
class MessageTemplateAdmin(admin.ModelAdmin):
545+
"""Admin class for the MessageTemplate model"""
546546

547-
inlines = [EmailTemplateMailDomainInline, EmailTemplateMailboxInline]
547+
inlines = [MessageTemplateMailDomainInline, MessageTemplateMailboxInline]
548548
list_display = (
549549
"id",
550550
"name",
551-
"type",
551+
"kind",
552552
"is_active",
553553
"preview_content",
554554
"created_at",
555555
)
556556
list_filter = (
557-
"type",
557+
"kind",
558558
"is_active",
559559
"created_at",
560560
)
@@ -574,7 +574,7 @@ class EmailTemplateAdmin(admin.ModelAdmin):
574574
"id",
575575
"name",
576576
"description",
577-
"type",
577+
"kind",
578578
)
579579
},
580580
),
@@ -606,38 +606,38 @@ def get_queryset(self, request):
606606
if request.user.is_superuser:
607607
return qs
608608
# Filter by mailboxes the user has access to
609-
accessible_mailboxes = models.Mailbox.objects.filter(accesses__user=request.user)
609+
accessible_mailboxes = models.Mailbox.objects.filter(
610+
accesses__user=request.user
611+
)
610612
return qs.filter(mailboxes__in=accessible_mailboxes)
611613

612614
def preview_content(self, obj):
613615
"""Display a preview of the formatted content."""
614-
if obj.is_signature():
615-
formatted = obj.get_formatted_content()
616-
return format_html(
617-
'<div style="max-width: 400px; overflow: hidden; text-overflow: ellipsis;">{}</div>',
618-
formatted[:100] + "..." if len(formatted) > 100 else formatted,
619-
)
620-
return "N/A (not a signature)"
616+
formatted = obj.get_formatted_content()
617+
return format_html(
618+
'<div style="max-width: 400px; overflow: hidden; text-overflow: ellipsis;">{}</div>',
619+
formatted[:100] + "..." if len(formatted) > 100 else formatted,
620+
)
621621

622622
preview_content.short_description = "Content Preview"
623623
preview_content.allow_tags = True
624624

625625

626-
@admin.register(models.EmailTemplateMailDomain)
627-
class EmailTemplateMailDomainAdmin(admin.ModelAdmin):
628-
"""Admin class for the EmailTemplateMailDomain model"""
626+
@admin.register(models.MessageTemplateMailDomain)
627+
class MessageTemplateMailDomainAdmin(admin.ModelAdmin):
628+
"""Admin class for the MessageTemplateMailDomain model"""
629629

630630
list_display = ("id", "template", "maildomain", "is_default")
631-
list_filter = ("is_default", "template__type", "maildomain")
631+
list_filter = ("is_default", "template__kind", "maildomain")
632632
search_fields = ("template__name", "maildomain__name")
633633
ordering = ("template__name", "maildomain__name")
634634

635635

636-
@admin.register(models.EmailTemplateMailbox)
637-
class EmailTemplateMailboxAdmin(admin.ModelAdmin):
638-
"""Admin class for the EmailTemplateMailbox model"""
636+
@admin.register(models.MessageTemplateMailbox)
637+
class MessageTemplateMailboxAdmin(admin.ModelAdmin):
638+
"""Admin class for the MessageTemplateMailbox model"""
639639

640640
list_display = ("id", "template", "mailbox", "is_default")
641-
list_filter = ("is_default", "template__type", "mailbox__domain")
641+
list_filter = ("is_default", "template__kind", "mailbox__domain")
642642
search_fields = ("template__name", "mailbox__local_part", "mailbox__domain__name")
643643
ordering = ("template__name", "mailbox__local_part")

src/backend/core/api/serializers.py

Lines changed: 30 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -853,20 +853,20 @@ class ImportIMAPSerializer(ImportBaseSerializer):
853853
)
854854

855855

856-
class EmailTemplateSerializer(AbilitiesModelSerializer):
857-
"""Serialize email templates."""
856+
class MessageTemplateSerializer(AbilitiesModelSerializer):
857+
"""Serialize message templates."""
858858

859-
type = IntegerChoicesField(choices_class=models.TemplateTypeChoices)
859+
kind = IntegerChoicesField(choices_class=models.MessageTemplateKindChoices)
860860

861861
class Meta:
862-
model = models.EmailTemplate
862+
model = models.MessageTemplate
863863
fields = [
864864
"id",
865865
"name",
866866
"description",
867867
"html_body",
868868
"text_body",
869-
"type",
869+
"kind",
870870
"is_active",
871871
"created_at",
872872
"updated_at",
@@ -875,14 +875,22 @@ class Meta:
875875

876876
def validate(self, attrs):
877877
"""Validate template data."""
878+
html_body = attrs.get("html_body", "")
879+
text_body = attrs.get("text_body", "")
880+
881+
if not html_body and not text_body:
882+
raise serializers.ValidationError(
883+
"At least one of html_body or text_body must be provided."
884+
)
885+
878886
# Ensure only one default template per type per mailbox/maildomain
879887
if attrs.get("is_active"):
880-
template_type = attrs.get("type")
888+
template_kind = attrs.get("kind")
881889

882-
if template_type:
883-
# Check if another default template exists for this type
884-
existing_default = models.EmailTemplate.objects.filter(
885-
type=template_type,
890+
if template_kind:
891+
# Check if another default template exists for this kind
892+
existing_default = models.MessageTemplate.objects.filter(
893+
kind=template_kind,
886894
is_active=True,
887895
)
888896

@@ -892,20 +900,20 @@ def validate(self, attrs):
892900

893901
if existing_default.exists():
894902
raise serializers.ValidationError(
895-
f"Another default {template_type} template already exists."
903+
f"Another default {template_kind} template already exists."
896904
)
897905

898906
return attrs
899907

900908

901-
class EmailTemplateMailDomainSerializer(serializers.ModelSerializer):
902-
"""Serialize EmailTemplate to MailDomain relationships."""
909+
class MessageTemplateMailDomainSerializer(serializers.ModelSerializer):
910+
"""Serialize MessageTemplate to MailDomain relationships."""
903911

904912
template_name = serializers.CharField(source="template.name", read_only=True)
905913
maildomain_name = serializers.CharField(source="maildomain.name", read_only=True)
906914

907915
class Meta:
908-
model = models.EmailTemplateMailDomain
916+
model = models.MessageTemplateMailDomain
909917
fields = [
910918
"id",
911919
"template",
@@ -919,14 +927,14 @@ class Meta:
919927
read_only_fields = ["id", "created_at", "updated_at"]
920928

921929

922-
class EmailTemplateMailboxSerializer(serializers.ModelSerializer):
923-
"""Serialize EmailTemplate to Mailbox relationships."""
930+
class MessageTemplateMailboxSerializer(serializers.ModelSerializer):
931+
"""Serialize MessageTemplate to Mailbox relationships."""
924932

925933
template_name = serializers.CharField(source="template.name", read_only=True)
926934
mailbox_email = serializers.CharField(source="mailbox", read_only=True)
927935

928936
class Meta:
929-
model = models.EmailTemplateMailbox
937+
model = models.MessageTemplateMailbox
930938
fields = [
931939
"id",
932940
"template",
@@ -940,12 +948,12 @@ class Meta:
940948
read_only_fields = ["id", "created_at", "updated_at"]
941949

942950

943-
class EmailTemplateLightSerializer(serializers.ModelSerializer):
944-
"""Light serializer for email templates in dropdowns and lists."""
951+
class MessageTemplateLightSerializer(serializers.ModelSerializer):
952+
"""Light serializer for message templates in dropdowns and lists."""
945953

946-
type = IntegerChoicesField(choices_class=models.TemplateTypeChoices)
954+
kind = IntegerChoicesField(choices_class=models.MessageTemplateKindChoices)
947955

948956
class Meta:
949-
model = models.EmailTemplate
950-
fields = ["id", "name", "type", "is_active"]
957+
model = models.MessageTemplate
958+
fields = ["id", "name", "kind", "is_active"]
951959
read_only_fields = fields

src/backend/core/api/viewsets/__init__.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99
import rest_framework as drf
1010
from rest_framework import viewsets
1111

12-
1312
logger = logging.getLogger(__name__)
1413

1514
ITEM_FOLDER = "item"

src/backend/core/api/viewsets/email_template.py renamed to src/backend/core/api/viewsets/message_template.py

Lines changed: 32 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,44 +1,43 @@
1-
"""API ViewSet for email templates."""
1+
"""API ViewSet for message templates."""
22

3-
from django.shortcuts import get_object_or_404
43

54
from drf_spectacular.utils import OpenApiResponse, extend_schema
65
from rest_framework import status, viewsets
76
from rest_framework.decorators import action
87
from rest_framework.response import Response
98

109
from core.api import permissions
11-
from core.api.serializers import EmailTemplateSerializer
12-
from core.models import EmailTemplate, Mailbox, MailDomain, TemplateTypeChoices
10+
from core.api.serializers import MessageTemplateSerializer
11+
from core.models import Mailbox, MessageTemplate
1312

1413

15-
@extend_schema(tags=["email-templates"])
16-
class EmailTemplateViewSet(viewsets.ModelViewSet): # pylint: disable=too-many-ancestors
14+
@extend_schema(tags=["message-templates"])
15+
class MessageTemplateViewSet(viewsets.ModelViewSet): # pylint: disable=too-many-ancestors
1716
"""
18-
ViewSet for managing email templates and signatures.
17+
ViewSet for managing message templates and signatures.
1918
2019
This ViewSet provides endpoints for creating, reading, updating, and deleting
21-
email templates and signatures. Templates can be used for replies, forwards,
20+
message templates and signatures. Templates can be used for replies, forwards,
2221
new messages, auto-replies, and signatures.
2322
2423
Filtering:
25-
- type: Filter by template type (reply, forward, new_message, auto_reply, signature)
24+
- kind: Filter by template kind (reply, forward, new_message, auto_reply, signature)
2625
- is_active: Filter by active status (true/false)
2726
- mailbox: Filter by mailbox UUID
2827
- maildomain: Filter by mail domain UUID
2928
- is_default: Filter by default status (true/false) - works with mailbox or maildomain
3029
"""
3130

3231
permission_classes = [permissions.IsAuthenticated]
33-
serializer_class = EmailTemplateSerializer
32+
serializer_class = MessageTemplateSerializer
3433
filterset_fields = [
35-
"type",
34+
"kind",
3635
"is_active",
3736
]
3837

3938
ordering_fields = [
4039
"name",
41-
"type",
40+
"kind",
4241
"created_at",
4342
"updated_at",
4443
]
@@ -49,11 +48,13 @@ def get_queryset(self):
4948
user = self.request.user
5049

5150
if user.is_superuser:
52-
queryset = EmailTemplate.objects.all()
51+
queryset = MessageTemplate.objects.all()
5352
else:
5453
# Filter by mailboxes the user has access to
5554
accessible_mailboxes = Mailbox.objects.filter(accesses__user=user)
56-
queryset = EmailTemplate.objects.filter(mailboxes__in=accessible_mailboxes)
55+
queryset = MessageTemplate.objects.filter(
56+
mailboxes__in=accessible_mailboxes
57+
)
5758

5859
# Apply custom filters
5960
mailbox_id = self.request.query_params.get("mailbox")
@@ -64,19 +65,22 @@ def get_queryset(self):
6465
queryset = queryset.filter(template_mailboxes__mailbox_id=mailbox_id)
6566
if is_default is not None:
6667
is_default_bool = is_default.lower() in ("true", "1", "yes")
67-
queryset = queryset.filter(template_mailboxes__is_default=is_default_bool)
68+
queryset = queryset.filter(
69+
template_mailboxes__is_default=is_default_bool
70+
)
6871

6972
if maildomain_id:
70-
queryset = queryset.filter(template_maildomains__maildomain_id=maildomain_id)
73+
queryset = queryset.filter(
74+
template_maildomains__maildomain_id=maildomain_id
75+
)
7176
if is_default is not None:
7277
is_default_bool = is_default.lower() in ("true", "1", "yes")
73-
queryset = queryset.filter(template_maildomains__is_default=is_default_bool)
78+
queryset = queryset.filter(
79+
template_maildomains__is_default=is_default_bool
80+
)
7481

7582
return queryset.distinct()
7683

77-
78-
79-
8084
@extend_schema(
8185
responses={
8286
200: OpenApiResponse(
@@ -95,11 +99,15 @@ def get_queryset(self):
9599
)
96100
@action(detail=True, methods=["get"], url_path="render")
97101
def render_template(self, request, pk=None): # pylint: disable=unused-argument
98-
"""Render a template with the provided context."""
102+
"""Render a template with the provided mailbox uuid."""
99103
template = self.get_object()
100-
# context -> mailbox uuid (+ fullname of user identified)
101-
context = request.query_params.get("context", {})
102-
104+
mailbox_id = request.query_params.get("mailbox_id", None)
105+
user = request.user
106+
mailbox = Mailbox.objects.get(id=mailbox_id)
107+
context = {
108+
"mailbox": str(mailbox),
109+
"fullname": user.get_full_name(),
110+
}
103111
try:
104112
rendered = template.render_template(context)
105113
return Response(rendered)

src/backend/core/enums.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -79,8 +79,8 @@ class UserAbilityChoices(models.TextChoices):
7979
CAN_CREATE_MAILDOMAINS = "create_maildomains", "Can create maildomains"
8080

8181

82-
class TemplateTypeChoices(models.IntegerChoices):
83-
"""Defines the possible types of email templates."""
82+
class MessageTemplateKindChoices(models.IntegerChoices):
83+
"""Defines the possible kinds of message templates."""
8484

8585
REPLY = 1, "reply"
8686
FORWARD = 2, "forward"

0 commit comments

Comments
 (0)