web-dev-qa-db-fra.com

Quelle est la différence entre un champ OneToOne, ManyToMany et un champ ForeignKey dans Django?

J'ai un peu de mal à comprendre les relations dans les modèles Django.

Quelqu'un pourrait-il expliquer quelle est la différence entre un OneToOne, ManyToMany et ForeignKey?

42
user3915903

Eh bien, il y a essentiellement deux questions ici:

  1. Quelle est la différence (en général) entre une à une, plusieurs à plusieurs et des relations clés étrangères
  2. Quelles sont leurs différences spécifiques à Django.

Il est assez facile de répondre à ces deux questions via une simple recherche Google, mais comme je ne peux pas trouver une dupe exacte de cette question sur SO, je vais continuer et répondre.

Notez que dans Django, les relations ne doivent être définies que d'un côté de la relation.


Clé étrangère

Une relation de clé étrangère est généralement connue sous le nom de relation plusieurs-à-un. Notez que l'inverse de cette relation est un-à-plusieurs (qui Django fournit des outils d'accès). Comme son nom l'indique, de nombreux objets peuvent être liés à un seul.

Person >--| Birthplace
   ^           ^
   |           |
  Many        One 

Dans cet exemple, une personne peut n'avoir qu'un seul lieu de naissance, mais un lieu de naissance peut être lié à de nombreuses personnes. Regardons cet exemple dans Django. Dites que ce sont nos modèles:

class Birthplace(models.Model):
    city = models.CharField(max_length=75)
    state = models.CharField(max_length=25)

    def __unicode__(self):
        return "".join(self.city, ", ", self.state)

class Person(models.Model):
    name = models.CharField(max_length=50)
    birthplace = models.ForeignKey(Birthplace)

    def __unicode__(self):
        return self.name

Vous pouvez voir qu'aucune relation n'est définie dans le modèle Birthplace et qu'une relation ForeignKey est définie dans le modèle Person. Supposons que nous créons les instances suivantes de nos modèles (évidemment pas dans Python):

  • Lieu de naissance: Dallas, Texas
  • Lieu de naissance: New York City, New York
  • Personne: John Smith, lieu de naissance: (Dallas, Texas)
  • Personne: Maria Lee, lieu de naissance: (Dallas, Texas)
  • Personne: Daniel Lee, lieu de naissance: (New York City, New York)

Maintenant, nous pouvons voir comment Django nous permet d'utiliser ces relations (notez que ./manage.py Shell Est votre ami!):

>> from somewhere.models import Birthplace, Person
>> Person.objects.all()
[<Person: John Smith>, <Person: Maria Lee>, <Person: Daniel Lee>]
>> Birthplace.objects.all()
[<Birthplace: Dallas, Texas>, <Birthplace: New York City, New York>]

Vous pouvez voir les instances de modèle que nous avons créées. Voyons maintenant le lieu de naissance de quelqu'un:

>> person = Person.object.get(name="John Smith")
>> person.birthplace
<Birthplace: Dallas, Texas>
>> person.birthplace.city
Dallas

Disons que vous voulez voir toutes les personnes avec un lieu de naissance donné. Comme je l'ai dit plus tôt, Django vous permet d'accéder aux relations inverses. Par défaut, Django crée un gestionnaire ( RelatedManager ) sur votre modèle pour gérer cela, nommé <model>_set, où <model> est le nom de votre modèle en minuscules.

>> place = Birthplace.objects.get(city="Dallas")
>> place.person_set.all()
[<Person: John Smith>, <Person: Maria Lee>]

Notez que nous pouvons changer le nom de ce gestionnaire en définissant l'argument du mot clé related_name Dans notre relation de modèle. Ainsi, nous changerions le champ birthplace dans le modèle Person en:

birthplace = models.ForeignKey(Birthplace, related_name="people")

Maintenant, nous pouvons accéder à cette relation inverse avec un joli nom:

>> place.people.all()
[<Person: John Smith>, <Person: Maria Lee>]

Un par un

Une relation un-à-un est assez similaire à une relation plusieurs-à-un, sauf qu'elle limite deux objets à une relation unique. Un exemple de ceci serait un utilisateur et un profil (qui stocke des informations sur l'utilisateur). Deux utilisateurs ne partagent pas le même profil.

User |--| Profile
  ^          ^
  |          |
 One        One

Regardons cela dans Django. Je ne prendrai pas la peine de définir le modèle utilisateur, comme Django le définit pour nous. Notez cependant que Django suggère d'utiliser Django.contrib.auth.get_user_model() pour importer l'utilisateur, c'est ce que nous allons faire. Le modèle de profil peut être défini comme suit:

