Saltar al contenido

Capítulo 2 de 14

Rutas, views y contratos HTTP

Dar nombre a las direcciones, validar parámetros y elegir respuestas que el navegador y otros clientes puedan interpretar.

#Una ruta es un contrato público

Una URL no describe dónde vive un fichero Python. Identifica algo que tu aplicación permite consultar o hacer. Cambiar la estructura interna no debería obligar a cambiar todos los enlaces; por eso damos nombres a las rutas y hacemos que Django construya las URLs.

Seguimos en web-articulos. En este capítulo añadimos una respuesta de ejemplo sin base de datos; el siguiente sustituirá esos valores por registros. Mantén listado del capítulo anterior y añade a articulos/views.py:

from django.http import JsonResponse
from django.views.decorators.http import require_GET


@require_GET
def resumen(request, pk):
    return JsonResponse({"id": pk, "titulo": f"Artículo {pk}"})

Un decorador es una función que envuelve otra para añadir comportamiento. @require_GET comprueba el método antes de ejecutar el cuerpo y devuelve 405 si no es GET. JsonResponse transforma un diccionario en JSON, un formato de datos que otros programas pueden leer, y establece su tipo de contenido. Esto aún no prueba que exista un artículo: el título se genera deliberadamente.

Añade esta entrada a urlpatterns en articulos/urls.py:

path("<int:pk>/resumen/", views.resumen, name="resumen"),

<int:pk> acepta un entero no negativo y lo entrega como pk a la función. La conversión de tipos ocurre antes de la view. /articulos/abc/resumen/ no coincide y devuelve 404. Una conversión correcta no significa que el usuario tenga permiso ni que exista la fila con ese identificador.

curl -i http://127.0.0.1:8000/articulos/7/resumen/
curl -i http://127.0.0.1:8000/articulos/abc/resumen/
curl -i -X PUT http://127.0.0.1:8000/articulos/7/resumen/

Esperas 200 y JSON en la primera, 404 en la segunda. La tercera puede ser rechazada antes de la view por CSRF con 403: el middleware protege métodos inseguros. En una petición que supera ese control, el decorador rechaza PUT con 405 y anuncia GET en Allow. No desactives CSRF para forzar una prueba; el cliente de pruebas permite aislar las capas más adelante.

#Parámetros de ruta y de consulta

La ruta responde «qué recurso». Los parámetros de consulta suelen responder «con qué filtros u orden». request.GET contiene esos parámetros incluso aunque el nombre sugiera el método; request.POST contiene formularios codificados como tales, no cualquier cuerpo JSON.

from django.http import HttpResponseBadRequest


def buscar(request):
    termino = request.GET.get("q", "").strip()
    if len(termino) > 100:
        return HttpResponseBadRequest("La búsqueda admite hasta 100 caracteres.")
    return JsonResponse({"busqueda": termino, "resultados": []})

Es un contrato de ejemplo: todavía no ejecuta búsquedas. Añade path("buscar/", views.buscar, name="buscar") y prueba /articulos/buscar/?q=django. .get evita una excepción por un campo ausente; .strip() quita espacios extremos; el límite evita aceptar entradas arbitrariamente largas para una función pequeña. Este límite de aplicación no sustituye límites de cuerpo en el servidor o proxy.

El orden de rutas importa: Django usa la primera coincidencia. Coloca nuevo/ antes de una captura general <slug:slug>/ que también aceptaría la palabra «nuevo». Un conversor entero no tiene ese conflicto.

#Nombres de ruta y redirecciones

En python manage.py shell, prueba:

from django.urls import reverse

print(reverse("articulos:resumen", kwargs={"pk": 7}))

El resultado es /articulos/7/resumen/. kwargs es un diccionario de argumentos con nombre; su clave debe coincidir con el nombre de la captura. Si mañana cambias el patrón a fichas/<int:pk>/, el mismo reverse construirá el nuevo camino.

from django.shortcuts import redirect


def portada(request):
    return redirect("articulos:listado")

Esta función no dibuja la página del listado: devuelve una respuesta 302 y el navegador hace otra petición a la URL indicada. Más adelante este recorrido evita que recargar una página repita un POST. No redirijas a una URL arbitraria recibida del usuario sin validar su destino; las views de autenticación de Django ya realizan controles para su parámetro next.

#Errores que cambian lo que debe hacer el cliente

Estado Situación Qué suele hacer el cliente
400 Datos mal formados o entrada inválida Corregir la petición
401 API sin credenciales válidas Autenticarse según su contrato
403 Identidad conocida pero acción no permitida; también rechazo CSRF No repetir igual esperando otro resultado
404 Recurso inexistente o fuera del ámbito visible Volver a un listado permitido
409 Datos válidos que chocan con el estado actual Recargar disponibilidad o cambiar la operación
405 Método no aceptado Usar uno anunciado en Allow
503 Dependencia temporalmente no disponible Esperar; repetir solo operaciones seguras

En una web con sesiones, login_required suele responder con una redirección al login, no con 401. La respuesta adecuada depende del contrato de interfaz. Una API que espera JSON no debería recibir silenciosamente una página HTML de login con 200 final.

Un Http404 es una decisión esperada del producto. Un AttributeError por una variable mal utilizada es un fallo de código. Capturar todas las excepciones y devolver 404 convierte problemas internos en falsos «no existe» y dificulta diagnosticarlos.

#Funciones primero; clases cuando reduzcan repetición real

Una view basada en clase agrupa comportamientos en métodos. Django necesita convertir la clase en una función mediante .as_view(). Este ejemplo equivale conceptualmente a una respuesta GET simple:

from django.http import HttpResponse
from django.views import View


class EstadoView(View):
    def get(self, request):
        return HttpResponse("Disponible")

Se registraría con path("estado/", views.EstadoView.as_view(), name="estado"). View despacha al método get, post, etc. No instancies la clase manualmente ni guardes datos de una petición en atributos compartidos de clase. Las clases genéricas ListView o CreateView reducen código cuando su flujo coincide con tu problema, pero no eliminan la obligación de filtrar permisos y comprender cuándo se consulta o guarda.

Para este recorrido usaremos funciones: podrás ver exactamente dónde se valida, dónde se autoriza y dónde empieza una transacción. Más adelante puedes sustituir una view rutinaria por una genérica conservando sus pruebas de comportamiento.

#Qué pertenece a la view

La view traduce HTTP a una operación de aplicación y después traduce su resultado a HTTP. Una regla como «esta sala no admite dos reservas simultáneas» también debe aplicarse si mañana la acción llega desde una tarea o un comando. Por eso una operación compleja acabará en una función de servicio explícita, llamada desde la view. No necesitas un sistema de capas para devolver una frase; sí un lugar claro para una regla reutilizada con transacciones y permisos.

Comprueba tu comprensión: registra EstadoView, abre su URL y compara con resumen. Cambia solo el patrón de resumen y comprueba que reverse sigue funcionando. Después explica por qué el conversor entero no puede decidir si el usuario tiene acceso al artículo.

    ↑ ↓ para recorrer · Intro para abrir · Ctrl K para buscar