Request Body y Schema#

Este es el duodécimo artículo de la serie tutorial de Django Ninja.
Tras las presentaciones de los artículos anteriores, ya hemos aprendido a manejar los parámetros de ruta y de consulta. Sin embargo, en el mundo real, con frecuencia necesitamos procesar datos de solicitud más complejos.
Por ejemplo, formularios enviados por usuarios, archivos subidos, entre otros. En el caso de las API, lo más común es el request body en formato JSON.
Este artículo explorará cómo Django Ninja procesa el request body y presentará cómo definir y validar datos a través de Schema.
Todos los cambios de código de este artículo se pueden consultar en este PR.
Proyecto de ejemplo en GitHub#
1. ¿Qué es el Request Body?#
El request body se refiere a los datos que se envían junto con la solicitud HTTP, y suele utilizarse en solicitudes como POST y PUT, las cuales requieren crear o actualizar «recursos».
Estos datos no aparecen en la URL, sino que se envían en formato JSON u otros formatos (como XML o form-data) como el cuerpo principal de la solicitud.
Por ejemplo, cuando un usuario va a publicar un nuevo artículo, podría enviar el siguiente request body en formato JSON:
{
"title": "Mi primer artículo",
"content": "¡Este es mi primer artículo en Ninja Forum, espero que les guste!"
}
Este request body contiene los campos title y content, y Django Ninja nos asistirá en el procesamiento y validación de estos datos.
2. Cambios en el proyecto de ejemplo#
En el proyecto de ejemplo, vamos a crear una API que reciba un request body: «Crear publicación».
Además, añadiremos un nuevo módulo de Python en el directorio de la app post de Django: schemas.py. Este es el lugar destinado a colocar todos los Schema utilizados en la API.
Presentaremos el código concreto en las siguientes explicaciones.
A partir de este artículo, los nombres de las ramas ya no usarán chino, ya que los nombres de ramas en chino generaban advertencias constantes en GitHub:
The head ref may contain hidden characters: ...
Además, ¡probablemente muy pocas personas usan chino para nombrar ramas de git! En un principio usé chino para que fuera más fácil de leer para los lectores 🥹
Así que a partir de esta rama, cambiará al formato número+inglés, por ejemplo «12-request-body» para este artículo. Sin embargo, los títulos de los PR seguirán manteniéndose en chino.
3. Uso de Schema para definir y validar el Request Body#
Al igual que FastAPI, Django Ninja utiliza Pydantic BaseModel para procesar el body de la solicitud.
Sin embargo, debido a que el nombre BaseModel se confunde fácilmente con los Models de Django, Django Ninja lo renombró como Schema.
Schema hereda de BaseModel, por lo que la sustancia real de ambos es muy cercana (Django Ninja le añade su propio toque):
Volviendo al proyecto, veamos el ejemplo en él; este es el Schema que define el request body para la API de «Crear publicación»:
# post/schemas.py
from ninja import Schema
class CreatePostRequest(Schema):
title: str
content: str
user_id: int
Este Schema requiere que los datos del body contengan obligatoriamente estos tres campos: title, content y user_id, y además los tipos de datos deben coincidir.
Uso de Schema en funciones view#
Una vez definido el Schema de la «solicitud», puedes utilizarlo dentro de la función view en forma de parámetro de función:
from post.schemas import CreatePostRequest
...
@router.post(path='/posts/')
def create_post(..., payload: CreatePostRequest): # Aquí
...
Establecemos el type hint del parámetro payload de la función con el CreatePostRequest que acabamos de definir.
Cuando se envía una solicitud a esta API, Django Ninja analizará (parsing) y validará los datos del body a través de este Schema CreatePostRequest.
Una vez que la validación sea exitosa, pasará los datos al parámetro payload de la función view. En este punto, el parámetro payload dentro de la función es esencialmente un objeto Schema (es decir, Pydantic BaseModel).
Validación automática de datos y manejo de errores#
Si falta algún campo en el request body o el tipo de datos no es correcto, Django Ninja devolverá automáticamente una respuesta 422, proporcionando información detallada del error:
{
"detail": [
{
"type": "missing",
"loc": [
"body",
"payload",
"content"
],
"msg": "Field required"
}
]
}
El mensaje de error indica: en el body falta el campo content.
4. Campos opcionales (Optional) y valores predeterminados#
En el desarrollo real de API, no todos los campos de solicitud son obligatorios.
Podemos definir campos opcionales mediante Pydantic y type hints. Supongamos que ahora el contenido del artículo es completamente opcional: (Presta atención al campo content)
Al usar el operador = para establecer el valor predeterminado del campo content en None, ese campo se convertirá en un campo opcional. En este punto, el type hint de content también debe cambiarse a str | None.
Vale la pena señalar que si el Schema se utiliza en la solicitud, aunque esta configuración pueda pasar la validación, también debes prestar atención a si el campo correspondiente del Django Model (es decir, el campo de la base de datos) permite NULL. De lo contrario, seguirá ocurriendo un error:
django.db.utils.IntegrityError: NOT NULL constraint failed: post_post.content
Además de definir el campo como opcional, también se puede dar directamente un valor predeterminado, como la cadena vacía aquí. Cuando el usuario no ingrese nada, se rellenará directamente con el valor predeterminado:
Sin embargo, a excepción de los valores predeterminados None, el acto de asignar un valor predeterminado en un Schema se debe «usar con suma precaución». Discutiremos esta parte nuevamente en la «Entrega 18: Ejemplos de configuración de Pydantic Field y valores predeterminados».
5. Orden de evaluación de parámetros en Django Ninja#
¿Alguna vez te has preguntado cómo sabe Django Ninja qué corresponde a qué cuando hay tantos tipos de parámetros en una función view?
De hecho, Django Ninja determina automáticamente el origen de los parámetros (si son parámetros de ruta, parámetros de consulta o request body) según la firma de parámetros de la función view. Su orden de evaluación es el siguiente:
- Parámetros de ruta: Cualquier variable definida en el URL path (como
iden/items/{id}) se identificará prioritariamente como parámetro de ruta. - Parámetros de consulta: Los demás parámetros de tipo escalar/singular en la función (como
int,float,bool,str, y nolistodict), si no están etiquetados como parámetros de ruta, se identificarán como parámetros de consulta. - Request body: Solo los parámetros de tipo Schema serán considerados como request body.
En principio, una función view solo puede tener un parámetro Schema. Después de todo, una solicitud solo tiene un body.
Final de la segunda sección#
El contenido de esta sección ha llegado casi a su fin.
En esta sección aprendimos a usar Django Ninja para procesar solicitudes HTTP e introdujimos el uso básico de Schema.
Aún hay muchos usos y variaciones para Schema; esto es solo una pequeña muestra. En la tercera sección «Respuestas HTTP», verás más configuraciones sobre Schema.
Antes de entrar en la siguiente sección, tomemos un descanso intermedio... y algunos preparativos.
Descanso intermedio y preparativos#
En la siguiente sección, haremos que las API del proyecto funcionen de verdad. ¿Recuerdas por qué mencionamos antes que no se podían usar actualmente?
- No hay datos en la base de datos.
- No se han creado los Schema.
Ya hemos aprendido a usar Schema, aunque aún no de forma exhaustiva. Ahora el problema de los «datos en la base de datos» también debe resolverse.
Django Fixtures#
Ciertamente podríamos llamar a la API POST para añadir manualmente usuarios y publicaciones, ¡pero eso sería demasiado tedioso! Sin mencionar que el proyecto aún no cuenta con la API de «añadir usuario».
Así que no nos complicaremos.
Importaremos directamente los datos de prueba predefinidos por mí a través de los Django fixtures.
Para una introducción sobre los Django fixtures, puedes consultar el artículo «Importar y exportar datos con Django Fixture».
En la rama 13-response del siguiente artículo, ya podrás ver los datos de fixtures que exporté:
users.json.posts.json.
Si deseas usarlos, simplemente impórtalos en orden:
Es imprescindible importar primero users; de lo contrario, las publicaciones se quedarán sin asociación por falta de autor.
Una vez completada la importación, obtendrás 2 usuarios (Alice y Bob), así como las 30 publicaciones que cada uno de ellos realizó.

Eh, se ha colado mi primera publicación de prueba, ¡les pido comprensión! 😅
Tras importar con éxito, ya podemos continuar.