Quelle est la différence entre django OneToOneField et ForeignKey?

402

Quelle est la différence entre Django OneToOneFieldet ForeignKey?

redice
la source

Réponses:

507

Veillez à réaliser qu'il existe des différences entre OneToOneField(SomeModel)et ForeignKey(SomeModel, unique=True). Comme indiqué dans le guide définitif de Django :

OneToOneField

Une relation un à un. Conceptuellement, ceci est similaire à un ForeignKeyavec unique=True, mais le côté "inverse" de la relation renvoie directement un seul objet.

Contrairement à la OneToOneFieldrelation "inverse", une relation ForeignKey"inverse" renvoie a QuerySet.

Exemple

Par exemple, si nous avons les deux modèles suivants (code complet du modèle ci-dessous):

  1. Car modèle utilise OneToOneField(Engine)
  2. Car2 modèle utilise ForeignKey(Engine2, unique=True)

De l'intérieur, python manage.py shellexécutez ce qui suit:

OneToOneField Exemple

>>> from testapp.models import Car, Engine
>>> c = Car.objects.get(name='Audi')
>>> e = Engine.objects.get(name='Diesel')
>>> e.car
<Car: Audi>

ForeignKeyavec l' unique=Trueexemple

>>> from testapp.models import Car2, Engine2
>>> c2 = Car2.objects.get(name='Mazda')
>>> e2 = Engine2.objects.get(name='Wankel')
>>> e2.car2_set.all()
[<Car2: Mazda>]

Code modèle

from django.db import models

class Engine(models.Model):
    name = models.CharField(max_length=25)

    def __unicode__(self):
        return self.name

class Car(models.Model):
    name = models.CharField(max_length=25)
    engine = models.OneToOneField(Engine)

    def __unicode__(self):
        return self.name

class Engine2(models.Model):
    name = models.CharField(max_length=25)

    def __unicode__(self):
        return self.name

class Car2(models.Model):
    name = models.CharField(max_length=25)
    engine = models.ForeignKey(Engine2, unique=True, on_delete=models.CASCADE)

    def __unicode__(self):
        return self.name
Matthew Rankin
la source
5
@MarkPNeyer: pour autant que je sache, un champ OneToOne n'est que cela: un à un. Il ne doit pas nécessairement être activé. Voir cet exemple : un endroit ne doit pas nécessairement être un restaurant.
osa
21
Cette réponse dit "il y a des différences", puis nomme une différence. Y en a-t-il d'autres?
Chris Martin
6
Je me demande la même chose que Chris. S'agit-il simplement de sucre syntaxique, y a-t-il une différence sous-jacente dans la façon d'accéder aux données, entraînant des différences de performances?
Carlos
4
Y a-t-il une raison fondamentale pour laquelle Django ne pourrait pas avoir de règle telle que si la clé étrangère est unique et non nulle, elle e.carfonctionne également?
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
4
Alors ... quand voudrait-on même utiliser un ForeignKeyavec unique=Trueplutôt qu'un OneToOneField? Je vois dans d'autres questions que Django prévient même que OneToOneFieldles intérêts de chacun sont généralement les meilleurs. L'inverse QuerySetn'aura jamais plus d'un élément, non?
Andy
121

Une clé étrangère est pour un à plusieurs, donc un objet Car peut avoir plusieurs roues, chaque roue ayant une clé étrangère à la voiture à laquelle il appartient. Un OneToOneField serait comme un moteur, où un objet Car peut en avoir un et un seul.

Dan Breen
la source
4
merci, Dose OneToOneField (someModel) signifie ForeignKey (SomeModel, unique = True)?
redice
9
Oui: 'Un OneToOneField est essentiellement le même qu'une ForeignKey, à l'exception qui porte toujours une contrainte "unique" avec lui et la relation inverse renvoie toujours l'objet pointé (car il n'y en aura jamais qu'un), plutôt que de renvoyer un liste.'
Dan Breen
1
Qu'en est-il de plusieurs voitures ayant le même moteur?
Oleg Belousov
3
@OlegTikhonov Ils pourraient avoir une copie de la même conception de moteur, mais j'aimerais voir un exemple où plusieurs voitures partagent le même moteur physique.
Dan Breen
3
Il y a un peu de confusion sur les termes de cette réponse. ForeignKey n'est pas une relation un-à-plusieurs, mais une relation plusieurs-à-un selon la documentation officielle de django: docs.djangoproject.com/en/2.0/ref/models/fields/…
Kutay Demireren
45

Le meilleur moyen et le plus efficace d'apprendre de nouvelles choses est de voir et d'étudier des exemples pratiques du monde réel. Supposons un instant que vous souhaitiez créer un blog dans django où les journalistes peuvent écrire et publier des articles de presse. Le propriétaire du journal en ligne souhaite autoriser chacun de ses reporters à publier autant d'articles qu'il le souhaite, mais ne souhaite pas que différents reporters travaillent sur le même article. Cela signifie que lorsque les lecteurs vont lire un article, ils ne voient qu'un seul auteur dans l'article.

Par exemple: Article de John, Article de Harry, Article de Rick. Vous ne pouvez pas avoir un article de Harry & Rick parce que le patron ne veut pas que deux ou plusieurs auteurs travaillent sur le même article.

Comment pouvons-nous résoudre ce «problème» avec l'aide de Django? La clé de la solution de ce problème est le django ForeignKey.

Ce qui suit est le code complet qui peut être utilisé pour implémenter l'idée de notre patron.

from django.db import models

# Create your models here.

class Reporter(models.Model):
    first_name = models.CharField(max_length=30)

    def __unicode__(self):
        return self.first_name


class Article(models.Model):
    title = models.CharField(max_length=100)
    reporter = models.ForeignKey(Reporter)

    def __unicode__(self):
        return self.title

