Как упомянуто в комментариях, причина того, что «с этой настройкой нужны оба», заключается в том, что вы забыли добавить blank=True
поля FK, так что ваше ModelForm
(либо пользовательское, либо сгенерированное по умолчанию администратором) сделает поле формы обязательным. , На уровне схемы БД вы можете заполнить оба, либо один, либо ни один из этих FK, это будет нормально, поскольку вы сделали эти поля БД обнуляемыми (с null=True
аргументом).
Кроме того, (см. Мои другие комментарии), вы можете проверить, действительно ли вы хотите, чтобы FK были уникальными. Это технически превращает ваши отношения один-ко-многим в отношения один-к-одному - вам разрешена только одна запись «осмотра» для данного GroupID или SiteId (у вас не может быть двух или более «проверок» для одного GroupId или SiteId) , Если это действительно то, что вы хотите, вы можете вместо этого использовать явный OneToOneField (схема БД будет такой же, но модель будет более явной, а связанный дескриптор гораздо более пригодным для этого варианта использования).
В качестве примечания: в модели Django поле ForeignKey материализуется как экземпляр связанной модели, а не как необработанный идентификатор. IOW, учитывая это:
class Foo(models.Model):
name = models.TextField()
class Bar(models.Model):
foo = models.ForeignKey(Foo)
foo = Foo.objects.create(name="foo")
bar = Bar.objects.create(foo=foo)
тогда bar.foo
разрешу foo
, а не foo.id
. Таким образом , вы , конечно , хотите переименовать InspectionID
и SiteID
поле собственно inspection
и site
. Кстати, в Python соглашение об именах называется all_lower_with_underscores для всего, кроме имен классов и псевдоконстант.
Теперь о вашем основном вопросе: не существует конкретного стандартного способа SQL для принудительного применения «одного или другого» ограничения на уровне базы данных, поэтому обычно это делается с использованием ограничения CHECK , что делается в модели Django с мета-«ограничениями» модели. вариант .
При этом, как фактически поддерживаются и применяются ограничения на уровне базы данных, зависит от вашего поставщика БД (MySQL <8.0.16 просто игнорирует их, например), и вид ограничения, который вам здесь понадобится , не будет применяться в форме или проверка на уровне модели , только при попытке сохранить модель, поэтому вы также хотите добавить проверку либо на уровне модели (предпочтительно), либо на уровне формы, в обоих случаях в (соответственно) модели или clean()
методе формы .
Короче говоря:
сначала проверьте, действительно ли вы хотите это unique=True
ограничение, и если да, то замените поле FK на OneToOneField.
добавьте blank=True
аргумент в оба поля FK (или OneToOne)
- добавьте правильное проверочное ограничение в метаданные вашей модели - документ является кратким, но все еще достаточно явным, если вы знаете, что делать сложные запросы с помощью ORM (а если нет, пора учиться ;-))
- добавьте
clean()
метод в вашу модель, который проверяет, есть ли у вас одно или другое поле, и выдает ошибку проверки еще
и вы должны быть в порядке, если, конечно, ваша СУБД соблюдает контрольные ограничения.
Просто отметьте, что с этим дизайном ваша Inspection
модель является абсолютно бесполезной (но дорогостоящей!) Ссылкой - вы получите те же функции с меньшими затратами, переместив FK (и ограничения, валидацию и т. Д.) Непосредственно в InspectionReport
.
Теперь может быть другое решение - сохранить модель Inspection, но поместить FK как OneToOneField на другом конце отношения (в Site и Group):
class Inspection(models.Model):
id = models.AutoField(primary_key=True) # a pk is always unique !
class InspectionReport(models.Model):
# you actually don't need to manually specify a PK field,
# Django will provide one for you if you don't
# id = models.AutoField(primary_key=True)
inspection = ForeignKey(Inspection, ...)
date = models.DateField(null=True) # you should have a default then
comment = models.CharField(max_length=255, blank=True default="")
signature = models.CharField(max_length=255, blank=True, default="")
class Group(models.Model):
inspection = models.OneToOneField(Inspection, null=True, blank=True)
class Site(models.Model):
inspection = models.OneToOneField(Inspection, null=True, blank=True)
И тогда вы можете получить все отчеты для данного сайта или группы с yoursite.inspection.inspectionreport_set.all()
.
Это избавляет от необходимости добавлять какие-либо конкретные ограничения или проверки, но за счет дополнительного уровня косвенности ( join
пункт SQL и т. Д.).
То, какое из этих решений будет «лучшим», действительно зависит от контекста, поэтому вы должны понять значение обоих и проверить, как вы обычно используете свои модели, чтобы выяснить, что больше подходит для ваших собственных нужд. Насколько мне известно, без какого-либо контекста (или сомнений) я бы предпочел использовать решение с меньшим уровнем косвенности, но с YMMV.
Обратите внимание на родовые отношения: они могут быть полезны, когда у вас действительно много возможных связанных моделей и / или вы заранее не знаете, какие модели вы хотите связать с вашими собственными. Это особенно полезно для многократно используемых приложений (например, «комментарии» или «теги» и т. Д.) Или расширяемых (структуры управления контентом и т. Д.). Недостатком является то, что это делает запросы намного более тяжелыми (и довольно непрактичными, когда вы хотите выполнять ручные запросы к вашей базе данных). Исходя из опыта, они могут быстро стать ботом PITA по отношению к коду и программному обеспечению, поэтому лучше их хранить, когда нет лучшего решения (и / или когда нет проблем с обслуживанием и временем выполнения).
Мои 2 цента.
Inspection
класс, а затем подкласс вSiteInspection
иGroupInspection
для не являющихся -Общих частей.