Comment exprimer une relation un-à-plusieurs dans Django

167

Je suis en train de définir mes modèles Django en ce moment et j'ai réalisé qu'il n'y avait pas OneToManyFieldde type de champ de modèle. Je suis sûr qu'il existe un moyen de le faire, donc je ne suis pas sûr de ce que je manque. J'ai essentiellement quelque chose comme ça:

class Dude(models.Model):
    numbers = models.OneToManyField('PhoneNumber')

class PhoneNumber(models.Model):
    number = models.CharField()

Dans ce cas, chacun Dudepeut avoir plusieurs PhoneNumbers, mais la relation doit être unidirectionnelle, en ce sens que je n'ai pas besoin de savoir à partir de PhoneNumberqui le Dudepossède, en soi, car je pourrais avoir de nombreux objets différents qui possèdent des PhoneNumberinstances, comme un Businessfor exemple:

class Business(models.Model):
    numbers = models.OneToManyField('PhoneNumber')

Qu'est-ce que je remplacerais OneToManyField(qui n'existe pas) dans le modèle pour représenter ce type de relation? Je viens d'Hibernate / JPA où déclarer une relation un-à-plusieurs était aussi simple que:

@OneToMany
private List<PhoneNumber> phoneNumbers;

Comment puis-je exprimer cela dans Django?

Naftuli Kay
la source

Réponses:

135

Pour gérer les relations un-à-plusieurs dans Django, vous devez utiliser ForeignKey.

La documentation sur ForeignKey est très complète et devrait répondre à toutes les questions que vous vous posez:

https://docs.djangoproject.com/en/dev/ref/models/fields/#foreignkey

La structure actuelle dans votre exemple permet à chaque mec d'avoir un numéro, et chaque numéro d'appartenir à plusieurs mecs (même chose avec Business).

Si vous voulez la relation inverse, vous devez ajouter deux champs ForeignKey à votre modèle PhoneNumber, un à Dude et un à Business. Cela permettrait à chaque numéro d'appartenir à un mec ou à une entreprise, et permettrait aux mecs et aux entreprises de posséder plusieurs numéros. Je pense que c'est peut-être ce que vous recherchez.

class Business(models.Model):
    ...
class Dude(models.Model):
    ...
class PhoneNumber(models.Model):
    dude = models.ForeignKey(Dude)
    business = models.ForeignKey(Business)
Pierre roulante
la source
3
Pouvez-vous me donner un exemple compte tenu du problème ci-dessus? Cela me manque probablement complètement, mais je lis la documentation de Django depuis un certain temps maintenant et je ne sais toujours pas comment créer ce type de relation.
Naftuli Kay
4
peut vouloir rendre les deux ForeignKeys non requis (blank = True, null = True), ou ajouter une sorte de validation personnalisée pour s'assurer qu'il existe au moins l'un ou l'autre. qu'en est-il du cas d'une entreprise ayant un numéro générique? ou un mec au chômage?
j_syk
1
@j_syk bon point sur la validation personnalisée. mais il semble un peu piraté d'inclure à la fois une clé étrangère à mec et une clé étrangère à entreprise, puis de faire une validation personnalisée (externe à la définition du modèle). semble qu'il doit y avoir un moyen plus propre, mais je ne peux pas le comprendre non plus.
andy
Dans cette situation, il est approprié d'utiliser le ContentTypes Framework qui permet des relations génériques avec différents objets. Cependant, l'utilisation de types de contenu peut devenir très complexe très rapidement, et si c'est votre seul besoin, cette approche plus simple (si piratée) pourrait être souhaitable.
kball
57
Une chose pertinente à mentionner est l'argument "related_name" de ForeignKey. Donc, dans la classe PhoneNumber que vous auriez dude = models.ForeignKey(Dude, related_name='numbers')et que vous pouvez utiliser some_dude_object.numbers.all()pour obtenir tous les numéros associés (si vous ne spécifiez pas de "related_name", il sera par défaut "number_set").
markshep
40

Dans Django, une relation un-à-plusieurs est appelée ForeignKey. Cependant, cela ne fonctionne que dans un sens, donc plutôt que d'avoir un numberattribut de classe, Dudevous aurez besoin

class Dude(models.Model):
    ...

class PhoneNumber(models.Model):
    dude = models.ForeignKey(Dude)

De nombreux modèles peuvent avoir un ForeignKeyà un autre modèle, il serait donc valide d'avoir un deuxième attribut PhoneNumbertel que

class Business(models.Model):
    ...
class Dude(models.Model):
    ...
class PhoneNumber(models.Model):
    dude = models.ForeignKey(Dude)
    business = models.ForeignKey(Business)

Vous pouvez accéder aux PhoneNumbers pour un Dudeobjet davec d.phonenumber_set.objects.all(), puis faire de même pour un Businessobjet.

encore à apprendre
la source
1
J'étais sous l'hypothèse que cela ForeignKeysignifiait «un pour un». En utilisant votre exemple ci-dessus, je devrais avoir un Dudequi en a beaucoup, PhoneNumbersnon?
Naftuli Kay
6
J'ai modifié ma réponse pour refléter cela. Oui. ForeignKeyn'est qu'un-à-un si vous spécifiez ForeignKey(Dude, unique=True), donc avec le code ci-dessus, vous obtiendrez un Dudeavec plusieurs PhoneNumbers.
stillLearning
1
@rolling stone- merci, j'ajoutais cela après avoir réalisé mon erreur en commentant. Unique = True ne fonctionne pas exactement comme OneToOneField, je voulais expliquer que ForeignKey n'utilise une relation un-à-un que si vous spécifiez Unique = True.
stillLearning
8
+1 pour l'avoir fait depuis le PhoneNumber. Maintenant, cela commence à avoir un sens. ForeignKeyest essentiellement plusieurs-à-un, vous devez donc le faire à l'envers pour obtenir un un-à-plusieurs :)
Naftuli Kay
2
Quelqu'un peut-il expliquer la signification du nom du champ phonenumber_set? Je ne le vois défini nulle part. Est-ce le nom du modèle, en minuscules, accompagné de "_set"?
James Wierzba
15

