Saltar al contenido
🐍 Curso de Python
/

Módulo 5 · Lección 15 de 30

Vistas de API y genéricas

Cuatro capas de abstracción, los diez renglones que hay detrás de cada una, y el 201 al que le falta una cabecera.

⏱ 16 min de lectura

DRF ofrece cuatro formas de escribir una vista, y van de "escríbelo todo tú" a "una línea". La tentación es empezar por la última, y el problema es que entonces no sabes qué hacer cuando algo no encaja.

Esta lección recorre las cuatro de abajo arriba, enseñando qué código te ahorra cada capa, para que la magia deje de serlo.

Nivel 1: una función#

Lo más básico, y sigue siendo útil:

python
from rest_framework.decorators import api_view
from rest_framework.response import Response
from rest_framework import status


@api_view(['GET', 'POST'])
def libros(peticion):
        if peticion.method == 'GET':
                serializador = LibroSerializer(Libro.objects.all(), many=True)
        return Response(serializador.data)

    serializador = LibroSerializer(data=peticion.data)
    serializador.is_valid(raise_exception=True)
    serializador.save()
    return Response(serializador.data, status=status.HTTP_201_CREATED)

El decorador es lo que convierte una vista de Django en una de DRF, y hace cuatro cosas:

Convierte la petición en una de DRF, con peticion.data que entiende JSON, formularios y varias partes por igual. Es la diferencia con request.POST, que solo entiende formularios.

Convierte Response al formato que pida el cliente, que es la negociación de contenido de la lección 12.

Rechaza los métodos que no están en la lista, con un 405.

Traduce las excepciones. Ese raise_exception=True es la pieza clave: convierte los errores de validación en un 400 con el JSON de la lección 13, sin escribir nada.

El @api_view es la respuesta correcta para un punto de acceso suelto que no es un recurso: una comprobación de salud, un informe, algo que recibe un fichero.

Nivel 2: APIView#

Lo mismo con un método por verbo, que se lee mejor:

python
from rest_framework.views import APIView


class LibroLista(APIView):
        def get(self, peticion):
                serializador = LibroSerializer(Libro.objects.all(), many=True)
        return Response(serializador.data)

    def post(self, peticion):
                serializador = LibroSerializer(data=peticion.data)
        serializador.is_valid(raise_exception=True)
        serializador.save()
        return Response(serializador.data, status=status.HTTP_201_CREATED)

La ventaja sobre la función es que los permisos, la autenticación y el límite de uso se declaran como atributos:

python
class LibroLista(APIView):
        permission_classes = [IsAuthenticated]
    throttle_classes = [UserRateThrottle]

Sigue sin haber nada automático: si quieres paginación o filtros, los escribes.

Nivel 3: las genéricas#

Aquí aparece la magia, y es poca:

python
from rest_framework import generics


class LibroLista(generics.ListCreateAPIView):
        queryset = Libro.objects.select_related('editorial')
    serializer_class = LibroSerializer


class LibroDetalle(generics.RetrieveUpdateDestroyAPIView):
        queryset = Libro.objects.select_related('editorial')
    serializer_class = LibroSerializer

Cuatro líneas dan el recurso completo, con paginación y filtros incluidos. Y están hechas de piezas:

Salida en pantalla
ListAPIView                   [ListModelMixin, GenericAPIView]
CreateAPIView                 [CreateModelMixin, GenericAPIView]
ListCreateAPIView             [ListModelMixin, CreateModelMixin, GenericAPIView]
RetrieveUpdateDestroyAPIView  [RetrieveModelMixin, UpdateModelMixin,
                               DestroyModelMixin, GenericAPIView]

Cinco piezas que se combinan. Y cada pieza son diez renglones:

python
class ListModelMixin:
        def list(self, request, *args, **kwargs):
                queryset = self.filter_queryset(self.get_queryset())

        page = self.paginate_queryset(queryset)
        if page is not None:
                        serializer = self.get_serializer(page, many=True)
            return self.get_paginated_response(serializer.data)

        serializer = self.get_serializer(queryset, many=True)
        return Response(serializer.data)

Ahí está toda la magia de un listado, y merece leerse despacio porque enseña los puntos de extensión:

MétodoPara qué se sobrescribe
get_queryset()Filtrar por el usuario, por permisos, por lo que sea
filter_queryset()Los filtros de la petición (lección 19)
paginate_queryset()La paginación (lección 20)
get_serializer()Elegir el serializador según la acción (lección 14)

Y el de crear enseña el suyo:

python
class CreateModelMixin:
        def create(self, request, *args, **kwargs):
                serializer = self.get_serializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        self.perform_create(serializer)
        headers = self.get_success_headers(serializer.data)
        return Response(serializer.data, status=201, headers=headers)

    def perform_create(self, serializer):
                serializer.save()

Fíjate en que perform_create existe solo para que lo sobrescribas. Es el sitio donde poner lo que hay que hacer al guardar:

python
def perform_create(self, serializador):
        serializador.save(autor=self.request.user)

Esa línea es la que asigna el usuario actual, y es la equivalente al form_valid de la lección 25 del módulo 4. Ponerlo ahí y no en el serializador tiene un motivo: el serializador no debería saber de peticiones HTTP.

Nivel 4: los conjuntos de vistas#

Una clase por recurso, y el enrutador genera las rutas:

python
class LibroViewSet(viewsets.ModelViewSet):
        queryset = Libro.objects.select_related('editorial').prefetch_related('autores')
    serializer_class = LibroSerializer
Salida en pantalla
ModelViewSet: [CreateModelMixin, RetrieveModelMixin, UpdateModelMixin,
               DestroyModelMixin, ListModelMixin, GenericViewSet]

ReadOnlyModelViewSet: [RetrieveModelMixin, ListModelMixin, GenericViewSet]

Las mismas cinco piezas de antes, todas juntas. Y ReadOnlyModelViewSet, que son dos, merece recordarse: para un recurso de consulta, es una palabra en vez de tres permisos. Un catálogo público, un listado de países, cualquier cosa que nadie deba modificar.

Es la lección 16 entera, así que aquí solo la mención.

Cuál usar#

CapaCuándo
@api_viewUn punto suelto que no es un recurso
APIViewLo mismo, con permisos declarados o mucha lógica
GenéricasRutas a medida, o recursos que no son un CRUD entero
ViewSetUn recurso con las cinco operaciones. Lo normal.

La recomendación práctica es empezar por el conjunto de vistas para los recursos y usar @api_view para lo suelto, sin pasar por las capas intermedias. Y bajar de nivel cuando de verdad estorbe.

La señal de que has bajado demasiado tarde: un conjunto de vistas con get_queryset, get_serializer_class, get_permissions, tres acciones propias y dos condicionales por método. Eso ya no ahorra nada; ahí una APIView explícita se lee mejor.