Exécutez python manage.py syncdbpour exécuter le code SQL et créer les tables de votre application dans votre base de données. Ensuite, utilisez python manage.py shellpour ouvrir un shell python.

Créez l'objet Reporter R1.

In [49]: from thepub.models import Reporter, Article

In [50]: R1 = Reporter(first_name='Rick')

In [51]: R1.save()

Créez l'objet Article A1.

In [5]: A1 = Article.objects.create(title='TDD In Django', reporter=R1)

In [6]: A1.save()

Utilisez ensuite le code suivant pour obtenir le nom du reporter.

In [8]: A1.reporter.first_name
Out[8]: 'Rick'

Créez maintenant l'objet Reporter R2 en exécutant le code python suivant.

In [9]: R2 = Reporter.objects.create(first_name='Harry')

In [10]: R2.save()

Essayez maintenant d'ajouter R2 à l'objet Article A1.

In [13]: A1.reporter.add(R2)

Cela ne fonctionne pas et vous obtiendrez une AttributeError disant que l'objet 'Reporter' n'a pas d'attribut 'add'.

Comme vous pouvez le voir, un objet Article ne peut pas être lié à plusieurs objets Reporter.

Et R1? Pouvons-nous y attacher plusieurs objets Article?

In [14]: A2 = Article.objects.create(title='Python News', reporter=R1)

In [15]: R1.article_set.all()
Out[15]: [<Article: Python News>, <Article: TDD In Django>]

Cet exemple pratique nous montre que django ForeignKeyest utilisé pour définir des relations plusieurs-à-un.

OneToOneField est utilisé pour créer des relations un à un.

Nous pouvons utiliser reporter = models.OneToOneField(Reporter)dans le fichier models.py ci-dessus mais cela ne sera pas utile dans notre exemple car un auteur ne pourra pas poster plus d'un article.

Chaque fois que vous souhaitez publier un nouvel article, vous devrez créer un nouvel objet Reporter. C'est long, n'est-ce pas?

Je recommande fortement d'essayer l'exemple avec le OneToOneFieldet de réaliser la différence. Je suis presque sûr qu'après cet exemple, vous saurez complètement la différence entre django OneToOneFieldet django ForeignKey.

jetbird13
la source
J'aime ça. La différence fondamentale entre OneToOne et ForeignKey est une relation un à un et un à plusieurs. Vous pouvez utiliser ForeignKey et unique = True pour faire un à un, la différence subtile est indiquée dans la réponse de Matthew.
FrankZhu
13

OneToOneField (one-to-one) réalise, en orientation objet, la notion de composition, tandis que ForeignKey (one-to-many) concerne l'agrégation.

andrers52
la source
3
Belle analogie, mais ce n'est pas toujours comme ça. Il y a quelques cas marginaux qui ne rentrent pas dans cette explication. Disons par exemple que nous avons des classes Patientet Organ. Patientpeut avoir plusieurs Organs, mais un Organne peut appartenir qu'à un seul Patient. Lorsque Patientest supprimé, tous les Organs sont également supprimés. Ils ne peuvent pas exister sans Patient.
cezar
4

Il OneToOneFieldest également utile d'être utilisé comme clé primaire pour éviter la duplication des clés. On peut ne pas avoir de champ automatique implicite / explicite

models.AutoField(primary_key=True)

mais utilisez OneToOneFieldplutôt comme clé primaire (imaginez un UserProfilemodèle par exemple):

user = models.OneToOneField(
    User, null=False, primary_key=True, verbose_name='Member profile')
Dmitriy Sintsov
la source
3

Lorsque vous accédez à OneToOneField, vous obtenez la valeur du champ que vous avez interrogé. Dans cet exemple, le champ «titre» d'un modèle de livre est un champ OneToOneField:

>>> from mysite.books.models import Book
>>> b = Book.objects.get(id=50)
>>> b.title
u'The Django Book'

Lorsque vous accédez à une clé étrangère, vous obtenez l'objet de modèle associé, sur lequel vous pouvez ensuite effectuer d'autres requêtes. Dans cet exemple, le champ «éditeur» du même modèle de livre est une clé étrangère (en corrélation avec la définition du modèle de classe Publisher):

>>> b = Book.objects.get(id=50)
>>> b.publisher
<Publisher: Apress Publishing>
>>> b.publisher.website
u'http://www.apress.com/'

Avec les champs ForeignKey, les requêtes fonctionnent également dans l'autre sens, mais elles sont légèrement différentes en raison de la nature non symétrique de la relation.

>>> p = Publisher.objects.get(name='Apress Publishing')
>>> p.book_set.all()
[<Book: The Django Book>, <Book: Dive Into Python>, ...]

Dans les coulisses, book_set est juste un QuerySet et peut être filtré et découpé comme n'importe quel autre QuerySet. Le nom d'attribut book_set est généré en ajoutant le nom du modèle en minuscule à _set.

Ouaip.
la source
1

OneToOneField: si la deuxième table est liée à

table2_col1 = models.OneToOneField(table1,on_delete=models.CASCADE, related_name='table1_id')

table2 contiendra un seul enregistrement correspondant à la valeur pk de table1, c'est-à-dire que table2_col1 aura une valeur unique égale à pk de table

table2_col1 == models.ForeignKey(table1, on_delete=models.CASCADE, related_name='table1_id')

table2 peut contenir plusieurs enregistrements correspondant à la valeur pk de table1.

aarif faridi
la source
1

ForeignKey vous permet de recevoir des sous-classes si c'est la définition d'une autre classe mais OneToOneFields ne peut pas le faire et il n'est pas attachable à plusieurs variables

Bitdom8
la source