Django REST Framework POST Nested Objects

I ran into a little problem right now with the Django Rest Framework. I am trying to publish an object with nested objects.

Here is my serializers.py :

 class ClassSerializer(serializers.ModelSerializer): class Meta: model = Class fields = ('number', 'letter') class SubjectSerializer(serializers.ModelSerializer): class Meta: model = Subject fields = ('title',) class ExamSerializer(serializers.ModelSerializer): subject = SubjectSerializer() clazz = ClassSerializer() class Meta: model = Exam fields = ('id', 'subject', 'clazz', 'topic', 'date', 'details') depth = 1 def create(self, validated_data): return Exam.objects.create(**validated_data) def update(self, instance, validated_data): instance.__dict__.update(**validated_data) instance.save() return instance 

And create() from views.py :

 def create(self, request): serializer = self.serializer_class(data=request.data) serializer.is_valid(raise_exception=True) self.perform_create(serializer) return Response(serializer.validated_data, status=status.HTTP_201_CREATED) 

And here is the postman's answer: Postman response

I read several posts here about this issue, but I'm still stuck with it. I tried to fix this in several ways, but it still returns "This field is required." ,

+20
json python rest django django-rest-framework
source share
4 answers

You are dealing with the issue of nested serialization . Please read the related documentation before proceeding.

Your question relates to a complex area of โ€‹โ€‹issues in DRF, and therefore requires some explanation and discussion to understand how serializers and sets of views work.

I will discuss the problem of presenting the data of your Subject and Class through the same endpoint, using different data representations for different HTTP methods, because this is usually a problem when people want to represent their data in nested formats; they want to provide their user interfaces with enough information for clean use, for example with dropdown selectors.

By default, Django and Django REST Framework (DRF) refer to related objects (your Subject and Class ) by their primary keys. By default, these are Django auto-incrementing keys. If you want to access them in other ways, you must write overrides for this. There are several different options.

  1. The first option is to specialize the creation and update logic: refer to your class through some other attribute and manually write a search to create, or set the key that you are accessing as the primary key of your class. You can set your class name, UUID, or any other attribute as the primary key of the database, if this is a unique single field (the reason I mention is that at the moment you are looking up your Class models with a compound search, which consists of a composite (number, letter) search term). For example, you can override the search for related objects in your view's create method (for POST), but then you will have to handle similar searches in your view's update method (for PUT and PATCH).
  2. Secondly, in my opinion, the preferred option is to specialize the representations of your object: usually access your classes through the primary key and create one serializer to read the object and one to create and update it. This can easily be achieved by inheriting the serializer class and overriding your views. Use the primary key in POST, PUT, PATCH, etc. To update class references and foreign keys.

Option 1. Look at the class and theme with an arbitrary attribute when creating and updating:

Install your nested class serializers read-only:

 class ExamSerializer(serializers.ModelSerializer): subject = SubjectSerializer(read_only=True) clazz = ClassSerializer(read_only=True) 

Override your create view to find related classes in freeform attributes. Also, check out how DRF implements this with mixins . You will also have to override your update method in order to handle them correctly, and consider PATCH support (partial update) in addition to PUT (update) if you choose this route:

 def create(self, request): # Look up objects by arbitrary attributes. # You can check here if your students are participating # the classes and have taken the subjects they sign up for. subject = get_object_or_404(Subject, title=request.data.get('subject')) clazz = get_object_or_404( Class, number=request.data.get('clazz_number') letter=request.data.get('clazz_letter') ) serializer = self.get_serializer(data=request.data) serializer.is_valid(raise_exception=True) serializer.save(clazz=clazz, subject=subject) headers = self.get_success_headers(serializer.data) return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers) 

Option 2: Specialize your serializers for reading and writing and use primary keys; This is an idiomatic approach:

