Повторяющиеся записи, когда treeforeignkey имеет значение null в 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
с идентификатором и TreeForeignKey.
Даже с unique_together
Я все еще могу добавить дубликаты, когда я устанавливаю родителя в нуль. Как я могу избежать этого?
1 ответ
Это проектное решение SQL.
Черновик SQL 2011, страница 474 гласит:
Если в T нет двух строк, так что значение каждого столбца в одной строке не равно нулю и не отличается от значения соответствующего столбца в другой строке, то результатом является True; в противном случае результатом является Ложь.
Это означает, что два значения NULL считаются разными, когда речь идет об уникальном ограничении. Это противоречит определению типа данных NULL на странице 41:
Два нулевых значения не различаются.
Нулевое значение и ненулевое значение различны.
Два ненулевых значения различаются, если Общие правила подпункта 8.15 "" возвращают True.
Общие правила подпункта 8.15 гласят:
Если и V1, и V2 являются нулевым значением, то результатом является False.
Подвести итоги:
Когда речь идет о типе данных, "различимость" двух нулей равна False, что означает NULL == NULL.
Но уникальное ограничение на уровне таблицы говорит иначе: NULL!= NULL. В поле таблицы может быть много NULL, которые говорят, что они должны быть уникальными.
Отслеживание билетов Django, это #1751 unique_together, не работает, когда любое из перечисленных полей содержит FK. Обходной путь должен определить ваш собственный .validate_unique
модельный метод, как указано в документации.
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()