Dav*_* D. 8 django django-signals django-models
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!
Run Code Online (Sandbox Code Playgroud)
我想确保对于任何徽章,对于给定的餐馆,它必须具有唯一的标识符.以下是我的4个想法:
unique_together- >不适用于M2M字段,如[文档]中所述(https://docs.djangoproject.com/en/2.1/ref/models/options/#unique-together)save()方法.不能完全使用M2M,因为在调用add或remove方法时,save()不会调用.想法#3:使用一个明确的through模型,但由于我生活在生产中,我想避免冒险迁移重要的结构,如论文.编辑:在考虑之后,我看不出它实际上是如何帮助的.
想法#4: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)
))
Run Code Online (Sandbox Code Playgroud)
...直到今天,当我在我的数据库中发现许多具有相同标识符但没有餐厅的徽章(不应该在业务层面发生)时,我知道信号和信号之间没有原子性save().这意味着,如果用户在尝试创建徽章时出现关于唯一性的错误,则会创建徽章,但不会将餐馆链接到该徽章.
所以,问题是:如何在模型级别确保如果信号引发错误,save()则不会提交?
谢谢!
我在这里看到两个不同的问题:
您想要对数据实施特定的约束。
如果违反约束,您希望恢复以前的操作。特别是,Badge如果Restaurants在同一请求中添加了任何违反约束的实例,您希望恢复实例的创建。
关于1,你的约束很复杂,因为它涉及多个表。这排除了数据库约束(好吧,您可能可以使用触发器来完成)或简单的模型级验证。
您上面的代码显然可以有效地防止adds违反约束。但请注意,如果现有标识符Badge发生更改,也可能会违反此约束。想必您也想阻止这种情况发生?如果是这样,您需要添加类似的验证Badge(例如在Badge.clean())。
关于2,如果您希望在违反约束时恢复实例的创建Badge,则需要确保操作包装在数据库事务中。您还没有告诉我们这些对象区域创建的视图(自定义视图?Django admin?),因此很难给出具体的建议。本质上,你想要这样:
with transaction.atomic():
badge_instance.save()
badge_instance.add(...)
Run Code Online (Sandbox Code Playgroud)
如果这样做,M2M 信号引发的异常pre_add将回滚事务,并且您将不会Badge在数据库中获得剩余内容。请注意,管理视图默认在事务中运行,因此如果您正在使用管理,这应该已经发生。
Badge另一种方法是在创建对象之前进行验证。例如,请参阅有关在 Django 管理中使用验证的答案。ModelForm