First, define the default ModelSerializer that you want to use for normal operations (POST, PUT, PATCH):

 class ExamSerializer(serializers.ModelSerializer) class Meta: model = Exam fields = ('id', 'subject', 'clazz', 'topic', 'date', 'details') 

Then replace the required fields with the type of view you want to give them for reading data (GET):

 class ExamReadSerializer(ExamSerializer): subject = SubjectSerializer(read_only=True) clazz = ClassSerializer(read_only=True) 

Then specify the serializer that you want to use for the various operations of your ViewSet. Here we return the nested Subject and Class data for read operations, but use only their primary keys for update operations (much simpler):

 class ExamViewSet(viewsets.ModelViewSet): queryset = Exam.objects.all() def get_serializer_class(self): # Define your HTTP method-to-serializer mapping freely. # This also works with CoreAPI and Swagger documentation, # which produces clean and readable API documentation, # so I have chosen to believe this is the way the # Django REST Framework author intended things to work: if self.request.method in ['GET']: # Since the ReadSerializer does nested lookups # in multiple tables, only use it when necessary return ExamReadSerializer return ExamSerializer 

As you can see, option 2 seems rather less complicated and error prone and contains only 3 lines of handwritten code on top of DRF (implementation of get_serializer_class). Just let the logic of the framework figure out the views and create and update objects for you.

I saw many other approaches, but so far they have created the least amount of code that I would support, and have carefully taken advantage of the development of DRF.

+39
source share

A simpler approach without additional classes is to take over serialization:

 class ExamSerializer(serializers.ModelSerializer): class Meta: model = Exam fields = ('id', 'subject', 'clazz', 'topic', 'date', 'details') def to_representation(self, instance): data = super().to_representation(instance) data['subject'] = SubjectSerializer( Subject.objects.get(pk=data['subject'])).data data['clazz'] = ClassSerializer( Class.objects.get(pk=data['clazz'])).data return data 
+3
source share

I had the same problem when trying to send a nested JSON object to DRF (Django Rest Framework).

Once you have correctly configured the recording of nested serializers (see the documents in writeable nested serializers), you can verify that this works using the browsable API and placing / placing data there. If this works, and you still get "This field is required" errors on your nested models when sending / installing JSON objects, you may need to set the content type of your request.

This answer provided the solution I needed, and he summarized it below.

 $.ajax ({ // Other parameters eg url, type data: JSON.stringify(data), dataType: "json", contentType: "application/json; charset=utf-8", }); 

I needed to set "contentType" and also "tune" my js object.

0
source share

To solve your problem you can use this package drf-rw-serializers

All you have to do is use two serializers (one for reading and one for writing):

serializers.py

 class ClassSerializer(serializers.ModelSerializer): class Meta: model = Class fields = ('number', 'letter') class SubjectSerializer(serializers.ModelSerializer): class Meta: model = Subject fields = ('title',) class ExamSerializer(serializers.ModelSerializer): subject = SubjectSerializer() clazz = ClassSerializer() class Meta: model = Exam fields = ('id', 'subject', 'clazz', 'topic', 'date', 'details') class WriteExamSerializer(serializers.ModelSerializer): subject = SubjectSerializer() clazz = ClassSerializer() class Meta: model = Exam fields = ('id', 'subject', 'clazz', 'topic', 'date', 'details') def create(self, validated_data): subject = validated_data.pop('subject', None) # logic to update subject clazz = validated_data.pop('clazz', None) # logic to update clazz return super().create(validated_data) def update(self, validated_data): subject = validated_data.pop('subject', None) # logic to update subject clazz = validated_data.pop('clazz', None) # logic to update clazz return super().update(validated_data) 

api_views.py

 from drf_rw_serializers import generics from .models import Exam from .serializers import WriteExamSerializer, ExamSerializer class ExamListCreateView(generics.ListCreateAPIView): queryset = Exam.objects.all() write_serializer_class = WriteExamSerializer read_serializer_class = ReadExamSerializer 
0
source share

All Articles