Comme mentionné dans les commentaires, la raison pour laquelle "avec cette configuration, il a besoin des deux" est simplement que vous avez oublié d'ajouter le blank=True
à vos champs FK, de sorte que votre ModelForm
(personnalisé ou généré par défaut par l'administrateur) rendra le champ de formulaire requis . Au niveau du schéma db, vous pouvez remplir les deux, un ou aucun de ces FK, ce serait correct puisque vous avez rendu ces champs db nullables (avec l' null=True
argument).
De plus, (cf. mes autres commentaires), vous voudrez peut-être vérifier que vous voulez vraiment que les FK soient uniques. Techniquement, cela transforme votre relation un à plusieurs en une relation un à un - vous n'êtes autorisé qu'à un seul enregistrement `` d'inspection '' pour un GroupID ou SiteId donné (vous ne pouvez pas avoir deux ou plusieurs `` inspections '' pour un GroupId ou SiteId) . Si c'est VRAIMENT ce que vous voulez, vous voudrez peut-être utiliser un OneToOneField explicite à la place (le schéma db sera le même mais le modèle sera plus explicite et le descripteur associé beaucoup plus utilisable pour ce cas d'utilisation).
En guise de remarque: dans un modèle Django, un champ ForeignKey se matérialise comme une instance de modèle associée, et non comme un identifiant brut. IOW, étant donné ceci:
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
va alors se résoudre à foo
, pas à foo.id
. Vous voulez donc certainement renommer vos champs InspectionID
et SiteID
en propres inspection
et site
. BTW, en Python, la convention de dénomination est 'all_lower_with_underscores' pour autre chose que les noms de classe et les pseudo-constantes.
Maintenant, pour votre question principale: il n'y a pas de méthode SQL standard spécifique pour appliquer une contrainte "l'une ou l'autre" au niveau de la base de données, donc cela se fait généralement en utilisant une contrainte CHECK , ce qui est fait dans un modèle Django avec les méta "contraintes" du modèle option .
Cela étant dit, comment les contraintes sont effectivement prises en charge et mises en œuvre au niveau db dépend de votre fournisseur DB (MySQL <8.0.16 tout simplement les ignorer par exemple), et le type de contrainte que vous aurez besoin ici ne seront pas appliquées à la forme ou la validation au niveau du modèle , uniquement lorsque vous essayez d'enregistrer le modèle, vous devez donc également ajouter la validation au niveau du modèle (de préférence) ou au niveau du formulaire, dans les deux cas dans le modèle (resp.) ou la clean()
méthode du formulaire .
Donc, pour faire une longue histoire:
vérifiez d'abord que vous voulez vraiment cette unique=True
contrainte, et si oui, remplacez votre champ FK par un OneToOneField.
ajouter un blank=True
argument à vos deux champs FK (ou OneToOne)
- ajoutez la contrainte de vérification appropriée dans la méta de votre modèle - le doc est succint mais toujours suffisamment explicite si vous savez faire des requêtes complexes avec l'ORM (et si vous ne le faites pas, il est temps d'apprendre ;-))
- ajoutez une
clean()
méthode à votre modèle qui vérifie que vous avez l'un ou l'autre champ et déclenche une erreur de validation sinon
et vous devriez être d'accord, en supposant que votre SGBDR respecte les contraintes de vérification bien sûr.
Notez simplement qu'avec cette conception, votre Inspection
modèle est une indirection totalement inutile (mais coûteuse!) - vous obtiendriez exactement les mêmes fonctionnalités à moindre coût en déplaçant directement les FK (et les contraintes, la validation, etc.) InspectionReport
.
Maintenant, il pourrait y avoir une autre solution - conserver le modèle d'inspection, mais placer le FK en tant que OneToOneField à l'autre extrémité de la relation (dans Site et Groupe):
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)
Et puis, vous pouvez obtenir tous les rapports pour un site ou un groupe donné avec yoursite.inspection.inspectionreport_set.all()
.
Cela évite d'avoir à ajouter une contrainte ou une validation spécifique, mais au prix d'un niveau d'indirection supplémentaire ( join
clause SQL , etc.).
Laquelle de ces solutions serait "la meilleure" dépend vraiment du contexte, vous devez donc comprendre les implications des deux et vérifier comment vous utilisez généralement vos modèles pour trouver celle qui convient le mieux à vos propres besoins. En ce qui me concerne et sans plus de contexte (ou de doute), je préfère utiliser la solution avec les niveaux d'indirection les moins nombreux, mais YMMV.
NB concernant les relations génériques: celles-ci peuvent être utiles lorsque vous avez vraiment beaucoup de modèles liés possibles et / ou que vous ne savez pas au préalable quels modèles vous voudrez associer aux vôtres. Ceci est particulièrement utile pour les applications réutilisables (pensez aux fonctionnalités "commentaires" ou "tags", etc.) ou extensibles (cadres de gestion de contenu, etc.). L'inconvénient est que cela rend les requêtes beaucoup plus lourdes (et plutôt peu pratiques lorsque vous souhaitez effectuer des requêtes manuelles sur votre base de données). Par expérience, ils peuvent rapidement devenir un bot wrt / code et perfs PITA, donc mieux les conserver quand il n'y a pas de meilleure solution (et / ou lorsque la surcharge de maintenance et d'exécution n'est pas un problème).
Mes 2 cents.
Inspection
classe, puis une sous-classe dansSiteInspection
etGroupInspection
pour les parties non communes.