Saltar al contenido

# creacion-de-aplicaciones-web-en-django-y-python

Creación de Aplicaciones Web en Django y Python

Volver a la portada del curso
Notebook

Creación de Vistas

Sección 4 · Interacción con el Usuario

Notebook: creación de vistas

Una vista es una función que recibe una petición HTTP y devuelve una respuesta. Todo lo que el cliente de PyShop ve en el catálogo sale de cuatro vistas de función. Esta guía acompaña al notebook vistas-y-formularios: explica cómo abrirlo, qué hace cada sección y qué resultados debes obtener. El notebook no arranca un servidor: prueba las vistas con el cliente de pruebas de Django, que simula el navegador dentro de Python.

Instalación y apertura

python -m venv .venv
.venv\Scripts\activate          # Windows
source .venv/bin/activate       # Linux y macOS
pip install "django==5.2.*" jupyter
jupyter notebook

El curso usa Django 5.2.18 (serie LTS) con Python 3.13; Django 5.2 funciona con Python 3.10 a 3.14. El notebook crea el proyecto de PyShop completo en una carpeta temporal con datos.preparar: escribe los archivos, migra la base SQLite y carga los 8 productos, los usuarios ana y luis y 3 reseñas. No hace falta descargar nada.

Objetivos

  • Seguir el camino de una URL hasta su vista.
  • Escribir vistas de función con render y get_object_or_404.
  • Usar plantillas con herencia.
  • Probar vistas con Client y leer códigos de estado.
  • Devolver JSON, validar un formulario y proteger una vista con login.

1. Del URL a la vista

El proyecto monta las rutas de la app con include("catalogo.urls"). En catalogo/urls.py, app_name = "catalogo" da el espacio de nombres y path("producto/<slug:slug>/", ...) captura el slug y se lo pasa a la vista como argumento. reverse hace el camino inverso: del nombre a la URL.

from django.urls import reverse

reverse("catalogo:lista")                          # '/'
reverse("catalogo:detalle", args=["taza-pyshop"])  # '/producto/taza-pyshop/'

2. Vistas de lista y detalle

def lista_productos(request):
    productos = Producto.objects.filter(disponible=True).select_related("categoria")
    categoria = request.GET.get("categoria")
    if categoria:
        productos = productos.filter(categoria__slug=categoria)
    return render(request, "catalogo/lista.html", {"productos": productos})

render junta la plantilla con un diccionario de contexto y devuelve un HttpResponse. request.GET trae los parámetros de la URL (?categoria=ropa). La vista de detalle usa get_object_or_404(Producto, slug=slug, disponible=True): si el producto no existe o no está disponible, responde 404 en lugar de lanzar un error.

3. Plantillas y herencia

templates/base.html define el esqueleto (cabecera, menú, hoja de estilos) y deja huecos con {% block contenido %}{% endblock %}. Cada página empieza con {% extends "base.html" %} y solo llena sus bloques. La lista recorre los productos con {% for %} y muestra «No hay productos.» con {% empty %}; los enlaces se generan con {% url %} o con producto.get_absolute_url, nunca escritos a mano. El menú cambia según user.is_authenticated: «Hola, ana» y el botón Salir, o el enlace Entrar.

4. Probar las vistas con Client

Estos son los resultados que debes ver:

  • GET / → 200, con 7 productos en la lista (el Libro Django práctico no está disponible) y las plantillas catalogo/lista.html y base.html.
  • GET /?categoria=ropa → 3 productos.
  • Detalle de la taza → 200 y el texto «Calificación: 4,5 de 5». Con LANGUAGE_CODE = "es", el filtro floatformat usa coma decimal.
  • Producto no disponible (/producto/libro-django-practico/) → 404; slug inexistente → 404.

5. Una API con JsonResponse

api_productos devuelve los productos disponibles como JSON. GET /api/productos/ responde 200 con tipo application/json y 7 productos; el primero es {"nombre": "Gorra PyShop", "categoria": "Ropa", "precio": 249.0, "url": "/producto/gorra-pyshop/"}. El precio se convierte con float porque un Decimal no es un tipo de JSON.

6. ModelForm y validación

ResenaForm es un ModelForm con los campos calificacion y comentario: hereda las reglas del modelo. Una calificación de 9 da el error «Asegúrese de que este valor es menor o igual a 5.»; una de 0, «…mayor o igual a 1.»; un comentario vacío, «Este campo es obligatorio.». Son mensajes de Django en español y se citan tal cual.

7. login_required y el flujo de login

La vista nueva_resena lleva @login_required. Sin sesión, GET /producto/taza-pyshop/resena/ responde 302 a /cuentas/login/?next=/producto/taza-pyshop/resena/; con client.login de luis, 200. El notebook también prueba el formulario de login real: con contraseña mala responde 200 y muestra el error; con la buena, 302 a /.

8. Guardar la reseña

Con form.save(commit=False) se obtiene la reseña sin guardarla, se completan producto y autor (el usuario de la sesión) y luego se llama a save(). Un POST válido (calificación 2, «El asa se despegó.») responde 302 a /producto/taza-pyshop/, y el promedio de la taza baja de 4.5 a 3.67 (5, 4 y 2). Redirigir tras un POST evita que recargar la página envíe el formulario otra vez.

9. CSRF y contraseñas

Cada formulario POST incluye {% csrf_token %}. Con un cliente que exige el token (Client(enforce_csrf_checks=True)), un POST sin él responde 403. Las contraseñas no se guardan en texto plano: el campo password empieza con pbkdf2_sha256 y usa 1,000,000 iteraciones.

Ejercicios

Los ejercicios se comprueban con assert y tienen soluciones al final. Resuélvelos antes de mirarlas.

Qué observar

  • El código de estado dice más que el HTML: 200, 302, 403 y 404 resumen lo que hizo la vista.
  • Un 302 de login_required conserva la página de destino en next.
  • Ninguna URL está escrita a mano en las plantillas: todas salen de nombres.

Recursos