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)
]