Esc*_*her 11 python django django-rest-framework
我在尝试访问serializer.data之前遇到错误,然后返回Response(serializer.data, status=something):
尝试获取
<field>序列化程序字段的值时获取KeyError<serializer>.
这发生在所有字段上(因为事实证明我试图访问.data父节点而不是子节点,见下文)
类定义如下所示:
class BulkProductSerializer(serializers.ModelSerializer):
list_serializer_class = CustomProductListSerializer
user = serializers.CharField(source='fk_user.username', read_only=False)
class Meta:
model = Product
fields = (
'user',
'uuid',
'product_code',
...,
)
Run Code Online (Sandbox Code Playgroud)
CustomProductListSerializer是一个serializers.ListSerializer有一个重写的save()方法,允许它正确处理批量创建和更新.
以下是批量产品的示例视图ViewSet:
def partial_update(self, request):
serializer = self.get_serializer(data=request.data,
many=isinstance(request.data, list),
partial=True)
if not serializer.is_valid():
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
serializer.save()
pdb.set_trace()
return Response(serializer.data, status=status.HTTP_200_OK)
Run Code Online (Sandbox Code Playgroud)
试图访问serializer.data跟踪(或明显后面的行)会导致错误.这是完整的跟踪(tl; dr跳过下面我用调试器诊断的地方):
Traceback (most recent call last):
File "/lib/python3.5/site-packages/django/core/handlers/exception.py", line 41, in inner
response = get_response(request)
File "/lib/python3.5/site-packages/django/core/handlers/base.py", line 249, in _legacy_get_response
response = self._get_response(request)
File "/lib/python3.5/site-packages/django/core/handlers/base.py", line 187, in _get_response
response = self.process_exception_by_middleware(e, request)
File "/lib/python3.5/site-packages/django/core/handlers/base.py", line 185, in _get_response
response = wrapped_callback(request, *callback_args, **callback_kwargs)
File "/lib/python3.5/site-packages/django/views/decorators/csrf.py", line 58, in wrapped_view
return view_func(*args, **kwargs)
File "/lib/python3.5/site-packages/rest_framework/viewsets.py", line 86, in view
return self.dispatch(request, *args, **kwargs)
File "/lib/python3.5/site-packages/rest_framework/views.py", line 489, in dispatch
response = self.handle_exception(exc)
File "/lib/python3.5/site-packages/rest_framework/views.py", line 449, in handle_exception
self.raise_uncaught_exception(exc)
File "/lib/python3.5/site-packages/rest_framework/views.py", line 486, in dispatch
response = handler(request, *args, **kwargs)
File "/application/siop/views/API/product.py", line 184, in partial_update
return Response(serializer.data, status=status.HTTP_200_OK)
File "/lib/python3.5/site-packages/rest_framework/serializers.py", line 739, in data
ret = super(ListSerializer, self).data
File "/lib/python3.5/site-packages/rest_framework/serializers.py", line 265, in data
self._data = self.to_representation(self.validated_data)
File "/lib/python3.5/site-packages/rest_framework/serializers.py", line 657, in to_representation
self.child.to_representation(item) for item in iterable
File "/lib/python3.5/site-packages/rest_framework/serializers.py", line 657, in <listcomp>
self.child.to_representation(item) for item in iterable
File "/lib/python3.5/site-packages/rest_framework/serializers.py", line 488, in to_representation
attribute = field.get_attribute(instance)
File "/lib/python3.5/site-packages/rest_framework/fields.py", line 464, in get_attribute
raise type(exc)(msg)
KeyError: "Got KeyError when attempting to get a value for field `user` on serializer `BulkProductSerializer`.\nThe serializer field might be named incorrectly and not match any attribute or key on the `OrderedDict` instance.\nOriginal exception text was: 'fk_user'."
Run Code Online (Sandbox Code Playgroud)
在追溯的L657(来源这里),我有:
iterable = data.all() if isinstance(data, models.Manager) else data
return [
self.child.to_representation(item) for item in iterable
]
Run Code Online (Sandbox Code Playgroud)
这让我想知道(在追踪中进一步挖掘)为什么serializer.fields不可用.我怀疑这是因为序列化器是CustomProductListSerializer父母,而不是BulkProductSerializer孩子,我是对的.在返回之前的pdb跟踪中Response(serializer.data):
(Pdb) serializer.fields
*** AttributeError: 'CustomProductListSerializer' object has no attribute 'fields'
(Pdb) serializer.child.fields
{'uuid': UUIDField(read_only=False, required=False, validators=[]) ...(etc)}
(Pdb) 'user' in serializer.child.fields
True
(Pdb) serializer.data
*** KeyError: "Got KeyError when attempting to get a value for field `user` on serializer `BulkProductSerializer`.\nThe serializer field might be named incorrectly and not match any attribute or key on the `OrderedDict` instance.\nOriginal exception text was: 'fk_user'."
(Pdb) serializer.child.data
{'uuid': '08ec13c0-ab6c-45d4-89ab-400019874c63', ...(etc)}
Run Code Online (Sandbox Code Playgroud)
好的,那么在我serializer.data所描述的情况下,获取完整并将其返回到父序列化程序类的resopnse中的正确方法是什么?partial_updateViewSet
编辑:
class CustomProductListSerializer(serializers.ListSerializer):
def save(self):
instances = []
result = []
pdb.set_trace()
for obj in self.validated_data:
uuid = obj.get('uuid', None)
if uuid:
instance = get_object_or_404(Product, uuid=uuid)
# Specify which fields to update, otherwise save() tries to SQL SET all fields.
# Gotcha: remove the primary key, because update_fields will throw exception.
# see https://stackoverflow.com/a/45494046
update_fields = [k for k,v in obj.items() if k != 'uuid']
for k, v in obj.items():
if k != 'uuid':
setattr(instance, k, v)
instance.save(update_fields=update_fields)
result.append(instance)
else:
instances.append(Product(**obj))
if len(instances) > 0:
Product.objects.bulk_create(instances)
result += instances
return result
Run Code Online (Sandbox Code Playgroud)
正如评论中提到的,我仍然认为异常可能是因为BulkProductSerializer类中的用户字段,并不是真的与ListSerializer
如文档中提到的有可能是在串行DRF另一个小错误(但很重要)这里.以下是如何指定list_serializer_class:
class CustomListSerializer(serializers.ListSerializer):
...
class CustomSerializer(serializers.Serializer):
...
class Meta:
list_serializer_class = CustomListSerializer
Run Code Online (Sandbox Code Playgroud)
请注意,它是在Meta类内部指定的,而不是在外部指定的.所以我认为在你的代码中,切换到List Serializer并不明白many=True.这应该导致不更新的问题.
似乎问题更多的是关于实现嵌套List Serializer更新的通用方法而不是实际错误.因此,我将尝试提供示例代码.
一些说明:
这很简单
class Product(models.Model):
name = models.CharField(max_length=200)
user = models.ForeignKey(User, null=True, blank=True)
def __unicode__(self):
return self.name
Run Code Online (Sandbox Code Playgroud)
class ProductViewSet(viewsets.ModelViewSet):
serializer_class = ProductSerializer
queryset = Product.objects.all()
def get_serializer(self, *args, **kwargs):
# checking for post only so that 'get' won't be affected
if self.request.method.lower() == 'post':
data = kwargs.get('data')
kwargs['many'] = isinstance(data, list)
return super(ProductViewSet, self).get_serializer(*args, **kwargs)
Run Code Online (Sandbox Code Playgroud)
class ProductListSerializer(serializers.ListSerializer):
def create(self, validated_data):
new_products = [Product(**p) for p in validated_data if not p.get('id')]
updating_data = {p.get('id'): p for p in validated_data if p.get('id')}
# query old products
old_products = Product.objects.filter(id__in=updating_data.keys())
with transaction.atomic():
# create new products
all_products = Product.objects.bulk_create(new_products)
# update old products
for p in old_products:
data = updating_data.get(p.id, {})
# pop id to remove
data.pop('id')
updated_p = Product(id=p.id, **data)
updated_p.save()
all_products.append(updated_p)
return all_products
class ProductSerializer(serializers.ModelSerializer):
user = serializers.SlugRelatedField(slug_field='username', queryset=User.objects.all())
id = serializers.IntegerField(required=False)
class Meta:
model = Product
fields = '__all__'
list_serializer_class = ProductListSerializer
Run Code Online (Sandbox Code Playgroud)
在跟踪中我尝试访问serializer.data并获取 KeyError 的地方,我注意到serializer.data仅包含来自 的键/值对initial_data,而不包含实例数据(因此,我认为是 KeyError;某些模型字段的键不存在为这是一个partial_update请求)。但是,serializer.child.data确实包含列表中最后一个子项的所有实例数据。
因此,我转到定义的rest_framework/serializers.py源代码data:
249 @property
250 def data(self):
251 if hasattr(self, 'initial_data') and not hasattr(self, '_validated_data'):
252 msg = (
253 'When a serializer is passed a `data` keyword argument you '
254 'must call `.is_valid()` before attempting to access the '
255 'serialized `.data` representation.\n'
256 'You should either call `.is_valid()` first, '
257 'or access `.initial_data` instead.'
258 )
259 raise AssertionError(msg)
260
261 if not hasattr(self, '_data'):
262 if self.instance is not None and not getattr(self, '_errors', None):
263 self._data = self.to_representation(self.instance)
264 elif hasattr(self, '_validated_data') and not getattr(self, '_errors', None):
265 self._data = self.to_representation(self.validated_data)
266 else:
267 self._data = self.get_initial()
268 return self._data
Run Code Online (Sandbox Code Playgroud)
第 265 行有问题。serializer.child.to_representation({'uuid': '87956604-fbcb-4244-bda3-9e39075d510a', 'product_code': 'foobar'})我可以通过在断点处调用来复制错误。
调用partial_update()在单个实例上工作正常(因为self.instance已设置,所以self.to_representation(self.instance)有效)。但是,对于批量partial_update() 实现,self.validated_data缺少模型字段,并且to_representation()无法工作,因此我将无法访问该.data属性。
一种选择是维护某种Product 实例列表,并覆盖第 265 行self.instances的定义:data
self._data = self.to_representation(self.instances)
Run Code Online (Sandbox Code Playgroud)
不过,我真的更希望来自对此类问题更有经验的人的回答,因为我不确定这是否是一个明智的解决方案,因此我将悬赏开放,希望有人可以建议一些更聪明的事情。
| 归档时间: |
|
| 查看次数: |
6504 次 |
| 最近记录: |