我有这个型号:
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时添加重复项。我怎么能避免这种情况?
答案 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()