Cómo expresar una relación uno a muchos en Django


167

Estoy definiendo mis modelos de Django en este momento y me di cuenta de que no había un OneToManyFieldtipo de campo en el modelo. Estoy seguro de que hay una manera de hacer esto, así que no estoy seguro de lo que me estoy perdiendo. Básicamente tengo algo como esto:

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

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

En este caso, cada uno Dudepuede tener múltiples PhoneNumbers, pero la relación debe ser unidireccional, en la que no necesito saber desde el PhoneNumbercual Dudelo posee, por sí mismo, ya que podría tener muchos objetos diferentes que los propios PhoneNumbercasos, como una Businessde ejemplo:

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

¿Con qué reemplazaría OneToManyField(que no existe) en el modelo para representar este tipo de relación? Vengo de Hibernate / JPA donde declarar una relación de uno a muchos fue tan fácil como:

@OneToMany
private List<PhoneNumber> phoneNumbers;

¿Cómo puedo expresar esto en Django?

Respuestas:


134

Para manejar las relaciones uno a muchos en Django, debe usar ForeignKey.

La documentación en ForeignKey es muy completa y debe responder a todas las preguntas que tenga:

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

La estructura actual en su ejemplo permite que cada tipo tenga un número y que cada número pertenezca a varios tipos (lo mismo con Business).

Si desea la relación inversa, deberá agregar dos campos ForeignKey a su modelo PhoneNumber, uno a Dude y otro a Business. Esto permitiría que cada número pertenezca a un tipo o una empresa, y que los tipos y las empresas puedan tener varios números. Creo que esto podría ser lo que buscas.

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

3
¿Podría darme un ejemplo dado el problema anterior? Probablemente me lo estoy perdiendo por completo, pero he estado leyendo la documentación de Django durante algún tiempo y todavía no tengo claro cómo crear este tipo de relación.
Naftuli Kay

44
puede querer hacer que las dos claves foráneas no sean necesarias (en blanco = verdadero, nulo = verdadero), o agregar algún tipo de validación personalizada para asegurarse de que haya al menos una u otra. ¿Qué pasa con el caso de una empresa que tiene un número genérico? o un tipo desempleado?
j_syk

1
@j_syk buen punto sobre la validación personalizada. pero parece un poco raro incluir tanto una clave extranjera para amigo como una clave extranjera para negocios, y luego hacer una validación personalizada (externa a la definición del modelo). Parece que tiene que haber una forma más limpia, pero tampoco puedo resolverlo.
Andy

En esta situación, es apropiado usar el Marco de ContentTypes que permite relaciones genéricas con diferentes objetos. Sin embargo, el uso de tipos de contenido puede volverse muy complejo muy rápidamente, y si esta es su única necesidad, este enfoque más simple (si es hacky) podría ser deseable.
kball

57
Una cosa relevante a mencionar es el argumento "related_name" de ForeignKey. Entonces, en la clase PhoneNumber que tendría dude = models.ForeignKey(Dude, related_name='numbers')y luego puede usar some_dude_object.numbers.all()para obtener todos los números relacionados (si no especifica un "nombre_definido", el valor predeterminado será "número_conjunto").
markshep

40

En Django, una relación de uno a muchos se llama ForeignKey. Sin embargo, solo funciona en una dirección, por lo que, en lugar de tener un numberatributo de clase Dude, necesitará

class Dude(models.Model):
    ...

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

Muchos modelos pueden tener ForeignKeyuno a otro modelo, por lo que sería válido tener un segundo atributo de PhoneNumbertal manera que

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

Puede acceder a la PhoneNumbers para un Dudeobjeto dcon d.phonenumber_set.objects.all(), y luego hacer lo mismo para un Businessobjeto.


1
Estaba bajo la suposición de que ForeignKeysignificaba "uno a uno". Usando su ejemplo anterior, debería tener un Dudeque tiene muchos PhoneNumbers¿verdad?
Naftuli Kay

66
Edité mi respuesta para reflejar esto. Si. ForeignKeyes solo uno a uno si lo especifica ForeignKey(Dude, unique=True), por lo que con el código anterior obtendrá un Dudecon múltiples PhoneNumbers.
stillLearning

