繁体   English   中英

如何在 Django REST 框架中验证完整的 model 数据

[英]How to validate against full model data in Django REST Framework

TL;博士

在我的Serializer.validate方法中,如果传入数据中没有设置,我需要能够访问attrs['field']并回self.instance.field ,我想知道是否有一个通用模式这样做。

问题

以 DRF 序列化器文档的对象级验证部分为例:

from rest_framework import serializers

class EventSerializer(serializers.Serializer):
    description = serializers.CharField(max_length=100)
    start = serializers.DateTimeField()
    finish = serializers.DateTimeField()

    def validate(self, attrs):
        """
        Check that start is before finish.
        """
        if attrs['start'] > attrs['finish']:
            raise serializers.ValidationError("finish must occur after start")
        return attrs

(这使用了普通的Serializer ,但想象一下它是Event ModelSerializer的 ModelSerializer。)

当创建或更新数据中包含startfinish属性的事件时,此序列化程序将按预期工作。

但是,如果您提出以下请求:

client.patch(f"/events/{event.id}", {"start": "2021-01-01"})

然后序列化器将失败,因为尝试访问attrs['finish']会导致KeyError 在这种情况下,我需要能够回self.instance.finish ,因为start < finish验证仍然是必要的。

是否有解决此问题的通用模式?

当前解决方案

您可以通过在所有validate方法的开头添加一个片段来解决此问题:

def validate(self, attrs):
    full_attrs = attrs
    if self.instance is not None:
        full_attrs = {
            **self.to_internal_value(self.__class__(self.instance).data),
            **full_attrs,
        }

然后使用full_attrs代替attrs 这会将实例数据的序列化版本添加到attrs

有没有更好的方法来实现这一点?

(这样做的一个“缺点”是,如果数据失去完整性,它可能会阻止其他有效更新。因此,例如,如果开发人员直接更新数据库以使event.start晚于event.finish ,则 API 用户将不再能够更新event.description因为此验证将失败。但我认为优点肯定大于缺点,至少对于我当前的用例而言。)

我将就这个问题提出我的看法,因为我在我的一个项目中遇到了这个问题。

我在 model 层中进行了验证检查,因为:

  1. 您不再需要在序列化程序层上运行验证检查。
  2. 验证逻辑更接近数据库层,因此如果有人决定使用 django 的 ORM 并从后端创建对象(例如导入脚本),您不必担心会创建“坏”数据。
  3. 您的验证逻辑更接近正在创建/保存 object 的代码,因此更易于调试

在 model 层上验证它非常简单。 您可以覆盖 model class 的save方法或clean方法并在save方法中运行clean方法(或full_clean )。 更多细节在这里

from django.db import models
from django.core.exceptions import ValidationError


class MyModel(models.Model):
    start = ...
    finish = ...
    ...

    def clean(self):
        if self.finish < self.start:
            raise ValidationError("Finish must occur after start")

    def save(self, *args, **kwargs):
        self.full_clean()
        super().save(*args, **kwargs)

现在这里是关于 django 的 ValidationError 的事情。 DRF 不知道如何处理它。 如果您将一些无效数据传递给序列化程序,您将不会得到很好的 400 响应。 为了让 DRF 处理错误,您编写自己的自定义错误处理程序并将其设置为您的settings.py中的EXCEPTION_HANDLER

# myapp/exceptions.py

from django.core.exceptions import ValidationError

from rest_framework.views import exception_handler
from rest_framework.response import Response


def django_error_handler(exc, context):
    """Handle django core's errors."""
    # Call REST framework's default exception handler first,
    # to get the standard error response.
    response = exception_handler(exc, context)
    if response is None and isinstance(exc, ValidationError):
        return Response(status=400, data=exc.message_dict)
    return response
# settings.py

REST_FRAMEWORK = {
    ...,
    'EXCEPTION_HANDLER': 'myapp.exceptions.django_error_handler'
}

最后注

我注意到您正在使用通用serializers.Serializer化程序。序列化程序 class 用于您的序列化程序。 如果您已经有一个Event model,使用serializers.ModelSerializer会更容易,因为这会抽象出许多 object 创建/更新逻辑。 另一个好处是,由于它会查看模型的字段定义,它会根据您在 model 中指定字段的方式构建字段,因此您无需在序列化程序中定义字段(例如,如果字段有max_length ,它将创建具有最大长度的相应 DRF 字段)。

而是通过使用CheckConstraint在数据库级别另外强制执行完整性,这样您就不必担心引入脏数据

class Meta:
        constraints = [
            models.CheckConstraint(
                check=models.Q(
                    finish > models.F("start")     
                    )
                ),
                name="start_gt_finish",
            )
        ]

要回答验证的选择,实际上没有最佳解决方案。 如果您的应用程序是 ModelForms / Django admin 和ModelForms的混合体,则可能很想通过调用full_clean()调用 go 但这可能也有问题,因为您需要在某处修补它,因为validate()不会调用full_clean()也不会正确转换所有 Django ValidationErrors


我会说您的解决方案看起来不错并用作序列化程序验证层,如果没有数据库约束,您可能会检查attrs中是否不存在 start 和 stop 以避免验证检查修补的数据是否不包含 start 或 stop

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM