序列化器抱怨请求数据

时间:2019-04-17 18:28:59

标签: django django-rest-framework

我需要在请求中添加一些数据,所以我做了以下事情:

data = {'my_data': 1, **request.data}
...
serializer = MySerializer(data=data)
serializer.is_valid()

但是序列化程序抱怨字段的格式不正确:

  

“无效的字符串。”

所有这些都表示相同。有道理,因为我看到它正在创建一个填充有列表的字典:

{'attr1':[1], 'attr2':[2], ..., 'my_data':1}

没有任何意义的是,它可以正常工作:

serializer = MySerializer(data=request.data)
serializer.is_valid()

即使QueryDict对象也将所有字段都包装在列表中

我还尝试了以下方法:

data = {'my_data': [1], **request.data}

但是现在它也抱怨这个新领域。

我在做什么错了?

编辑:

仅需澄清一下,一种解决方法是仅解开所有项目:

data = {**{k: v for k, v in request.data.items()}}

但是,为什么序列化程序在普通字典和QueryDict上表现不同?

2 个答案:

答案 0 :(得分:0)

QueryDict不仅是一个以字段值作为列表的字典,而且是一个具有覆盖方法的复杂结构,这些方法定义了某些操作符的行为。以下是 MultiValueDict 中定义的getitem方法,该方法是 QueryDict 的基类。

def __getitem__(self, key):
    """
    Return the last data value for this key, or [] if it's an empty list;
    raise KeyError if not found.
    """
    try:
        list_ = super().__getitem__(key)
    except KeyError:
        raise MultiValueDictKeyError(key)
    try:
        return list_[-1]
    except IndexError:
        return []

使用此方法,即使键的值是列表,当您尝试从Querypct之类的

获取项目时,
my_query_dict[my_key]

返回列表中的最后一项。但是,当您在查询dict上使用**构建新字典时,您得到的只是带有条目作为列表的常规字典,因此,当您获得键的值时,您会返回一个列表。举例来说,让my_query_dict是一个QueryDict实例,并保留键“ a”的值[1]。

my_query_dict['a'] # returns 1
{**my_query_dict}['a'] # returns [1]

答案 1 :(得分:0)

有人可能会说,弄混request.data不是“正确的方法”。但是,如果仍然需要,最好创建可变的QueryDict副本:

data = request.data.copy()
data['my_data'] = 1

rest_framework具有许多mixin / generics / builtin-views,它们仅将序列化程序初始化为SerializerCls(data=request.data, instance=something_or_none, context={'request': view.request, 'view': view}。而且,如果您在多个视图中使用同一序列化程序,那么最好使序列化程序“智能”,而不要修改每个视图。

“智能”串行器应从“原始request.data / request”中计算所有额外数据。 “部分智能”序列化程序可能需要更改视图。

如果只需要在保存时为某个模型字段设置自定义值,则可以更改视图的方法perform_update / perform_create(如果使用的是GenericAPIView-s)< / p>

def perform_update(self, serializer):
    serializer.save(my_data=1)

如果您确实需要在验证过程中考虑此自定义数据:您可以使用上下文:

class MySerializer(...):
    def validate(self, data):
        if 'my_data' in self.context:
            data['my_data'] = self.context['my_data']
        if data.get('my_data'): 
            perform_some_extra_validation(data)
        return data

serializer = MySerializer(data=data, context={'my_data': 1})
serializer.is_valid()
# for ModelView-s you can change context by modifying `get_serializer_context` method

如果您可以根据my_datarequest动态计算request.user,则:

  • 您可以使用上下文直接在serializer.validate中进行操作:
class MySerializer(...):
    def validate(self, data):
        if 'request' in self.context:
            data['my_data'] = build_my_data_from_request(self.context['request'])
        return data

serializer = MySerializer(data=data, context={'request': request})
serializer.is_valid()

# in ModelView-s serializers are already initialized with 'request' in context
  • 或者您可以在字段中使用上下文和default arg的组合:


class MyDataDefault(object):
    def set_context(self, serializer_field):
        self.parent = serializer_field.parent

    def __call__(self):
        if 'request' in self.parent.context:
            return build_my_data_from_request(self.parent.context['request'])


class MySerializer(...):
    my_data = serializers.SomeField(default=MyDataDefault())


# if my_data should only be set during creation (not during update)
from rest_framework.serializers import CreateOnlyDefault

class AlternativeMySerializer(...):
    my_data = serializers.SomeField(default=CreateOnlyDefault(MyDataDefault()))


serializer = MySerializer(data=data, context={'request': request})
serializer.is_valid()
# in ModelView-s serializers are already initialized with 'request' in context

如果您使用的是ModelViewSet或其他基于GenericAPIView的视图,则get_serializer已经执行了类似于SerializerCls(data=request.data, instance=..., context={'request': request, 'view': self})的操作:

    def get_serializer(self, *args, **kwargs):
        """
        Return the serializer instance that should be used for validating and
        deserializing input, and for serializing output.
        """
        serializer_class = self.get_serializer_class()
        kwargs['context'] = self.get_serializer_context()
        return serializer_class(*args, **kwargs)

    ...


    def get_serializer_context(self):
        """
        Extra context provided to the serializer class.
        """
        return {
            'request': self.request,
            'format': self.format_kwarg,
            'view': self
        }