如何在 Django REST Framework 中验证完整模型数据

Mic*_*ays 5 python django django-rest-framework

长话短说

在我的方法中,如果传入数据中未设置它,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
Run Code Online (Sandbox Code Playgroud)

(这使用正常的Serializer,但想象它是模型ModelSerializer的a Event。)

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

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

client.patch(f"/events/{event.id}", {"start": "2021-01-01"})
Run Code Online (Sandbox Code Playgroud)

那么序列化器将会失败,因为尝试访问会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,
        }
Run Code Online (Sandbox Code Playgroud)

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

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

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

Scr*_*urr 8

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

我在模型层进行了验证检查,因为:

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

在模型层上验证它非常简单。您可以重写save模型类的方法或clean方法并在方法中运行该clean方法(或full_cleansave。更多详细信息请参见此处

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)
Run Code Online (Sandbox Code Playgroud)

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

# 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
Run Code Online (Sandbox Code Playgroud)
# settings.py

REST_FRAMEWORK = {
    ...,
    'EXCEPTION_HANDLER': 'myapp.exceptions.django_error_handler'
}
Run Code Online (Sandbox Code Playgroud)

最后的注释

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