Saltar al contenido
🐍 Curso de Python
/

Módulo 5 · Lección 13 de 30

Serializadores

La pieza central de DRF, los tres sitios donde validar, y el PATCH con una errata que contesta 200 sin cambiar nada.

⏱ 16 min de lectura

El serializador es la pieza central de DRF y hace dos trabajos que conviene no confundir:

Salida en pantalla
objetos de Python  ->  JSON      (serializar, salida)
JSON  ->  datos validados         (deserializar, entrada)

La primera dirección es fácil. La segunda es donde está todo, porque los datos vienen de fuera y no te puedes fiar de ellos. Un serializador es, sobre todo, un validador.

Si te recuerda a los formularios del módulo 4, es que la semejanza es deliberada: misma idea, is_valid(), validated_data, errors y validación en tres niveles. Lo que cambia es que aquí no hay HTML y los datos llegan como JSON.

Uno a mano#

Empezamos sin modelo, que es como mejor se ve lo que hace:

python
from rest_framework import serializers


class ContactoSerializer(serializers.Serializer):
        nombre = serializers.CharField(max_length=100)
    correo = serializers.EmailField()
    edad = serializers.IntegerField(min_value=0, max_value=130, required=False)
    activo = serializers.BooleanField(default=True)

Se usa dándole datos y preguntándole:

Salida en pantalla
data={'nombre': 'Óscar', 'correo': 'o@t.es', 'edad': 40}

is_valid():      True
validated_data:  {'nombre': 'Óscar', 'correo': 'o@t.es', 'edad': 40, 'activo': True}

Fíjate en que activo aparece con su valor por defecto aunque no se enviara. Y con datos malos:

Salida en pantalla
data={'nombre': '', 'correo': 'no-es-correo', 'edad': 200}

is_valid():  False
errors:
{
  "nombre": ["Este campo no puede estar en blanco."],
  "correo": ["Introduzca una dirección de correo electrónico válida."],
  "edad":   ["Asegúrese de que este valor es menor o igual a 130."]
}

Los tres errores a la vez, y en español. Que sean los tres importa: una API que solo informa del primer fallo obliga a quien la usa a corregir de uno en uno.

Leer y escribir no son simétricos#

Casi nunca quieres los mismos campos en las dos direcciones, y esta es la asimetría que hay que manejar:

python
class LibroSerializer(serializers.Serializer):
        id = serializers.IntegerField(read_only=True)
    titulo = serializers.CharField(max_length=200)
    precio = serializers.DecimalField(max_digits=7, decimal_places=2)
    creado = serializers.DateTimeField(read_only=True)
    clave_interna = serializers.CharField(write_only=True)
OpciónSale en la respuestaSe acepta al enviar
normalSíSí
read_only=TrueSíNo
write_only=TrueNoSí

read_only es para lo que genera el servidor: identificadores, fechas automáticas, campos calculados. Si no lo pones, un cliente puede mandar "creado": "1999-01-01" y salirse con la suya.

write_only es para lo que entra y no debe salir: contraseñas, tokens, datos que se usan para calcular algo. Es la protección contra publicar por accidente lo que acabas de recibir.

Los tres sitios donde validar#

Igual que en los formularios de Django, hay tres niveles y el orden importa:

python
class RangoSerializer(serializers.Serializer):
        desde = serializers.DateField()
    hasta = serializers.DateField()
    titulo = serializers.CharField(max_length=10)

    def validate_titulo(self, valor):
                if valor.lower().startswith('el '):
                        raise serializers.ValidationError('No empieces por artículo.')
        return valor.title()

    def validate(self, datos):
                if datos['desde'] > datos['hasta']:
                        raise serializers.ValidationError({'hasta': 'Debe ser posterior a desde.'})
        return datos

Nivel 1: el campo. max_length, min_value, EmailField y los validadores que le pases. Automáticos.

Nivel 2: validate_<campo>. Para reglas de un solo campo que el tipo no cubre. Recibe el valor y devuelve el valor, así que sirve también para normalizar:

Salida en pantalla
'holaaa'  ->  validate_titulo  ->  'Holaaa'

Olvidar el return es el error clásico: el campo se queda en None y no se entera nadie.

Nivel 3: validate. Para reglas que involucran a varios campos, como que una fecha sea posterior a otra.

Y el detalle que hay que interiorizar:

Salida en pantalla
con el título demasiado largo:
  [1] falla el nivel de campo
  errors: {"titulo": ["Asegúrese de que este campo no tenga más de 10 caracteres."]}
  -> validate() NI SE LLAMA

con el título correcto:
  [2] validate_titulo
  [3] validate
  errors: {"hasta": ["Debe ser posterior a desde."]}

Si falla cualquier campo, validate no se ejecuta. Es lógico —no tendría los datos— y tiene una consecuencia práctica: dentro de validate puedes dar por hecho que los campos que llegaron son válidos, pero no que estén todos, porque los opcionales pueden faltar. Por eso dentro de validate se usa datos.get('campo') y no datos['campo'].

Fíjate también en que validate lanza el error con el nombre del campo dentro:

python
raise serializers.ValidationError({'hasta': 'Debe ser posterior a desde.'})

Sin el diccionario, el mensaje va a parar a non_field_errors, donde la interfaz que consume la API no sabrá junto a qué casilla enseñarlo. Es un detalle de dos caracteres que cambia mucho la experiencia de quien la usa.

Crear y actualizar#

Un serializador a mano no sabe guardar nada. Si quieres que save() funcione, hay que decirle cómo:

python
class ContactoSerializer(serializers.Serializer):
    # ... campos ...

        def create(self, validated_data):
                return Contacto.objects.create(**validated_data)

    def update(self, instancia, validated_data):
                for campo, valor in validated_data.items():
                        setattr(instancia, campo, valor)
        instancia.save()
        return instancia

save() llama a uno o a otro según le hayas pasado una instancia:

python
ContactoSerializer(data=datos).save()                 # -> create
ContactoSerializer(contacto, data=datos).save()       # -> update
ContactoSerializer(contacto, data=datos, partial=True).save()   # -> update parcial

En la práctica escribirás muy pocos de estos, porque ModelSerializer los genera, y es la lección siguiente. Conviene verlos una vez para saber qué está generando.