One of solution asynchronous log. With Rest api and optional PUSH notification to ios/android devices
Our application consist of 2 parts:
-
Notifier
listen changes from models and fill notification model. With Rest API -
Pusher
listen changes fromNotifier
(Notification model) and send push to ios/android device
Requirements
-
Notification
model must be available throughAPI
(list of notifications for concrete user) -
Notification
model have optional case:PUSH
. If yes - send PUSH (ios/android) - Our application must be separate from other applications (is like as service)
- Use only Django signals to fill notification model
- asynchronous
- Different icons, title, body and data in each message
-
Notifier
work only in one direction: listen changes from models -> create notification in model -> (optional) send push
Important
Part 1: Notifier
For example, we want to create notification and send push in case:
- New article was created (
Article
model) - New comment was created (
ArticleComment
model) - User
A
send gift to userB
(Gift
model)
Ok. Let's get a started!
- Firstly, create
notifier
application inside Django project - Create models.py inside it and add:
class Notification(models.Model):
uid = models.UUIDField(default=uuid.uuid4,
editable=True,
unique=True)
name = models.CharField(max_length=250)
body = models.CharField(max_length=250)
recipients = models.ManyToManyField(get_user_model())
is_notify_screen = models.BooleanField(default=True)
is_push = models.BooleanField(default=True)
client_action = models.CharField(choices=ClientAction.choices,
default=ClientAction.NONE,
max_length=250)
icon = models.CharField(blank=True,
max_length=250,
choices=Icon.choices,
default=Icon.INFO)
3.Register models.
Now we want to register models and connect it with post_save
signal.
Our application must be fully separate and work only in one direction: listen changes and create notification.
Inside your app create tasks.py
and add:
models = [
Article,
ArticleComment,
Gift,
]
def register_models():
for model in models:
post_save.connect(_post_save,
sender=model,
dispatch_uid=uuid.uuid4())
def _post_save(sender, instance, **kwargs):
"""
Handler for our post_save.
PostSaveContext is a namedtuple - context from sync post_save to async post_save handler
"""
created = kwargs.get( 'created' )
if not created:
return
class_name = instance.__class__.__name__
action = f'{class_name}_POST_SAVE'.upper()
post_save_context = PostSaveContext( module=instance.__module__,
class_name=class_name,
instance_identifier=instance.pk,
action=action )
transaction.on_commit(
lambda: _async_post_save_handler.delay( post_save_context._asdict() )
)
@app.task
def _async_post_save_handler(post_save_context):
perform_action( post_save_context )
def perform_action(post_save_context):
"""
The core of notifier
"""
module = post_save_context.get( 'module' )
class_name = post_save_context.get( 'class_name' )
instance_identifier = post_save_context.get( 'instance_identifier' )
action = post_save_context.get( 'action' )
module = module.split( '.' )[1]
model = apps.get_model( app_label=module,
model_name=class_name )
instance = model.objects \
.filter( pk=instance_identifier ) \
.first()
if instance is None:
return
Good!.
Now we may listen creation instances from each model in models
and transfer information to _async_post_save_handler
.
The next step:fill our notification model
Because we have different content from models, I think the good solution create context
fabric (ContextFactory
) and pass instance
and action
to it.
4.Create factory.py
file and past to it:
class ContextFactory:
def __init__(self, instance, action, *args, **kwargs):
self.instance = instance
self.action = action
self.__dict__.update(kwargs)
def create_context(self):
if self.action == NotifierAction.ARTICLE_POST_SAVE.value:
recipients = self.instance.recipients.all()
body = self.instance.body
data = Data(name='New article available',
body=body,
recipients=recipients,
client_action=ClientAction.OPEN_ARTICLE,
is_notify_screen=True,
is_push=True,
icon=Icon.MAIL)
return data
if self.action == NotifierAction.ARTICLE_COMMENT_POST_SAVE.value:
recipients = self.instance.article.recipients.all()
data = Data(name='New comment',
body=self.instance.body,
client_action=ClientAction.OPEN_MESSAGE,
recipients=recipients,
is_notify_screen=True,
is_push=True,
icon=Icon.INFO)
return data
if self.action == NotifierAction.GIFT_POST_SAVE.value:
recipients = [self.instance.owner]
data = Data(
name='Gift for you!',
body='You got a new gift!',
recipients=recipients,
client_action=ClientAction.OPEN_GIFTS,
is_notify_screen=True,
is_push=True,
icon=Icon.GIFT
)
return data
Ok. Now we may create notification instance. I like to use Managers
for models.
Yep, many developers like to use services
to create instance or change it.
But for me Managers
is a good place to any model manipulations.
5.Create manager
file and past to it:
class NotificationManager(models.Manager):
def create_notification(self, context: Data):
context_dict = context._asdict()
instance = self.create(**context_dict) # Create notification firstly
if instance:
for recipient in context.recipients:
instance.recipients.add(recipient) # Add User to m2m
class Notification(models.Model):
....
actions = NotificationManager()
6.Now please add in perform_action
method:
def perform_action(post_save_context):
...
factory = ContextFactory(instance, action)
context = factory.create_context() # Create context to our model
Notification.actions.create_notification(context) # Create new notification
7.Finally, we must to start models listener:
from django.apps import AppConfig
class NotifierConfig(AppConfig):
name = 'apps.notifier'
def ready(self):
from apps.notifier.tasks import register_models
register_models() # Here!
Run celery, run project and create instance Dialog
or Gift
.
Now in our Notification
model we can see new notifications.
8.Create api
python package inside notifier
. And add:
- serializers.py
- views.py
- urls.py
views.py for our module:
class NotificationListGenericApiView(generics.ListAPIView):
"""
Get a list of notifications for a current user with pagination. Readonly
"""
serializer_class = NotificationSerializer
queryset = Notification.objects.all()
def get_queryset(self):
return Notification\
.actions\
.for_user(self.request.user)
...and urls:
urlpatterns = [
path('', NotificationListGenericApiView.as_view())
]
Now we can to list all notifications for a requested user.
Summary
- Our
notifier
as a separate module and only listenpost_save
signal from models - Our
notifier
as scalable. Add new model and addcontext
and it is all - Our
notifier
has ownAPI
-
notifier
work asynchronous
In the next part I show you pusher
- listen changes from notifier
and send push notification
to mobile devices
Top comments (0)