Serializadores en Django REST Framework¶
Los serializadores son uno de los componentes fundamentales de Django REST Framework (DRF). Su función principal es actuar como puente entre los objetos de Django y los datos que intercambia una API.
En términos simples, permiten transformar objetos de Python, como instancias de modelos y QuerySets, en datos que pueden ser enviados mediante una API, normalmente en formato JSON. También realizan el proceso inverso: reciben datos enviados por el cliente, los validan y los convierten en estructuras que Django puede utilizar para crear o modificar objetos.
flowchart TB
subgraph SERIALIZACION["SERIALIZACIÓN — Django → API"]
direction TB
M1["Modelo Django"]
S1["Serializer"]
D1["Datos del modelo"]
J1["JSON"]
M1 --> S1
S1 --> D1
D1 --> J1
end
subgraph DESERIALIZACION["DESERIALIZACIÓN — API → Django"]
direction TB
J2["JSON recibido"]
D2["Datos de entrada"]
S2["Serializer"]
V["Validación"]
M2["Modelo Django"]
J2 --> D2
D2 --> S2
S2 --> V
V -->|Válido| M2
V -->|Inválido| E["Errores de validación"]
end
¿Por qué necesitamos serializadores?¶
Django trabaja principalmente con objetos Python y modelos del ORM. Por ejemplo, podemos tener un modelo:
from django.db import models
class Game(models.Model):
title = models.CharField(max_length=100)
year = models.PositiveIntegerField()
developer = models.CharField(max_length=100)
def __str__(self):
return self.title
Una consulta utilizando el ORM nos devuelve una instancia:
(.venv) python manage.py shell#(1)!
>>> from games.models import Game#(2)!
>>>
>>> game = Game.objects.get(pk=1)#(3)!
>>> game.title#(4)!
'Super Mario Bros'#(5)!
>>> game.year#(6)!
1985#(7)!
- Ingresamos al Shell interactivo de Django.
- Importamos el modelo
Gamedesde la aplicacióngames. - Consultamos la base de datos y obtenemos el juego cuyo
ides1. - Accedemos al atributo
titledel objeto obtenido. - Django devuelve el título almacenado para ese juego.
- Accedemos al atributo
yeardel objeto obtenido. - Django devuelve el año almacenado para ese juego.
from games.models import Game
game = Game.objects.get(pk=1)
game.title
game.year
El objeto game es una instancia de Python. Sin embargo, una API REST normalmente necesita entregar los datos en un formato que pueda ser interpretado por el cliente:
{
"id": 1,
"title": "Super Mario Bros",
"year": 1985,
"developer": "Nintendo"
}
El serializador se encarga precisamente de realizar esta transformación.
Serialización¶
La serialización consiste en transformar objetos Python o instancias de modelos en un formato adecuado para ser enviado al cliente.
Por ejemplo:
flowchart LR
A["Modelo Django"] --> B["Serializer"] --> C["Representación JSON"]
Podemos utilizar un serializador para transformar una instancia del modelo:
from rest_framework import serializers
from .models import Game
class GameSerializer(serializers.ModelSerializer):
class Meta:
model = Game
fields = "__all__"
Posteriormente, realizamos la serialización, pasando como argumento al serializador la instancia del objeto que deseamos serializar.
(.venv) python manage.py shell
>>> from games.models import Game#(1)!
>>> from games.serializers import GameSerializer#(2)!
>>> import json#(3)!
>>>
>>> game = Game.objects.get(pk=1)#(4)!
>>> serializer = GameSerializer(game)#(5)!
>>>
>>> print(json.dumps(serializer.data, indent=2))#(6)!
{
"id": 1,
"title": "Super Mario Bros",
"year": 1985,
"developer": "Nintendo"
}
- Importamos el modelo
Gamepara consultar un registro de la base de datos. - Importamos
GameSerializer, encargado de convertir el objetoGamea datos serializados. - Importamos
jsonpara mostrar los datos serializados con formato JSON. - Obtenemos desde la base de datos el juego cuyo
ides1. - Pasamos el objeto
Gameal serializador para obtener sus datos. - Convertimos los datos serializados a una cadena JSON y los mostramos con una indentación de dos espacios.
from games.models import Game
from games.serializers import GameSerializer
import json
game = Game.objects.get(pk=1)
serializer = GameSerializer(game)
print(json.dumps(serializer.data, indent=2))
El resultado será una estructura válida en JSON:
{
"id": 1,
"title": "Super Mario Bros",
"year": 1985,
"developer": "Nintendo"
}
Cuando DRF genera la respuesta HTTP, estos datos pueden representarse como JSON.
Deserialización¶
El proceso inverso se denomina deserialización.
En este caso, el cliente envía datos a la API:
{
"title": "Super Mario World",
"year": 1990,
"developer": "Nintendo"
}
El serializador recibe esos datos:
serializer = GameSerializer(data=request.data)
Pero recibir los datos no significa que sean válidos.
Antes de utilizarlos, debemos ejecutar:
serializer.is_valid()
Por ejemplo:
serializer = GameSerializer(data=request.data)
if serializer.is_valid():
serializer.save()
El flujo completo sería:
flowchart LR
A["JSON recibido"]
B["Serializer"]
C["Validación"]
D{"¿Válido?"}
E["save()"]
F["errors"]
G["Modelo Django"]
A --> B --> C --> D
D -->|Sí| E --> G
D -->|No| F
Serializer vs ModelSerializer¶
DRF proporciona diferentes clases para construir serializadores.
Las dos más importantes son:
serializers.Serializerserializers.ModelSerializer
Serializer¶
Serializer permite definir manualmente los campos que tendrá nuestra representación.
Por ejemplo:
| games/serializers.py | |
|---|---|
1 2 3 4 5 6 7 | |
Este enfoque proporciona un control muy detallado sobre el comportamiento del serializador.
Sin embargo, cuando estamos trabajando directamente con modelos Django, tenemos una alternativa más conveniente.
ModelSerializer¶
ModelSerializer está diseñado específicamente para trabajar con modelos de Django.
| games/serializers.py | |
|---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 | |
DRF utiliza la definición del modelo para generar automáticamente los campos correspondientes.
Esto evita tener que declarar manualmente cada campo.
flowchart TD
A[Game Model]
--> B[ModelSerializer]
B ---> C[Campos]
B --> D[Validaciones]
B --> E[Serialización]
C --> C1[id]
C --> C2[title]
C --> C3[year]
C --> C4[developer]
D ---> D1[required]
D ---> D2[unique]
D ---> D3[max_length]
E --> F[JSON]
Por eso, ModelSerializer es generalmente la opción más utilizada cuando una API expone información de modelos Django.
Selección de campos¶
Una buena práctica es especificar explícitamente qué campos queremos exponer.
Por ejemplo:
class GameSerializer(serializers.ModelSerializer):
class Meta:
model = Game
fields = (
"id",
"title",
"year",
"developer",
)
También existe "__all__":
class GameSerializer(serializers.ModelSerializer):
class Meta:
model = Game
fields = "__all__"
que incluye todos los campos del modelo.
Sin embargo, en APIs reales suele ser preferible definir los campos explícitamente.
Esto permite controlar exactamente qué información queda disponible públicamente.
Campos de solo lectura¶
Podemos establecer determinados campos como solo lectura.
Por ejemplo, el identificador de un objeto normalmente no debería ser enviado por el cliente para crear un registro:
class GameSerializer(serializers.ModelSerializer):
class Meta:
model = Game
fields = (
"id",
"title",
"year",
"developer",
)
read_only_fields = (
"id",
)
De esta forma, id puede aparecer en las respuestas, pero no será utilizado como un campo modificable durante la creación o actualización.
Validación¶
Una de las funciones más importantes de los serializadores es la validación.
Supongamos que queremos impedir que un juego tenga un año inferior a 1950:
class GameSerializer(serializers.ModelSerializer):
class Meta:
model = Game
fields = (
"id",
"title",
"year",
"developer",
)
def validate_year(self, value):
if value < 1950:
raise serializers.ValidationError(
"El año debe ser igual o superior a 1950."
)
return value
Ahora, cuando el cliente envíe:
{
"title": "Juego antiguo",
"year": 1800,
"developer": "Nintendo"
}
la validación fallará.
Podemos consultar los errores:
serializer.is_valid()
print(serializer.errors)
Y DRF devolverá información sobre el campo que no cumple las reglas.
Validación de varios campos¶
También podemos validar una combinación de campos utilizando validate().
Por ejemplo, podríamos impedir que el año de lanzamiento sea posterior al año actual:
from datetime import date
class GameSerializer(serializers.ModelSerializer):
class Meta:
model = Game
fields = (
"id",
"title",
"year",
"developer",
)
def validate(self, attrs):
if attrs["year"] > date.today().year:
raise serializers.ValidationError(
"El año de lanzamiento no puede estar en el futuro."
)
return attrs
La diferencia importante es:
def validate_year(self, value):
se utiliza para validar un campo específico.
Mientras que:
def validate(self, attrs):
permite validar varios campos en conjunto.
Mensajes de error¶
Cuando una validación falla, podemos generar errores específicos:
raise serializers.ValidationError(
"El año de lanzamiento no es válido."
)
DRF incorpora estos errores en la respuesta de la API.
Por ejemplo:
{
"year": [
"El año de lanzamiento no es válido."
]
}
Esto resulta especialmente útil para que el frontend pueda identificar qué información debe corregir el usuario.
Crear objetos con save()¶
Una vez que los datos han sido validados:
serializer.is_valid()
podemos utilizar:
serializer.save()
En un ModelSerializer, DRF utiliza los datos validados para crear la instancia correspondiente.
Conceptualmente:
request.data
│
▼
Serializer
│
▼
is_valid()
│
▼
validated_data
│
▼
save()
│
▼
Modelo Django
Por ejemplo:
serializer = GameSerializer(data=request.data)
if serializer.is_valid():
game = serializer.save()
La variable game contiene la instancia creada.
Actualizar objetos¶
Los serializadores también permiten actualizar objetos existentes.
game = Game.objects.get(pk=1)
serializer = GameSerializer(
game,
data=request.data,
)
if serializer.is_valid():
serializer.save()
En este caso, el primer argumento representa la instancia que queremos modificar.
Actualizaciones parciales¶
Si queremos modificar solamente algunos campos, podemos utilizar:
partial=True
Por ejemplo:
serializer = GameSerializer(
game,
data={
"title": "Super Mario Bros. Deluxe",
},
partial=True,
)
if serializer.is_valid():
serializer.save()
Esto permite actualizar únicamente title sin necesidad de enviar todos los campos obligatorios.
Esta operación está relacionada directamente con el método HTTP:
PUT → actualización completa
PATCH → actualización parcial
Campos personalizados¶
Los serializadores no están limitados a representar directamente los campos del modelo.
Podemos agregar información calculada.
Por ejemplo:
class GameSerializer(serializers.ModelSerializer):
platform_name = serializers.CharField(
source="platform.name",
read_only=True,
)
class Meta:
model = Game
fields = (
"id",
"title",
"year",
"platform_name",
)
De esta forma, podemos obtener:
{
"id": 1,
"title": "Super Mario World",
"year": 1990,
"platform_name": "SNES"
}
Aunque platform_name no sea un campo directamente almacenado en el modelo Game, el serializador puede construirlo a partir de la relación.
SerializerMethodField¶
Otra alternativa para generar información calculada es SerializerMethodField.
class GameSerializer(serializers.ModelSerializer):
description = serializers.SerializerMethodField()
class Meta:
model = Game
fields = (
"id",
"title",
"description",
)
def get_description(self, obj):
return f"{obj.title} fue lanzado en {obj.year}."
La respuesta podría ser:
{
"id": 1,
"title": "Super Mario World",
"description": "Super Mario World fue lanzado en 1990."
}
Este recurso es útil cuando necesitamos construir información que no existe directamente como campo del modelo.
Relaciones entre modelos¶
Los serializadores también permiten representar relaciones.
Supongamos que tenemos:
class Game(models.Model):
title = models.CharField(max_length=100)
platform = models.ForeignKey(
Platform,
on_delete=models.PROTECT,
)
Podemos representar la plataforma mediante su identificador:
class GameSerializer(serializers.ModelSerializer):
class Meta:
model = Game
fields = (
"id",
"title",
"platform",
)
La respuesta podría ser:
{
"id": 1,
"title": "Super Mario World",
"platform": 3
}
También podemos utilizar un serializador anidado:
class PlatformSerializer(serializers.ModelSerializer):
class Meta:
model = Platform
fields = (
"id",
"name",
)
class GameSerializer(serializers.ModelSerializer):
platform = PlatformSerializer()
class Meta:
model = Game
fields = (
"id",
"title",
"platform",
)
La respuesta ahora podría ser:
{
"id": 1,
"title": "Super Mario World",
"platform": {
"id": 3,
"name": "SNES"
}
}
Esto resulta útil cuando queremos entregar información más completa sobre una relación.
Serialización de múltiples objetos¶
También podemos serializar un QuerySet completo.
games = Game.objects.all()
serializer = GameSerializer(
games,
many=True,
)
El parámetro:
many=True
indica que estamos trabajando con múltiples objetos.
El resultado tendrá una estructura similar a:
[
{
"id": 1,
"title": "Super Mario Bros.",
"year": 1985
},
{
"id": 2,
"title": "Super Mario World",
"year": 1990
}
]
Sin many=True, DRF espera recibir una sola instancia.
El atributo .data¶
Después de serializar una instancia:
serializer = GameSerializer(game)
podemos acceder a:
serializer.data
Este atributo contiene los datos ya preparados para ser representados en la respuesta de la API.
Por ejemplo:
{
"id": 1,
"title": "Super Mario World",
"year": 1990
}
Es importante diferenciarlo de:
serializer.validated_data
que contiene los datos después de haber pasado correctamente por el proceso de validación.
serializer.data
│
└── Datos serializados para representar
serializer.validated_data
│
└── Datos recibidos y validados
.is_valid() y raise_exception¶
Normalmente podemos escribir:
if serializer.is_valid():
serializer.save()
Pero DRF también permite:
serializer.is_valid(
raise_exception=True,
)
serializer.save()
Si los datos no son válidos, DRF genera automáticamente una excepción de validación que puede convertirse en una respuesta HTTP adecuada.
Esta forma resulta especialmente conveniente dentro de las vistas de una API.
Serializadores y seguridad¶
Los serializadores también cumplen un papel importante en la seguridad de una API.
No deberíamos exponer automáticamente toda la información de nuestros modelos.
Por ejemplo, si un modelo de usuario contiene:
id
username
email
password
is_staff
is_superuser
no sería correcto exponer todos esos campos mediante un serializador público.
Por eso debemos seleccionar cuidadosamente los campos:
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = (
"id",
"username",
"email",
)
La regla práctica es:
Un modelo puede contener información que una API no necesariamente debe exponer.
El serializador funciona como una capa de control entre nuestros modelos y los consumidores de la API.
Flujo completo¶
Cuando un cliente consulta una API:
Cliente
│
│ GET /api/games/
▼
View / ViewSet
│
▼
ORM de Django
│
▼
QuerySet
│
▼
GameSerializer
│
▼
JSON
│
▼
Cliente
Cuando el cliente envía información:
Cliente
│
│ POST /api/games/
│ JSON
▼
View / ViewSet
│
▼
GameSerializer
│
▼
is_valid()
│
├───────────────┐
│ │
▼ ▼
Válido Inválido
│ │
▼ ▼
save() errors
│ │
▼ ▼
Modelo Respuesta
Django HTTP 400
Resumen¶
Los serializadores son una pieza fundamental de Django REST Framework porque permiten controlar cómo los datos entran y salen de nuestra API.
Sus principales responsabilidades son:
- Convertir objetos Django a representaciones adecuadas para una API.
- Convertir datos recibidos desde el cliente en estructuras que Django pueda procesar.
- Validar los datos de entrada.
- Crear y actualizar instancias de modelos.
- Representar relaciones entre modelos.
- Agregar campos calculados o personalizados.
- Controlar qué información se expone mediante la API.
En la práctica, ModelSerializer será probablemente la clase que más utilicemos cuando construyamos APIs basadas en modelos Django.
La idea fundamental que debemos recordar es:
SERIALIZADOR
┌───────────────────────────┐
│ │
│ API REST │
│ │
└─────────────┬─────────────┘
│
┌──────▼──────┐
│ Serializer │
└──────┬──────┘
│
┌──────────┴──────────┐
│ │
▼ ▼
Serialización Deserialización
│ │
▼ ▼
Modelo → datos datos → Modelo
│
▼
Validación
Por lo tanto, el serializador no es simplemente un conversor de Django → JSON. También constituye una capa de validación, transformación y control de los datos que nuestra API recibe y entrega.