Model observer

Subscribing to all instances of a model.

Introduction

In this first example, we will create a User model with a Comment related model, create the serializers for each one. And create a Consumer for the User model, with a model observer method for all comment instances.

Creating models.

We will have the following models.py file, with a User model, and a Comment model that is related to the user.

# models.py
from django.db import models
from django.contrib.auth.models import AbstractUser

class User(AbstractUser):
    pass

class Comment(models.Model):
    text = models.TextField()
    user = models.ForeignKey(User, related_name="comments", on_delete=models.CASCADE)
    date = models.DatetimeField(auto_now_add=True)

Creating the serializers.

In the serializers.py file, we will have the serializers for the models in the models.py file.

# serializers.py
from rest_framework import serializers
from .models import User, Comment

class UserSerializer(serializers.ModelSerializer):
    class Meta:
        model = User
        fields = ["id", "username", "email"]

class CommentSerializer(serializers.ModelSerializer):
    class Meta:
        model = Comment
        fields = ["id", "text", "user"]

Creating the consumers.

Now in the consumers.py file, we will create a websocket consumer for the users, with a model observer method for all instances of the Comment model.

These are the important methods of the class.

  • A method, called comment_activity decorated with the model_observer decorator and as argument we will add the Comment model.

  • A subscribe_to_comment_activity action to subscribe the model_observer method.

  • A method (it can be named the same as the model_observer method) decorated with the @comment_activity.serializer, this will return the serializer based on the instance.

# consumers.py

from djangochannelsrestframework.consumers import GenericAsyncAPIConsumer
from djangochannelsrestframework.observer import model_observer
from djangochannelsrestframework.decorators import action

from .serializers import UserSerializer, CommentSerializer
from .models import User, Comment


class MyConsumer(GenericAsyncAPIConsumer):
    queryset = User.objects.all()
    serializer_class = UserSerializer

    @model_observer(Comment)
    async def comment_activity(
        self,
        message: CommentSerializer,
        observer=None,
        subscribing_request_ids=[],
        **kwargs
    ):
        await self.send_json(dict(message.data))

    @comment_activity.serializer
    def comment_activity(self, instance: Comment, action, **kwargs) -> CommentSerializer:
        """This will return the comment serializer"""
        return CommentSerializer(instance)

    @action()
    async def subscribe_to_comment_activity(self, request_id, **kwargs):
        await self.comment_activity.subscribe(request_id=request_id)

Manual testing the output.

Now we will have a websocket client in javascript listening to the messages, after subscribing to the comment activity. This code block can be used in the browser console.

Note

In production the ws: is wss:, we can check it with the following code:
const ws_schema = window.location.protocol === "http:" ? "ws:" : "wss:";
const ws = new WebSocket("ws://localhost:8000/ws/my-consumer/")
ws.onopen = function(){
    ws.send(JSON.stringify({
        action: "subscribe_to_comment_activity",
        request_id: new Date().getTime(),
    }))
}
ws.onmessage = function(e){
    console.log(e)
}

In the IPython shell we will create some comments for different users and in the browser console we will see the log.

Warning

At this point we should have some users in our database, otherwise create them.

We will create a comment using the user_1, and we will see the log in the browser console.

>>> from my_app.models import User, Comment
>>> user_1 = User.objects.get(pk=1)
>>> user_2 = User.objects.get(pk=2)
>>> Comment.objects.create(text="user 1 creates a new comment", user=user_1)

In the console log we will see something like this:

{
    action: "subscribe_to_comment_activity",
    errors: [],
    response_status: 200,
    request_id: 15606042,
    data: {
        id: 1,
        text: "user 1 creates a new comment",
        user: 1
    }
}

Now we will create a comment with user_2.

>>> Comment.objects.create(text="user 2 creates a second comment", user=user_2)

In the console log we will see something like this:

{
    action: "subscribe_to_comment_activity",
    errors: [],
    response_status: 200,
    request_id: 15606042,
    data: {
        id: 2,
        text: "user 2 creates a second comment",
        user: 2,
    },
}

Conclusions

In this example we subscribed to all instances of the comment model, in the next section we will see how to filter them.