31

I have a big problem regarding the serialization of a Many to Many relationship with intermediate model in DRF: If the request method is get everything works perfectly. But as soon as i try to POST or PUT Data to the API I get the following Error:

Traceback (most recent call last):
 File "/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/django/core/handlers/base.py", line 149, in get_response
 response = self.process_exception_by_middleware(e, request)
 File "/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/django/core/handlers/base.py", line 147, in get_response
 response = wrapped_callback(request, *callback_args, **callback_kwargs)
 File "/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/django/views/decorators/csrf.py", line 58, in wrapped_view
 return view_func(*args, **kwargs)
 File "/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/django/views/generic/base.py", line 68, in view
 return self.dispatch(request, *args, **kwargs)
 File "/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/djangorestframework-3.5.3-py2.7.egg/rest_framework/views.py", line 477, in dispatch
 response = self.handle_exception(exc)
 File "/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/djangorestframework-3.5.3-py2.7.egg/rest_framework/views.py", line 437, in handle_exception
 self.raise_uncaught_exception(exc)
 File "/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/djangorestframework-3.5.3-py2.7.egg/rest_framework/views.py", line 474, in dispatch
 response = handler(request, *args, **kwargs)
 File "/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/djangorestframework-3.5.3-py2.7.egg/rest_framework/generics.py", line 243, in post
 return self.create(request, *args, **kwargs)
 File "/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/djangorestframework-3.5.3-py2.7.egg/rest_framework/mixins.py", line 21, in create
 self.perform_create(serializer)
 File "/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/djangorestframework-3.5.3-py2.7.egg/rest_framework/mixins.py", line 26, in perform_create
 serializer.save()
 File "/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/djangorestframework-3.5.3-py2.7.egg/rest_framework/serializers.py", line 214, in save
 self.instance = self.create(validated_data)
 File "/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/djangorestframework-3.5.3-py2.7.egg/rest_framework/serializers.py", line 888, in create
 raise_errors_on_nested_writes('create', self, validated_data)
 File "/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/djangorestframework-3.5.3-py2.7.egg/rest_framework/serializers.py", line 780, in raise_errors_on_nested_writes
 class_name=serializer.__class__.__name__
AssertionError: The `.create()` method does not support writable nested fields by default.
Write an explicit `.create()` method for serializer `manager.serializers.EquipmentSerializer`, or set `read_only=True` on nested serializer fields.

I am not really sure how to write proper create and update functions and i don ́t really understand it, how it is explained in the documentation.

Code:

views.py:

from django.shortcuts import render 
from django.contrib.auth.models import User, Group
from manager.serializers import *
from rest_framework import generics 
from rest_framework import viewsets 
from rest_framework.decorators import api_view 
from rest_framework.response import Response 
from rest_framework.views import APIView 
from django.forms.models import model_to_dict
class OrderSetDetails(generics.RetrieveUpdateDestroyAPIView): 
 queryset = Order.objects.all() 
 serializer_class = OrderSerializer 
class OrderSetList(generics.ListCreateAPIView): 
 queryset = Order.objects.all() 
 serializer_class = OrderSerializer 
class EquipmentSetDetails(generics.RetrieveUpdateDestroyAPIView): 
 queryset = Equipment.objects.all() 
 serializer_class = EquipmentSerializer 
class EquipmentSetList(generics.ListCreateAPIView): 
 queryset = Equipment.objects.all() 
 serializer_class = EquipmentSerializer
class UserViewSet(viewsets.ModelViewSet):
 queryset = User.objects.all().order_by('-date_joined')
 serializer_class = UserSerializer
class GroupViewSet(viewsets.ModelViewSet):
 queryset = Group.objects.all()
 serializer_class = GroupSerializer 
class ClientList(generics.ListCreateAPIView): 
 queryset = client.objects.all() 
 serializer_class = ClientSerializer 

serializers.py

