diff --git a/backend/notifications/admin/views.py b/backend/notifications/admin/views.py index 707cf83a71..4aea5bbe47 100644 --- a/backend/notifications/admin/views.py +++ b/backend/notifications/admin/views.py @@ -15,4 +15,8 @@ def view_email_template(request, object_id): def view_sent_email(request, object_id): sent_email = cast(SentEmail, SentEmail.objects.get(id=object_id)) - return HttpResponse(sent_email.body) + return HttpResponse( + sent_email.body_file.read().decode("utf-8") + if sent_email.body_file + else sent_email.body + ) diff --git a/backend/notifications/migrations/0022_sentemail_body_file_sentemail_text_body_file.py b/backend/notifications/migrations/0022_sentemail_body_file_sentemail_text_body_file.py new file mode 100644 index 0000000000..bf98ddd7fa --- /dev/null +++ b/backend/notifications/migrations/0022_sentemail_body_file_sentemail_text_body_file.py @@ -0,0 +1,24 @@ +# Generated by Django 5.1.4 on 2025-03-09 22:29 + +import notifications.models +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('notifications', '0021_alter_emailtemplate_identifier'), + ] + + operations = [ + migrations.AddField( + model_name='sentemail', + name='body_file', + field=models.FileField(blank=True, null=True, upload_to=notifications.models.sent_email_body_upload_to, verbose_name='body file'), + ), + migrations.AddField( + model_name='sentemail', + name='text_body_file', + field=models.FileField(blank=True, null=True, upload_to=notifications.models.sent_email_body_upload_to, verbose_name='text body file'), + ), + ] diff --git a/backend/notifications/models.py b/backend/notifications/models.py index 1af21ce945..01abf7079b 100644 --- a/backend/notifications/models.py +++ b/backend/notifications/models.py @@ -8,6 +8,7 @@ from model_utils.models import TimeStampedModel from django.utils.translation import gettext_lazy as _ from django.conf import settings +from django.core.files.base import ContentFile BASE_PLACEHOLDERS = ["conference"] @@ -253,12 +254,20 @@ def send_email( placeholders=placeholders, subject=processed_email_template.subject, preview_text=processed_email_template.preview_text, - body=processed_email_template.html_body, - text_body=processed_email_template.text_body, + body="", + text_body="", reply_to=self.reply_to, cc_addresses=self.cc_addresses, bcc_addresses=self.bcc_addresses, ) + sent_email.body_file.save( + "body.html", + ContentFile(processed_email_template.html_body.encode("utf-8")), + ) + sent_email.text_body_file.save( + "text_body.txt", + ContentFile(processed_email_template.text_body.encode("utf-8")), + ) transaction.on_commit(lambda: send_pending_email.delay(sent_email.id)) @@ -290,6 +299,10 @@ class Meta: ] +def sent_email_body_upload_to(instance, filename): + return f"sent_emails/{instance.id}/{filename}" + + class SentEmail(TimeStampedModel): class Status(models.TextChoices): pending = "pending", _("Pending") @@ -333,7 +346,13 @@ class Status(models.TextChoices): subject = models.TextField(_("subject")) body = models.TextField(_("body")) + body_file = models.FileField( + _("body file"), upload_to=sent_email_body_upload_to, blank=True, null=True + ) text_body = models.TextField(_("text body")) + text_body_file = models.FileField( + _("text body file"), upload_to=sent_email_body_upload_to, blank=True, null=True + ) preview_text = models.TextField(_("preview text"), blank=True) from_email = models.EmailField(_("from email")) diff --git a/backend/notifications/tasks.py b/backend/notifications/tasks.py index 347c5aba54..88f5be319d 100644 --- a/backend/notifications/tasks.py +++ b/backend/notifications/tasks.py @@ -63,9 +63,19 @@ def send_pending_email(self, sent_email_id: int): def send_email(sent_email, email_backend_connection): logger.info(f"Sending sent_email_id={sent_email.id}") + if sent_email.body_file: + html_body = sent_email.body_file.read().decode("utf-8") + else: + html_body = sent_email.body + + if sent_email.text_body_file: + text_body = sent_email.text_body_file.read().decode("utf-8") + else: + text_body = sent_email.text_body + email_message = EmailMultiAlternatives( subject=sent_email.subject, - body=sent_email.text_body, + body=text_body, from_email=sent_email.from_email, to=[sent_email.recipient_email], cc=sent_email.cc_addresses, @@ -73,6 +83,6 @@ def send_email(sent_email, email_backend_connection): reply_to=[sent_email.reply_to], connection=email_backend_connection, ) - email_message.attach_alternative(sent_email.body, "text/html") + email_message.attach_alternative(html_body, "text/html") email_message.send() return email_message.extra_headers.get("message_id", f"local-{uuid4()}") diff --git a/backend/notifications/tests/test_models.py b/backend/notifications/tests/test_models.py index cb210f21bb..6233dd6945 100644 --- a/backend/notifications/tests/test_models.py +++ b/backend/notifications/tests/test_models.py @@ -74,7 +74,7 @@ def test_send_email_template_to_recipient_email( assert sent_email.recipient_email == "example@example.com" assert sent_email.subject == "Subject abc" - assert "Body abc" in sent_email.body + assert "Body abc" in sent_email.body_file.read().decode("utf-8") assert sent_email.preview_text == "Preview abc" assert sent_email.reply_to == "replyto@example.com" @@ -102,7 +102,8 @@ def test_send_email_template_to_recipient_user(): assert sent_email.recipient_email == user.email assert sent_email.subject == "Subject abc" - assert "Body abc" in sent_email.body + assert "Body abc" in sent_email.body_file.read().decode("utf-8") + assert "Body abc" in sent_email.text_body_file.read().decode("utf-8") assert sent_email.preview_text == "Preview abc" assert sent_email.reply_to == "replyto@example.com" @@ -137,7 +138,7 @@ def test_send_system_template_email(settings): assert sent_email.from_email == "example@example.com" assert sent_email.subject == "Subject abc" - assert "Body abc" in sent_email.body + assert "Body abc" in sent_email.body_file.read().decode("utf-8") assert sent_email.preview_text == "Preview abc" assert sent_email.reply_to == "replyto@example.com" diff --git a/backend/pycon/storages.py b/backend/pycon/storages.py index e76000a7c2..6a64851464 100644 --- a/backend/pycon/storages.py +++ b/backend/pycon/storages.py @@ -6,6 +6,11 @@ from django.core.files.storage.memory import InMemoryStorage from django.core.files.storage import FileSystemStorage from django.urls import reverse +from django.core.files.storage import storages + + +def private_storage_getter(): + return storages["private"] @dataclass diff --git a/backend/visa/models.py b/backend/visa/models.py index 9111e25c83..fa3ffe4340 100644 --- a/backend/visa/models.py +++ b/backend/visa/models.py @@ -1,6 +1,7 @@ from functools import cached_property from django.db import transaction +from pycon.storages import private_storage_getter from submissions.models import Submission from users.models import User from grants.models import Grant @@ -10,7 +11,6 @@ from django.db import models from django.db.models import UniqueConstraint, Q from django.utils.translation import gettext_lazy as _ -from django.core.files.storage import storages class InvitationLetterRequestStatus(models.TextChoices): @@ -31,10 +31,6 @@ def invitation_letter_upload_to(instance, filename): return f"invitation_letters/{instance.conference.code}/{instance.id}/{filename}" -def private_storage_getter(): - return storages["private"] - - class InvitationLetterRequest(TimeStampedModel): objects = InvitationLetterRequestQuerySet().as_manager()