Django DRY模型/表格/序列化验证

时间:2016-11-19 20:36:07

标签: python django validation serialization django-rest-framework

我有一些问题要弄清楚在Django中引入验证逻辑的最佳(读取:DRY和可维护),即在模型,表单和DRF序列化器之间。

我已经与Django合作多年,并且一直遵循处理模型,表单和REST API端点验证的各种约定。我已经尝试了很多变体来确保整体数据的完整性,但我最近遇到了一些绊脚石。以下是我查看了许多文章,SO帖子和门票后我尝试过的简要列表:

  1. 模型级别的验证;即,通过覆盖myModel.save()(以及特定于字段和唯一的方法),在调用myModel.clean()之前确保所有自定义约束都匹配。为此,我确保在myModel.full_clean()中调用myForm.clean()(对于表单 - 管理面板实际上已经执行此操作)和mySerializer.validate()(对于DRF序列化程序)方法。

  2. 在表单和序列化程序级别进行验证,为可维护的DRY代码调用共享方法。

  3. 在表单和序列化程序级别进行验证,每种方法都有一个独特的方法,以确保最大的灵活性(即表单和端点具有不同的约束时)。

  4. 当表单和序列化程序具有相同的约束时,方法一对我来说似乎最直观,但在实践中有点混乱;首先,数据由表单或序列化程序自动清理和验证,然后实例化模型实体,再次运行更多验证 - 这有点复杂,可能会变得复杂。

    方法三是Django Rest Framework从3.0版开始推荐的;他们消除了很多model.save()钩子,并且更愿意将验证留给应用程序面向用户的方面。这对我来说很有意义,因为Django的基础model.save()实现无论如何都不会调用model.full_clean()

    所以,方法二对我来说似乎是最好的整体推广结果;验证生活在一个独特的地方 - 在触摸模型之前 - 由于共享验证逻辑,代码库不那么杂乱/更干。

    不幸的是,我遇到的大部分麻烦都是让Django Rest Framework的序列化程序合作。这三种方法都适用于表单,实际上适用于大多数HTTP方法(最值得注意的是在POST实体创建时) - 但在更新现有实体(PUT,PATCH)时似乎都没有。

    长话短说,事实证明,当输入数据不完整时(或其他方面有效 - 通常是PATCH的情况)验证输入数据相当困难。请求数据可以仅包含一些字段 - 包含不同/新信息的字段 - 并且为所有其他字段维护模型实例的现有信息。事实上,DRF issue #4306完美地总结了这一特殊挑战。

    我还考虑在视图级别运行自定义模型验证(填充serializer.validated_data并且serializer.instance存在之后,但在调用serializer.save()之前),但我还在努力由于处理更新的复杂性,提出了一种干净,通用的方法。

    TL; DR Django Rest Framework使得在一个显而易见的地方编写干净,可维护的验证逻辑变得有点困难,特别是对于依赖于现有模型数据和传入请求数据的混合的部分更新

    我很想让一些Django大师对他们的工作产生影响,因为我没有看到任何方便的解决方案。

    感谢。

3 个答案:

答案 0 :(得分:1)

I agree, the link between models/serializers/validation is broken.

The best DRY solution I've found is to keep validation in model, with validators specified on fields, then if needed, model level validation in clean() overridden.

Then in serializer, override validate and call the model clean() e.g. in MySerializer:

def validate(self, data):
    instance = FooModel(**data)
    instance.clean()
    return data

It's not nice, but I prefer this to 2-level validation in serializer and model.

答案 1 :(得分:0)

刚才意识到我从未将我的解决方案发回这个问题。我最后编写了一个模型mixin,以便在保存之前始终运行验证;它有点不方便,因为验证在技术上会以Django的形式运行两次(即在管理面板中),但它让我保证运行验证 - 无论触发模型保存的是什么。我通常不使用Django的表单,因此这对我的应用程序没有太大影响。

这是一个快速的代码片段:

class ValidatesOnSaveModelMixin:
    """ ValidatesOnSaveModelMixin
    A mixin that ensures valid model state prior to saving.
    """
    def save(self, **kwargs):
        self.full_clean()
        super(ValidatesOnSaveModelMixin, self).save(**kwargs)

以下是您使用它的方式:

class ImportantModel(ValidatesOnSaveModelMixin, models.Model):
    """ Will always ensure its fields pass validation prior to saving. """

有一个重要的警告:任何Django的直接数据库操作(即ImportantModel.objects.update())都不会调用模型的save()方法,因此不会被验证。关于这一点没什么可做的,因为这些方法实际上是通过跳过一堆数据库调用来优化性能 - 所以如果你使用它们就要注意它们的影响。

答案 2 :(得分:0)

只想补充SamuelMS的答案。 如果您使用F()表达式和类似的表达式。如here所述,这将失败。

class ValidatesOnSaveModelMixin:
    """ ValidatesOnSaveModelMixin
    A mixin that ensures valid model state prior to saving.
    """
    def save(self, **kwargs):
        if 'clean_on_save_exclude' in kwargs:
             self.full_clean(exclude=kwargs.pop('clean_on_save_exclude', None)
        else:
             self.full_clean()
        super(ValidatesOnSaveModelMixin, self).save(**kwargs)

然后按照他解释的相同方式使用它。 现在,当调用保存时,如果您使用查询表达式就可以调用

instance.save(clean_on_save_exclude=['field_name'])

就像您要调用full_clean并排除带有查询表达式的字段一样。 参见https://docs.djangoproject.com/en/2.2/ref/models/instances/#django.db.models.Model.full_clean