from rest_framework import serializers 
from django.contrib.auth.models import User, Group
from storage.models import * 
class AssignmentSerializer(serializers.HyperlinkedModelSerializer): 
 id = serializers.ReadOnlyField(source = 'Order.id') 
 name = serializers.ReadOnlyField(source = 'Order.name') 
 class Meta:
 model = Assignment 
 fields = ('id', 'name', 'quantity') 
class EquipmentSerializer(serializers.ModelSerializer): 
 event = AssignmentSerializer(source= 'assignment_set', many = True)
 class Meta: 
 model = Equipment 
 fields = '__all__' 
class ClientSerializer(serializers.ModelSerializer): 
 class Meta: 
 model = client 
 fields = '__all__' 
class UserSerializer(serializers.HyperlinkedModelSerializer):
 class Meta:
 model = User
 fields = ('url', 'username', 'email', 'groups')
class GroupSerializer(serializers.HyperlinkedModelSerializer):
 class Meta:
 model = Group
 fields = ('url', 'name') 
class OrderSerializer(serializers.ModelSerializer):
 class Meta: 
 model = Order 
 fields = '__all__' 

models.py:

from __future__ import unicode_literals
from django.db import models 
from storage.choices import *
# Create your models here.
class Equipment(models.Model): 
 name = models.CharField(max_length=30) 
 fabricator = models.CharField(max_length=30, default='-') 
 storeplace = models.IntegerField() 
 labor = models.CharField(max_length=1, choices=labor_choices) 
 event = models.ManyToManyField('Order', blank = True, through= 'Assignment', through_fields=('Equipment', 'Order')) 
 max_quantity = models.IntegerField(default=1, null = True) 
 status = models.CharField(max_length=8, choices = STATUS_CHOICES, default = 'im Lager') 
 def __str__(self): 
 return self.name 
class client(models.Model): 
 firstname = models.CharField(max_length=30) 
 secondname = models.CharField(max_length=30) 
 email = models.EmailField() 
 post_code = models.IntegerField()
 city = models.CharField(max_length=30) 
 street= models.CharField(max_length=30) 
 def __str__(self): 
 return "%s %s" % (self.firstname, self.secondname)
class Order(models.Model): 
 name = models.CharField(max_length=30) 
 Type = models.CharField(
 max_length=2,
 choices=TYPE_CHOICES,
 default='Rental', 
 )
 city = models.CharField(max_length=30) 
 street= models.CharField(max_length=30)
 date = models.DateField() 
 GuestNumber = models.IntegerField() 
 description = models.TextField() 
 client = models.ForeignKey("client", on_delete=models.CASCADE, blank = True, null = True) 
 status = models.CharField(max_length=30, choices=order_choices, default='glyphicon glyphicon-remove') 
 def __str__(self): 
 return self.name
class Assignment(models.Model): 
 Equipment = models.ForeignKey('Equipment', on_delete=models.CASCADE) 
 Order = models.ForeignKey('Order', on_delete=models.CASCADE) 
 quantity = models.PositiveIntegerField(default=1)

Thanks in Advance.

asked Dec 30, 2016 at 10:35

4 Answers 4

30

DRF does not support create method for nested serializers. If you want to show related fields in an extended layout and not only with pks then you can override the to_representation method instead of rewriting default mtm field. You should also override a create method, because of the third model in mtm link:

class EquipmentSerializer(serializers.ModelSerializer): 
 class Meta: 
 model = Equipment 
 fields = '__all__'
 def create(self, validated_data):
 order = Order.objects.get(pk=validated_data.pop('event'))
 instance = Equipment.objects.create(**validated_data)
 Assignment.objects.create(Order=order, Equipment=instance)
 return instance
 def to_representation(self, instance):
 representation = super(EquipmentSerializer, self).to_representation(instance)
 representation['assigment'] = AssignmentSerializer(instance.assigment_set.all(), many=True).data
 return representation 

Now it'll save mtm fields properly passing list of pks, like [1, 2, 3] and for representation of that mtm related model, EquipmentSerializer will use AssignmentSerializer.

