Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

No signal #16

Draft
wants to merge 7 commits into
base: signals
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 27 additions & 33 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,34 +86,26 @@ 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
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.
Expand All @@ -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
Expand Down Expand Up @@ -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).



77 changes: 68 additions & 9 deletions contrib/handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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()
Expand All @@ -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,
Expand All @@ -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:
Expand All @@ -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
Expand All @@ -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):
Expand Down
14 changes: 12 additions & 2 deletions contrib/notification.py
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -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.

Expand Down
5 changes: 0 additions & 5 deletions management/commands/send_email_notifications.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand All @@ -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,
)
5 changes: 0 additions & 5 deletions management/commands/send_web_notifications.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand All @@ -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,
)
4 changes: 3 additions & 1 deletion migrations/0001_initial.py
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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'],
Expand Down
6 changes: 1 addition & 5 deletions models/email_notification.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down
12 changes: 12 additions & 0 deletions models/notification.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import uuid

from django.db import models
from django.utils.translation import gettext as _

Expand Down Expand Up @@ -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"]
6 changes: 1 addition & 5 deletions models/web_notification.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down
7 changes: 0 additions & 7 deletions signals/__init__.py

This file was deleted.

4 changes: 0 additions & 4 deletions signals/email_notification_sent.py

This file was deleted.

4 changes: 0 additions & 4 deletions signals/web_notification_sent.py

This file was deleted.

4 changes: 4 additions & 0 deletions tests/factories.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand All @@ -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"
2 changes: 1 addition & 1 deletion tests/test_commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
SebCorbin marked this conversation as resolved.
Show resolved Hide resolved
call_command("send_web_notifications")
self.web_notification.refresh_from_db()
# now web notification should be in sent state
Expand Down