Django уникальный вместе с nullable ForeignKey

Я использую Django 1.8.4 в моей машине dev с помощью Sqlite, и у меня есть следующие модели:

class ModelA(Model):
    field_a = CharField(verbose_name='a', max_length=20)
    field_b = CharField(verbose_name='b', max_length=20)

    class Meta:
        unique_together = ('field_a', 'field_b',)


class ModelB(Model):
    field_c = CharField(verbose_name='c', max_length=20)
    field_d = ForeignKey(ModelA, verbose_name='d', null=True, blank=True)

    class Meta:
        unique_together = ('field_c', 'field_d',)

Я запустил правильную миграцию и зарегистрировал их в Администраторе Django. Итак, используя Admin я сделал эти тесты:

  • Я могу создавать записи ModelA и Django запрещает мне создавать дубликаты записей - как и ожидалось!
  • Я не могу создавать идентичные записи ModelB, когда field_b не пуст
  • но, я могу создать идентичные Modelb записи, при использовании field_d как пустой

мой вопрос: как применить unique_together для nullable ForeignKey?

самый последний ответ, который я нашел для этой проблемы уже 5 год... Я думаю, что Django эволюционировали, и проблема может быть не той же.

2 ответов


обновление: предыдущая версия моего ответа была функциональной, но имела плохой дизайн, это учитывает некоторые комментарии и другие ответы.

в SQL NULL не равно NULL. Это означает, если у вас есть два объекта, где field_d == None and field_c == "somestring" они не равны, поэтому вы можете создать оба.

можно переопределить Model.clean чтобы добавить чек:

class ModelB(Model):
    #...
    def validate_unique(self, exclude=None):
        if ModelB.objects.exclude(id=self.id).filter(field_c=self.field_c, \
                                 field_d__isnull=True).exists():
            raise ValidationError("Duplicate ModelB")
        super(ModelB, self).validate_unique(exclude)

если используется вне форм, вы должны позвонить full_clean или validate_unique.

позаботьтесь отрегулировать однако состояние гонки.


@ivan, я не думаю, что есть простой способ для django управлять этой ситуацией. Вам нужно подумать обо всех операциях создания и обновления, которые не всегда поступают из формы. Кроме того, вы должны думать о расовых условиях...

и поскольку вы не заставляете эту логику на уровне БД, возможно, что на самом деле будут удвоены записи, и вы должны проверить ее при запросе результатов.

и о вашем решении, это может быть хорошо для формы, но я не ожидаю, что метод save может вызвать ValidationError.

Если это возможно, то лучше делегировать эту логику в БД. В этом конкретном случае можно использовать два частичных индекса. Есть похожий вопрос о stackoverflow -создать уникальное ограничение с нулевыми столбцами

таким образом, вы можете создать миграцию django, которая добавляет два частичных индекса в вашу DB

пример:

# Assume that app name is just `example`

CREATE_TWO_PARTIAL_INDEX = """
    CREATE UNIQUE INDEX model_b_2col_uni_idx ON example_model_b (field_c, field_d)
    WHERE field_d IS NOT NULL;

    CREATE UNIQUE INDEX model_b_1col_uni_idx ON example_model_b (field_c)
    WHERE field_d IS NULL;
"""

DROP_TWO_PARTIAL_INDEX = """
    DROP INDEX model_b_2col_uni_idx;
    DROP INDEX model_b_1col_uni_idx;
"""


class Migration(migrations.Migration):

    dependencies = [
        ('example', 'PREVEOUS MIGRATION NAME'),
    ]

    operations = [
        migrations.RunSQL(CREATE_TWO_PARTIAL_INDEX, DROP_TWO_PARTIAL_INDEX)
    ]