1
@rolling stone- gracias, estaba agregando eso después de darme cuenta de mi error mientras comentabas. Unique = True no funciona exactamente como OneToOneField, quise explicar que ForeignKey solo usa una relación uno a uno si especifica Unique = True.
stillLearning

8
+1 por hacerlo desde el PhoneNumber. Ahora está empezando a tener sentido. ForeignKeyes esencialmente muchos a uno, por lo que debe hacerlo al revés para obtener uno a muchos :)
Naftuli Kay

2
¿Alguien puede explicar la importancia del nombre del campo phonenumber_set? No lo veo definido en ningún lado. ¿Es el nombre del modelo, en minúsculas, junto con "_set"?
James Wierzba


15

Puede usar una clave externa en muchos lados de la OneToManyrelación (es decir, ManyToOnerelación) o usar ManyToMany(en cualquier lado) con una restricción única.


8

djangoes lo suficientemente inteligente En realidad no necesitamos definir el oneToManycampo. Será generado automáticamente por djangousted :-). Solo necesitamos definir foreignKeyen la tabla relacionada. En otras palabras, solo necesitamos definir la ManyToOnerelación mediante el uso 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 queremos obtener la lista de ruedas de un automóvil en particular. Utilizaremos el python'sobjeto generado automáticamente wheel_set. Para el auto cusarásc.wheel_set.all()


5

Si bien la respuesta de Rolling Stone es buena, directa y funcional, creo que hay dos cosas que no resuelve.

  1. Si OP quería imponer un número de teléfono, no puede pertenecer tanto a Dude como a Business
  2. El inevitable sentimiento de tristeza como resultado de definir la relación en el modelo PhoneNumber y no en los modelos Dude / Business. Cuando los terrestres adicionales llegan a la Tierra, y queremos agregar un modelo Alien, necesitamos modificar el número de teléfono (suponiendo que los ET tengan números de teléfono) en lugar de simplemente agregar un campo "phone_numbers" al modelo Alien.

Presente el marco de tipos de contenido , que expone algunos objetos que nos permiten crear una "clave externa genérica" ​​en el modelo PhoneNumber. Entonces, podemos definir la relación inversa en Dude y 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)

Consulte los documentos para obtener más información, y quizás consulte este artículo. para obtener un tutorial rápido.

Además, aquí hay un artículo que argumenta en contra del uso de FK genéricos.


0

Si el modelo "muchos" no justifica la creación de un modelo per se (no es el caso aquí, pero podría beneficiar a otras personas), otra alternativa sería confiar en tipos de datos específicos de PostgreSQL, a través del paquete Django Contrib

Postgres pueden hacer frente a la matriz o JSON tipos de datos, y esto puede ser una buena solución para manejar Uno-A-Muchos, cuando los muchos-s sólo pueden estar vinculados a una única entidad del uno .

Postgres le permite acceder a elementos individuales de la matriz, lo que significa que las consultas pueden ser realmente rápidas y evitar gastos generales a nivel de aplicación. Y, por supuesto, Django implementa una API genial para aprovechar esta característica.

Obviamente tiene la desventaja de no ser portátil para el backend de la base de datos de otros, pero creo que aún vale la pena mencionarlo.

Espero que pueda ayudar a algunas personas que buscan ideas.


0

Antes que nada hacemos un recorrido:

01) relación uno a muchos:

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 no proporciona ninguna relación OneToMany. Entonces no podemos usar el método superior en Django. Pero necesitamos convertir en modelo relacional. Entonces, ¿qué podemos hacer? En esta situación, necesitamos convertir el modelo relacional en un modelo relacional inverso.

Aquí:

modelo relacional = OneToMany

Entonces, modelo relacional inverso = ManyToOne

NB: Django admite la relación ManyToOne y en Django ManyToOne está representado por ForeignKey.

02) relación de muchos a uno:

SOLVE:

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

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

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

NB: ¡PIENSE SIMPLEMENTE!

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.