【发布时间】:2019-02-08 08:43:28
【问题描述】:
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 个想法:
-
idea #1:使用
unique_together-> 不适用于 M2M 字段,如 [in documentation] 所述 (https://docs.djangoproject.com/en/2.1/ref/models/options/#unique-together) -
想法#2:覆盖
save()方法。不完全适用于 M2M,因为在调用add或remove方法时,不会调用save()。 idea #3:使用显式的
through模型,但由于我在生产环境中,我想避免在迁移重要结构(如论文)时冒险。 编辑:想了想,我看不出它实际上有什么帮助。想法#4:在调用
add()方法时,使用m2m_changedsignal 来检查唯一性。
我最终得到了idea 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() 不会被提交?
谢谢!
【问题讨论】:
-
为什么不创建一个带有 m2m 到餐厅的
IdentifiedBadge。通过这种方式,您可以保证这一点设计。通常最好按设计强制执行,然后以修补这些为目标。信号、.save()等可以被绕过(例如在批量更新时)。 -
这确实是第五个想法,谢谢。然而,我不喜欢仅仅为了处理其他类模型的完整性而添加一个新类的想法。这个解决方案如何优于idea #3?
-
save 不应该捕获信号中引发的错误,这是发送信号github.com/django/django/blob/stable/2.1.x/django/db/models/… 的源代码,因为您可以看到它包装到事务原子并且不会捕获错误。你确定你的信号被执行了吗?
-
标识符在数据库级别不是唯一的硬性要求吗?或者这只是一种偏好?
-
@ubadub 硬性要求
标签: django django-models django-signals