# creacion-de-aplicaciones-web-en-django-y-python
Creación de Aplicaciones Web en Django y Python
Volver a la portada del cursoCreació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
renderyget_object_or_404. - Usar plantillas con herencia.
- Probar vistas con
Clienty 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 plantillascatalogo/lista.htmlybase.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 filtrofloatformatusa 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_requiredconserva la página de destino ennext. - Ninguna URL está escrita a mano en las plantillas: todas salen de nombres.