无论数据源如何,使用 Django/Django Rest Framework 验证和保存数据的正确过程是啥?

Posted

技术标签:

【中文标题】无论数据源如何,使用 Django/Django Rest Framework 验证和保存数据的正确过程是啥?【英文标题】:What is the proper process for validating and saving data with with Django/Django Rest Framework regardless the data source?无论数据源如何,使用 Django/Django Rest Framework 验证和保存数据的正确过程是什么? 【发布时间】:2018-06-21 13:15:38 【问题描述】:

我有一个特定的模型,我想对其执行自定义验证。我想保证在创建新实例时始终存在至少一个标识符字段,这样就不可能在没有这些字段之一的情况下创建实例,尽管没有特别需要单独的字段。

from django.db import models

class Security(models.Model):
    symbol = models.CharField(unique=True, blank=True)
    sedol = models.CharField(unique=True, blank=True)
    tradingitemid = models.Charfield(unique=True, blank=True)

无论原始数据来自何处(例如,API 帖子或从其他来源(如 .csv 文件)获取此数据的内部函数),我都想要一种干净、可靠的方法。

我知道我可以覆盖模型的 .save() 方法并执行验证,但最佳实践表明 here 建议在 .save() 方法中引发验证错误是一个坏主意,因为视图只会返回 500响应而不是向发布请求返回验证错误。

我知道我可以使用 Django Rest Framework 为该模型定义一个带有 validator 的自定义序列化程序来验证数据(这对于创建对象的 ModelViewSet 来说是一个很好的解决方案,我可以保证这个序列化程序是每次使用)。但是这种数据完整性保证只在那个 API 端点上是好的,然后就像开发人员记住每次在代码库的其他地方创建对象时都使用该序列化程序一样好(对象可以在整个代码库中从源之外的源创建Web API)。

我也熟悉 Django 的.clean() and .full_clean() methods。这些似乎是完美的解决方案,但它再次依赖于开发人员始终记得调用这些方法——这种保证与开发人员的记忆力一样好。我知道在使用 ModelForm 时会自动调用这些方法,但同样,对于我的用例模型,也可以从 .csv 下载中创建——我需要一个通用的保证,这是最佳实践。我可以将 .clean() 放在模型的 .save() 方法中,但是这个 answer(以及帖子中的相关 cmets 和链接)似乎使这种方法引起争议,并且可能是一种反模式。

是否有一种干净、直接的方法可以保证如果没有以下三个字段之一,该模型永远不会被保存:1. 不会通过视图引发 500 错误,2. 不依赖于开发人员在创建对象时在整个代码库中明确使用正确的序列化程序,以及 3. 不依赖于将 .clean() 调用侵入模型的 .save() 方法(看似反模式)?我觉得这里必须有一个干净的解决方案,而不是在序列化程序中进行一些验证,一些在 .clean() 方法中,破解 .save() 方法来调用 .clean() (它会通过 ModelForms 的保存被调用两次)等...

【问题讨论】:

【参考方案1】:

您当然可以想象save() 为您承担双重职责并为您处理验证的设计。由于各种原因(部分总结在链接here 中),Django 决定将其分为两步。因此,我同意您发现的共识,即尝试将验证硬塞到Model.save() 是一种反模式。它与 Django 的设计背道而驰,并且可能会在未来引起问题。

您已经找到了“完美解决方案”,即使用Model.full_clean() 进行验证。我不同意你的观点,记住这对开发人员来说是个负担。我的意思是,记住做任何正确的事情可能很困难,尤其是对于一个庞大而强大的框架,但这个特殊的事情很简单,有据可查,并且是 Django 的 ORM 设计的基础。

当您考虑到实际上对开发人员来说可能很困难时尤其如此,这就是错误处理本身。这不像开发人员可以做model.validate_and_save()。相反,他们必须这样做:

try:
    model.validate_and_save()
except ValidationError:
    # handle error - this is the hard part

而 Django 的成语是:

try:
    model.full_clean()
except ValidationError:
    # handle error - this is the hard part
else:
    model.save()

我不觉得 Django 的版本更难。 (也就是说,没有什么能阻止您编写自己的 validate_and_save 便捷方法。)

最后,我建议也为您的要求添加一个数据库约束。当您添加一个知道如何在数据库级别强制执行的约束时,这就是 Django 所做的。例如,当您在字段上使用unique=True 时,Django 将创建数据库约束并添加 Python 代码来验证该要求。但是如果你想创建一个 Django 不知道的约束,你可以自己做同样的事情。除了在clean() 中编写您自己的Python 版本之外,您只需编写一个Migration 来创建适当的数据库约束。这样,如果您的代码中存在错误并且未完成验证,您最终会得到一个未捕获的异常 (IntegrityError) 而不是损坏的数据。

【讨论】:

很好的答案,@Kevin——谢谢!看起来正确使用 Djanjo try/except ValidationError/else save 成语是解决方案的重要组成部分。为了让数据库保证我不能写入损坏的数据,您认为最好编写自定义迁移还是简单地将验证逻辑添加到模型代码中明确的 .save() 方法而不是隐藏在迁移中?或者有没有办法使用 Django 在迁移之外编写这个约束?我担心没有人关注迁移,因此它可能不是代码的最佳位置。感谢您的精彩回答! 在这里尝试了解保证数据库级别完整性的最佳方法。回答我自己的评论似乎在.save() 方法中添加验证不是最佳的,因为.save() 没有在queryset.update() 上调用,所以我仍然容易收集无效数据。 (值得注意的是,Django Admin 界面使用queryset.update(),因此如果在.save() 级别进行验证,这将为无效数据敞开大门。)您能否发布并解释针对我的模型的数据库级别完整性约束的自定义django 迁移?跨度> @ColtonHicks:如果我不清楚,对不起。随意忽略最后一段。您的问题的答案是将验证放入clean()not 放入save()。我在最后一段中要说的是,如果您的规则是可以在数据库级别(带有 SQL 约束)表达的规则,那么 这样做会很有用。它为您的数据提供额外的保护,适用于任何框架,可以提供性能改进等。数据库约束是数据库定义的一部分(因此是迁移),它不是 Python 并且不会在每次保存时完成, 最后一段非常有帮助!您的回答建议了我想要的数据完整性保证的两个部分:1. 在整个代码中使用try: model.full_clean(), except: ValidationError, else: .save() 模式。 2. 实现一个数据库级别的约束,禁止在这些字段之一为空白的情况下保存我的模型(以防在我的代码模式之外输入数据,例如 Django 管理界面)。如果可以的话,我会很感激你最后一段的代码示例,展示了如何实现这个数据库级别的约束——我想你会完全解决这个问题:) @ColtonHicks:我发现this blog post 准确地解释了我在说什么。如果您无法确定特定约束的 SQL,我建议发布一个关于该约束的新问题并标记您的数据库(因为语法可能因一种而异)。

以上是关于无论数据源如何,使用 Django/Django Rest Framework 验证和保存数据的正确过程是啥?的主要内容,如果未能解决你的问题,请参考以下文章

Django - Django ORM 如何管理用户在数据库中上传的表

django-1-新手如何使用django

Django---Django返回用户输入数据

如何使用WSGI部署Django

如何将 django-admin-bootstrapped 与 django 1.10 集成

django的单元测试怎么用