`.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()` 方法默认不支持可写嵌套字段。的主要内容,如果未能解决你的问题,请参考以下文章