Come accennato nei commenti, il motivo per cui "con questa configurazione ha bisogno di entrambi" è solo che hai dimenticato di aggiungere i blank=True
campi FK, quindi il tuo ModelForm
(uno personalizzato o quello predefinito generato dall'amministratore) renderà necessario il campo modulo . A livello di schema db, potresti riempire entrambi, uno o nessuno di questi FK, sarebbe ok dato che hai reso quei campi db nulli (con l' null=True
argomento).
Inoltre, (vedi altri miei commenti), potresti voler verificare che tu voglia davvero che gli FK siano unici. Questo tecnicamente trasforma la tua relazione uno a più in una relazione uno a uno: ti è permesso solo un singolo record di 'ispezione' per un determinato GroupID o SiteId (non puoi avere due o più 'ispezioni' per un GroupId o SiteId) . Se è REALMENTE quello che vuoi, potresti voler usare un OneToOneField esplicito (lo schema db sarà lo stesso ma il modello sarà più esplicito e il descrittore correlato molto più utilizzabile per questo caso d'uso).
Come nota a margine: in un modello Django, un campo ForeignKey si materializza come un'istanza del modello correlata, non come un ID grezzo. IOW, dato questo:
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)
allora bar.foo
risolverà a foo
, non a foo.id
. Quindi sicuramente vuoi rinominare il tuo InspectionID
e i SiteID
campi in proprio inspection
e site
. A proposito, in Python, la convenzione di denominazione è "all_lower_with_underscores" per nient'altro che nomi di classe e pseudo-costanti.
Ora per la tua domanda principale: non esiste un modo SQL standard specifico per imporre un vincolo "l'uno o l'altro" a livello di database, quindi di solito viene fatto usando un vincolo CHECK , che viene eseguito in un modello Django con i meta "vincoli" del modello opzione .
Detto questo, il modo in cui i vincoli sono effettivamente supportati e applicati a livello di db dipende dal tuo fornitore di database (MySQL <8.0.16 semplicemente li ignora, per esempio), e il tipo di vincolo di cui avrai bisogno qui non sarà applicato nel modulo o livello modello di validazione , solo quando si cerca di salvare il modello, in modo da anche da aggiungere la convalida sia a livello di modello (preferibilmente) o di convalida dei livelli forma, in entrambi i casi nel modello (resp.) o di forma di clean()
metodo.
Quindi, per farla breve:
prima ricontrolla che vuoi davvero questo unique=True
vincolo e, in caso affermativo, sostituisci il tuo campo FK con OneToOneField.
aggiungi un blank=True
arg a entrambi i campi FK (o OneToOne)
- aggiungi il vincolo di controllo appropriato nella meta meta del tuo modello: il documento è succinto ma ancora abbastanza esplicito se sai di fare query complesse con l'ORM (e se non lo fai è tempo che impari ;-))
- aggiungi un
clean()
metodo al tuo modello per verificare se hai l'uno o l'altro campo e genera un altro errore di convalida
e dovresti essere a posto, supponendo che il tuo RDBMS rispetti i vincoli del controllo ovviamente.
Basta notare che, con questo design, il tuo Inspection
modello è un'inversione totalmente inutile (ma costosa!): Otterrai le stesse identiche funzionalità a un costo inferiore spostando direttamente gli FK (e vincoli, validazione ecc.) InspectionReport
.
Ora potrebbe esserci un'altra soluzione: mantenere il modello Inspection, ma inserire l'FK come OneToOneField all'altra estremità della relazione (in Sito e Gruppo):
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)
E quindi puoi ottenere tutti i rapporti per un determinato sito o gruppo con yoursite.inspection.inspectionreport_set.all()
.
Ciò evita di dover aggiungere vincoli o convalide specifici, ma a costo di un ulteriore livello di riferimento indiretto ( join
clausola SQL ecc.).
Quale di queste soluzioni sarebbe "la migliore" dipende dal contesto, quindi devi capire le implicazioni di entrambi e controllare come usi i tuoi modelli per scoprire quale è più appropriato per le tue esigenze. Per quanto mi riguarda e senza più contesto (o in dubbio) preferirei usare la soluzione con i livelli meno indiretti, ma YMMV.
NB per quanto riguarda le relazioni generiche: quelle possono essere utili quando si hanno davvero molti possibili modelli correlati e / o non si conosce in anticipo quali modelli si vorranno mettere in relazione con i propri. Ciò è particolarmente utile per le app riutilizzabili (pensa a funzionalità di "commenti" o "tag" ecc.) O estensibili (framework di gestione dei contenuti, ecc.). Il rovescio della medaglia è che rende le query molto più pesanti (e piuttosto poco pratiche quando si desidera fare query manuali sul proprio db). Per esperienza, possono rapidamente diventare un wrt / code e perf di bot PITA, quindi è meglio tenerli per quando non c'è soluzione migliore (e / o quando l'overhead di manutenzione e runtime non è un problema).
I miei 2 centesimi.
Inspection
classe e quindi sottoclassare inSiteInspection
eGroupInspection
per le parti non comuni.