\chapter{Filtres}



A ce stade, nous pouvons juste récupérer des informations présentes dans
notre base de données, mais à part les parcourir, il est difficile d'en
faire quelque chose.

Il est possible de jouer avec les URLs en définissant une nouvelle route
ou avec les paramètres de l'URL, ce qui demanderait alors de programmer
chaque cas possible - sans que le consommateur ne puisse les déduire
lui-même. Une solution élégante consiste à autoriser le consommateur à
filtrer les données, directement au niveau de l'API. Ceci peut être
fait. Il existe deux manières de restreindre l'ensemble des résultats
retournés:

\begin{enumerate}
\item
  Soit au travers d'une recherche, qui permet d'effectuer une recherche
  textuelle, globale et par ensemble à un ensemble de champs,
\item
  Soit au travers d'un filtre, ce qui permet de spécifier une valeur
  précise à rechercher.
\end{enumerate}

Dans notre exemple, la première possibilité sera utile pour rechercher
une personne répondant à un ensemble de critères. Typiquement,
\texttt{/api/v1/people/?search=raymond\ bond} ne nous donnera aucun
résultat, alors que \texttt{/api/v1/people/?search=james\ bond} nous
donnera le célèbre agent secret (qui a bien entendu un contrat chez
nous\ldots\hspace{0pt}).

Le second cas permettra par contre de préciser que nous souhaitons
disposer de toutes les personnes dont le contrat est ultérieur à une
date particulière.

Utiliser ces deux mécanismes permet, pour Django-Rest-Framework, de
proposer immédiatement les champs, et donc d'informer le consommateur
des possibilités:

\includegraphics{images/rest/drf-filters-and-searches.png}

\section{Recherches}

La fonction de recherche est déjà implémentée au niveau de
Django-Rest-Framework, et aucune dépendance supplémentaire n'est
nécessaire. Au niveau du \texttt{viewset}, il suffit d'ajouter deux
informations:

\begin{minted}{python}
...
from rest_framework import filters, viewsets
...

class PeopleViewSet(viewsets.ModelViewSet):
    ...
    filter_backends = [filters.SearchFilter]
    search_fields = ["last_name", "first_name"]
    ...
\end{minted}

\subsection{Filtres}

Nous commençons par installer le paquet django-filter \url{https://www.django-rest-framework.org/api-guide/filtering/\#djangofilterbackend})
et nous l'ajoutons parmi les applications installées:

\begin{verbatim}
    pip install django-filter
    Collecting django-filter
    Downloading django_filter-2.4.0-py3-none-any.whl (73 kB)
    | 73 kB 2.6 MB/s
    Requirement already satisfied: Django>=2.2 in c:\users\fred\sources\.venvs\r
    ps\lib\site-packages (from django-filter) (3.1.7)
    Requirement already satisfied: asgiref<4,>=3.2.10 in c:\users\fred\sources
    \.venvs\rps\lib\site-packages (from Django>=2.2->django-filter) (3.3.1)
    Requirement already satisfied: sqlparse>=0.2.2 in c:\users\fred\sources\.
    venvs\rps\lib\site-packages (from Django>=2.2->django-filter) (0.4.1)
    Requirement already satisfied: pytz in c:\users\fred\sources\.venvs\rps\lib
    \site-packages (from Django>=2.2->django-filter) (2021.1)
    Installing collected packages: django-filter
    Successfully installed django-filter-2.4.0
\end{verbatim}

Une fois l'installation réalisée, il reste deux choses à faire:

\begin{enumerate}
    \def\labelenumi{\arabic{enumi}.}
    \item
      Ajouter \texttt{django\_filters} parmi les applications installées:
    \item
      Configurer la clé \texttt{DEFAULT\_FILTER\_BACKENDS} à la valeur
      \texttt{{[}\textquotesingle{}django\_filters.rest\_framework.DjangoFilterBackend\textquotesingle{}{]}}.
    \end{enumerate}
    
Vous avez suivi les étapes ci-dessus, il suffit d'adapter le fichier
\texttt{settings.py} de la manière suivante:
    
\begin{minted}{python}
REST_FRAMEWORK = {
    'DEFAULT_PAGINATION_CLASS':
    'rest_framework.pagination.PageNumberPagination',
    'PAGE_SIZE': 10,
    'DEFAULT_FILTER_BACKENDS':
    ['django_filters.rest_framework.DjangoFilterBackend']
}
\end{minted}


Au niveau du viewset, il convient d'ajouter ceci:

\begin{minted}{python}
...
from django_filters.rest_framework import DjangoFilterBackend
from rest_framework import viewsets
...

class PeopleViewSet(viewsets.ModelViewSet):
    ...
    filter_backends = [DjangoFilterBackend]
    filterset_fields = ('last_name',)
    ...
\end{minted}


A ce stade, nous avons deux problèmes:

\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
  Le champ que nous avons défini au niveau de la propriété
  \texttt{filterset\_fields} exige une correspondance exacte. Ainsi,
  \texttt{/api/v1/people/?last\_name=Bon} ne retourne rien, alors que
  \texttt{/api/v1/people/?last\_name=Bond} nous donnera notre agent
  secret préféré.
\item
  Il n'est pas possible d'aller appliquer un critère de sélection sur la
  propriété d'une relation. Notre exemple proposant rechercher
  uniquement les relations dans le futur (ou dans le passé) tombe à
  l'eau.
\end{enumerate}

Pour ces deux points, nous allons définir un nouveau filtre, en
surchargeant une nouvelle classe dont la classe mère serait de type
\texttt{django\_filters.FilterSet}.

TO BE CONTINUED.

A noter qu'il existe un paquet
{[}Django-Rest-Framework-filters{]}(\url{https://github.com/philipn/django-rest-framework-filters}),
mais il est déprécié depuis Django 3.0, puisqu'il se base sur
\texttt{django.utils.six} qui n'existe à présent plus. Il faut donc le
faire à la main (ou patcher le paquet\ldots\hspace{0pt}).