answered Dec 30, 2016 at 10:41
Sign up to request clarification or add additional context in comments.

4 Comments

This solves the AssertionError but now i get a 400 bad request detail: "JSON parse error - No JSON object could be decoded"
@nictec ah I don't saw you used third model for mtm link
@nictec updated answer. Your code is not so clear it is really hard to debug from SO. If you'll have any error link it here and also update your answer with post request body
Some certifications are required in this answer: in create function, change the following order = Order.objects.filter(pk__in=validated_data.pop('event')) also, for loop will be required here since it is a ManyToManyField
8

Maybe for most people who is having the same issue, this question is a quite long.

The short answer is that DRF does not support natively create method for nested serializers. so what to do?

Simply overriding the default behaviour. Check out a full example in the Official DRF docs

answered Jun 16, 2020 at 20:15

Comments

2

I had a similar problem but with the update() method ...

The solution was simple thanks to this thread: https://github.com/beda-software/drf-writable-nested/issues/104...

All I had to do was installing the library pip install drf-writable-nested and import it:

from drf_writable_nested import WritableNestedModelSerializer

the code should look like this:

(credit to: https://github.com/Leonardoperrella)

--serializers.py--

from drf_writable_nested import WritableNestedModelSerializer
class ProductsSerializer(serializers.ModelSerializer):
 class Meta:
 model = Products
 fields = ('name', 'code', 'price')
class VendorsSerializer(WritableNestedModelSerializer,
 serializers.ModelSerializer):
 products = ProductsSerializer(source='vendor', many=True)
 class Meta:
 model = Vendors
 fields = ('name', 'cnpj', 'city', 'products')

--models.py--

class Vendors(models.Model):
 name = models.CharField('Name', max_length=50)
 cnpj = models.CharField('CNPJ', max_length=14, unique=True, validators=[validate_cnpj])
 city = models.CharField('City', max_length=100, blank=True)
 class Meta:
 verbose_name = "Vendor"
 verbose_name_plural = "Vendors"
 def __str__(self):
 return self.name
class Products(models.Model):
 name = models.CharField('Name', max_length=60)
 code = models.CharField('Code', max_length=13)
 price = models.DecimalField('Price',
 max_digits=15,
 decimal_places=2,
 default=0.00,
 validators=[MinValueValidator(Decimal("0.01"))])
 vendor = models.ForeignKey('Vendors', on_delete=models.CASCADE, related_name='vendor')
 class Meta:
 verbose_name = "Product"
 verbose_name_plural = "Products"
 def __str__(self):
 return self.name
answered Jun 14, 2021 at 5:28

Comments

0

I think that the cause for error: JSON parse error - No JSON object could be decoded is because you forgot to put .data at the 2nd line from @Ivan Semochkin solution: representation['assigment'] = AssignmentSerializer(instance.assigment_set.all(), many=True).data.

Thus I find out that I will stumble upon Keyword Error: 'event' from line: representation = super(EquipmentSerializer, self).to_representation(instance) since the EquipmentSeralizer object contain the intermediary assignment_set instead of event.

Here are the end result adapting from @Ivan Semochkin solution I do. Correct me if I'm wrong/inappropriate in practice.

class EquipmentSerializer(serializers.ModelSerializer): 
 class Meta: 
 model = Equipment 
 fields = '__all__'
 def create(self, validated_data):
 order = Order.objects.get(pk=validated_data.pop('assignment_set').get('id'))
 instance = Equipment.objects.create(**validated_data)
 Assignment.objects.create(Order=order, Equipment=Equipment)
 return instance
 def to_representation(self, instance):
 representation = super(EquipmentSerializer, self).to_representation(instance)
 representation['assigment'] = AssignmentSerializer(instance.assigment_set.all(), many=True).data
 return representation 

Please correct me if I'm wrong. I'm new to Django.

Eric Aya
70.2k36 gold badges190 silver badges266 bronze badges
answered Feb 10, 2019 at 14:54

Comments

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

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.