¿Cuál es la diferencia entre django OneToOneField y ForeignKey?

¿Cuál es la diferencia entre django OneToOneField y ForeignKey?

Tenga cuidado de darse cuenta de que hay algunas diferencias entre OneToOneField(SomeModel) y ForeignKey(SomeModel, unique=True) . Como se indica en The Definitive Guide to Django :

OneToOneField

Una relación uno a uno. Conceptualmente, esto es similar a una ForeignKey con unique=True , pero el lado “inverso” de la relación devolverá directamente un solo objeto.

A diferencia de la OneToOneField “inversa” de OneToOneField , una relación “inversa” de ForeignKey devuelve un QuerySet .

Ejemplo

Por ejemplo, si tenemos los siguientes dos modelos (código de modelo completo a continuación):

  1. Car modelo de Car usa OneToOneField(Engine)
  2. Car2 modelo Car2 usa ForeignKey(Engine2, unique=True)

Desde python manage.py shell ejecuta lo siguiente:

Ejemplo OneToOneField

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

ForeignKey con unique=True Example

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

Código de modelo

 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 

Una ForeignKey es para uno-a-muchos, por lo que un objeto de automóvil puede tener muchas ruedas, cada rueda tiene una clave externa para el automóvil al que pertenece. Un OneToOneField sería como un Engine, donde un objeto Car puede tener uno y solo uno.

La mejor y más efectiva forma de aprender cosas nuevas es ver y estudiar ejemplos prácticos del mundo real. Supongamos por un momento que desea construir un blog en django donde los periodistas pueden escribir y publicar artículos de noticias. El propietario del periódico en línea quiere permitir que cada uno de sus reporteros publique tantos artículos como desee, pero no quiere que diferentes periodistas trabajen en el mismo artículo. Esto significa que cuando los lectores van a leer un artículo, verán solo un autor en el artículo.

Por ejemplo: Artículo de John, Artículo de Harry, Artículo de Rick. No puede tener el artículo de Harry y Rick porque el jefe no quiere que dos o más autores trabajen en el mismo artículo.

¿Cómo podemos resolver este ‘problema’ con la ayuda de django? La clave para la solución de este problema es django ForeignKey .

El siguiente es el código completo que puede usarse para implementar la idea de nuestro jefe.

 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 

Ejecute python manage.py syncdb para ejecutar el código sql y comstackr las tablas para su aplicación en su base de datos. Luego use el python manage.py shell para abrir un shell de python.

Crea el objeto Reporter R1.

 In [49]: from thepub.models import Reporter, Article In [50]: R1 = Reporter(first_name='Rick') In [51]: R1.save() 

Crea el objeto de Artículo A1.

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

Luego use la siguiente pieza de código para obtener el nombre del periodista.

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

Ahora crea el objeto Reporter R2 ejecutando el siguiente código python.

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

Ahora intenta agregar R2 al objeto de Artículo A1.

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

No funciona y obtendrá un AttributeError diciendo que el objeto ‘Reporter’ no tiene el atributo ‘add’.

Como puede ver, un objeto Artículo no se puede relacionar con más de un objeto Reporter.

¿Qué hay de R1? ¿Podemos adjuntarle más de un artículo?

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

Este ejemplo práctico nos muestra que django ForeignKey se usa para definir relaciones de muchos a uno.

OneToOneField se usa para crear relaciones uno a uno.

Podemos usar reporter = models.OneToOneField(Reporter) en el archivo models.py anterior, pero no va a ser útil en nuestro ejemplo, ya que un autor no podrá publicar más de un artículo.

Cada vez que desee publicar un nuevo artículo, deberá crear un nuevo objeto Reporter. Esto lleva mucho tiempo, ¿no?

Recomiendo probar el ejemplo con OneToOneField y darse cuenta de la diferencia. Estoy bastante seguro de que después de este ejemplo sabrá por OneToOneField la diferencia entre django OneToOneField y django ForeignKey .

OneToOneField (uno a uno) se da cuenta, en orientación a objetos, de la noción de composición, mientras que ForeignKey (uno a muchos) se relaciona con la agregación.

Cuando accede a OneToOneField obtiene el valor del campo que ha consultado. En este ejemplo, el campo ‘título’ de un modelo de libro es OneToOneField:

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

Cuando accede a ForeignKey obtiene el objeto modelo relacionado, que luego puede preformar otras consultas en contra. En este ejemplo, el campo ‘publicador’ del mismo libro es una ForeignKey (que se correlaciona con la definición del modelo de la clase Publisher):

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

Las consultas de campos de ForeignKey también funcionan a la inversa, pero son ligeramente diferentes debido a la naturaleza no simétrica de la relación.

 >>> p = Publisher.objects.get(name='Apress Publishing') >>> p.book_set.all() [, , ...] 

Detrás de escena, book_set es solo un QuerySet y se puede filtrar y dividir como cualquier otro QuerySet. El nombre del atributo book_set se genera al agregar el nombre del modelo en minúscula a _set.

También OneToOneField es útil para ser utilizado como clave principal para evitar la duplicación de claves. Uno puede no tener autofield implícito / explícito

 models.AutoField(primary_key=True) 

pero use OneToOneField como clave principal en su lugar (imagine el modelo UserProfile por ejemplo):

 user = models.OneToOneField( User, null=False, primary_key=True, verbose_name='Member profile')