1

I'm trying to call an existing class based view for Model1 from another class based view of Model2. The data I'm receiving from the front end in JSON has the data for Model1 and Model 2 together in below format:

request.data format:

{
    "model1": {
        "key1": "val1"
    },
    "model2": {
        "key2": "val2"
    }
}

In my Model1View, I'm doing some additional things which I want to do whenever I have to save some new data to Model1. So I intend on using the same view. (These additional things include creating other model instances etc).

Objective: When I call Model2View, I want to seperate the request data for Model1 and call Model1View first. Once I successfully get a response from this view, I want to use the model1_id (primary key of Model1), to add to the request data and save Model2.

Problem: While testing with this view, I'm getting an error:

django.http.request.RawPostDataException: You cannot access body after reading from request's data stream

Models:

from django.db import models

class Model1(models.Model):
    model1_id = models.AutoField(primary_key=True)
    key1 = models.CharField(max_length=20, blank=False, null=False)

class Model2(models.Model):
    model2_id = models.AutoField(primary_key=True)
    key2 = models.CharField(max_length=20, blank=False, null=False)
    model1_id = models.ForeignKey('Model1', blank=False, null=False, on_delete=models.PROTECT)

Serializers:

from rest_framework import serializers
from .models import *

class Model1Serializer(serializers.ModelSerializer):
    class Meta:
        model = Model1
        fields = '__all__'

class Model2Serializer(serializers.ModelSerializer):
    class Meta:
        model = Model2
        fields = '__all__'

Views:

from django.shortcuts import render

from rest_framework import generics
from rest_framework.response import Response
from rest_framework import status

from .models import *
from .serializers import *

class Model1View(generics.ListCreateAPIView):
    queryset = Model1.objects.all()
    serializer_class = Model1Serializer

    def post(self, request, *args, **kwargs):
        if ('data' in kwargs):
            request.data.update(kwargs.get('data'))
        # Call super to save the data in Model1
        response = super().post(request, *args, **kwargs)
        # Do some additional things
        return Response(response.data, status.HTTP_201_CREATED)

class Model2View(generics.ListCreateAPIView):
    queryset = Model2.objects.all()
    serializer_class = Model2Serializer

    def post(self, request, *args, **kwargs):
        # Get Model1 data and Model2 data from request
        model1_data = request.data.pop("model1")
        model2_data = request.data.pop("model2")

        # Call Model1View to create Model1 Instance
        model1_response = Model1View.as_view()(request._request, data=model1_data)
        # Save Model1 Instance ID to data and then save Model2 Instance
        model2_data["model1_id"] = model1_response['model1_id']
        model2_serializer = Model2Serializer(data=model2_data)
        if model2_serializer.is_valid():
            model2_serializer.save()
            return Response(model2_serializer.data, status.HTTP_201_CREATED)
        return Response(model2_serializer.errors, status.HTTP_400_BAD_REQUEST)

Some trials: Removing the request.data.pop statements resolves the error but then, how do I extract the data from the request? If I in anyway interact with the request object, either by copying it or updating data etc, before I call the Model1View, I end up getting the same error.

1 Answer 1

3

For this problem statement, you need one view and then serialize model1 and model2 separately in a custom serializer. In the model1 and model2 serializers you can perform the customization that you intend to perform. Something like this

class Model1Serializer(serializers.ModelSerializer):
    class Meta:
        model = Model1
        fields = '__all__'

class Model2Serializer(serializers.ModelSerializer):
    class Meta:
        model = Model2
        fields = '__all__'

class ModelSerializer(serializers.ModelSerializer):
    model1 = Model1Serializer(many=False, required=True)
    model2 = Model2Serializer(many=False, required=True)

Use the ModelSerializer in your view class to serialize both the model1 and model2 instances.

Sign up to request clarification or add additional context in comments.

6 Comments

Thanks for the input. Single serializer will accomplish the problem of posting request data to 2 different models through a single view. But, I also intend to use the "do some additional things" code in Model1View without having to repeat the code. How can I accomplish this?
In your code I see that you have written "do some additional things" after the createApiView post function. You can use the post save signal dispatcher to do the same. If the "additional things" can be done before saving the object to the db, you can go by validate function in serializer or pre save signal dispatcher in models. Hope that clears it up. On that note, it is always best practice to have fat models and thin views in django. So minimize the number of operations in your view class.
All that @lazycoderboi is right. This is clearly a job for a serializer or these operations can be covered in models' logic.
@VasanthS Thanks again for the input. I did not know about signal dispatchers earlier. I am learning about them now. Also, could you please help me with another doubt related to the above problem. If I combine serializer for 2 models, then while posting new request, how will Model2 get the model1_id. Since I am passing the data for Model1 and Model2 in a single JSON request, is there a way to create Model1 instance and then use model1_id to create Model2 instance using single serializer method. I'm sorry if this is obvious but I'm not very experienced with drf and coding in general.
If you override the create() function in the ModelSerializer, you will have access to both Model1 and Model2 values. In other words, you can create both model1 and model2 database entries in the main serializer create() function.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.