Mon problème est que j'ai un modèle qui peut prendre l'une des deux clés étrangères pour dire de quel type de modèle il s'agit. Je veux qu'il en prenne au moins un mais pas les deux. Puis-je avoir encore un modèle ou dois-je le diviser en deux types. Voici le code:
class Inspection(models.Model):
InspectionID = models.AutoField(primary_key=True, unique=True)
GroupID = models.ForeignKey('PartGroup', on_delete=models.CASCADE, null=True, unique=True)
SiteID = models.ForeignKey('Site', on_delete=models.CASCADE, null=True, unique=True)
@classmethod
def create(cls, groupid, siteid):
inspection = cls(GroupID = groupid, SiteID = siteid)
return inspection
def __str__(self):
return str(self.InspectionID)
class InspectionReport(models.Model):
ReportID = models.AutoField(primary_key=True, unique=True)
InspectionID = models.ForeignKey('Inspection', on_delete=models.CASCADE, null=True)
Date = models.DateField(auto_now=False, auto_now_add=False, null=True)
Comment = models.CharField(max_length=255, blank=True)
Signature = models.CharField(max_length=255, blank=True)
Le problème, c'est le Inspection
modèle. Cela doit être lié à un groupe ou à un site, mais pas aux deux. Actuellement, avec cette configuration, il a besoin des deux.
Je préfère ne pas avoir à diviser cela en deux modèles presque identiques GroupInspection
et SiteInspection
, donc toute solution qui le garderait comme un seul modèle serait idéale.
django
django-models
CalMac
la source
la source
Inspection
classe, puis une sous-classe dansSiteInspection
etGroupInspection
pour les parties non communes.unique=True
partie dans vos champs FK signifie qu'une seuleInspection
instance peut exister pour une instance donnéeGroupID
ouSiteID
- IOW, c'est une relation un à un, pas une à plusieurs. C'est vraiment ce que tu veux?Inspection
un lien entre leGroup
ouSite
et leInspectionID
, alors je peux avoir plusieurs "inspections" sous la forme deInspectionReport
pour cette seule relation. Cela a été fait afin que je puisse plus facilement trier parDate
tous les enregistrements liés à unGroup
ouSite
. J'espère que cela a du sensRéponses:
Je suggère que vous fassiez une telle validation à la manière de Django
en redéfinissant la
clean
méthode du modèle Djangola source
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 votreModelForm
(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:
bar.foo
va alors se résoudre àfoo
, pas àfoo.id
. Vous voulez donc certainement renommer vos champsInspectionID
etSiteID
en propresinspection
etsite
. 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)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 sinonet 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):
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.
la source
Django a une nouvelle interface (depuis 2.2) pour créer des contraintes de base de données: https://docs.djangoproject.com/en/3.0/ref/models/constraints/
Vous pouvez utiliser un
CheckConstraint
pour appliquer un et un seul n'est pas nul. J'en utilise deux pour plus de clarté:Cela n'appliquera la contrainte qu'au niveau de la base de données. Vous devrez valider manuellement les entrées dans vos formulaires ou sérialiseurs.
En remarque, vous devriez probablement utiliser
OneToOneField
au lieu deForeignKey(unique=True)
. Vous voudrez aussiblank=True
.la source
Je pense que vous parlez de relations génériques , docs . Votre réponse ressemble à celle-ci .
Il y a quelque temps, j'avais besoin d'utiliser des relations génériques, mais j'ai lu dans un livre et ailleurs que l'utilisation devait être évitée, je pense que c'était Two Scoops of Django.
J'ai fini par créer un modèle comme celui-ci:
Je ne sais pas si c'est une bonne solution et comme vous l'avez mentionné, vous préférez ne pas l'utiliser, mais cela fonctionne dans mon cas.
la source
Il est peut-être tard pour répondre à votre question, mais j'ai pensé que ma solution pourrait convenir au cas d'une autre personne.
Je créerais un nouveau modèle, appelons-le
Dependency
, et appliquerais la logique de ce modèle.Ensuite, j'écrirais la logique pour être applicable de manière très explicite.
Il ne vous reste plus qu'à créer un single
ForeignKey
pour votreInspection
modèle.Dans vos
view
fonctions, vous devez créer unDependency
objet puis l'attribuer à votreInspection
dossier. Assurez-vous que vous utilisezcreate_dependency_object
dans vosview
fonctions.Cela rend à peu près votre code explicite et à l'épreuve des bogues. L'application peut être contournée trop facilement. Mais le fait est qu'il a besoin d'une connaissance préalable de cette limitation exacte pour être contourné.
la source