`.create()` 方法默认不支持可写嵌套字段。

Posted

技术标签:

【中文标题】`.create()` 方法默认不支持可写嵌套字段。【英文标题】:The `.create()` method does not support writable nested fields by default. 【发布时间】:2017-05-14 16:01:29 【问题描述】:

我在 DRF 中与中间模型的多对多关系的序列化方面有一个大问题:如果请求方法是让一切正常工作。但是,一旦我尝试将数据 POST 或 PUT 到 API,我就会收到以下错误:

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.

我不太确定如何编写正确的创建和更新函数,我也不太明白,文档中是如何解释的。

代码:

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  

序列化器.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)

提前致谢。

【问题讨论】:

【参考方案1】:

DRF 不支持嵌套序列化程序的 create 方法。如果您想在扩展布局中显示相关字段,而不仅仅是使用 pks,那么您可以覆盖 to_representation 方法,而不是重写默认的 mtm 字段。您还应该覆盖create 方法,因为mtm 链接中的第三个模型:

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 

现在它将保存 mtm 字段,正确传递 pk 列表,如 [1, 2, 3],并且对于该 mtm 相关模型的表示,EquipmentSerializer 将使用 AssignmentSerializer

【讨论】:

这解决了 AssertionError 但现在我收到 400 错误请求详细信息:“JSON 解析错误 - 无法解码 JSON 对象” @nictec 啊我没看到你用第三个模型做 mtm 链接 @nictec 更新了答案。您的代码不太清楚,很难从 SO 中调试。如果您有任何错误,请在此处链接,并使用发布请求正文更新您的答案 这个答案需要一些认证:在create函数中,也更改以下order = Order.objects.filter(pk__in=validated_data.pop('event')),这里需要for循环,因为它是ManyToManyField【参考方案2】:

也许对于大多数遇到相同问题的人来说,这个问题很长。

简短的回答是 DRF 本身不支持嵌套序列化程序的 create 方法。 那该怎么办?

只需覆盖默认行为。查看Official DRF docs中的完整示例

【讨论】:

【参考方案3】:

我认为错误的原因:JSON parse error - No JSON object could be decoded 是因为您忘记将.data 放在@Ivan Semochkin 解决方案的第二行:representation['assigment'] = AssignmentSerializer(instance.assigment_set.all(), many=True).data

因此我发现我会从以下行偶然发现Keyword Error: 'event'representation = super(EquipmentSerializer, self).to_representation(instance),因为 EquipmentSeralizer 对象包含中介 assignment_set 而不是 event

这是我根据@Ivan Semochkin 解决方案改编的最终结果。如果我在实践中错了/不合适,请纠正我。

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 

如果我错了,请纠正我。我是 Django 新手。

【讨论】:

【参考方案4】:

我遇到了类似的问题,但使用 update() 方法...

感谢这个帖子,解决方案很简单:https://github.com/beda-software/drf-writable-nested/issues/104..。

我所要做的就是安装库pip install drf-writable-nested 并导入它:

from drf_writable_nested import WritableNestedModelSerializer

代码应如下所示:

(感谢: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

【讨论】:

以上是关于`.create()` 方法默认不支持可写嵌套字段。的主要内容,如果未能解决你的问题,请参考以下文章

nodejs中sequelize的create方法默认执行语句中的id,如何去掉(我的数据库不需要设置id字段)

Django 可写嵌套序列化程序更新

Django REST 序列化程序方法可写字段

嵌套可写序列化程序上的验证错误

Mybatis-Plus select不列出全部字段

让Proxmox VE支持嵌套虚拟化