Stack: DRF + drf-spectacular · Proyecto: TicketFlow Estado: Publicada Prerrequisito: Lección 14 — Idempotencia
Objetivos
- Ordenar las capas de validación: sintaxis → tipos → negocio (y qué vive en cada una).
- Escribir serializers que fallan con mensajes útiles (y que no filtran campos).
- Generar el contrato OpenAPI con drf-spectacular y mantenerlo verificado en CI.
1. Las capas de validación (en orden)
- Sintaxis/parsing: ¿es JSON válido? ¿Content-Type correcto? → 400 antes de tocar dominio.
- Forma/tipos: serializer: campos requeridos, tipos, max_length, choices, formato (email, fecha). → 400 con
errorspor campo. - Negocio: reglas del dominio (asiento libre, evento abierto, saldo suficiente). → 409/422 con
typedel RFC 7807 (13).
El error de capas: validar negocio en el serializer (validate_seats que consulta la BD) puede estar bien para casos simples, pero la exclusión mutua vive en la transacción (10), no en el validador. Regla: el serializer valida forma y referencias triviales; el servicio valida estado del mundo bajo bloqueo.
2. Serializers honestos
class ReserveInputSerializer(serializers.Serializer):
event = serializers.UUIDField() # uuid público (13)
seats = serializers.ListField(
child=serializers.DictField(child=serializers.CharField()),
min_length=1, max_length=8) # límites explícitos
def validate_seats(self, value):
# forma: cada entrada sector/row/number presente
...Detalles que separan al senior: min_length/max_length en listas (evita POSTs de 10.000 asientos); write_only para campos de entrada que no vuelven; read_only en uuid/created_at; mensajes de error en el idioma del negocio ("El asiento A-12 no existe en este evento", no "Invalid pk"); nunca aceptar id del cliente en creación (mass assignment: fields explícitos, jamás __all__).
3. OpenAPI con drf-spectacular
INSTALLED_APPS += ["drf_spectacular"]
REST_FRAMEWORK["DEFAULT_SCHEMA_CLASS"] = "drf_spectacular.openapi.AutoSchema"
# urls.py
path("api/schema/", SpectacularAPIView.as_view(), name="schema"),
path("api/docs/", SpectacularSwaggerView.as_view(url_name="schema")),El contrato se genera del código (serializers + viewsets + @extend_schema para lo ambiguo). @extend_schema(request=ReserveInputSerializer, responses=ReservationOut, parameters=[...]) — donde el AutoSchema adivina, tú declaras. El contrato publicado (/api/schema/) es lo que consume el frontend (TypeScript generado) y los integradores (OpenAPI en Postman/Insomnia).
4. El contrato en CI
Contrato que no se verifica, caduca. Pipeline (Lección 41 adelantada):
manage.py spectacular --file schema.yml --fail-on-warn— si un endpoint no documenta responses, falla.- Diff de schema en PR: si cambió, el PR lo dice en el título (contract change review).
- Tests de contrato: el ejemplo del schema es válido contra la respuesta real (drf-spectacular lo audita; para E2E: schemathesis en la 35).
Autoevaluación
- ¿Por qué la exclusión mutua de asientos NO vive en
validate_seatsdel serializer? - ¿Qué tres atributos del serializer evitan el mass assignment y los POSTs gigantes?
- ¿Qué gana un integrador con
/api/schema/que no tenía leyendo tu documentación a mano? - ¿Cuándo escribes
@extend_schemaa mano y qué pasa si nunca lo haces? - ¿Qué rompe
--fail-on-warnen un equipo y por qué vale la pena?
Continúa con los ejercicios. Las solutions.md solo tras intentarlo.