在django mptt中treeforeignkey为null时重复记录

时间:2016-11-27 21:02:02

标签: python django django-models mptt

我有这个型号:

class Genre(MPTTModel):
    id = models.CharField(max_length=100)
    name = models.CharField(max_length=100)
    parent = TreeForeignKey(
        'self', 
        null=True, 
        blank=True, 
        related_name='subgenre'
    )

    def __str__(self):
        return self.name

    class Meta:
        unique_together = (('id', 'parent'),)

我不想拥有重复的记录,所以我使用带有id和TreeForeignKey的unique_together

即使使用unique_together,我仍然可以在将父级设置为null时添加重复项。我怎么能避免这种情况?

1 个答案:

答案 0 :(得分:0)

这是一个SQL设计决定。

SQL 2011 draft,第474页读取:

  

如果T中没有两行,那么一行中每列的值都是非空的并且不是不同的   从另一行中相应列的值开始,然后是结果   真正;否则,结果是假的。

这意味着当涉及唯一约束时,两个NULL值被认为是不同的。这与第41页中的NULL数据类型定义相矛盾:

  

两个空值不同。

     

空值和非空值是不同的。

     

如果子条款8.15的一般规则“”返回True,则两个非空值是不同的。

8.15的一般规则说:

  

如果V1和V2都是空值,则结果为False。

总结:

说到数据类型,两个空值的“区别性”是False意味着NULL == NULL。

但是表级别的唯一约束表示否则:NULL!= NULL。表的字段中可以有许多NULL表示它们应该是唯一的。

跟踪此事件的Django票证是#1751 unique_together does not work when any of the listed fields contains a FK。解决方法是定义您自己的.validate_unique模型方法,如documentation中所述。

from django.core.exceptions import ValidationError
from django.db import transaction

def validate_unique(self, exclude=None):
    with transaction.atomic():
        if Genre.objects.select_for_update().filter(parent=self.parent, id=self.id).exists():
            params = {
                'model_name': _('Genre'),
                'field_labels': _('Parent and ID')
            }
            raise ValidationError(
                message=_(
                    '%(model_name)s with this %(field_labels)s already exists.'
                ), code='unique_together', params=params,
            )

select_for_update创建一个锁以避免竞争条件。

此解决方案适用于表单提交,在直接访问Genre.objects.create()方法时不起作用。在这种情况下,您需要分三步创建Genre实例:

genre = Genre(id='id1')
genre.validate_unique()
genre.save()