HOWTO · Django

Búsquedas inversas de claves externas en Django

Más información sobre las búsquedas inversas de claves externas en Django

En esta página

Para encontrar filas que hacen referencia a una instancia concreta de un modelo de Django, sigue la ForeignKey en sentido inverso desde esa instancia. Django expone esta relación como un administrador; puedes llamar a métodos como all() o filter() para obtener un QuerySet. La opción related_name asigna un nombre legible a ese administrador inverso.

Cómo funcionan las búsquedas inversas de ForeignKey en Django

La ForeignKey se declara en el modelo que contiene la referencia. Por ejemplo, cada fila de Student puede hacer referencia a una fila de Teacher. Si partes de una instancia de Teacher, la búsqueda inversa devuelve las filas de Student cuya ForeignKey apunta a ese profesor. Esto resulta útil cuando una página o una tarea parte de un registro principal y necesita acceder a sus registros relacionados.

En este ejemplo, un alumno puede tener un profesor de clase y un profesor favorito. Ambos campos apuntan a Teacher, así que cada uno necesita un related_name distinto. Estos nombres se usan desde una instancia de Teacher:

En un modelo de Django, define Teacher con los campos name y subject; después, asigna a Student dos campos ForeignKey anulables hacia Teacher: class_teacher con related_name="class_students" y favorite_teacher con related_name="favorite_students". Los nombres son distintos a propósito: teacher.class_students sigue la relación class_teacher, mientras que teacher.favorite_students sigue favorite_teacher. Si omites related_name, Django suele derivar un acceso inverso del nombre del modelo; dos ForeignKey de Student a Teacher no pueden usar el mismo acceso inverso. Asignar ambos nombres de forma explícita deja claras las rutas. Estas declaraciones son específicas de Django y deben colocarse en un proyecto configurado con este framework.

Recuperar todos los objetos relacionados

Si existe un Teacher con clave primaria 1, puedes recuperar los alumnos que lo tienen como profesor de clase con Teacher.objects.get(pk=1) y, a continuación, teacher.class_students.all().

El valor de students es un QuerySet que representa las filas coincidentes y permite iterarlas. La segunda relación funciona igual: usa teacher.favorite_students.all() para obtener los alumnos que indican que ese profesor es su favorito. El nombre del administrador proviene del related_name de la ForeignKey, no del modelo Teacher. La búsqueda con get() presupone que existe un Teacher con esa clave primaria.

Para ver en una pequeña ilustración en memoria el mismo sentido de búsqueda, parte de un registro de profesor y selecciona los registros de alumno que hacen referencia a su identificador:

El código y las etiquetas o salidas visibles de esta sección se mantienen en inglés.

teacher_id = 1
students = [
    {"name": "Asha", "class_teacher_id": 1},
    {"name": "Ben", "class_teacher_id": 2},
]
related_students = [
    student for student in students
    if student["class_teacher_id"] == teacher_id
]
print([student["name"] for student in related_students])
['Asha']

Esta ilustración en Python puro muestra el sentido de la relación; un QuerySet de Django realiza la búsqueda equivalente en la base de datos.

Filtrar objetos relacionados para un registro concreto

Usa filter() en el administrador inverso cuando también necesites aplicar una condición al modelo relacionado. Por ejemplo, para buscar un alumno con clave primaria 5 entre los alumnos de clase del profesor 25, llama a Teacher.objects.get(pk=25) y luego a teacher.class_students.filter(pk=5).

filter() devuelve un QuerySet, no una instancia única de Student. En este ejemplo contiene como máximo una fila, porque las claves primarias son únicas, aunque puede no contener ninguna si ese alumno no está relacionado con este profesor. Si necesitas actuar sobre las coincidencias, itera sobre el resultado o comprueba si contiene algún elemento.

Gestionar los resultados vacíos y los errores habituales

Si no hay filas de Student que coincidan, all() o filter() en un administrador inverso devuelve un QuerySet vacío; no devuelve un objeto Student inexistente. Por eso puedes iterar sobre el resultado con seguridad aunque no haya coincidencias. Usa get() para la búsqueda inicial de Teacher solo si esperas que ese profesor exista: a diferencia de filter(), get() genera Teacher.DoesNotExist si no lo encuentra.

El modelo anterior permite que cualquiera de las dos ForeignKey sea nula. Un Student cuyo class_teacher sea nulo no aparece en el resultado inverso class_students de ningún Teacher; lo mismo sucede con un favorite_teacher nulo y favorite_students. Si tu aplicación requiere que todos los alumnos tengan un profesor, elimina null=True y planifica la migración de base de datos necesaria. Si un modelo tiene varias ForeignKey al mismo destino, mantén distintos sus dos nombres inversos.