【问题标题】:Django DRY Model/Form/Serializer ValidationDjango DRY 模型/表单/序列化程序验证
【发布时间】:2023-03-12 20:37:01
【问题描述】:

我在确定在 Django 中引入验证逻辑的最佳(阅读:DRY 和可维护)位置时遇到了一些问题,即在模型、表单和 DRF 序列化程序之间。

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

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

  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 issue #4306 完美地总结了这一特殊挑战。

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

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

我很想请一些 Django 专家来评估他们的工作,因为我没有看到任何方便的解决方案。

谢谢。

【问题讨论】:

  • 你找到答案了吗?
  • 是的,我也很好奇!

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


【解决方案1】:

我同意,模型/序列化器/验证之间的链接已断开。

我发现的最佳 DRY 解决方案是在模型中保留验证,并在字段上指定验证器,然后如果需要,覆盖 clean() 中的模型级别验证。

然后在序列化程序中,覆盖验证并调用模型clean(),例如在MySerializer:

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

这不是很好,但我更喜欢它而不是序列化程序和模型中的 2 级验证。

【讨论】:

    【解决方案2】:

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

    这是一个可以解决问题的快速 sn-p:

    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() 方法,因此不会被验证。对此没什么可做的,因为这些方法实际上是通过跳过一堆数据库调用来优化性能——所以如果你使用它们,请注意它们的影响。

    【讨论】:

      【解决方案3】:

      只是想补充 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)
      

      然后按照他解释的方式使用它。 而现在调用save的时候,如果使用查询表达式就可以调用

      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

      【讨论】:

      • 您在 instance.save 块中缺少 ),您还需要提供类父类,在 Python 3 中 super 不需要参数。
      • 感谢添加。是的,我保留它 python 2。其他 super().save(**kwargs) 就足够了。
      猜你喜欢
      • 2020-08-14
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-05-23
      • 2018-02-10
      • 2018-11-18
      • 2017-10-20
      • 2018-07-06
      相关资源
      最近更新 更多