Vous pouvez utiliser une clé étrangère sur plusieurs côtés de la OneToManyrelation (c'est-à-dire la ManyToOnerelation) ou utiliser ManyToMany(de n'importe quel côté) avec une contrainte unique.

Кирилл Лавров
la source
8

djangoest assez intelligent. En fait, nous n'avons pas besoin de définir le oneToManychamp. Il sera automatiquement généré par djangopour vous :-). Il suffit de définir foreignKeydans la table associée. En d'autres termes, il suffit de définir la ManyToOnerelation en utilisant foreignKey.

class Car(models.Model):
    // wheels = models.oneToMany() to get wheels of this car [**it is not required to define**].


class Wheel(models.Model):
    car = models.ForeignKey(Car, on_delete=models.CASCADE)  

si nous voulons obtenir la liste des roues d'une voiture particulière. nous utiliserons python'sun objet généré automatiquement wheel_set. Pour la voiture que cvous utiliserezc.wheel_set.all()

Cheikh Abdul Wahid
la source
5

Bien que la réponse de Rolling Stone soit bonne, simple et fonctionnelle, je pense qu'il y a deux choses qu'elle ne résout pas.

  1. Si OP voulait imposer un numéro de téléphone ne peut pas appartenir à la fois à un mec et à une entreprise
  2. Le sentiment inéluctable de tristesse résultant de la définition de la relation sur le modèle PhoneNumber et non sur les modèles Dude / Business. Lorsque des extraterrestres arrivent sur Terre et que nous voulons ajouter un modèle Alien, nous devons modifier le PhoneNumber (en supposant que les ETs ont des numéros de téléphone) au lieu d'ajouter simplement un champ "phone_numbers" au modèle Alien.

Présentez le cadre des types de contenu , qui expose certains objets qui nous permettent de créer une «clé étrangère générique» sur le modèle PhoneNumber. Ensuite, nous pouvons définir la relation inverse sur Dude et Business

from django.contrib.contenttypes.fields import GenericForeignKey, GenericRelation
from django.contrib.contenttypes.models import ContentType
from django.db import models

class PhoneNumber(models.Model):
    number = models.CharField()

    content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
    object_id = models.PositiveIntegerField()
    owner = GenericForeignKey()

class Dude(models.Model):
    numbers = GenericRelation(PhoneNumber)

class Business(models.Model):
    numbers = GenericRelation(PhoneNumber)

Consultez la documentation pour plus de détails et consultez peut-être cet article pour un didacticiel rapide.

Aussi, voici un article qui plaide contre l'utilisation de FK génériques.

rabeye
la source
0

Si le modèle «plusieurs» ne justifie pas la création d'un modèle en soi (ce n'est pas le cas ici, mais cela pourrait profiter à d'autres personnes), une autre alternative serait de s'appuyer sur des types de données PostgreSQL spécifiques, via le package Django Contrib

Postgres peut gérer les types de données Array ou JSON , ce qui peut être une bonne solution de contournement pour gérer One-To-Many lorsque les plusieurs-s ne peuvent être liés qu'à une seule entité de l' un .

Postgres vous permet d'accéder à des éléments uniques du tableau, ce qui signifie que les requêtes peuvent être très rapides et éviter les frais généraux au niveau de l'application. Et bien sûr, Django implémente une API intéressante pour tirer parti de cette fonctionnalité.

Il a évidemment l'inconvénient de ne pas être portable vers d'autres bases de données, mais je pense que cela vaut la peine d'être mentionné.

J'espère que cela peut aider certaines personnes à la recherche d'idées.

Edouardtheron
la source
0

Tout d'abord, nous faisons un tour:

01) Relation un-à-plusieurs:

ASSUME:

class Business(models.Model):
    name = models.CharField(max_length=200)
    .........
    .........
    phone_number = models.OneToMany(PhoneNumber) (NB: Django do not support OneToMany relationship)

class Dude(models.Model):
    name = models.CharField(max_length=200)
    .........
    .........
    phone_number = models.OneToMany(PhoneNumber) (NB: Django do not support OneToMany relationship)

class PhoneNumber(models.Model):
    number = models.CharField(max_length=20)
    ........
    ........

NB: Django ne propose aucune relation OneToMany. Nous ne pouvons donc pas utiliser la méthode supérieure dans Django. Mais nous devons nous convertir en modèle relationnel. Alors, que pouvons-nous faire? Dans cette situation, nous devons convertir le modèle relationnel en modèle relationnel inversé.

Ici:

modèle relationnel = OneToMany

Donc, modèle relationnel inversé = ManyToOne

NB: Django supporte la relation ManyToOne & dans Django ManyToOne est représenté par ForeignKey.

02) Relation plusieurs à un:

SOLVE:

class Business(models.Model):
    .........
    .........

class Dude(models.Model):
    .........
    .........

class PhoneNumber(models.Model):
    ........
    ........
    business = models.ForeignKey(Business)
    dude = models.ForeignKey(Dude)

NB: PENSEZ SIMPLEMENT !!

renaître
la source