# creacion-de-aplicaciones-web-en-django-y-python
Creación de Aplicaciones Web en Django y Python
Volver a la portada del cursoConstrucción de un Backend
Sección 5 · Proyecto Final
Construcción de un backend
El backend es la parte de la aplicación que el usuario no ve: guarda los datos, decide quién puede hacer qué y responde a cada petición. En la tienda de PyShop ya existen todas las piezas por separado. Esta lectura las ordena en capas, porque el proyecto final consiste justamente en agregar funciones nuevas a cada una sin romper las demás.
Las capas del backend
- Datos: los modelos (
Categoria,Producto,Resena) y sus migraciones. Definen qué se guarda y qué reglas cumple cada campo. - Lógica: las vistas. Leen la petición, consultan con el ORM, validan con formularios y deciden la respuesta.
- Rutas:
urls.pyconecta cada URL con su vista. - Presentación: plantillas HTML para el navegador y JSON para el código que corre en el navegador o en otra aplicación.
- Seguridad: autenticación, permisos y protección CSRF, en gran parte a cargo de los middleware.
- Pruebas: el código que comprueba que todo lo anterior sigue funcionando.
Una regla práctica: las reglas sobre los datos (rangos válidos, campos obligatorios) van en el modelo; la vista solo coordina. Así el formulario, el admin y cualquier vista futura las respetan sin repetir código. La calificación de 1 a 5 de Resena funciona así: el validador vive en el modelo y ResenaForm lo hereda.
Endpoints JSON
Una vista no tiene que devolver HTML. api_productos devuelve datos para que el frontend los pinte con JavaScript:
def api_productos(request):
productos = Producto.objects.filter(disponible=True).select_related("categoria")
datos = [
{
"nombre": p.nombre,
"categoria": p.categoria.nombre,
"precio": float(p.precio),
"url": p.get_absolute_url(),
}
for p in productos
]
return JsonResponse({"productos": datos})
La respuesta tiene tipo application/json y 7 productos. Devolver un diccionario con una clave ("productos") en lugar de una lista suelta deja lugar para agregar datos después, como el total o la página, sin romper a quien ya consume la API. Para APIs grandes existe Django REST framework, un paquete aparte; para un catálogo como este, JsonResponse basta.
Autenticación y permisos
django.contrib.auth ya trae usuarios, contraseñas con hash y las vistas de login y logout (include("django.contrib.auth.urls")). En el backend se usan tres niveles:
- ¿Hay sesión?
@login_requiredmanda a los anónimos al login (302) y conserva el destino ennext. - ¿Quién es?
request.useridentifica al usuario: la reseña toma su autor de ahí, nunca de un campo del formulario que cualquiera podría alterar. - ¿Qué puede hacer?
is_staffabre el panel de administración y los permisos por modelo limitan qué edita cada persona. Un anónimo que entra a/admin/recibe 302 a/admin/login/?next=/admin/.
Consultas eficientes
Si una plantilla muestra la categoría de cada producto, cada acceso a p.categoria puede lanzar una consulta nueva. Medido con CaptureQueriesContext sobre los 7 productos disponibles:
from django.db import connection
from django.test.utils import CaptureQueriesContext
with CaptureQueriesContext(connection) as ctx:
filas = [(p.nombre, p.categoria.nombre)
for p in Producto.objects.filter(disponible=True)]
print(len(ctx.captured_queries)) # 8: 1 para productos + 7 para categorías
with CaptureQueriesContext(connection) as ctx:
filas = [(p.nombre, p.categoria.nombre)
for p in Producto.objects.filter(disponible=True).select_related("categoria")]
print(len(ctx.captured_queries)) # 1: un JOIN trae todo
Es el problema de las «N+1 consultas»: con 7 productos no se nota, con 7000 sí. select_related sirve para ForeignKey; para relaciones inversas o de muchos a muchos, como las reseñas de cada producto, existe prefetch_related.
Pruebas automáticas
TestCase crea una base de datos de prueba vacía, ejecuta cada prueba dentro de una transacción y la deshace al terminar. self.client es el mismo cliente de pruebas de las lecciones anteriores:
from django.test import TestCase
class CatalogoTests(TestCase):
@classmethod
def setUpTestData(cls):
ropa = Categoria.objects.create(nombre="Ropa", slug="ropa")
Producto.objects.create(categoria=ropa, nombre="Gorra PyShop",
slug="gorra-pyshop", precio="249.00")
def test_lista(self):
r = self.client.get("/")
self.assertContains(r, "Gorra PyShop")
def test_resena_sin_sesion_redirige(self):
r = self.client.get("/producto/gorra-pyshop/resena/")
self.assertRedirects(r, "/cuentas/login/?next=/producto/gorra-pyshop/resena/")
Se ejecutan con python manage.py test catalogo. Una prueba del 404 de un slug inexistente completa el trío básico: lo que debe funcionar, lo que debe estar protegido y lo que no existe.
Trampas comunes
- Lógica de negocio en las plantillas. Calcular promedios o filtrar en el HTML la vuelve difícil de probar; se calcula en la vista o en el modelo.
- Confiar en datos del cliente para el autor, el precio o los permisos. Salen del servidor (
request.user, la base). - Bucles con consultas ocultas. Revisa el número de consultas de las vistas con listas.
- Probar solo el camino feliz. Los 302 sin sesión, los 404 y los formularios inválidos son donde aparecen los errores.
El notebook tienda-pyshop-completa recorre estas capas archivo por archivo, mide las consultas y ejecuta las pruebas. Úsalo como punto de partida del proyecto final.