【问题标题】:Django REST Framework (DRF): ModelSerializer does not validate models on serializationDjango REST Framework (DRF):ModelSerializer 不验证序列化模型
【发布时间】:2020-05-23 16:51:53
【问题描述】:

我想问一下如何正确使用 Django REST Framework (DRF) ModelSerializers 来从模型序列化。

我有两个必填字段的 Django 模型:

class Book(models.Model):
    title = models.CharField()
    desc = models.CharField()

我有 DRF ModelSerializer:

class BookSerializer(serializers.ModelSerializer):
    class Meta:
        model = Book
        fields = ['title', 'desc']

我可以使用以下方法对传入的请求进行反序列化和验证:

serializer = BookSerializer(data=request.data)
serializer.is_valid(raise_exception=True)

但是如何序列化和发送响应呢? DRF 允许我断开使用 ModelSerializer 建立的联系。如果我忘记设置必填的 Book 字段之一,它仍然会通过 BookSerializer!

invalid_book = Book(title="Foo")    # but forgotten to set "desc"
serializer = BookSerializer(instance=invalid_book)
serializer.data        # it contains book without required "desc"

如果我尝试is_validate(),使用实例参数创建的序列化会引发错误。

为什么 ModelSerializer 可以验证传入的数据,但不能传出?

【问题讨论】:

  • 当您的验证器在保存它们时没有捕获它们时,会保存数据库中的无效数据。因此,您应该对此进行处理,以免在您的数据库中保存无效数据。顺便说一句,当数据无效时你想做什么?

标签: django-rest-framework


【解决方案1】:

仅在反序列化时执行验证。根据documentation

序列化器还提供反序列化,允许在首先验证传入数据后将解析的数据转换回复杂类型。

这是有道理的(edit:在 Django Rest Framework 似乎被解释的方式中)。因为序列化程序的“角色”不是确保您将要序列化的复杂数据(例如查询集和模型实例(例如您的Book 实例)被“合法地”解释),因此它们也不会'在序列化时不验证。

因此,如果您像 invalid_book.save() 这样保存实例,Django 会因为缺少字段而引发错误。

编辑

在评论成为“观点”并因此被认为之后,我想强调并明确指出这似乎是 Django Rest Framework (DRF) 的解释方式。在深入挖掘 SO 之后,我链接 this answer 支持。

此外,如果您阅读了 DRF 的文档,则在某种程度上暗示 serializationvalidation 是两个独立的概念。

此外,analyzingserializers.py 明确表示仅在调用 is_valid() 时运行验证,并且仅在提供的 data 标志上运行验证。事实上,只提供一个实例时,它甚至无法运行:

def __init__(self, instance=None, data=empty, **kwargs):
    self.instance = instance
    if data is not empty:
        self.initial_data = data
    self.partial = kwargs.pop('partial', False)
    self._context = kwargs.pop('context', {})
    kwargs.pop('many', None)
    super().__init__(**kwargs)

...

def is_valid(self, raise_exception=False):
    assert hasattr(self, 'initial_data'), (
        'Cannot call `.is_valid()` as no `data=` keyword argument was '
        'passed when instantiating the serializer instance.'
    )

    if not hasattr(self, '_validated_data'):
        try:
            self._validated_data = self.run_validation(self.initial_data)
        except ValidationError as exc:
            self._validated_data = {}
            self._errors = exc.detail
        else:
            self._errors = {}

    if self._errors and raise_exception:
        raise ValidationError(self.errors)

    return not bool(self._errors)

【讨论】:

  • 我不能同意这种“观点”。序列化器是我提供给外部世界的合同。诸如确保无效数据不会被接受但也不会出现在 API 之外的障碍之类的东西。
  • 所以答案是“ModerSerialer 无法验证序列化,只能验证反序列化”。对吗?
【解决方案2】:

您的假设非常错误。序列化器(不是反序列化器)只做一件事。将对象转换为 JSON。在这里,您正在创建一个对象 Book(name='sad book')。这只是一个普通的 Python 对象。 Django 序列化器将尝试序列化任何传递给它的对象。

您可能想知道模型中需要该字段,但为什么序列化程序不验证?由于 DRF 处理序列化的方式。我将展示一些 DRF 源代码的摘录。

这是计算数据属性的方式。

class BaseSerializer():
...
...
@property
def data(self):
    if hasattr(self, 'initial_data') and not hasattr(self, '_validated_data'):
        msg = (
            'When a serializer is passed a `data` keyword argument you '
            'must call `.is_valid()` before attempting to access the '
            'serialized `.data` representation.\n'
            'You should either call `.is_valid()` first, '
            'or access `.initial_data` instead.'
        )
        raise AssertionError(msg)


    if not hasattr(self, '_data'):
        if self.instance is not None and not getattr(self, '_errors', None):
            # THIS IS WHERE WE GO. THE to_representation() CAN BE FOUND IN THE IMPLEMENTATION
            # OF ModelSerializer() which inherits from this class BaseSerializer
            self._data = self.to_representation(self.instance)
        elif hasattr(self, '_validated_data') and not getattr(self, '_errors', None):
            self._data = self.to_representation(self.validated_data)
        else:
            self._data = self.get_initial()
    return self._data

ModelSerializer.to_representation() 中发生了什么?

class ModelSerializer(BaseSerializer):
    ...
    ...

    def to_representation(self, instance):
    """
    Object instance -> Dict of primitive datatypes.
    """
    ret = OrderedDict()
    fields = self._readable_fields

    for field in fields:
        try:
            attribute = field.get_attribute(instance)
        except SkipField:
            continue

        # We skip `to_representation` for `None` values so that fields do
        # not have to explicitly deal with that case.
        #
        # For related fields with `use_pk_only_optimization` we need to
        # resolve the pk value.
        check_for_none = attribute.pk if isinstance(attribute, PKOnlyObject) else attribute
        if check_for_none is None:
            ret[field.field_name] = None
        else:
            ret[field.field_name] = field.to_representation(attribute)

    return ret

如您所见,在这种情况下,序列化程序仅映射正在传递的对象中的字段。因此,序列化期间没有验证。有关更多信息,请查看 DRF 的源代码。如果你使用 Pycharm Pro,这很容易。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2020-07-29
    • 2023-03-18
    • 2018-07-06
    • 1970-01-01
    • 2013-11-27
    • 2015-10-22
    • 2017-03-21
    • 1970-01-01
    相关资源
    最近更新 更多