class Profile(models.Model):
    user = models.OneToOneField(settings.AUTH_USER_MODEL) # Note that Django suggests getting the User from the settings for relationship definitions
    fruit = models.CharField(max_length=50, help_text="Favorite Fruit")
    facebook = models.CharField(max_length=100, help_text="Facebook Username")

    def __unicode__(self):
        return "".join(self.fruit, " ", self.facebook)

Tout ce dont nous avons besoin, c'est d'un utilisateur avec un profil pour tester cela dans le shell:

  • Utilisateur: johndt6
  • Profil: utilisateur: johndt6, "Kiwi", "blah_blah"

Maintenant, vous pouvez facilement accéder au profil de l'utilisateur à partir du modèle utilisateur:

>> user = User.objects.all()[0]
>> user.username
johndt6
>> user.profile
<Profile: Kiwi blah_blah>
>> user.profile.fruit
Kiwi
>> profile = Profile.objects.get(user=user)
>> profile.user
<User: johndt6>

Bien sûr, vous pouvez personnaliser le nom de la relation inverse en utilisant l'argument related_name Comme ci-dessus.


Plusieurs à plusieurs

Les relations plusieurs-à-plusieurs peuvent être un peu délicates. Permettez-moi de commencer par dire que les champs plusieurs-à-plusieurs sont désordonnés et doivent être évités autant que possible. Compte tenu de cela, il existe de nombreuses situations où une relation plusieurs-à-plusieurs a du sens.

Une relation plusieurs-à-plusieurs entre deux modèles définit que zéro, un ou plusieurs objets du premier modèle peuvent être liés à zéro, un ou plusieurs objets du deuxième modèle. À titre d'exemple, imaginons une entreprise qui définit son flux de travail à travers des projets. Un projet peut être lié à aucune commande, à une seule commande ou à plusieurs commandes. Une commande peut être liée à aucun projet, à un projet ou à plusieurs.

Order >--< Project
  ^           ^
  |           |
 Many        Many

Définissons nos modèles comme suit:

class Order(models.Model):
    product = models.CharField(max_length=150)  # Note that in reality, this would probably be better served by a Product model
    customer = models.CharField(max_length=150)  # The same may be said for customers

    def __unicode__(self):
        return "".join(self.product, " for ", self.customer)

class Project(models.Model):
    orders = models.ManyToManyField(Order)

    def __unicode__(self):
        return "".join("Project ", str(self.id))

Notez que Django créera un RelatedManager pour le champ orders pour accéder à la relation plusieurs-à-plusieurs.

Créons les instances suivantes de nos modèles (dans ma syntaxe incohérente!):

  • Ordre: "Vaisseau spatial", "NASA"
  • Ordre: "Sous-marin", "US Navy"
  • Commande: "Voiture de course", "NASCAR"
  • Projet: commandes: []
  • Projet: commandes: [(Commande: "Vaisseau spatial", "NASA")]
  • Projet: commandes: [(Commande: "Vaisseau spatial", "NASA"), (Commande: "Voiture de course", "NASCAR")]

Nous pouvons accéder à ces relations comme suit:

>> Project.objects.all()
[<Project: Project 0>, <Project: Project 1>, <Project: Project 2>]
>> for proj in Project.objects.all():
..     print(proj)
..     proj.orders.all()  # Note that we must access the `orders`
..                        # field through its manager
..     print("")
Project 0
[]

Project 1
[<Order: Spaceship for NASA>]

Project 2
[<Order: Spaceship for NASA>, <Order: Race car for NASCAR>]

Notez que l'ordre de la NASA est lié à 2 projets et que l'ordre de l'US Navy n'est lié à aucun. Notez également qu'un projet n'a pas de commandes et un en a plusieurs.

Nous pouvons également accéder à la relation dans le sens inverse de la même manière que précédemment:

>> order = Order.objects.filter(customer="NASA")[0]
>> order.project_set.all()
[<Project: Project 0>, <Project: Project 2>]

Guide de cardinalité ASCII

Dans le cas probable où mes diagrammes ASCII sont un peu déroutants, les explications suivantes pourraient être utiles:

  • > Ou < Signifie "à plusieurs"
  • | Signifie "à un"

Donc ... A --| B Signifie qu'une instance de A peut être liée à une seule instance de B.

Et A --< B Signifie qu'une instance de A peut être liée à de nombreuses instances de B.

A >--< B Équivaut à ....

A --< B
A >-- B

Ainsi, chaque "côté" ou direction de la relation peut être lu séparément. Il est juste pratique de les écraser ensemble.

Élargir l'une de ces relations pourrait avoir plus de sens:

               +---- John Smith
               |
 Dallas|-------+---- Jane Doe
               |
               +---- Joe Smoe

Ressources

Bonne explication des relations db fourni par @MarcB

page Wikipedia sur la cardinalité

Django Docs:

models.ForeignKey

models.OneToOneField

models.ManyToManyField

Relations un à un

Relations plusieurs-à-plusieurs

107
Johndt6