如何在DRF中访问ListSerializer父类上的serializer.data? [英] How to access serializer.data on ListSerializer parent class in DRF?

查看:60
本文介绍了如何在DRF中访问ListSerializer父类上的serializer.data?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在访问 serializer.data 并在 Response(serializer.data,status = something)中返回之前,我遇到错误。


尝试获取字段< field>的值时获取KeyError; 在序列化器< serializer> 上。

在所有字段上都会发生这种情况(因为事实证明我试图在父级而不是子级上访问 .data ,请参见下文)

This occurs on all fields (because it turns out I'm trying to access .data on the parent and not the child, see below)

类定义如下:

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',
            ...,
        )

CustomProductListSerializer serializers.ListSerializer 并具有重写的 save()方法,该方法可以正确处理批量创建和更新。

CustomProductListSerializer is a serializers.ListSerializer and has an overridden save() method that allows it to correctly handle bulk create and update.

以下是批量产品 ViewSet 的示例视图:

Here's an example view from the bulk Product 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)

尝试访问<$跟踪中的c $ c> serializer.data (或后面的行)会导致错误。这是完整的跟踪记录(tl; dr跳过下面用调试器诊断的位置):

Trying to access serializer.data at the trace (or the line after, obviously) causes the error. Here's the full trace (tl;dr skip below where I diagnose with debugger):

 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'."

在回溯的L657(此处的资源)我得到了:

At the L657 of the traceback (source here) I've got:

iterable = data.all() if isinstance(data, models.Manager) else data
return [
    self.child.to_representation(item) for item in iterable
]

这让我想知道(深入探究)为什么serializer.fields不可用。我怀疑这是因为序列化程序是 CustomProductListSerializer 的父级,而不是 BulkProductSerializer 的子级,我是对的。在返回 Response(serializer.data)之前的pdb跟踪中:

This made me wonder (digging further down in the trace) why the serializer.fields were not available. I suspected it was because the serializer was a CustomProductListSerializer parent, and not a BulkProductSerializer child, and I was right. In the pdb trace just before returning the 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)}

确定,那么获取完整的 serializer.data 并将其返回的正确方法是什么在我的 ViewSet 中的 partial_update 描述的情况下,对父序列化器类的响应?

OK, so what's the right way to get the complete serializer.data and return it in the resopnse for the parent serializer class in the situation described by partial_update in my ViewSet?

编辑:

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


推荐答案

在跟踪中尝试访问 serializer.data 的位置KeyError,我注意到 serializer.data 仅包含 initial_data 中的键/值对,而不包含实例数据(因此,我想是KeyError;某些模型字段的键不存在,因为它是 partial_update 请求)。但是, serializer.child.data 确实包含列表中最后一个孩子的所有实例数据。

At the point in the trace where I try to access serializer.data and get the KeyError, I note that serializer.data only contains key/vaule pairs from the initial_data, not the instance data (hence, I suppose, the KeyError; some model fields' keys are not present as it is a partial_update request). However, serializer.child.data does contain all the instance data for the last child in the list.

因此,我转到 rest_framework / serializers.py 其中定义了数据

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

第265行有问题。我可以通过调用 serializer.child.to_representation({'uuid':'87956604-fbcb-4244-bda3-9e39075d510a','product_code':'foobar'})复制该错误

Line 265 is problematic. I can replicate the error by calling serializer.child.to_representation({'uuid': '87956604-fbcb-4244-bda3-9e39075d510a', 'product_code': 'foobar'}) at the breakpoint.

在单个实例上调用 partial_update()可以正常工作(因为<$ c $设置了c> self.instance self.to_representation(self.instance)有效)。但是,对于批量partial_update()实现, self.validated_data 缺少模型字段,而 to_representation()会'不能正常工作,因此我将无法访问 .data 属性。

Calling partial_update() works fine on a single instance (because self.instance is set, self.to_representation(self.instance) works). However, for a bulk partial_update() implementation, self.validated_data is missing model fields, and to_representation() won't work, so I won't be able to access the .data property.

一个选择是维护某种 self.instances 产品实例列表,并在第265行覆盖 data 的定义:

One option would be to maintain some sort of self.instances list of Product instances, and override the definition of data on line 265:

self._data = self.to_representation(self.instances)

我真的希望有一个在这种问题上更有经验的人提供答案,因为我不确定这是否是一个明智的解决方案,因此我会悬赏希望有人可以提出更明智的建议。

I'd really prefer an answer from someone more experienced in this sort of problem though, as I'm not sure if that's a sensible solution, hence I'm leaving the bounty open in the hope that someone can suggest something smarter to do.

这篇关于如何在DRF中访问ListSerializer父类上的serializer.data?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