class Badge(SafeDeleteModel):
owner = models.ForeignKey(settings.AUTH_USER_MODEL,
blank=True, null=True,
on_delete=models.PROTECT)
restaurants = models.ManyToManyField(Restaurant)
identifier = models.CharField(max_length=2048) # not unique at a DB level!
我想确保对于任何徽章,对于给定的餐厅,它必须具有唯一的标识符。这是我的4个想法:
unique_together
->不适用于[在文档中] M2M字段(https://docs.djangoproject.com/en/2.1/ref/models/options/#unique-together)
save()
方法。不适用于M2M,因为在调用add
或remove
方法时,不会调用save()
。 through
模型,但是由于我生活在生产环境中,因此我想避免冒险迁移诸如此类的重要结构。 编辑:考虑之后,我看不出它实际上有什么帮助。 m2m_changed
方法时,都使用add()
信号检查唯一性。 我最终想到了想法4 ,并认为一切正常,并发出了这个信号...
@receiver(m2m_changed, sender=Badge.restaurants.through)
def check_uniqueness(sender, **kwargs):
badge = kwargs.get('instance', None)
action = kwargs.get('action', None)
restaurant_pks = kwargs.get('pk_set', None)
if action == 'pre_add':
for restaurant_pk in restaurant_pks:
if Badge.objects.filter(identifier=badge.identifier).filter(restaurants=restaurant_pk):
raise BadgeNotUnique(MSG_BADGE_NOT_UNIQUE.format(
identifier=badge.identifier,
restaurant=Restaurant.objects.get(pk=restaurant_pk)
))
...直到今天,当我在数据库中找到许多具有相同标识符但没有餐厅的徽章(在业务级别上不应发生)
我了解
save()
和信号之间没有原子性。这意味着,如果用户在尝试创建徽章时遇到关于唯一性的错误,则会创建该徽章,但不会链接任何饭店。
因此,问题是:如何在模型级别确保如果信号引发错误,则不会提交
save()
?谢谢!
最佳答案
我在这里看到两个单独的问题:
Badge
,则要还原Restaurants
实例的创建。 关于1,您的约束很复杂,因为它涉及多个表。这就排除了数据库约束(或者,您可以通过触发器来做到)或简单的模型级验证。
上面的代码显然可以有效地防止
adds
违反约束。但是请注意,如果更改了现有Badge
的标识符,也可能会违反此约束。想必您也想防止这种情况吗?如果是这样,您需要向Badge
添加类似的验证(例如Badge.clean()
中)。关于2,如果希望在违反约束时还原
Badge
实例的创建,则需要确保将操作包装在数据库事务中。您尚未告诉我们这些对象区域的创建 View (自定义 View ?Django admin?),因此很难给出具体建议。本质上,您希望拥有:with transaction.atomic():
badge_instance.save()
badge_instance.add(...)
如果这样做,M2M
pre_add
信号引发的异常将回滚事务,并且您不会在数据库中得到剩余的Badge
。请注意,默认情况下,管理员 View 是在事务中运行的,因此,如果您使用的是管理员,则应该已经发生了。另一种方法是在创建
Badge
对象之前进行验证。有关在Django管理员中使用ModelForm
验证的信息,请参见this answer。关于带有M2M字段的Django对象唯一性 hell ,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/52147255/