Django Rest Framework Post Nested Objects

Django REST Framework POST nested objects

You are dealing with the problem of nested serialization. Please read the linked documentation before proceeding.

Your question relates to a complex area of problems in DRF and hence requires some explanation and discussion for understanding how serializers and viewsets work.

I will discuss the problem of representing your Subject and Class data via the same endpoint by using a different representation of data for different HTTP methods, because this is commonly the problem when people wish to represent their data in nested formats; they wish to provide their user interfaces enough information for clean use, e.g. through the dropdown selectors.

By default Django and Django REST Framework (DRF) refer to related objects (your Subject and Class) by their primary keys. These, by default, are auto-incrementing integer keys with Django. If you want to refer to them by other ways you have to write overrides for this. There are a few different options.

  1. First option is to specialize your creation and update logic: Refer to your class via some other attribute(s) and manually write the lookups for creation yourself, or set the key you are referring through as the primary key of your class. You can set your class' name, UUID or any other attribute as the primary database key, as long as it is a unique, single field (the reason I am mentioning this is because you are, at the moment, looking your Class models up with a composite search that consists of a composite (number, letter) search term). You can override related object lookups in your create view method (for POST), for example, but then you will have to handle similar lookups in your update view method as well (for PUT and PATCH).
  2. Second, in my opinion the preferable option, is to specialize your object representations: Refer to your classes normally via primary key and create one serializer for reading the object and one for creating and updating it. This can be easily achieved by serializer class inheritance and overriding your representations. Use the primary key in your POST, PUT, PATCH, etc. requests to update your class references and foreign keys.

Option 1: Look Class and Subject up with an arbitrary attribute in create and update:

Set your nested class serializers as read-only:

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

Override your view's create to look up the related classes on free-form attributes. Also, check out how DRF implements this with mixins. You will also have to override your update method to handle these correctly, and take into account PATCH (partial update) support in addition to PUT (update) if you take 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 read and write and use primary keys; This is the idiomatic approach:

First define a default ModelSerializer you wish to use for normal operations (POST, PUT, PATCH):

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

Then override the necessary fields with the kind of representation you want to give to them for reading the data (GET):

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

Then specify the serializer you wish to use for different operations for your ViewSet. Here we return the nested Subject and Class data for read operations, but only use their primary keys for update operations (far 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 fairly less complex and error-prone, containing only 3 lines of hand-written code on top of DRF (the get_serializer_class implementation). Just let the framework's logic figure out the representations and creation and updates of objects for you.

I have seen many other approaches, but so far these have been the ones that have produced the least code to maintain for me and take advantage of the design of DRF in a clean manner.

Deserialize Nested Object in Django-Rest-Framework

Solved it with the help of the specialized serializers post after a few re-reads.

I updated the serializers to this:

# This gets called for non-GET requests.
class AgentBusinessAddressSerializer(serializers.ModelSerializer):
class Meta:
model = AgentBusinessAddress
fields = ('__all__')

# This get called for GET requests.
class AgentBusinessAddressReadSerializer(AgentBusinessAddressSerializer):
province = ValidProvinceSerializer(read_only=True)
class Meta:
model = AgentBusinessAddress
fields = ('__all__')

I updated the viewset to this:

class AgentBusinessAddressViewSet(viewsets.ModelViewSet):
queryset = AgentBusinessAddress.objects.all()

def get_serializer_class(self):
if self.request.method in ['GET']:
return AgentBusinessAddressReadSerializer
return AgentBusinessAddressSerializer

Now I just send the primary key for the province in the PUT request:

{
"address": "123 Fake St",
"agent": 10000003,
"city": "Calgary",
"dlc": "2021-10-11 19:47:38",
"operator_id": 4,
"postal_code": "A1B 2C3",
"province": "NS",
"valid_address": "N"
}

I get a PUT 200 response back and verify in the DB that the province is now 'NS'.

{
"agent": 10000003,
"operator_id": 4,
"dlc": "2021-10-11T19:47:38",
"address": "123 Fake St",
"city": "Calgary",
"postal_code": "A1B 2C3",
"valid_address": "N",
"province": "NS",
}

django rest nested serializer object setting nested object in post method

i solved the problem :))
in django rest nested object we should not use unique validation in serializer instead we shoold set the unique validation in views.py after serializer.is_valid() and beore serializer.save() and all the code can be somting like this in views.py:

uniqueNameError = {"name":["there is another image with this name."]}
def unique_name(name):
for image in Image.objects.all():
if name == image.name:
return False
return True

class ImageView(APIView):
def post(self, request):
serializer = ImageSerializer(data=request.data)
if serializer.is_valid():
if not unique_name(request.data['name']):
return Response(uniqueNameError, status=status.HTTP_400_BAD_REQUEST)
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

POST request for nested objects

Ah, welcome to the limitations of DRF.

Presently, the way to work with nested data is by overriding the update method on your serializer:

serializers.py

class SurveyDetailSerializer(serializers.ModelSerializer):

...

def update(self, instance, validated_data):

# get the questions array:
questions = validated_data.pop('questions')

# create new questions, map to survey
...

# continue with regular update method:
super().update(self, instance, validated_data)

Django nested objects, different serializers GET and POST

If I understood correctly what you want is to get the nested object during get. I had the same problem which I resolved with this in my serializer.

class APIndexSerializer(serializers.ModelSerializer):
class Meta:
model = AP
fields = ['id','user','name']

def to_representation(self, obj):
self.fields['user'] = UserIndexSerializer()
return super(APIndexSerializer, self).to_representation(obj)

You can with this create with id and get with nested information of user.

how to create three tables in nested-serializer objects using django rest framework

First, you don't need to set the primary_key fields because django will do that for you. So you'd better remove project_id, site_id, assignment_id field from the 3 models respectively.
And some errors exist in the CreateNewProjetSerial.
site_name, assigned_to_id field should be read_only, and the additional fields are required to receive your payload if you can't upload them with the related object.

class CreateNewProjetSerial(serializers.ModelSerializer):
site_name = ProjectSiteSerializer(many=True, read_only = True)
assigned_to_id = AssignedUserSerializer(many=True, read_only = True)
site_names = serializers.ListField(
child = serializers.CharField(), write_only = True)
user_ids = serializers.ListField(
child = serializers.IntegerField(), write_only = True)

class Meta:
model = Project
fields = ['site_name','project_name','assigned_to_id', 'site_names', 'user_ids']

def create(self, validated_data):
site_names = validated_data.pop('site_names')
user_ids = validated_data.pop('user_ids')

new_project = Project.objects.create(**validated_data)

for site_name in site_names:
new_project_site = ProjectSite.objects.create(project_id=new_project, site_name=site_name)

for user_id in user_ids:
Assignment.objects.create(assigned_to_id__id=user_id, site_id=new_project_site)

return new_project

And in frontend, please upload like the following.

{
"site_names": ["site1", "site2"],
"project_name": "test_project",
"user_ids": [2, 3]
}


Related Topics



Leave a reply



Submit