Curso Gratuito de Django Admin

Clase 1: Dropdowns Dependientes con AJAX y jQuery

⏱️ Duración estimada: 20-30 min
📚 Nivel: Intermedio (requiere conocimientos básicos de Django)

¡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).

Video explicativo de la Clase 1: Dropdowns Dependientes.

---

Contexto del Ejemplo: ¿Qué vamos a lograr?

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.

¿Qué modelos de Django necesitamos para este ejemplo?

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!

---

1. Definir la Vista AJAX en 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:

ventas/views.py
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.

---

2. Exponer la URL en 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'),
]

¡Verifica tu URL en el navegador!

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.

---

3. Decirle al Admin que use un Template de Formulario Personalizado

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!".

---

4. Crear el Template 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.

templates/admin/ventas/presupuesto_change_form.html
{% 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 %}
---

5. ¡Paso Crítico! Verificar los ID de los <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:

  1. Abre el panel de administración de **Presupuesto** en tu navegador. Puedes ir a la página de "Añadir Presupuesto" o "Editar Presupuesto".
  2. Ahora, necesitas abrir las **Herramientas de Desarrollador** de tu navegador. Esto lo haces presionando **F12** (en Windows/Linux) o **Cmd + Option + I** (en Mac). Busca la pestaña que dice **"Elements"** o **"Inspector"**.
  3. Haz clic en el pequeño icono con forma de **flecha o lupa** (normalmente está en la esquina superior izquierda de las DevTools).
  4. Con la flecha activada, haz clic directamente en tu dropdown de **Cliente** en el formulario de Django Admin. En las DevTools, se resaltará el código HTML de ese elemento.
  5. **¡Mira el `id`!** Deberías ver algo como <select id="id_cliente" …>.
  6. Repite el proceso, pero esta vez haz clic en tu dropdown de **Vehículo**. Confirma que su ID sea 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!

---

6. ¡La Hora de la Verdad! Prueba Final

¡Hemos configurado todo lo necesario! Ahora viene el momento más emocionante: comprobar que toda la magia funciona como esperamos.

  1. Abre la página de creación o edición de **Presupuesto** en tu panel de administración de Django.
  2. Abre la **Consola** de las Herramientas de Desarrollador (recuerda: F12 o Cmd+Option+I y luego la pestaña "Console"). Mantén un ojo ahí por si aparece algún error.
  3. Ahora, **selecciona un Cliente diferente** en el primer dropdown.
  4. ¡Observa! El dropdown de **Vehículo** debería vaciarse de inmediato y, segundos después, se poblará solo con los vehículos que están asociados a ese cliente que acabas de seleccionar. ¡Impresionante, ¿verdad?!

✨ ¡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!

---

7. Consideraciones y Extensiones: ¡Sigue explorando!

Aunque ya logramos el objetivo principal, siempre hay más cositas para aprender y mejorar. Aquí te dejo algunas ideas y consideraciones extra:

  • ¿Y el CSRF? (Cross-Site Request Forgery): En este tutorial, usamos peticiones 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!
  • ¿Demasiadas opciones? La optimización: Si tu lista de vehículos es ¡enorme! (miles de opciones), cargar todos los datos en cada cambio podría ser lento. Para estos casos extremos, hay otras técnicas, como precargar solo los datos más comunes y cargar el resto por demanda, o incluso usar librerías más avanzadas para el manejo de dropdowns grandes.
  • Reutilización: ¡No te repitas! Este patrón de "dropdowns dependientes" es muy común. Una vez que lo domines, puedes convertir este código JavaScript en una función más genérica o incluso un pequeño "plugin" de jQuery. Así, en lugar de copiar y pegar, podrás reutilizarlo fácilmente para cualquier otro par de modelos "Padre → Hijo" en tu Django Admin. ¡Serás el rey/reina de la eficiencia!
  • Mensaje de "Cargando...": Para una experiencia de usuario aún más pulida, podrías añadir un pequeño mensaje o un "spinner" (un circulito giratorio) en el segundo `select` que diga "Cargando..." mientras la petición AJAX está buscando los vehículos. Así, el usuario sabe que algo está pasando y no piensa que la página se quedó "colgada".
---