django mptt 中 treeforeignkey 为 null 时重复记录
Duplicate records when treeforeignkey is null in django 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'),)
我不想有重复的记录,所以我将 unique_together
与 id 和 TreeForeignKey 一起使用。
即使使用 unique_together
,当我将父级设置为 null 时,我仍然能够添加重复项。我怎样才能避免这种情况?
这是一个 SQL 设计决定。
SQL 2011 draft,第 474 页显示:
If there are no two rows in T such that the value of each column in one row is non-null and is not distinct
from the value of the corresponding column in the other row, then the result of the is
True; otherwise, the result of the is False.
这意味着当涉及唯一约束时,两个 NULL 值被认为是不同的。这与第 41 页中的 NULL 数据类型定义相矛盾:
Two null values are not distinct.
A null value and a non-null value are distinct.
Two non-null values are distinct if the General Rules of Subclause 8.15, “”, return True.
第 8.15 款的一般规则说:
If both V1 and V2 are the null value, then the result is False.
总结:
说到数据类型,两个null的“区别”是False,意思是NULL == NULL。
但是 table 级别的唯一约束另有说明:NULL != NULL。 table 的字段中可以有许多 NULL,表示它们应该是唯一的。
跟踪此的 Django 票证是 #1751 unique_together does not work when any of the listed fields contains a FK. The workaround is to define your own .validate_unique
model method as mentioned in the 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()
我有这个型号:
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'),)
我不想有重复的记录,所以我将 unique_together
与 id 和 TreeForeignKey 一起使用。
即使使用 unique_together
,当我将父级设置为 null 时,我仍然能够添加重复项。我怎样才能避免这种情况?
这是一个 SQL 设计决定。
SQL 2011 draft,第 474 页显示:
If there are no two rows in T such that the value of each column in one row is non-null and is not distinct from the value of the corresponding column in the other row, then the result of the is True; otherwise, the result of the is False.
这意味着当涉及唯一约束时,两个 NULL 值被认为是不同的。这与第 41 页中的 NULL 数据类型定义相矛盾:
Two null values are not distinct.
A null value and a non-null value are distinct.
Two non-null values are distinct if the General Rules of Subclause 8.15, “”, return True.
第 8.15 款的一般规则说:
If both V1 and V2 are the null value, then the result is False.
总结:
说到数据类型,两个null的“区别”是False,意思是NULL == NULL。
但是 table 级别的唯一约束另有说明:NULL != NULL。 table 的字段中可以有许多 NULL,表示它们应该是唯一的。
跟踪此的 Django 票证是 #1751 unique_together does not work when any of the listed fields contains a FK. The workaround is to define your own .validate_unique
model method as mentioned in the 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()