diff --git a/README.md b/README.md index 6b5ca29..f255da7 100644 --- a/README.md +++ b/README.md @@ -86,16 +86,16 @@ notification = EmailNotification( html_content=html_content, ) ``` -Use this object to build the EmailMessage and create the EmailNotification : +Use this object to create the EmailNotification : ```python from osis_notification.contrib.handlers import EmailNotificationHandler -# Build the EmailMessage -email_message = EmailNotificationHandler.build(notification) # And finally create the EmailNotification -email_notification = EmailNotificationHandler.create(email_message) +email_notification = EmailNotificationHandler.create(notification=notification) ``` +This mail notification will automatically be sent by the task runner. + #### An example using osis_mail_template ```python @@ -103,17 +103,9 @@ from osis_notification.contrib.handlers import EmailNotificationHandler recipient = Person.objects.get(user__username="jmr") language = recipient.language -tokens = {"username": person.user.username} +tokens = {"username": recipient.user.username} email_message = generate_email(your_mail_template_id, language, tokens, recipients=[recipient]) -email_notification = EmailNotificationHandler.create(email_message) -``` - -Then you have to give the objet to the EmailNotificationHandler : - -```python -from osis_notification.contrib.handlers import EmailNotificationHandler - -EmailNotificationHandler.create(email_notification) +email_notification = EmailNotificationHandler.create(mail=email_message) ``` This mail notification will automatically be send by the task runner. @@ -132,27 +124,31 @@ call_command("send_web_notifications") The commands are calling the `process` function on their respective handlers for each notification that are found in the DB with the "Pending" state. -## Knowing when notifications are sent +## Add pre/post-processing treatment -We use signals to know when the task runner gets its job done. To know when a notification has been sent, just connect to the signals doing so : +You can interface code before and after a notification is processed. To do so, you must define your code inside `pre_process` and `post_process` : ```python -from django.dispatch import receiver -from osis_notification.signals import email_notification_sent, web_notification_sent - -@receiver(email_notification_sent) -def email_notification_has_been_sent(sender, notification, **kwargs): - print( - f"An email notification was sent from signal sender {sender} with the" - f"uuid {notification.uuid} to {notification.person}." - ) +from osis_notification.contrib.notification import EmailNotification, WebNotification + +class YourCustomEmailNotification(EmailNotification): + def pre_process(self): + # your custom actions before sending a notification should live here + print(self.recipient, self.subject, self.plain_text_content, self.html_content) + + def post_process(self): + # your custom actions after sending a notification should live here + print(self.recipient, self.subject, self.plain_text_content, self.html_content) + +# Same thing with a web notification +class YourCustomWebNotification(WebNotification): + def pre_process(self): + # your custom actions before sending a notification should live here + print(self.recipient, self.content) -@receiver(web_notification_sent) -def web_notification_has_been_sent(sender, notification, **kwargs): - print( - f"An web notification was sent from signal sender {sender} with the" - f"uuid {notification.uuid} to {notification.person}." - ) + def post_process(self): + # your custom actions after sending a notification should live here + print(self.recipient, self.content) ``` ## Cleaning notifications @@ -202,5 +198,3 @@ Then you can integrate the component: - `data-url` : API endpoint that returns all the notifications. - `data-interval` : The interval, in second, to fetch the notifications from the server (default to 300). - - diff --git a/contrib/handlers.py b/contrib/handlers.py index 37da9df..be30dba 100644 --- a/contrib/handlers.py +++ b/contrib/handlers.py @@ -41,21 +41,45 @@ def build(notification: EmailNotificationType) -> EmailMessage: @staticmethod def create( - mail: EmailMessage, - person: Optional[Person] = None, + mail: Optional[EmailMessage] = None, + notification: Optional[EmailNotificationType] = None, ) -> EmailNotification: - """Create a email notification from a python object and save it in the database. + """Create a email notification either from a python object or from an + EmailMessage and save it in the database. :param mail: The email message to be send as a notification. - :param person: The recipient of the notification. + :param notification: A python object representing the email notification. :return: The created EmailNotification.""" - if person is None: - person = Person.objects.get(email=mail["To"]) + if mail is None and notification is None: + raise ValueError( + "Please either give an email message or a EmailNotification" + ) + elif mail is None: + # build the mail from python object + mail = EmailNotificationHandler.build(notification) + person = notification.recipient + else: + person = Person.objects.get(user__email=mail["To"]) + plain_text_content = "" + html_content = "" + for part in mail.walk(): + if part.get_content_type() == "text/plain": + plain_text_content = part.get_payload() + elif part.get_content_type() == "text/html": + html_content = part.get_payload() + notification = EmailNotificationType( + recipient=person, + subject=mail['Subject'], + plain_text_content=plain_text_content, + html_content=html_content, + ) return EmailNotification.objects.create( person=person, payload=mail.as_string(), + built_from_module=notification.__module__, + built_from_class_name=notification.__class__.__name__, ) @staticmethod @@ -70,8 +94,10 @@ def process(notification: EmailNotification): notification.person.email, settings.LANGUAGE_CODE, ) - plain_text_content = '' - html_content = '' + plain_text_content = "" + html_content = "" + subject = unescape(strip_tags(email_message.get("subject"))) + for part in email_message.walk(): if part.get_content_type() == "text/plain": plain_text_content = part.get_payload() @@ -84,7 +110,7 @@ def process(notification: EmailNotification): receivers=[receiver], reference=None, connected_user=None, - subject=unescape(strip_tags(email_message.get("subject"))), + subject=subject, message=plain_text_content, html_message=html_content, from_email=settings.DEFAULT_FROM_EMAIL, @@ -94,7 +120,24 @@ def process(notification: EmailNotification): mail_sender.send_mail() notification.state = NotificationStates.SENT_STATE.name notification.sent_at = now() + + # rebuilt the base notification Python object + base_class = import_string( + f"{notification.built_from_module}.{notification.built_from_class_name}" + ) + # Now rebuilt the python object from the class we have + # given the notification object we have from the DB + base_object = base_class( + recipient=notification.person, + subject=subject, + plain_text_content=plain_text_content, + html_content=html_content, + ) + # Then call the pre_process method + base_object.pre_process() notification.save() + # Then call the post_process method + base_object.post_process() class WebNotificationHandler: @@ -108,6 +151,8 @@ def create(notification: WebNotificationType): return WebNotification.objects.create( person=notification.recipient, payload=notification.content, + built_from_module=notification.__module__, + built_from_class_name=notification.__class__.__name__, ) @staticmethod @@ -116,7 +161,21 @@ def process(notification: WebNotification): notification.state = NotificationStates.SENT_STATE.name notification.sent_at = now() + + # rebuilt the base notification Python object + base_class = import_string( + f"{notification.built_from_module}.{notification.built_from_class_name}" + ) + # Now rebuilt the python object from the class we have + # given the notification object we have from the DB + base_object = base_class( + recipient=notification.person, content=notification.payload + ) + # Then call the pre_process method + base_object.pre_process() notification.save() + # Then call the post_process method + base_object.post_process() @staticmethod def toggle_state(notification: WebNotification): diff --git a/contrib/notification.py b/contrib/notification.py index 01a0ccc..eaf8963 100644 --- a/contrib/notification.py +++ b/contrib/notification.py @@ -1,7 +1,17 @@ +import abc + from base.models.person import Person -class EmailNotification(object): +class Notification(abc.ABC): + def pre_process(self): + pass + + def post_process(self): + pass + + +class EmailNotification(Notification): def __init__( self, recipient: Person, @@ -24,7 +34,7 @@ def __init__( self.html_content = html_content -class WebNotification(object): +class WebNotification(Notification): def __init__(self, recipient: Person, content: str): """This class must be implemented in order to use the web notification handlers. diff --git a/management/commands/send_email_notifications.py b/management/commands/send_email_notifications.py index f907eea..789da83 100644 --- a/management/commands/send_email_notifications.py +++ b/management/commands/send_email_notifications.py @@ -2,7 +2,6 @@ from osis_notification.contrib.handlers import EmailNotificationHandler from osis_notification.models import EmailNotification -from osis_notification.signals import email_notification_sent class Command(BaseCommand): @@ -11,7 +10,3 @@ class Command(BaseCommand): def handle(self, *args, **options): for notification in EmailNotification.objects.pending(): EmailNotificationHandler.process(notification) - email_notification_sent.send( - sender=self.__class__, - notification=notification, - ) diff --git a/management/commands/send_web_notifications.py b/management/commands/send_web_notifications.py index e5e21e5..6792b4c 100644 --- a/management/commands/send_web_notifications.py +++ b/management/commands/send_web_notifications.py @@ -2,7 +2,6 @@ from osis_notification.contrib.handlers import WebNotificationHandler from osis_notification.models import WebNotification -from osis_notification.signals import web_notification_sent class Command(BaseCommand): @@ -11,7 +10,3 @@ class Command(BaseCommand): def handle(self, *args, **options): for notification in WebNotification.objects.pending(): WebNotificationHandler.process(notification) - web_notification_sent.send( - sender=self.__class__, - notification=notification, - ) diff --git a/migrations/0001_initial.py b/migrations/0001_initial.py index e29dfd0..0c738f2 100644 --- a/migrations/0001_initial.py +++ b/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 2.2.13 on 2021-06-03 11:08 +# Generated by Django 2.2.13 on 2021-06-30 11:04 from django.db import migrations, models import django.db.models.deletion @@ -26,6 +26,8 @@ class Migration(migrations.Migration): ('sent_at', models.DateTimeField(editable=False, null=True, verbose_name='Sent at')), ('read_at', models.DateTimeField(editable=False, null=True, verbose_name='Read at')), ('person', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='+', to='base.Person')), + ('built_from_class_name', models.TextField(editable=False, help_text='Entry built from this class', null=True)), + ('built_from_module', models.TextField(editable=False, help_text='Entry built from this module', null=True)), ], options={ 'ordering': ['-created_at'], diff --git a/models/email_notification.py b/models/email_notification.py index dd18148..689f668 100644 --- a/models/email_notification.py +++ b/models/email_notification.py @@ -28,11 +28,7 @@ def create(self, **kwargs): :return: The newly created EmailNotification object. """ - return super().create( - type=NotificationTypes.EMAIL_TYPE.name, - person=kwargs.get("person"), - payload=kwargs.get("payload"), - ) + return super().create(type=NotificationTypes.EMAIL_TYPE.name, **kwargs) class EmailNotification(Notification): diff --git a/models/notification.py b/models/notification.py index b2f6922..7c904b5 100644 --- a/models/notification.py +++ b/models/notification.py @@ -1,4 +1,5 @@ import uuid + from django.db import models from django.utils.translation import gettext as _ @@ -31,5 +32,16 @@ class Notification(models.Model): sent_at = models.DateTimeField(verbose_name=_("Sent at"), editable=False, null=True) read_at = models.DateTimeField(verbose_name=_("Read at"), editable=False, null=True) + built_from_module = models.TextField( + help_text=_("Entry built from this module"), + editable=False, + null=True, + ) + built_from_class_name = models.TextField( + help_text=_("Entry built from this class"), + editable=False, + null=True, + ) + class Meta: ordering = ["-created_at"] diff --git a/models/web_notification.py b/models/web_notification.py index aa66a86..7878fdd 100644 --- a/models/web_notification.py +++ b/models/web_notification.py @@ -40,11 +40,7 @@ def create(self, **kwargs): :return: The newly created WebNotification object. """ - return super().create( - type=NotificationTypes.WEB_TYPE.name, - person=kwargs.get("person"), - payload=kwargs.get("payload"), - ) + return super().create(type=NotificationTypes.WEB_TYPE.name, **kwargs) class WebNotification(Notification): diff --git a/signals/__init__.py b/signals/__init__.py deleted file mode 100644 index acf2678..0000000 --- a/signals/__init__.py +++ /dev/null @@ -1,7 +0,0 @@ -from osis_notification.signals.email_notification_sent import email_notification_sent -from osis_notification.signals.web_notification_sent import web_notification_sent - -__all__ = [ - "email_notification_sent", - "web_notification_sent", -] diff --git a/signals/email_notification_sent.py b/signals/email_notification_sent.py deleted file mode 100644 index 0c2cb8f..0000000 --- a/signals/email_notification_sent.py +++ /dev/null @@ -1,4 +0,0 @@ -from django.dispatch import Signal - - -email_notification_sent = Signal(providing_args=["notification"]) diff --git a/signals/web_notification_sent.py b/signals/web_notification_sent.py deleted file mode 100644 index 800e25f..0000000 --- a/signals/web_notification_sent.py +++ /dev/null @@ -1,4 +0,0 @@ -from django.dispatch import Signal - - -web_notification_sent = Signal(providing_args=["notification"]) diff --git a/tests/factories.py b/tests/factories.py index 27331bb..2e99e0c 100644 --- a/tests/factories.py +++ b/tests/factories.py @@ -8,6 +8,8 @@ class Meta: model = "osis_notification.EmailNotification" person = factory.SubFactory(PersonFactory) + built_from_module = "osis_notification.contrib.notification" + built_from_class_name = "EmailNotification" class WebNotificationFactory(factory.DjangoModelFactory): @@ -16,3 +18,5 @@ class Meta: person = factory.SubFactory(PersonFactory) payload = factory.fuzzy.FuzzyText() + built_from_module = "osis_notification.contrib.notification" + built_from_class_name = "WebNotification" diff --git a/tests/test_commands.py b/tests/test_commands.py index fb19cc1..cc1a204 100644 --- a/tests/test_commands.py +++ b/tests/test_commands.py @@ -56,7 +56,7 @@ def test_send_web_notifications(self): self.assertEqual( self.web_notification.state, NotificationStates.PENDING_STATE.name ) - with self.assertNumQueriesLessThan(3): + with self.assertNumQueriesLessThan(4): call_command("send_web_notifications") self.web_notification.refresh_from_db() # now web notification should be in sent state