diff --git a/scipost_django/mails/admin.py b/scipost_django/mails/admin.py index 35e802ed9e2a8bed03dbae9a961fb0cc7374adc5..cff465bbc494e7d7132cab7815207edd8bfbc804 100644 --- a/scipost_django/mails/admin.py +++ b/scipost_django/mails/admin.py @@ -7,16 +7,12 @@ from django.contrib import admin from .models import MailLog, MailLogRelation -@admin.action( - description="Render and send email" -) +@admin.action(description="Render and send email") def send_email(modeladmin, request, queryset): for mail_id in queryset.values_list("id", flat=True): call_command("send_mails", id=mail_id) - - class MailLogRelationInline(admin.TabularInline): model = MailLogRelation @@ -26,8 +22,13 @@ class MailLogAdmin(admin.ModelAdmin): list_display = ["__str__", "to_recipients", "created", "status"] list_filter = ["status"] readonly_fields = ["created", "latest_activity"] - search_fields = ["to_recipients", "bcc_recipients", "from_email", "subject", "body"] + search_fields = [ + "to_recipients", + "cc_recipients", + "bcc_recipients", + "from_email", + "subject", + "body", + ] inlines = [MailLogRelationInline] actions = [send_email] - - diff --git a/scipost_django/mails/backends/filebased.py b/scipost_django/mails/backends/filebased.py index 1a40f8a2e1033d90dd418e261b048a4751a881d8..461ebf644ab71e4eaa6ce948328360f17b6440c4 100644 --- a/scipost_django/mails/backends/filebased.py +++ b/scipost_django/mails/backends/filebased.py @@ -8,8 +8,10 @@ from ..models import MailLog, MailLogRelation class EmailBackend(FileBackend): def write_message(self, message): + cc_str = ", ".join(message.cc).encode() bcc_str = ", ".join(message.bcc).encode() self.stream.write(b"Extended Mail FileBasedBackend\n\n") + self.stream.write(b"Cc: " + cc_str + b"\n") self.stream.write(b"Bcc: " + bcc_str + b"\n") super().write_message(message) @@ -41,6 +43,9 @@ class ModelEmailBackend(FileBackend): to_recipients = [ sanitize_address(addr, encoding) for addr in email_message.to if addr ] + cc_recipients = [ + sanitize_address(addr, encoding) for addr in email_message.cc if addr + ] bcc_recipients = [ sanitize_address(addr, encoding) for addr in email_message.bcc if addr ] @@ -68,6 +73,7 @@ class ModelEmailBackend(FileBackend): subject=subject, body_html=body_html, to_recipients=to_recipients, + cc_recipients=cc_recipients, bcc_recipients=bcc_recipients, from_email=from_email, status=status, diff --git a/scipost_django/mails/core.py b/scipost_django/mails/core.py index 3c205f2d04a765a24776648d5a77ad5a00af5000..7ece3c069f7f3b106cc0f6ba36753dda832c9f18 100644 --- a/scipost_django/mails/core.py +++ b/scipost_django/mails/core.py @@ -28,9 +28,10 @@ class MailEngine: "subject", "from_email", "from_name", + "cc", "bcc", ] - _email_fields = ["recipient_list", "from_email", "bcc"] + _email_fields = ["recipient_list", "from_email", "cc", "bcc"] _processed_template = False _mail_sent = False @@ -39,6 +40,7 @@ class MailEngine: mail_code, subject="", recipient_list=[], + cc=[], bcc=[], from_email="", from_name="", @@ -55,12 +57,14 @@ class MailEngine: The following arguments overwrite the default values, set in the configuration files: -- subject (str, optional) -- recipient_list (str, optional): List of email addresses or db-relations. + -- cc (str, optional): List of email addresses or db-relations. -- bcc (str, optional): List of email addresses or db-relations. -- from_email (str, optional): Plain email address. -- from_name (str, optional): Display name for from address. """ self.mail_code = mail_code self.extra_config = { + "cc": cc, "bcc": bcc, "subject": subject, "from_name": from_name, @@ -133,6 +137,7 @@ class MailEngine: self.mail_data.get("from_email", "noreply@%s" % get_current_domain()), ), self.mail_data["recipient_list"], + cc=self.mail_data["cc"], bcc=self.mail_data["bcc"], reply_to=[ self.mail_data.get("from_email", "noreply@%s" % get_current_domain()) @@ -244,7 +249,7 @@ class MailEngine: "key": email_key, } ) - for email_key in ["recipient_list", "bcc"]: + for email_key in ["recipient_list", "cc", "bcc"]: if email_key in self.mail_data and self.mail_data[email_key]: if not isinstance(self.mail_data[email_key], list): raise ConfigurationError( diff --git a/scipost_django/mails/factories.py b/scipost_django/mails/factories.py index 974dfa4cebb922683fbf2c4a9eb632e3538f93aa..4aa542fb0c7c2683cea7127692247f6d345d87fa 100644 --- a/scipost_django/mails/factories.py +++ b/scipost_django/mails/factories.py @@ -22,6 +22,7 @@ class MailLogFactory(factory.django.DjangoModelFactory): mail_code = factory.Faker("slug") subject = factory.Faker("word") to_recipients = factory.List([factory.Faker("ascii_safe_email") for _ in range(2)]) + cc_recipients = factory.List([factory.Faker("ascii_safe_email") for _ in range(2)]) bcc_recipients = factory.List([factory.Faker("ascii_safe_email") for _ in range(2)]) class Meta: diff --git a/scipost_django/mails/forms.py b/scipost_django/mails/forms.py index af1ad3656ee46d62e731c93b587e3caae50782b3..af8ef8f9ecb2018c81d08dd893b3dce6fb247f30 100644 --- a/scipost_django/mails/forms.py +++ b/scipost_django/mails/forms.py @@ -19,7 +19,10 @@ class EmailForm(forms.Form): subject = forms.CharField(max_length=255, label="Subject*") text = forms.CharField(widget=SummernoteEditor, label="Text*") - mail_field = MultiEmailField(label="Optional: bcc this email to", required=False) + cc_mail_field = MultiEmailField(label="Optional: cc this email to", required=False) + bcc_mail_field = MultiEmailField( + label="Optional: bcc this email to", required=False + ) prefix = "mail_form" extra_config = {} @@ -46,7 +49,10 @@ class EmailForm(forms.Form): data = { "%s-subject" % self.prefix: data.get("%s-subject" % self.prefix), "%s-text" % self.prefix: data.get("%s-text" % self.prefix), - "%s-mail_field" % self.prefix: data.get("%s-mail_field" % self.prefix), + "%s-cc_mail_field" + % self.prefix: data.get("%s-cc_mail_field" % self.prefix), + "%s-bcc_mail_field" + % self.prefix: data.get("%s-bcc_mail_field" % self.prefix), } else: # Reset to prevent having a false-bound form. @@ -75,7 +81,9 @@ class EmailForm(forms.Form): def save(self): self.engine.render_template(self.cleaned_data["text"]) self.engine.mail_data["subject"] = self.cleaned_data["subject"] - if bcc_mail_str := self.cleaned_data["mail_field"]: + if cc_mail_str := self.cleaned_data["cc_mail_field"]: + self.engine.mail_data["cc"] += [m.strip() for m in cc_mail_str.split(",")] + if bcc_mail_str := self.cleaned_data["bcc_mail_field"]: self.engine.mail_data["bcc"] += [m.strip() for m in bcc_mail_str.split(",")] self.engine.send_mail() return self.engine.template_variables["object"] diff --git a/scipost_django/mails/management/commands/send_mails.py b/scipost_django/mails/management/commands/send_mails.py index 80683051850bc1b59652055ba87ed4e031c911ec..c09c1985273984abae4fcac666130d0dd5358fc6 100644 --- a/scipost_django/mails/management/commands/send_mails.py +++ b/scipost_django/mails/management/commands/send_mails.py @@ -55,6 +55,7 @@ class Command(BaseCommand): db_mail.body, db_mail.from_email, db_mail.to_recipients, + cc=db_mail.cc_recipients, bcc=db_mail.bcc_recipients, reply_to=(db_mail.from_email,), connection=connection, diff --git a/scipost_django/mails/migrations/0008_maillog_cc_recipients.py b/scipost_django/mails/migrations/0008_maillog_cc_recipients.py new file mode 100644 index 0000000000000000000000000000000000000000..f41542c6599a2a9467506d795a2b4e20d135a074 --- /dev/null +++ b/scipost_django/mails/migrations/0008_maillog_cc_recipients.py @@ -0,0 +1,24 @@ +# Generated by Django 4.2.10 on 2024-03-26 16:59 + +import django.contrib.postgres.fields +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("mails", "0007_alter_maillogrelation_value"), + ] + + operations = [ + migrations.AddField( + model_name="maillog", + name="cc_recipients", + field=django.contrib.postgres.fields.ArrayField( + base_field=models.EmailField(max_length=254), + blank=True, + null=True, + size=None, + ), + ), + ] diff --git a/scipost_django/mails/models.py b/scipost_django/mails/models.py index 72c78a193cb2d6f3968efec7e080016853ecd511..4a2004b4bfef2299aba25f22240d4785c8743146 100644 --- a/scipost_django/mails/models.py +++ b/scipost_django/mails/models.py @@ -37,6 +37,7 @@ class MailLog(models.Model): body_html = models.TextField(blank=True) to_recipients = ArrayField(models.EmailField(), blank=True, null=True) + cc_recipients = ArrayField(models.EmailField(), blank=True, null=True) bcc_recipients = ArrayField(models.EmailField(), blank=True, null=True) from_email = models.CharField(max_length=254, blank=True) @@ -51,7 +52,11 @@ class MailLog(models.Model): return "{id}. {subject} ({count} recipients)".format( id=self.id, subject=self.subject[:30], - count=len(self.to_recipients) + len(self.bcc_recipients), + count=( + len(self.to_recipients) + + len(self.bcc_recipients) + + (len(self.cc_recipients) if self.cc_recipients else 0) + ), ) def get_full_context(self): diff --git a/scipost_django/mails/tests/test_model_based_backend.py b/scipost_django/mails/tests/test_model_based_backend.py index 2bbb51f67f743c6df267ec8a6347c4507188fc0a..1ace4af5d95d4cb5c39b1ec2f032f060bfefb91e 100644 --- a/scipost_django/mails/tests/test_model_based_backend.py +++ b/scipost_django/mails/tests/test_model_based_backend.py @@ -22,6 +22,7 @@ class ModelEmailBackendTests(TestCase): "tests/test_mail_code_1", subject="Test Subject Unique For Testing 93872", recipient_list=["test1@scipost.org"], + cc=["testcc@scipost.org"], bcc=["test2@scipost.org"], from_email="test3@scipost.org", from_name="Test Name", @@ -39,6 +40,7 @@ class ModelEmailBackendTests(TestCase): self.assertEqual(mail_log.body, "") self.assertEqual(mail_log.body_html, "") self.assertIn("test1@scipost.org", mail_log.to_recipients) + self.assertIn("testcc@scipost.org", mail_log.cc_recipients) self.assertIn("test2@scipost.org", mail_log.bcc_recipients) self.assertEqual("Test Name <test3@scipost.org>", mail_log.from_email)