Jak wspomniano w komentarzach, powodem, dla którego „przy tej konfiguracji wymaga obu” jest to, że zapomniałeś dodać blank=True
pola FK, więc twoje ModelForm
(niestandardowe lub domyślne wygenerowane przez administratora) spowoduje, że pole formularza będzie wymagane . Na poziomie schematu db można wypełnić oba, jeden lub żaden z tych FK, byłoby dobrze, ponieważ te pola db zostały dopuszczone do null (z null=True
argumentem).
Ponadto (porównaj moje inne komentarze), możesz chcieć sprawdzić, czy naprawdę chcesz, aby FK były wyjątkowe. Technicznie zmienia to relację jeden do wielu w relację jeden do jednego - dozwolony jest tylko jeden rekord „inspekcji” dla danego GroupID lub SiteId (nie można mieć dwóch lub więcej „inspekcji” dla jednego GroupId lub SiteId) . Jeśli jest to NAPRAWDĘ to, czego chcesz, możesz zamiast tego użyć jawnego OneToOneField (schemat db będzie taki sam, ale model będzie bardziej wyraźny, a powiązany deskryptor znacznie bardziej użyteczny w tym przypadku użycia).
Na marginesie: w modelu Django pole ForeignKey materializuje się jako instancja modelu pokrewnego, a nie jako surowy identyfikator. IOW, biorąc pod uwagę to:
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)
wtedy bar.foo
zdecyduje się na foo
, a nie na foo.id
. Więc na pewno chcesz zmienić nazwę pola InspectionID
i SiteID
pola na właściwe inspection
i site
. BTW, w Pythonie konwencja nazewnictwa to „all_lower_with_underscores” dla wszystkiego innego niż nazwy klas i pseudo-stałe.
Teraz najważniejsze pytanie: nie ma określonego standardowego sposobu SQL narzucenia ograniczenia „jedno lub drugie” na poziomie bazy danych, więc zwykle odbywa się to za pomocą ograniczenia CHECK , które jest wykonywane w modelu Django z meta „więzami” modelu opcja .
Biorąc to pod uwagę, sposób, w jaki ograniczenia są faktycznie obsługiwane i egzekwowane na poziomie db, zależy od twojego dostawcy DB (MySQL <8.0.16 po prostu zignoruj je na przykład), a rodzaj ograniczenia, którego będziesz potrzebować tutaj , nie będzie egzekwowany w formularzu lub sprawdzanie poprawności na poziomie modelu , tylko przy próbie zapisania modelu, dlatego też chcesz dodać sprawdzanie poprawności na poziomie modelu (najlepiej) lub sprawdzanie poprawności na poziomie formularza, w obu przypadkach w (odpowiednio) modelu lub clean()
metodzie formularza .
Krótko mówiąc:
najpierw dwukrotnie sprawdź, czy naprawdę chcesz tego unique=True
ograniczenia, a jeśli tak, zamień swoje pole FK na OneToOneField.
dodaj blank=True
argument do obu pól FK (lub OneToOne)
- dodaj odpowiednie ograniczenie sprawdzające do meta twojego modelu - dokument jest poprawny, ale nadal wystarczająco wyraźny, jeśli wiesz, że możesz wykonywać złożone zapytania z ORM (a jeśli nie, czas się uczyć ;-))
- dodaj
clean()
metodę do swojego modelu, która sprawdza, czy masz jedno lub drugie pole i podnosi inny błąd sprawdzania poprawności
i powinieneś być w porządku, zakładając, że RDBMS oczywiście przestrzega ograniczeń sprawdzania.
Pamiętaj tylko, że dzięki tej konstrukcji Twój Inspection
model jest całkowicie bezużyteczną (ale kosztowną!) Pośrednią - uzyskasz dokładnie te same funkcje przy niższym koszcie, przenosząc FK (i ograniczenia, walidację itp.) Bezpośrednio do InspectionReport
.
Teraz może istnieć inne rozwiązanie - zachowaj model Inspection, ale umieść FK jako OneToOneField na drugim końcu relacji (w witrynie i grupie):
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)
Następnie możesz uzyskać wszystkie raporty dla danej witryny lub grupy yoursite.inspection.inspectionreport_set.all()
.
Pozwala to uniknąć konieczności dodawania jakichkolwiek konkretnych ograniczeń lub sprawdzania poprawności, ale kosztem dodatkowego poziomu pośredniego ( join
klauzula SQL itp.).
To, które z tych rozwiązań będzie „najlepsze”, naprawdę zależy od kontekstu, więc musisz zrozumieć implikacje obu i sprawdzić, w jaki sposób zazwyczaj używasz swoich modeli, aby dowiedzieć się, które jest bardziej odpowiednie dla twoich potrzeb. Jeśli chodzi o mnie i bez większego kontekstu (lub wątpliwości) wolałbym używać rozwiązania z mniejszymi poziomami pośrednimi, ale YMMV.
Uwaga: ogólne relacje: mogą się przydać, gdy naprawdę masz wiele możliwych powiązanych modeli i / lub nie wiesz z góry, które modele chcesz odnosić do swoich. Jest to szczególnie przydatne w przypadku aplikacji wielokrotnego użytku (pomyśl „komentarze” lub „tagi” itp.) Lub rozszerzalnych (ramy zarządzania treścią itp.). Minusem jest to, że sprawia, że zapytania są znacznie cięższe (i raczej niepraktyczne, gdy chcesz wykonywać zapytania ręczne na db). Z doświadczenia mogą szybko stać się kodem / kodem i perfem bota PITA, więc lepiej je zachować, gdy nie ma lepszego rozwiązania (i / lub gdy narzut związany z konserwacją i czasem wykonywania nie stanowi problemu).
Moje 2 centy.
Inspection
klasę, a następnie do podklasySiteInspection
iGroupInspection
dla niezarejestrowanych części -common.