Reputation: 395
I have created a REST API using DRF, and that works well enough. The frontend is a simple page that allows data to be viewed and updated. Now I am trying to add more interactivity to the site using WebSockets with django-channels
. The Channels system is fairly simple to use and also works nicely.
However the issue I am now facing is trying to combine all of the moving pieces to work together. My idea is that the initial page refresh comes through the REST API, and any subsequent updates would automagically come through a WebSocket after every update (with the help of post_save
signal). I have nice DRF Serializers for all my models, but alas those do not work without a Request object (for instance HyperLinkedIdentityField
):
AssertionError: `HyperlinkedIdentityField` requires the request in the serializer context. Add `context={'request': request}` when instantiating the serializer.
So my question is, how do I somehow create/fake a proper Request object that the Serializers want when trying to serialize my model in a signal handler?
Edit
The more I think about this, the more obvious it becomes that this is not exactly the right way to go. There is no way to craft a single, generic Request
object for the serializers, since the model updates which trigger them can come from any source. Thus it would not make sense to even try creating one. I think I have to separate the "base" serializers (without any hyperlinks) and use those to send updates to the clients. Since the hyperlinks won't ever change, I think this is the proper way to go.
Upvotes: 4
Views: 1412
Reputation: 395
In case anyone might be interested, here is how I solved the issue. The main bits and pieces of code are below.
First a simple model (myapp/models.py):
from django.db import models
class MyModel(models.Model):
name = models.TextField()
Then the serializers (myapp/serializers.py):
from rest_framework import serializers
MyModelSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = MyModel
fields = ('url', 'id', 'name')
extra_kwargs = {'url': {'view_name': 'mymodel-detail'}}
MyModelBaseSerializer(serializers.ModelSerializer):
class Meta:
model = MyModel
fields = ('id', 'name')
And the views (myapp/views.py):
from rest_framework import viewsets
from myapp.models import MyModel
from myapp.serializers import MyModelSerializer
class MyModelViewSet(viewsets.ModelViewSet):
queryset = MyModel.objects.all()
serializer_class = MyModelSerializer
And finally the Channels message consumer (myapp/consumers.py):
import json
from django.db.models.signals import pre_save
from django.dispatch import receiver
from channels import Group
from myapp.models import MyModel
from myapp.serializers import MyModelBaseSerializer
def ws_add(message):
message.reply_channel.send({"accept": True})
Group("mymodel").add(message.reply_channel)
def ws_disconnect(message):
Group("mymodel").discard(message.reply_channel)
@receiver(post_save, sender=MyModel)
def mymodel_handler(sender, instance, **kwargs):
Group("mymodel").send({
"text": json.dumps({
"model": "mymodel",
"data": MyModelBaseSerializer(instance).data
})
})
I have omitted things like urls.py and routing.py but those are not relevant to the issue. As can be seen, the regular view uses the normal MyModelSerializer
which is includes the URL, and then the update handler MyModelBaseSerializer
has only fields which are not dependent on any Request
object.
Upvotes: 4