Py学习  »  Django

为百分比字段的总和使用Django检查约束

Python程序员 • 4 年前 • 282 次点击  

之前我曾介绍过使用Django的CheckConstraint类来确保将一个带有choices的字段限制为仅有效值。这是另一个用例,基于我开发的一个应用程序。它使用一个检查约束来确保代表百分比的一组字段的总和总是100。


添加检查约束


假设有这样一个图书应用程序,我们要在其中对以下三个类别中的图书百分比进度进行跟踪:

  • 我们已阅读的页面

  • 我们剩下要阅读的页面

  • 我们故意选择忽略的页面


我们可以使用一个Django模型为这三个类别中的每一个设置一个字段:



使用PositiveIntegerField意味着没有字段包含小于0的数字。这是一个很好的开始,但是字段仍然可以存储大于100的数字,或者它们的总和可能小于或大于100。


使用一个检查约束,我们可以强制执行此类约束,告诉数据库阻止不良数据的存储。在这个示例中,我们只需要强制要求它们的总和为100,从而自动地将各个字段限制在0到100之间。


我们在模型的Meta.constraints中添加了这样一个约束:



注释:

  • 我们总是必须将表达式包装在一个Q()对象中。这个对象表示一个过滤器,并采用与objects.filter()的参数相同的语法。

  • 我们使用F()对象来引用我们模型中的字段。

  • 我们使用Python的数学运算符来组合F()对象。这不会执行该运算操作,但是会建立一个表示,数据库会在我们进行迁移之后执行该表示。

  • 我们必须在左侧编写带有percent_read的表达式。也就是,percent_read == 100 - percent_unread - percent_ignored,而不是更清晰的percent_read + percent_unread + percent_ignored == 100。这归因于DjangoF()对象中的限制,我们可能会在将来的版本中删除它(例如PR#12041) 。


运行makemigrations会为我们生成这样一个迁移:



这看起来很像我们的模型定义。主要区别在于迁移框架已将F()对象交换为构造的CombinedExpression对象。


运行sqlmigrate会显示将执行的SQL:



我们可以使用migrate来应用此迁移,但前提是表中的所有数据均已符合约束条件。否则,数据库将引发一个IntegrityError:



除非你非常确定,否则在尝试部署迁移之前检查一下你的生产数据库是否有效可能是个好主意。你可以在 QuerySet.exclude()中使用传递给Q的参数来查找无效对象:



如果你有这样的无效数据,那你应该在约束迁移之前运行的数据迁移中对其进行修复。


在成功添加约束后,数据库将会对不良数据的插入或更新引发该IntegrityError。


表单验证


正如我在之前的博文中所写的那样,约束并没有在表单中以很好的错误信息显示出来。目前最好的解决方案是在我们的表单中用Python重新实现约束逻辑。


在这种情况下,我们需要实现一个Form.clean()方法,因为我们的验证涉及多个字段:



注释:

  • 我们使用try/except KeyError/else来计算总和。数据字典会对用户没有提供的字段引发一个KeyError。我在《在Python中限制你的Try子句》一文中介绍了使用else的方法。

  • 我们使用self.add_error(None, msg)来添加一个非字段错误。这比raise ValidationError更好一些,因为它将允许我们可能会添加到clean()中的其他步骤也能很好地运行。


对该表单进行的快速测试表明这个整洁的方法起作用了:



太棒了!


结语


在撰写这篇博客文章期间,我必须承认我在Django中发现了一个缺陷,#31197。添加这个约束后将无法在Django 2.2或3.0的SQLite上利索地进行迁移,但它已经在3.1中被修复,我在它的ticket(工单)上写了一个变通办法。感谢Simon Simon编写了这个修复程序,并感谢Mariusz Felisiak添加了一个测试。


希望本文可以帮助你更多地使用检查约束,


—Adam


英文原文:https://adamj.eu/tech/2020/03/10/django-check-constraints-sum-percentage-fields/ 
译者:好酒不上头

Python社区是高质量的Python/Django开发社区
本文地址:http://www.python88.com/topic/56987
 
282 次点击