From e800d44e23a3b4e4c483e20bc793fd1ebe3c43e0 Mon Sep 17 00:00:00 2001 From: mtyton Date: Thu, 22 Jun 2023 23:44:22 +0200 Subject: [PATCH] maling system is now enabled for orders --- artel/artel/settings/base.py | 2 +- artel/mailings/admin.py | 76 ++++++++++++++- artel/mailings/migrations/0001_initial.py | 4 +- artel/mailings/models.py | 16 ++-- artel/mailings/tests/__init__.py | 0 artel/mailings/tests/factories.py | 10 ++ .../{tests.py => tests/test_models.py} | 20 ++-- .../0006_remove_orderdocument_sent.py | 16 ++++ artel/store/models.py | 93 ++++++++++++------- artel/store/tests/test_models.py | 27 ++++-- 10 files changed, 202 insertions(+), 62 deletions(-) create mode 100644 artel/mailings/tests/__init__.py create mode 100644 artel/mailings/tests/factories.py rename artel/mailings/{tests.py => tests/test_models.py} (81%) create mode 100644 artel/store/migrations/0006_remove_orderdocument_sent.py diff --git a/artel/artel/settings/base.py b/artel/artel/settings/base.py index 0b62259..755a912 100644 --- a/artel/artel/settings/base.py +++ b/artel/artel/settings/base.py @@ -199,4 +199,4 @@ EMAIL_HOST_USER = os.environ.get('EMAIL_HOST_USER', '') EMAIL_HOST_PASSWORD = os.environ.get("EMAIL_HOST_PASSWORD", '') EMAIL_PORT = os.environ.get('EMAIL_PORT', 587) EMAIL_USE_TLS = True -DEFAULT_FROM_EMAIL = os.environ.get('DEFAULT_FROM_EMAIL', 'mtyton@tepewu.pl') +DEFAULT_FROM_EMAIL = os.environ.get('DEFAULT_FROM_EMAIL', 'artel-sklep@tepewu.pl') diff --git a/artel/mailings/admin.py b/artel/mailings/admin.py index 8c38f3f..4440840 100644 --- a/artel/mailings/admin.py +++ b/artel/mailings/admin.py @@ -1,3 +1,75 @@ -from django.contrib import admin +from django.forms import fields -# Register your models here. +from wagtail.contrib.modeladmin.options import ( + ModelAdmin, + ModelAdminGroup, + modeladmin_register +) + +from mailings import models + + +class MailTemplateAdmin(ModelAdmin): + model = models.MailTemplate + menu_label = "Mail templates" + menu_icon = 'mail' + menu_order = 100 + add_to_settings_menu = False + exclude_from_explorer = False + list_display = ( + "template_name", + ) + search_fields = ( + "template_name", + ) + list_filter = ( + "template_name", + ) + form_fields = ( + "template_name", + "template", + ) + + +class OutgoingMailAdmin(ModelAdmin): + model = models.OutgoingEmail + menu_label = "Outgoing mails" + menu_icon = 'mail' + menu_order = 100 + add_to_settings_menu = False + exclude_from_explorer = False + list_display = ( + "subject", + "to", + "sent", + ) + search_fields = ( + "subject", + "to", + ) + list_filter = ( + "subject", + "sender", + "recipient", + "template__template_name", + "sent", + ) + readonly_fields = ( + "subject", + "sender", + "recipient", + "sent" + ) + + +class MailingGroup(ModelAdminGroup): + menu_label = "Mailings" + menu_icon = 'mail' + menu_order = 200 + items = ( + MailTemplateAdmin, + OutgoingMailAdmin + ) + + +modeladmin_register(MailingGroup) diff --git a/artel/mailings/migrations/0001_initial.py b/artel/mailings/migrations/0001_initial.py index 3b6fdfc..a3de859 100644 --- a/artel/mailings/migrations/0001_initial.py +++ b/artel/mailings/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 4.1.9 on 2023-06-21 18:32 +# Generated by Django 4.1.9 on 2023-06-22 14:09 from django.db import migrations, models import django.db.models.deletion @@ -16,13 +16,13 @@ class Migration(migrations.Migration): ("id", models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")), ("template_name", models.CharField(max_length=255, unique=True)), ("template", models.FileField(upload_to="mail_templates")), - ("subject", models.CharField(max_length=255)), ], ), migrations.CreateModel( name="OutgoingEmail", fields=[ ("id", models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")), + ("subject", models.CharField(max_length=255)), ("sender", models.EmailField(max_length=254)), ("recipient", models.EmailField(max_length=254)), ("sent", models.BooleanField(default=False)), diff --git a/artel/mailings/models.py b/artel/mailings/models.py index dd5d119..8612435 100644 --- a/artel/mailings/models.py +++ b/artel/mailings/models.py @@ -22,15 +22,16 @@ def send_mail( to: list[str], attachments: list[Attachment], subject: str, - body: str, + content: str, sender_email: str = settings.DEFAULT_FROM_EMAIL ): message = EmailMessage( subject=subject, - body=body, + body=content, from_email=sender_email, to=to ) + message.content_subtype = 'html' for attachment in attachments: message.attach(attachment.name, attachment.content, attachment.contenttype) return bool(message.send()) @@ -42,7 +43,6 @@ class MailTemplate(models.Model): template = models.FileField( upload_to="mail_templates" ) - subject = models.CharField(max_length=255) def delete(self, *args, **kwargs): # delete file @@ -66,17 +66,20 @@ class MailTemplate(models.Model): class OutgoingEmailManager(models.Manager): def send( - self, template_name: str, + self, template_name: str, subject: str, recipient: str, context: dict | Context, sender:str, attachments: list[Attachment] = None ): template = MailTemplate.objects.get(template_name=template_name) - outgoing_email = self.create(template=template, recipient=recipient) + outgoing_email = self.create( + template=template, recipient=recipient, subject=subject, + sender=sender + ) attachments = attachments or [] # send email sent = send_mail( to=[recipient], sender_email=sender, - subject=template.subject, content=template.load_and_process_template(context), + subject=subject, content=template.load_and_process_template(context), attachments=attachments ) outgoing_email.sent = sent @@ -85,6 +88,7 @@ class OutgoingEmailManager(models.Manager): class OutgoingEmail(models.Model): + subject = models.CharField(max_length=255) template = models.ForeignKey(MailTemplate, on_delete=models.CASCADE) sender = models.EmailField() recipient = models.EmailField() diff --git a/artel/mailings/tests/__init__.py b/artel/mailings/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/artel/mailings/tests/factories.py b/artel/mailings/tests/factories.py new file mode 100644 index 0000000..14212c9 --- /dev/null +++ b/artel/mailings/tests/factories.py @@ -0,0 +1,10 @@ +from factory.django import DjangoModelFactory +from factory import Faker + + +class MailTemplateFactory(DjangoModelFactory): + class Meta: + model = "mailings.MailTemplate" + + template_name = Faker("name") + template = Faker("file_name", extension="html") diff --git a/artel/mailings/tests.py b/artel/mailings/tests/test_models.py similarity index 81% rename from artel/mailings/tests.py rename to artel/mailings/tests/test_models.py index 02bbbc3..3d98f03 100644 --- a/artel/mailings/tests.py +++ b/artel/mailings/tests/test_models.py @@ -1,5 +1,6 @@ from django.test import TestCase from django.core.files.uploadedfile import SimpleUploadedFile +from django.core import mail from mailings.models import ( MailTemplate, @@ -15,8 +16,7 @@ class TestMailTemplate(TestCase): template_name="test_template", template=SimpleUploadedFile( "test_template.html", b"{{test_var}}" - ), - subject="Test subject", + ) ) def test_load_and_process_template_success(self): @@ -43,22 +43,24 @@ class TestOutgoingEmail(TestCase): template_name="test_template", template=SimpleUploadedFile( "test_template.html", b"{{test_var}}" - ), - subject="Test subject", + ) ) def test_send_success(self): - mail = OutgoingEmail.objects.send( + email = OutgoingEmail.objects.send( template_name="test_template", recipient="test@stardust.io", context={}, - sender="sklep-test@stardust.io" + sender="sklep-test@stardust.io", + subject="Test subject" ) - self.assertEqual(mail.sent, True) - # TODO outbox + self.assertEqual(email.sent, True) + self.assertEqual(mail.outbox[0].subject, "Test subject") def test_send_missing_template_failure(self): with self.assertRaises(MailTemplate.DoesNotExist): OutgoingEmail.objects.send( template_name="missing_template", - recipient="", sender="", context={}\ + recipient="", sender="", context={}, + subject="Test subject" ) + self.assertEqual(len(mail.outbox), 0) diff --git a/artel/store/migrations/0006_remove_orderdocument_sent.py b/artel/store/migrations/0006_remove_orderdocument_sent.py new file mode 100644 index 0000000..65b8771 --- /dev/null +++ b/artel/store/migrations/0006_remove_orderdocument_sent.py @@ -0,0 +1,16 @@ +# Generated by Django 4.1.9 on 2023-06-22 16:40 + +from django.db import migrations + + +class Migration(migrations.Migration): + dependencies = [ + ("store", "0005_order_order_number"), + ] + + operations = [ + migrations.RemoveField( + model_name="orderdocument", + name="sent", + ), + ] diff --git a/artel/store/models.py b/artel/store/models.py index a8c1af2..1f29204 100644 --- a/artel/store/models.py +++ b/artel/store/models.py @@ -31,6 +31,10 @@ from store.utils import ( notify_user_about_order, notify_manufacturer_about_order ) +from mailings.models import ( + OutgoingEmail, + Attachment +) class PersonalData(models.Model): @@ -233,6 +237,44 @@ class OrderManager(models.Manager): year = datetime.datetime.now().year return f"{author.id}/{number_of_prev_orders:06}/{year}" + def _send_notifications( + self, order: models.Model, author: ProductAuthor, + customer_data: dict[str, Any], docs: list[models.Model] + ): + # for user + attachments = [ + Attachment( + content=doc.generate_document({"customer_data": customer_data}), + contenttype="application/pdf", + name=f"{doc.template.doc_type}_{order.order_number}.pdf" + ) for doc in docs + ] + mail_subject = f"Wygenerowano umowę numer {order.order_number} z dnia {order.created_at.strftime('%d.%m.%Y')}" + user_mail = OutgoingEmail.objects.send( + recipient=customer_data["email"], + subject=mail_subject, + context = { + "docs": docs, + "order_number": order.order_number, + "customer_email": customer_data["email"], + }, sender=settings.DEFAULT_FROM_EMAIL, + template_name="order_created_user", + attachments=attachments + ) + # for author + author_mail = OutgoingEmail.objects.send( + recipient=author.email, + subject=mail_subject, + context = { + "docs": docs, + "order_number": order.order_number, + "manufacturer_email": author.email, + }, sender=settings.DEFAULT_FROM_EMAIL, + template_name="order_created_author", + attachments=attachments + ) + return user_mail is not None and author_mail is not None + def create_from_cart( self, cart_items: list[dict[str, str|dict]], payment_method: models.Model| None, @@ -242,8 +284,9 @@ class OrderManager(models.Manager): orders_pks = [] payment_method = payment_method or PaymentMethod.objects.first() - agreement_template = DocumentTemplate.objects.get(doc_type=DocumentTypeChoices.AGREEMENT) - receipt_template = DocumentTemplate.objects.get(doc_type=DocumentTypeChoices.RECEIPT) + doc_templates = DocumentTemplate.objects.filter( + doc_type__in=[DocumentTypeChoices.AGREEMENT, DocumentTypeChoices.RECEIPT] + ) for item in cart_items: author = item["author"] @@ -255,39 +298,18 @@ class OrderManager(models.Manager): ) OrderProduct.objects.create_from_cart(author_products, order) orders_pks.append(order.pk) - agreement = OrderDocument.objects.create( - order=order, - template=agreement_template - ) - receipt = OrderDocument.objects.create( - order=order, - template=receipt_template - ) - extra_document_kwargs = { - "customer_data": customer_data - } - default_kwargs ={ - "docs": [ - agreement.generate_document(extra_document_kwargs), - receipt.generate_document(extra_document_kwargs) - ], - "order_number": order.order_number - } - user_kwargs = { - "customer_email": customer_data["email"], - } - user_kwargs.update(default_kwargs) - user_notified = notify_user_about_order(**user_kwargs) - manufacturer_kwargs = { - "manufacturer_email": author.email, - } - manufacturer_kwargs.update(default_kwargs) - manufacturer_notified = notify_manufacturer_about_order(**manufacturer_kwargs) - sent = user_notified and manufacturer_notified - agreement.sent = sent - receipt.sent = sent - agreement.save() - receipt.save() + docs = [] + for template in doc_templates: + doc = OrderDocument.objects.create( + order=order, template=template + ) + docs.append(doc) + sent = self._send_notifications(order, author, customer_data, docs) + + if not sent: + # TODO - store data temporarily + raise Exception("Error while sending emails") + return Order.objects.filter(pk__in=orders_pks) @@ -346,7 +368,6 @@ class DocumentTemplate(models.Model): class OrderDocument(models.Model): order = models.ForeignKey(Order, on_delete=models.CASCADE, related_name="documents") template = models.ForeignKey(DocumentTemplate, on_delete=models.CASCADE) - sent = models.BooleanField(default=False) def get_document_context(self): _context = { diff --git a/artel/store/tests/test_models.py b/artel/store/tests/test_models.py index 55aa5d8..e614333 100644 --- a/artel/store/tests/test_models.py +++ b/artel/store/tests/test_models.py @@ -1,9 +1,12 @@ + +from unittest.mock import patch from django.test import TestCase from django.urls import reverse from django.core import mail from store.tests import factories from store import models as store_models +from mailings.tests.factories import MailTemplateFactory # TODO - this is fine for now, but we'll want to use factoryboy for this: @@ -72,8 +75,11 @@ class OrderTestCase(TestCase): self.payment_method = factories.PaymentMethodFactory() factories.DocumentTemplateFactory() factories.DocumentTemplateFactory(doc_type="receipt") + MailTemplateFactory(template_name="order_created_user") + MailTemplateFactory(template_name="order_created_author") - def test_create_from_cart_success_single_author(self): + @patch("mailings.models.MailTemplate.load_and_process_template", return_value="test") + def test_create_from_cart_success_single_author(self, mocked_load): product = factories.ProductFactory(template__author=self.author, price=100) cart_items = [{ "author": self.author, @@ -86,10 +92,13 @@ class OrderTestCase(TestCase): ) self.assertEqual(orders.count(), 1) self.assertEqual(len(mail.outbox), 2) - self.assertEqual(mail.outbox[0].subject, f"Zamówienie {orders[0].order_number}") + self.assertEqual( + mail.outbox[0].subject, + f"Wygenerowano umowę numer {orders[0].order_number} z dnia {orders[0].created_at.strftime('%d.%m.%Y')}" + ) - - def test_create_from_cart_success_multpile_authors(self): + @patch("mailings.models.MailTemplate.load_and_process_template", return_value="test") + def test_create_from_cart_success_multpile_authors(self, mocked_load): product = factories.ProductFactory(template__author=self.second_author, price=100) cart_items = [ { @@ -107,5 +116,11 @@ class OrderTestCase(TestCase): ) self.assertEqual(orders.count(), 2) self.assertEqual(len(mail.outbox), 4) - self.assertEqual(mail.outbox[0].subject, f"Zamówienie {orders[0].order_number}") - self.assertEqual(mail.outbox[2].subject, f"Zamówienie {orders[1].order_number}") + self.assertEqual( + mail.outbox[0].subject, + f"Wygenerowano umowę numer {orders[0].order_number} z dnia {orders[0].created_at.strftime('%d.%m.%Y')}" + ) + self.assertEqual( + mail.outbox[2].subject, + f"Wygenerowano umowę numer {orders[1].order_number} z dnia {orders[1].created_at.strftime('%d.%m.%Y')}" + )