Teemu Karimerto
Teemu Karimerto

Reputation: 395

How to access/create a proper Request object for DRF Serializer?

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

Answers (1)

Teemu Karimerto
Teemu Karimerto

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

Related Questions