Повторяющиеся записи, когда 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() 
Другие вопросы по тегам