Saltar al contenido
🐍 Curso de Python
/

Módulo 5 · Lección 19 de 30

Filtros, búsqueda y ordenación

El filtro con una errata que devuelve las doscientas filas creyendo que filtró, y la búsqueda que no encuentra a Óscar si lo escribes en minúscula.

⏱ 16 min de lectura

Un listado sin filtros obliga a quien consume la API a traerse todo y filtrar en su lado, que es lo peor para los dos. DRF trae tres piezas que lo resuelven, y una de ellas necesita un paquete.

Lo interesante es dónde avisan y dónde no: un valor mal escrito da 400 y un nombre de parámetro mal escrito no da nada.

Las tres piezas#

python
from django_filters import rest_framework as filtros
from rest_framework import filters


class LibroViewSet(viewsets.ReadOnlyModelViewSet):
        queryset = Libro.objects.select_related('editorial').prefetch_related('autores')
    serializer_class = LibroSerializer

    filter_backends = [filtros.DjangoFilterBackend,
                       filters.SearchFilter,
                       filters.OrderingFilter]
    filterset_fields = ['estado', 'editorial']
    search_fields = ['titulo', 'isbn', 'autores__nombre']
    ordering_fields = ['titulo', 'precio', 'publicado_en']
    ordering = ['titulo']
PiezaParámetroDe dónde sale
Filtros?estado=publicadodjango-filter, un paquete
Búsqueda?search=algoDRF
Ordenación?ordering=-precioDRF

Solo la primera necesita instalar algo:

bash
pip install django-filter

Y añadir 'django_filters' a las aplicaciones. Es de los paquetes que casi todo proyecto con DRF acaba teniendo.

El ordering = ['titulo'] de abajo es el orden por defecto, y no es opcional: sin un orden estable, la paginación de la lección siguiente devuelve filas repetidas y saltadas, que es exactamente el problema que la lección 9 vio desde el lado del cliente.

Filtros a medida#

Los campos sueltos cubren la igualdad. Para lo demás, una clase:

python
class LibroFiltro(filtros.FilterSet):
        precio_min = filtros.NumberFilter(field_name='precio', lookup_expr='gte')
    precio_max = filtros.NumberFilter(field_name='precio', lookup_expr='lte')
    editorial = filtros.CharFilter(field_name='editorial__nombre',
                                   lookup_expr='iexact')
    publicado_desde = filtros.DateFilter(field_name='publicado_en',
                                         lookup_expr='gte')

    class Meta:
                model = Libro
        fields = ['estado', 'editorial', 'precio_min', 'precio_max',
                  'publicado_desde']
python
filterset_class = LibroFiltro

Funcionando sobre doscientos libros:

Salida en pantalla
(sin filtro)                  -> 200 filas
?estado=publicado             -> 120
?precio_min=40                ->  24
?precio_min=40&precio_max=42  ->   9
?editorial=Anagrama           ->  41
?publicado_desde=2024-01-01   ->  67

Lo bueno de la clase es que los nombres de la API no son los del modelo: quien la usa escribe precio_min y editorial=Anagrama, sin saber que por dentro hay un gte y una relación. Es lo de la lección 11 aplicado a los parámetros.

Lo que se valida y lo que no#

Aquí está el contraste de la lección. Un valor mal puesto da error:

Salida en pantalla
?estado=inventado        -> 400  {"estado": ["Escoja una opción válida.
                                   inventado no es una de las opciones disponibles."]}
?precio_min=hola         -> 400  {"precio_min": ["Introduzca un número."]}
?publicado_desde=nofecha -> 400  {"publicado_desde": ["Introduzca una fecha válida."]}

Perfecto: mensajes claros, en español, con el nombre del parámetro.

La búsqueda#

python
search_fields = ['titulo', 'isbn', 'autores__nombre']

Busca el texto en todos esos campos y devuelve lo que encaje en cualquiera. Y tiene dos comportamientos que conviene conocer.

Varias palabras se exigen todas, aunque puedan estar en campos distintos:

Salida en pantalla
?search=número      -> 200
?search=7           -> 201
?search=número 7    -> 200      (los que tienen las dos cosas)

Y las comillas buscan la frase entera, cosa que casi nadie sabe:

Salida en pantalla
?search="número 7"  ->  11

Once, que son los títulos donde esas dos palabras van juntas y en ese orden. Es un buscador más capaz de lo que aparenta.

Los prefijos cambian el tipo de búsqueda:

PrefijoQué hace
ningunoContiene, sin distinguir mayúsculas
^tituloEmpieza por
=tituloExacto
@tituloBúsqueda de texto completo (solo PostgreSQL)
$tituloExpresión regular

La ordenación#

python
ordering_fields = ['titulo', 'precio', 'publicado_en']
ordering = ['titulo']
Salida en pantalla
?ordering=precio   -> ['8.10', '8.11', '8.14']
?ordering=-precio  -> ['44.79', '44.63', '44.33']

El guion delante invierte, y se pueden encadenar con comas: ?ordering=-precio,titulo.