django rest framework es una librería que corre sobre django y permite de manera bastante limpia y sencilla construir una web API, esta herramienta tiene un conjunto de paquetes que fortalecen todo su ecosistema, ha tenido una gran aceptación dentro de la comunidad como se aprecia en la Figura 1 con mas de 20k estrellas en github
Actualmente trabajo con esta librería y he decidido comentar algunos tips que me parecen interesantes a la hora de trabajar con ella, quizas algunos ya los conocías quizás no
para instalar django rest framework es realmente sencillo yo utilizare python 3.7 y virtualenv
pip install django
pip install djangorestframework
y en la configuración de django en apps instaladas se agrega la siguiente configuración
INSTALLED_APPS = [
...
'rest_framework',
]
Paginación
django rest framework utiliza las implementaciones que tiene django y a partir de aquí las especializa para lo que necesite hacer cada uno, en este ejemplo voy a mostrar como personalizar la paginación utilizando APIView como clase base para construir mi servicio, por defecto cuando se implementa la paginacion se tiene como respuesta lo siguiente
HTTP 200 OK
{
"count": 1023
"next": "https://api.example.org/accounts/?page=5",
"previous": "https://api.example.org/accounts/?page=3",
"results": [
…
]
}
pero muchas veces dentro del equipo de desarrollo hay un estándar definido para las respuesta y este debe adaptarse a él, por ello en el siguiente código mostrare como se puede modificar en mi caso quiero que el formato de salida se vea como a continuación
HTTP 200 OK
{
"data":{
"totalItems": 1023
"next": "https://api.example.org/accounts/?page=5",
"previous": "https://api.example.org/accounts/?page=3",
"items": [
…
]
}
}
Para ello definimos un archivo llamado pagination.py y agregamos el siguiente codigo
aqui defino un tipo de paginación que es la PageNumberPagination la cual tiene unos métodos que voy a sobreescribir para adaptarlo a lo que necesito, para ello creó una clase llamada PaginationHandlerMixin en donde me me ubicare en el método que me interesa que es get_paginated_response este recibe un parámetro data que es donde viene la información serializada y con ella utilizando la clase Response de DRF se construye la nueva respuesta
def get_paginated_response(self, data):
return Response(
{
"next": self.paginator.get_next_link(),
"previous": self.paginator.get_previous_link(),
"totalItems": self.paginator.page.paginator.count,
"items": data,
}
)
Para poder implementarlo y sobreescribir este método la clase UserList en el archivo views.py debe heredar de esta clase como se muestra a continuación en el módulo views
Hemos creado una información para probar con una lista de 3 usuarios, entrando a la url tendríamos el siguiente formato de salida como se aprecia en la Figura 2
Con este código podemos adaptar la paginación al formato de salida que tengamos definido con el equipo de desarrollo y no tener que dejar el formato que django rest framework trae por defecto
Fechas
Cuando se utilizan serializers que es básicamente convertir la data de tipo nativo de python a json format a veces podemos tener algunos inconvenientes con los formatos de las fechas, aquí veremos dos sencillos trucos que pueden ser útiles
- El primero es con relación al formato de salida, en python manejamos dos tipos para fechas date y datetime pero en desarrollo puede requerir un formato en especial, para setear este formato podemos utilizar la siguiente porcion de codigo
from rest_framework import serializers
class UserSerializer(serializers.Serializer):
name = serializers.CharField()
created_at = serializers.DateTimeField(
format="iso-8601"
)
birthdate = serializers.DateField(format="%Y-%m-%d")
como podemos notar en ejemplo anterior podemos utilizar el parámetro format para darle el formato de fecha que mejor se nos adapte, en el ejemplo utilizo el formato de iso oficial o un formato personalizado aquí se puede utilizar los formatos de fecha de python y adaptar la salida a lo que se necesite
- El segundo es en relación a la zona horaria, en la documentación de django se sugiere que es buena idea guardar la fecha en UTC sin zona horaria, yo específicamente lo guardo sin zona horaria pero a la hora de entregar la información puede requerirse que si la tenga, para eso en los serializadores podemos agregar el siguiente código
import pytz
from rest_framework import serializers
class UserSerializer(serializers.Serializer):
name = serializers.CharField()
created_at = serializers.DateTimeField(
default_timezone=pytz.timezone("America/Bogota"), format="iso-8601"
)
birthdate = serializers.DateField(format="%Y-%m-%d")
aquí agregue la librería pytz que facilita trabajar con zonas horarias, al mismo ejemplo anterior agregue el parámetro default_timezone indicando timezone "America/Bogota" la implementación completa utilizando el código en los views sería el siguiente
from datetime import date, datetime
from pydantic import BaseModel
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status
from.serializers import UserSerializer
class User(BaseModel):
name:str
created_at:datetime
birthdate:date
class UserList(APIView):
def get(self, request, format=None):
user = User(
name="andres",
birthdate=date(day=15,year=1990,month=10),
created_at=datetime.today()
)
serializer = UserSerializer(user)
return Response(status=status.HTTP_200_OK, data={"data":serializer.data})
y entrando a la url el json de respuesta se vería de la siguiente forma como se aprecia en la Figura 3
Validación de múltiples permisos
En la documentación de DRF se puede ver que existe la posibilidad de hacer validación por clases restringiendo por roles quienes pueden acceder a partir de un token a cada recurso, pero algunas veces se necesitará validaciones para varios roles y algunos solamente de lectura (GET) para ello es posible utilizar el operador '&' y '|' para complementar estas validaciones como se puede ver en el codigo a continuacion donde agregare un archivo permissions.py que tendra los permisos disponibles y en el arrchivo views.py agregare la implementacion
En el codigo anterior agrege los permissions.py que simulan la validacion de permisos, aqui deberia ir la implementación para validar cada rol, en el archivo views.py el cual es el segundo agrege el atributo authentication_classes para decirle a DRF como se autentica el usuario y el atributo permission_classes el cual es el importante, este recibe una lista pero dentro de la lista cumple las siguientes condiciones
- La primera es que debe estar autenticado, y como ven usa el operador & por lo tanto IsAuthenticated debe ser true si no no seguirá validando
- La siguiente está dentro de un or por lo tanto el usuario puede ser tipo Admin o Mod pero si es Mod debe cumplirse ReadOnlyPermission esta validación asegura que solo permite hacer llamados GET, es decir si un usuario de tipo Mod realiza un llamado POST generará un error de permisos insuficientes
de esta manera se puede hacer las validaciones que se necesiten para cada recursos por rol, algunas pueden llegar a ser bastante complejas dependiendo de cada rol y que restricciones tiene, existen algunas librerias para hacerlo mas sencillo, o tambien pueden hacer validaciones conjuntas ya que al final son clases que retornan booleanos
Swagger documentación
Hay una librería llamada drf-yasg dentro del ecosistema de DRF que permite generar documentación de swagger a partir de decorators aquí puede existir un gran debate si usar directamente un archivo de configuración,o usar los decorators pero el punto es poder implementarla y ahorrar tiempo utilizando los serializadores para recibir y retornar información como veremos a continuación
En el código anterior he construido una clase UserDetail que retornara la información de un usuario por id, en este caso se usará el decorador @swagger_auto_schema este permite generar la documentación automática basada en los serializadores, por lo tanto a medida que cambie el serializador la documentación se ira actualizando
user_response = openapi.Response("user detail", UserSerializer)
@swagger_auto_schema(
manual_parameters=[id_param],
responses={200: user_response, 404: ErrorSerializer},
)
en esta porcion de codigo le indico a swagger que las posibles respuestas son 200 y 404 y las estructuras que responde en cada una pertenece a la clase UserSerializer y ErrorSerializer, esta librería automáticamente hace la introspección de los atributos y los documenta cómo se puede ver a continuación en la Figura 4
esto es de gran ayuda por que permite que a medida que el código cambie a largo plazo la documentación se va actualizando al mismo tiempo de manera automática
Conclusión
Estos son algunos trucos o tips que me han servido trabajando con esta librería y que en la documentación no se pueden encontrar o por lo menos no de forma directa, existen muchas más cosas que pueden aplicarse para lograr también mejorar rendimiento pero estas espero mostrarlas en próximos post.