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

Sam*_*lMS 12 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. 在表单和序列化程序级别进行验证,每种方法都有一个独特的方法,以确保最大的灵活性(即当表单和端点具有不同的约束时).

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

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

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

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

简而言之,事实证明,当输入数据不完整时(或其他有效的 - 通常是PATCH的情况),验证输入数据相当困难.请求数据可能只包含一些字段 - 包含不同/新信息的字段 - 并且为所有其他字段维护模型实例的现有信息.事实上,DRF问题#4306完美地总结了这一特殊挑战.

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

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

我很想让一些Django大师权衡他们的工作,因为我没有看到任何方便的解决方案.

谢谢.

Sam*_*lMS 6

刚刚意识到我从未将我的解决方案发布回这个问题。我最终编写了一个模型混合,以便在保存之前始终运行验证;这有点不方便,因为从技术上讲,验证将在 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)
Run Code Online (Sandbox Code Playgroud)

以下是您如何使用它:

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

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