Saltar al contenido
🐍 Curso de Python
/

Módulo 5 · Lección 20 de 30

Paginación

Las tres formas que trae DRF, la fila que aparece dos veces al recorrer un listado, y el contador que cuesta una consulta por página.

⏱ 15 min de lectura

La lección 9 vio la paginación desde el lado de quien consume una API, con sus tres estilos y sus problemas. Ahora toca elegir cuál ofreces, y las consecuencias son tuyas.

La lección 12 dejó dicho que la paginación se activa el primer día, porque cambia la forma de la respuesta. Aquí va la elección de cuál.

Las tres#

python
REST_FRAMEWORK = {
    'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
    'PAGE_SIZE': 20,
}

Y por recurso, con la configuración que conviene:

python
from rest_framework.pagination import (
    CursorPagination, LimitOffsetPagination, PageNumberPagination)


class PaginaNumerada(PageNumberPagination):
        page_size = 20
    page_size_query_param = 'tam'      # deja al cliente pedir otro tamaño
    max_page_size = 100                # y le pone tope


class PorDesplazamiento(LimitOffsetPagination):
        default_limit = 20
    max_limit = 100


class PorCursor(CursorPagination):
        page_size = 20
    ordering = '-creado'               # obligatorio, y tiene que ser estable

Lo que devuelve cada una:

Salida en pantalla
--- por número ---
count: 201
next: http://.../api/libros/?page=2
previous: None
results: 20 elementos

--- por desplazamiento ---
count: 201
next: http://.../api/libros/?limit=20&offset=20
previous: None
results: 20 elementos

--- por cursor ---
next: http://.../api/libros/?cursor=cD0yMDI2LTA5LTIyKzE5JTNBMTUlM0EyMS4xMDU4NzQ...
previous: None
results: 20 elementos

La de cursor no tiene count. Es la primera diferencia y es deliberada: contar es lo que cuesta.

Y el cursor, descodificado, no es un número de página:

Salida en pantalla
cursor:        cD0yMDI2LTA5LTIyKzE5JTNBMTUlM0EyMS4xMDU4NzQlMkIwMCUzQTAw
descodificado: p=2026-09-22+19:15:21.105874+00:00

Es la posición del último elemento devuelto. Va en base64 no para esconderlo sino para que quepa en una dirección, y eso explica todas sus propiedades: no se puede saltar a la página cincuenta, y no se puede desincronizar.

Lo que cuesta cada una#

Salida en pantalla
por número          3 consultas, de ellas 1 es un COUNT
por desplazamiento  3 consultas, de ellas 1 es un COUNT
por cursor          2 consultas, ninguna es un COUNT

Esa consulta de más es el count de la respuesta, y es una consulta entera por cada página que sirvas. Con una tabla pequeña no se nota; con millones de filas, contar puede tardar más que traer la página.

Y con filtros es peor, porque el contador cuenta las filas filtradas: no vale cachearlo.

Ese es el intercambio, y hay que decidirlo sabiendo que el contador se usa para pintar una barra de progreso y poco más.

Elegir#

Por númeroPor desplazamientoPor cursor
Total de elementosSíSíNo
Saltar a una páginaSíSíNo
Consultas por página332
Estable si cambian los datosNoNoSí
Coste con desplazamiento grandeAltoAltoConstante
Fácil de entenderSíSíMenos

Por número para lo que tiene interfaz de usuario con páginas numeradas y no cambia cada segundo: un catálogo, un archivo, un listado administrativo.

Por desplazamiento es lo mismo con otra sintaxis, y lo único que añade es que el cliente controle el tamaño con más naturalidad. Tiene el mismo problema de estabilidad.

Por cursor para cualquier cosa que se recorra entera, que cambie a menudo o que sea grande: un registro de eventos, una cola de mensajes, una sincronización, una línea de tiempo. Y para tablas de millones de filas, donde la diferencia de coste es abismal:

Con desplazamiento, OFFSET 100000 obliga a la base de datos a generar y descartar cien mil filas antes de devolver las veinte que quieres. Con cursor, es una búsqueda por índice: cuesta lo mismo en la página 1 que en la 5000.

La respuesta práctica para muchos proyectos es las dos: por número en el recurso que consume tu propia interfaz, y por cursor en un punto de acceso pensado para recorrer.