¡Hola, futuro programador/a! 👋 Bienvenido a este nuevo curso gratuito donde vamos a darle un súper poder a tu panel de administración de Django. ¿Listo para que tus formularios sean mucho más inteligentes?
En esta primera clase, aprenderás a crear unas **listas desplegables "mágicas"**, donde lo que eliges en una, cambia lo que ves en la otra. Usaremos **AJAX** (para comunicarnos con el servidor sin recargar la página) y **jQuery** (para que sea más fácil manipular la web).
---Imagina esta situación: estás desarrollando una aplicación en Django para una empresa que hace presupuestos de trabajos para vehículos. Cuando el personal va a crear un nuevo presupuesto en el panel de administración (esa parte genial que Django te da gratis), necesitan registrar la fecha, elegir un cliente de una lista... ¡y luego seleccionar el vehículo de ese cliente al que le van a hacer el presupuesto!
El problema es que si el `select` de vehículos muestra *todos* los vehículos de *todos* los clientes, ¡sería un caos! Sería larguísimo y muy fácil equivocarse.
💡 Nuestra meta es que, en el mismo momento que seleccionas un **Cliente** de la primera lista desplegable, la segunda lista (la de los vehículos) se actualice al instante para mostrar SOLO los vehículos que pertenecen a ese cliente específico. ¡Sin necesidad de guardar el formulario ni recargar la página! Es una maravilla para la experiencia del usuario.
Para entender cómo funciona la magia, vamos a pensar que tenemos los siguientes modelos relacionados en Django:
Cliente
: Guarda la información básica de cada cliente (nombre, dirección, etc.).Vehiculo
: Aquí registramos los detalles de los vehículos (patente, modelo, marca). Este modelo es como el "maestro" de todos los vehículos que existen.ClienteVehiculo
: Este es un modelo "intermedio" muy importante. Imagínalo como una tabla que conecta a un Cliente
con un Vehiculo
específico. Así sabemos "qué cliente es dueño de qué vehículo".Presupuesto
: Este es nuestro modelo principal para el formulario. Aquí es donde cargaremos la fecha, elegiremos al Cliente
, y luego, gracias a este tutorial, podremos seleccionar de forma inteligente el Vehiculo
relacionado para el presupuesto.La clave de este tutorial es que el dropdown de "Vehículos" no mostrará *todos* los vehículos, sino solo aquellos que estén vinculados al cliente que hayas elegido en el momento. ¡Simplificamos la vida del usuario y evitamos errores!
---views.py
El primer paso es crear una función en tu archivo views.py
que actúe como un "puente" entre el navegador y tu base de datos. Esta función recibirá el ID del cliente que se seleccionó y devolverá una lista de sus vehículos en un formato que la web entienda fácilmente: **JSON**.
Crea (o abre) el archivo ventas/views.py
(o la app donde tengas tus modelos de ventas) y añade el siguiente código:
from django.http import JsonResponse
from .models import VehiculoCliente # Importa tu modelo intermedio
def cargar_vehiculos(request):
cliente_id = request.GET.get('cliente') # Obtenemos el ID del cliente de la URL
# ¡Importante! Si no nos pasaron un cliente, devolvemos un error para que la app no falle.
if not cliente_id:
return JsonResponse({'error': 'No se proporcionó un cliente'}, status=400)
# Filtramos los VehiculoCliente que pertenezcan al cliente que recibimos
# .select_related('vehiculo_maestro') es para optimizar y traer datos relacionados
qs = VehiculoCliente.objects.filter(cliente_id=cliente_id).select_related('vehiculo_maestro')
# Preparamos los datos para que el navegador los entienda (formato JSON)
datos = [
{
'id': v.id,
'display': str(v) # Aquí decides cómo se mostrará el vehículo (ej. patente, modelo)
# `str(v)` usa el __str__ de tu modelo VehiculoCliente
}
for v in qs
]
# safe=False permite devolver una lista JSON (no solo diccionarios)
return JsonResponse(datos, safe=False)
Un consejito del mortal: Asegúrate de que tus modelos Cliente
y VehiculoCliente
estén bien definidos en tu models.py
y que hayas ejecutado las migraciones (`python manage.py makemigrations` y `python manage.py migrate`). Esto es clave para que Django sepa cómo interactuar con tus datos.
urls.py
Ahora que tenemos la función que devuelve los vehículos, necesitamos decirle a Django que la haga accesible a través de una dirección web específica. Piensa en esto como darle una "puerta de entrada" a nuestra función AJAX.
Lo ideal es añadir esta URL en el archivo urls.py
de tu aplicación (por ejemplo, ventas/urls.py
). Si no tienes uno para tu app, puedes hacerlo en el urls.py
principal de tu proyecto.
# ventas/urls.py (o project/urls.py)
from django.urls import path
from ventas.views import cargar_vehiculos # ¡Asegúrate de importar tu función!
urlpatterns = [
# ... tus otras rutas (las que ya tenías) ...
# ¡Esta es la URL para nuestra función AJAX!
path('ajax/cargar-vehiculos/', cargar_vehiculos, name='cargar_vehiculos'),
]
Antes de seguir, es una **muy buena práctica** comprobar que esta nueva URL funcione. Abre tu navegador web y escribe una dirección similar a esta (cambia localhost:8000
por la dirección de tu servidor y 3
por un ID de cliente que exista en tu base de datos):
http://localhost:8000/ajax/cargar-vehiculos/?cliente=3
Si todo está configurado correctamente, deberías ver una lista de vehículos en formato JSON (se verá como texto con corchetes y llaves). Si ves un error 400, un error de página no encontrada, o una lista vacía cuando esperabas vehículos, es momento de revisar tu views.py
y la base de datos.
Por defecto, el panel de administración de Django es un genio y te arma los formularios automáticamente. Pero para meterle mano y añadir nuestra lógica JavaScript, necesitamos que Django Admin use **nuestra propia versión** del template de formulario. ¡No te preocupes, no es tan complicado como suena!
En el archivo ventas/admin.py
(donde tienes registrada tu clase `PresupuestoAdmin`), busca la clase que administra tu modelo Presupuesto
y añade la línea change_form_template
:
@admin.register(Presupuesto)
class PresupuestoAdmin(ImportExportModelAdmin): # O tu clase Admin que uses
# ... aquí va el resto de tu configuración actual para PresupuestoAdmin ...
# ¡Esta línea es la clave! Le dice a Django que use nuestro template especial
change_form_template = 'admin/ventas/presupuesto_change_form.html'
Con esto, le estamos diciendo a Django: "Cuando alguien vaya a crear o editar un Presupuesto en el Admin, no uses tu template por defecto, ¡usa el mío!".
---presupuesto_change_form.html
Ahora que le dijimos a Django que espere nuestro template, ¡hay que crearlo! Este archivo HTML va a ser la base de nuestro formulario en el Admin, y ahí es donde vamos a meter el código JavaScript que hace la magia de los dropdowns dependientes.
Crea el archivo en la siguiente ruta dentro de la carpeta templates
de tu aplicación ventas
. Es importante que la ruta sea exactamente esta:
tu_proyecto/
└── ventas/
└── templates/
└── admin/ # ¡Ojo con esta carpeta! Django la busca así.
└── ventas/ # Esta es el nombre de tu aplicación
└── presupuesto_change_form.html
Copia y pega este contenido dentro de ese archivo. Lo que hace es "extender" (heredar) del template original de Django Admin, y luego, en la sección extrahead
(que es para añadir cosas en la cabecera del HTML), ponemos nuestro script.
{% extends "admin/change_form.html" %} {# Heredamos del template base de Django Admin #}
{% load static i18n %} {# Cargamos etiquetas para archivos estáticos e internacionalización #}
{% block extrahead %} {# Aquí es donde inyectamos nuestro código JavaScript #}
{# Cargamos jQuery desde un CDN. Es una librería que facilita mucho el trabajo con JavaScript y el DOM. #}
{% endblock %}
<select>
Este punto es **SÚPER importante**. Nuestro código JavaScript (el del paso anterior) se basa en encontrar tus dropdowns de Cliente y Vehículo usando sus "IDs" (`#id_cliente` y `#id_vehiculo`). Si Django les pone nombres diferentes en tu HTML, ¡el código no funcionará!
Así que, vamos a confirmarlo:
<select id="id_cliente" …>
.id_vehiculo
.🚨 ¡ALERTA IMPORTANTE! Si por alguna razón los IDs que ves en tus DevTools son distintos (por ejemplo, id_vehiculocliente
, id_mi_cliente
, etc.), no te preocupes. Solo necesitas **ajustar el código JavaScript** del Paso 4.
Busca estas líneas en el JavaScript:
var $cliente = $('#id_cliente');
var $vehiculo = $('#id_vehiculo');
Y cámbialas para que coincidan con los IDs que encontraste en tus DevTools. Por ejemplo, si tu ID de cliente fuera `id_mi_cliente`, quedaría así:
var $cliente = $('#id_mi_cliente');
var $vehiculo = $('#id_vehiculo'); {# Este podría seguir igual o ajustarse también #}
Una vez ajustado, guarda el archivo y recarga la página. ¡Ya debería funcionar!
¡Hemos configurado todo lo necesario! Ahora viene el momento más emocionante: comprobar que toda la magia funciona como esperamos.
✨ ¡Felicidades! Lo lograste. Has implementado con éxito una funcionalidad de listas desplegables dependientes en el panel de administración de Django. Esto no solo hace tus formularios más eficientes, sino que también mejora un montón la experiencia de quienes los usan. ¡Eres un crack!
Aunque ya logramos el objetivo principal, siempre hay más cositas para aprender y mejorar. Aquí te dejo algunas ideas y consideraciones extra:
GET
para cargar los vehículos (es decir, solo pedimos información, no la modificamos). Por eso, no necesitamos preocuparnos por los tokens CSRF. Pero, si en el futuro decides enviar datos sensibles o modificar cosas con peticiones POST
(cuando crees/actualices información), **es crucial** que incluyas {% csrf_token %}
en tu formulario y lo envíes en el encabezado de tu petición AJAX. ¡Es una medida de seguridad importante!