Як згадувалося в коментарях, причиною того, що "за допомогою цього налаштування йому потрібно і те й інше", є лише те, що ви забули додати blank=Trueсвої поля до FK, тому ваше ModelForm(або спеціальне, або за замовчуванням згенероване адміністратором) зробить поле форми необхідним . На рівні схеми db ви можете заповнити обидва, або один, або жоден із цих FK, це було б нормально, оскільки ви зробили ці nb-поля нульовими (з null=Trueаргументом).
Крім того, (див. Інші мої коментарі), ви можете перевірити, чи дійсно ви хочете, щоб FKS були унікальними. Це технічно перетворює відносини один до багатьох на відносини один на один - вам дозволяється лише один єдиний запис "перевірки" для даного GroupID або SiteId (у вас не може бути двох і більше "перевірок" для одного GroupId або SiteId) . Якщо це дійсно те, що ви хочете, ви можете скористатися явним OneToOneField замість цього (схема db буде такою ж, але модель буде більш чіткою і пов'язаний дескриптор набагато більш придатний для цього випадку використання).
Як бічна примітка: у моделі 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. BTW, в Python, умовна назва називає "all_lower_with_underscores" для будь-чого іншого, крім імен класів та псевдоконстант.
Тепер для вашого основного питання: немає специфічного стандартного способу SQL примусового застосування "одного чи іншого" обмеження на рівні бази даних, тому зазвичай це робиться за допомогою обмеження CHECK , яке робиться в моделі Django з мета "обмеженнями" моделі. варіант .
Це говорить про те, як фактично підтримуються та застосовуються обмеження на рівні db, залежить від вашого постачальника баз даних (MySQL <8.0.16, просто проігноруйте їх, наприклад), і вид обмеження, яке вам знадобиться тут , не буде застосовано до форми або валідація рівня моделі , лише при спробі збереження моделі, тому ви також хочете додати валідацію або на рівні моделі (бажано), або перевірку рівня форми, в обох випадках у clean()методі (відповідно) моделі чи формі .
Отже, щоб коротко розповісти:
спочатку перевірте, чи дійсно ви хочете цього unique=Trueобмеження, і якщо так, то замініть поле FK на OneToOneField.
додайте blank=Trueаргумент у обидва поля FK (або OneToOne)
- додайте належне обмеження перевірки в мета вашої моделі - документ є стислим, але все ще досить явним, якщо ви знаєте робити складні запити з ORM (і якщо ви цього не встигли, то навчитесь ;-))
- додайте
clean()у свою модель метод, який перевіряє ваше поле або інше, і інше викликає помилку перевірки
і вам повинно бути все в порядку, якщо припустити, що ваш RDBMS поважає обмеження перевірки.
Зауважте лише, що при такому дизайні ваша Inspectionмодель є абсолютно марною (але дорогою!) Непрямою діяльністю - ви отримаєте такі самі функції за меншу ціну, перемістивши FK (та обмеження, валідацію тощо) безпосередньо в InspectionReport.
Тепер може бути інше рішення - збережіть модель перевірки, але поставте FK як OneToOneField на інший кінець взаємозв'язку (у Сайті та Групі):
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.
Примітка щодо загальних відносин: вони можуть бути зручними, коли у вас дійсно багато можливих пов'язаних моделей та / або не знаєте заздалегідь, які моделі ви хочете пов’язати з вашими. Це особливо корисно для багаторазових програм (думайте, що "коментарі" або "теги" тощо) або розширюваних (рамки управління вмістом тощо). Мінусом є те, що він робить запити набагато важчими (і досить непрактично, коли ви хочете робити запити вручну на своєму db). З досвіду, вони можуть швидко стати ботом wrt / код та перфоманти PITA, тому краще тримати їх, коли немає кращого рішення (та / або коли накладні витрати на технічне обслуговування та час роботи не є проблемою).
Мої 2 копійки.
Inspectionклас, а потім підклас вSiteInspectionіGroupInspectionдля не є -Загальна частин.