[英]How to validate against full model data in 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
(这使用了普通的Serializer
,但想象一下它是Event
ModelSerializer
的 ModelSerializer。)
当创建或更新数据中包含start
和finish
属性的事件时,此序列化程序将按预期工作。
但是,如果您提出以下请求:
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 层中进行了验证检查,因为:
在 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.