Django & DRF 101, partie 2 : DRF & API 101
Tags: django, drf, djangorestframework, api
Published: 2020-02-18 13:34:55

Dans la [1ère partie](https://dev.to/zorky/django-drf-101-partie-1-2p2), nous avons vu une introduction à l'[ORM de Django](https://docs.djangoproject.com/fr/3.0/topics/db/queries/) avec ses capacités.

Cette **2ème partie** sera consacrée à une introduction à [DRF](https://www.django-rest-framework.org/) qui nous permettra de développer une API, API qui pourra être exploitée dans une application de type Angular (ou toute autre [SPA](https://en.wikipedia.org/wiki/Single-page_application)).

Les sources se trouvent sur le repository https://github.com/zorky/library : l'application et le serveur d'applications containérisé (Docker).

## Sommaire
  * [Nota Bene](#nb)
  * [Préparation de l'environnement](#preparation)
  * [Initialisation Django](#init)
  * [Modèles](#modeles)
  * [Admin](#admin)
  * [DRF](#drf)
    * [REST](#rest)
      * [Architecture REST](#archirest)
      * [API](#api)
    * [DRF initialisation](#drf-1)
    * [1ères APIs avec DRF](#drf-2) : 
      * [Serializers et viewsets](#drf-21)
         * [Serializers](#drf-210)
         * [Nested serializers / serializers imbriqués](#drf-211)
         * [Viewsets](#drf-212)
      * [Actions](#drf-22)
    * [Pagination, filtres et recherche avec DRF](#drf-3)
      * [Pagination](#pagination)
      * [Recherche et Filtres](#filtres)
         * [Filtres personnalisés](#filtres-perso)
      * [Tri](#order)

# Note Bene <a name="nb"></a>

Dans la suite de l'article, le serveur d'application Web django pourra fonctionner selon le type d'environnement :

* http://localhost:8000 dans le cas du virtualenv
* http://plateform dans le cas de containers Docker

Utiliser la bonne url selon votre environnement. Dans la suite de l'article si j'utilise http://plateform/, à remplacer par http://localhost:8000 si vous êtes en environnement virtualenv

# Préparation de l'environnement <a name="preparation"></a>

Voir l'[article en question](https://dev.to/zorky/django-drf-101-initialisation-1e39 )

On se basera sur la version 3.0.4 de django et la 3.11.x de DRF et quelques modules utiles.

# Django et applications

## Initialisation <a name="init"></a>

Django peut fonctionner en mode "plateforme" c'est à dire que vous créez un **projet** dans lequel vous allez créer des **applications**, toutes partageront la même base de données (une convention est prise alors pour le nommage des tables de la forme _application_table_), les configurations, "utilisateur" (au sens _user_ de django) sont aussi partagés, ça a le mérite de mutualiser l'ensemble : pour le développement, les déploiements et l'usage d'éléments communs (serveur d'applications, authentification, "utilisateur" django, configs, etc) sans reproduire l'environnement pour chaque application.

La commande *manage.py* gère ce type de création.

Création du projet commun :

$ django-admin startproject backend
$ cd backend

on aura l’arborescence et les fichiers suivants de créer dans _backend_

```
manage.py
backend/
    __init__.py
    settings.py
    urls.py
    wsgi.py
```

Créer notre api que l'on nommera **api_library** :

$ django-admin startapp api_library

on aura ces fichiers de créer dans _api_library_

```
migrations/
    __init__.py
__init__.py
admin.py
apps.py
models.py
tests.py
views.py

```

A la fin des manipulations, on a alors l'arborescence suivante, **backend** qui est le coeur de django et **api_library** :

```
./api_library
./api_library/admin.py
./api_library/apps.py
./api_library/migrations
./api_library/migrations/0001_initial.py
./api_library/models.py
./api_library/serializers.py
./api_library/tests.py
./api_library/urls.py
./api_library/views.py
./api_library/__init__.py
./backend
./backend/settings.py
./backend/urls.py
./backend/webapi.py
./backend/wsgi.py
./backend/__init__.py
./db.sqlite3
./manage.py
./__init__.py

```

On initialise notre plateforme :

- les migrations django (par défaut, django utilise sqlite, cela sera très bien en local), cela va créer toutes les tables dont Django a besoin

```
$ python manage.py migrate
Operations to perform:
  Apply all migrations: admin, auth, contenttypes, sessions
Running migrations:
  Applying contenttypes.0001_initial... OK
  Applying auth.0001_initial... OK
  Applying admin.0001_initial... OK
  Applying admin.0002_logentry_remove_auto_add... OK
  Applying admin.0003_logentry_add_action_flag_choices... OK
  Applying contenttypes.0002_remove_content_type_name... OK
  Applying auth.0002_alter_permission_name_max_length... OK
  Applying auth.0003_alter_user_email_max_length... OK
  Applying auth.0004_alter_user_username_opts... OK
  Applying auth.0005_alter_user_last_login_null... OK
  Applying auth.0006_require_contenttypes_0002... OK
  Applying auth.0007_alter_validators_add_error_messages... OK
  Applying auth.0008_alter_user_username_max_length... OK
  Applying auth.0009_alter_user_last_name_max_length... OK
  Applying auth.0010_alter_group_name_max_length... OK
  Applying auth.0011_update_proxy_permissions... OK
  Applying sessions.0001_initial... OK
```

- on crée un utilisateur pour l'admin : 

```
$ python manage.py createsuperuser 
Username (leave blank to use 'root'): admin
Email address: admin@gmail.com
Password:
Password (again):
The password is too similar to the username.
This password is too short. It must contain at least 8 characters.
This password is too common.
Bypass password validation and create user anyway? [y/N]: y
Superuser created successfully.

```

Lançons le serveur 

```
$ python manage.py runserver
```

par défaut il est accessible sur http://127.0.0.1:8000

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/e6dye2rxwsku2e09gi0y.png)

Arrêtons le et créons au moins un utilisateur pour accéder à l'**admin** Django : l'admin de django permet d'accéder à la gestion de vos données de façon simple, je l'utilise souvent pour, en développement, saisir quelques données avant d'avoir à développer les interfaces côté frontend (en Angular ou autre), pratique


Accédons à l'URL http://127.0.0.1:8000/admin en se connectant avec le compte précédemment créé, une interface apparaît pour une gestion de groupes ou d'utilisateurs, cette interface permet aussi facilement d'utiliser l'admin pour une application pour avoir des formulaires en ligne de nos entités et les garnir sans passer par la ligne de commande comme vue dans la [partie 1](https://dev.to/zorky/django-drf-101-partie-1-2p2) sur l'ORM avec shell_plus.

## Amusons-nous avec quelques modèles <a name="modeles"></a>

L'objectif est de schématiser une librairie : livres et leur auteur, ce qui donne : **Book * --- 1 Author**

```python
from django.db import models

class TimeStampedModel(models.Model):
    """
    Classe utilitaire, champs dt_created / dt_updated
    """
    dt_created = models.DateTimeField(auto_now_add=True)
    dt_updated = models.DateTimeField(auto_now=True)

    class Meta:
        abstract = True

class Author(TimeStampedModel):
    first_name = models.CharField(max_length=50, null=False, blank=False)
    last_name = models.CharField(max_length=50, null=False, blank=False)

    def __str__(self):
        return '{} {}'.format(self.first_name, self.last_name)

class Book(TimeStampedModel):
    name = models.CharField(max_length=100, null=False, blank=False, db_index=True)
    nb_pages = models.IntegerField()

    author = models.ForeignKey(Author, related_name='books', null=False, on_delete=models.CASCADE)

    enabled = models.BooleanField(default=True, help_text='disponible ou non')

    def __str__(self):
        return '{} : {}'.format(self.name, self.author)
```
La propriété _blank_ sert pour les formulaires Django (dans l'admin que nous voyons plus bas par exemple), le champ est donc optionnel.
_str_ permet de surcharger ce que l'on souhaite afficher lorsque l'on fait un 

```python
print(instance_author)
```

il affichera alors la concaténation de _first_name_ et _last_name_ sinon par défaut, django affiche la clé _id_

**Créons les migrations et les tables**


```
$ python manage.py makemigrations api_library
Migrations for 'api_library':
  api_library\migrations\0001_initial.py
    - Create model Author
    - Create model Book
```

```
$ python manage.py migrate
Operations to perform:
  Apply all migrations: admin, api_library, auth, contenttypes, sessions
Running migrations:
  Applying api_library.0001_initial... OK

```

## Admin <a name="admin"></a>

Ajoutons la possibilité d'ajouter des auteurs et livres grâce à des formulaires en utilisant l'admin django.

Il suffit, dans le fichier api_library/admin.py de déclarer les modèles de la sorte

```python
from django.contrib import admin

from .models import Author, Book

admin.site.register(Author)
admin.site.register(Book)
```

En se connectant sur http://127.0.0.1:8000/admin apparaît alors une interface pour gérer des auteurs et livres, le CRUD (ajout, modification, suppression), c'est très pratique au démarrage d'un projet ou de nouveaux modèles pour avoir de la donnée.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/0dgf9exo5tuv61eyn0f9.png)

## DRF & API <a name="drf"></a>

[Django REST Framework](https://www.django-rest-framework.org/) est un module compatible avec Django qui permet de développer des API REST assez rapidement.

### REST <a name="rest"></a>

#### Architecture REST <a name="archirest"></a>

**REST** est un paradigme / une architecture d'échange (service Web) entre un client (navigateur dans la plupart des cas) et un serveur, en se basant sur HTTP, décrit par [Roy Fielding](https://fr.wikipedia.org/wiki/Roy_Fielding) dans [sa thèse (chapitre 5)](http://archive.wikiwix.com/cache/?url=https%3A%2F%2Fwww.ics.uci.edu%2F~fielding%2Fpubs%2Fdissertation%2Ffielding_dissertation.pdf).

REST se base sur l'antique protocole **[HTTP](https://fr.wikipedia.org/wiki/Hypertext_Transfer_Protocol)** :

- [verbes HTTP](https://www.restapitutorial.com/lessons/httpmethods.html) pour la communication client vers le serveur : GET (obtenir un ou des éléments), POST (créer un élément), PUT (modifier un élément), DELETE (supprimer un élément), PATCH (modifier une partie d'un élément) (et OPTIONS que nous verrons plus tard), ces verbes HTTP sont utilisés conjointement à des URLs côté client (par exemple : GET https://domain.ntld/api/todolist)
- [codes HTTP](https://fr.wikipedia.org/wiki/Liste_des_codes_HTTP) pour les acquittements (ACK) entre le serveur et le client utilisateur : 2xx, 4xx, 5xx, ...

REST est **sans état** (ou __stateless__) : pour résumer, tout est porté par l'URL de l'API (demande de la ressource, pagination, identité du demandeur, etc), autrement dit, la notion de session n'existe pas, pas de stockage côté serveur (a contrario des modèles clients / serveurs habituels de type MVC ou autre), si une information est gardée, elle le sera côté client

REST a été créé pour palier à certains défauts des techniques antérieures utilisées et **faciliter l'échange de données** dans un monde où l'échange et l'interopérabilité entre systèmes (entre un client et un serveur) sont devenus proéminents. 

Ces échanges "REST" s'effectuent la plupart du temps au format **[JSON](https://fr.wikipedia.org/wiki/JavaScript_Object_Notation)** qui est une notation Javascript pour décrire des **objets**, objets qui seront utilisés pour l'échange de données en entrée ou en sortie entre 2 parties : le client et le serveur, le lien entre ces parties étant les APIs. Comme nous le verrons ci-après, l'avantage de JSON vs. XML est que c'est un format __natif__ Javascript qui pourra être interprété directement par le moteur JS du navigateur sans avoir besoin de librairie tierce pour le faire.

#### API : à quoi sert une API et quelques rappels historiques <a name="api"></a>

Une architecture APIs est une architecture "backoffice", c'est à dire, toute la mécanique / logique est portée par les APIs, reste à développer des frontaux devant pour adresser ces APIs : applications Web, une application Mobile, une SPA, ou des scripts / routines, etc, une API ne s'occupe pas de la manière dont sera présentée la donnée (la vue), charge aux frontaux de l'implémenter.

Un schéma qui résume très bien (pris sur [https://data-flair.training/blogs/django-rest-framework/](https://data-flair.training/blogs/django-rest-framework/))

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/j2lkdntgwdy472r5rmya.png)

*API publiques*

La majeure partie des plateformes (Dropbox, Amazon, Google, etc) proposent dorénavant des APIs qui permettent d'interagir avec ces dernières et l'interopérabilité entre plateformes (notamment de l'échange de données), ce qui facilite l'intégration de leurs services dans d'autres applications Web, pour y apporter plus de services à l'utilisateur. 

Avant cela, afin **d'intégrer de la donnée** externe à une application, l'un des moyen de faire était soit d'interroger directement la **base de données**, ce qui est bien évidemment impossible avec Amazon par exemple et cela pose également des soucis "d'abstraction" de la donnée (ie : connaître le schéma de la base et de ses tables, quid de l'évolution de ce schéma, etc), soit d'utiliser [**SOAP**](https://fr.wikipedia.org/wiki/SOAP) pour échanger à une époque pas si lointaine, SOAP étant fait à la base surtout pour de l'échange entre serveurs (un serveur d'application JEE ou .NET ou ...) et reste un format inapproprié pour l'échange entre client (entendre navigateur) et serveur : par défaut le format d'échange de SOAP renvoie du XML, format non natif Javascript, contrairement à **JSON**, ce qui oblige l'usage de librairie pour transformer la donnée, et une "complexité" (à cause du WSDL) de SOAP pour les plateformes n'étant pas natives "SOAP / XML" (comme .NET ou J2EE) :  enveloppe / entête nécessaires, souci de cache, WSDL "complexe" pour décrire les entrées / sorties du service. 

*API privées*

Avec l’avènement de l'utilisation d'[**Ajax**](https://fr.wikipedia.org/wiki/Ajax_(informatique), autrement dit les appels XMLHttpRequest) à partir des années 2000 et de cette volonté d'améliorer l'expérience utilisateur sur les interfaces Web (UX), cette **UX** étant portée principalement par Javascript / Ajax : toute la page n'est pas rechargée, seules des parties de la page sont mises à jour, souvent avec des effets (spinner d'attente, accordéon, autocompletion, etc), ce qui donne une impression d'être sur une application bien plus **réactive**. 

Pour cela, on utilisait ou on utilise bien souvent la libraire **[jQuery](https://jquery.com/)** qui s'est imposée comme librairie pour les appels "Ajax" (à l'époque, l'objet utilisé pour faire des appels Ajax n'était pas standardisé sur les navigateurs, IE, Chrome, Firefox, cela permettait d'avoir une couche d'abstraction pour le développeur sans se soucier du navigateur) puis pour la manipulation du [DOM](https://developer.mozilla.org/fr/docs/Web/API/Document_Object_Model) grâce aux [**sélecteurs**](https://api.jquery.com/category/selectors/) (pouvoir rechercher un élément "HTML" dans la page pour l'afficher, le cacher, lui ajouter d'autres éléments, etc) : on développait des [SPA](https://en.wikipedia.org/wiki/Single-page_application) sans vraiment en faire : les pages se rechargeaient malgré tout avec une certaine latence (car utilisation d'un serveur d'application bien souvent MVC), le code devenait très vite du [**code spaghetti**](https://fr.wikipedia.org/wiki/Programmation_spaghetti) à cause bien souvent d'une mauvaise utilisation de Javascript (via des fonctions et non de la POO) et de l'excès de jQuery : des sélecteurs partout, des fonctions javascript éparpillées dans de multiples fichiers JS inclus ou non dans les pages, ou côté MVC, avec des vues partielles et des modèles injectés comme on pouvait. L'**entropie** de ce type d'application était forte : **maintenabilité et évolutivité réduites** qui peuvent devenir complexes, sans compter les régressions et du côté utilisateur, un ressenti et une **UX** perfectible.

De là, sont nés des frameworks / librairies de type "**SPA**" pour améliorer les critiques sur les applications "anciennes générations" à base de jQuery / MVC (appelés aussi MPA : Multi Pages Applications) : Ember, KnockoutJS, VueJS ou ReactJS et bien sûr Angular.

Le principe d'une SPA est de se baser sur des **services REST ou APIs** pour obtenir, et/ou mettre à jour la donnée, sous forme de multiples appels "Ajax" et de l'usage du __binding__ (attachement) automatique entre ces données et la vue pour les afficher. Les derniers __frameworks__ ont également une approche __components__ / **composants**, qui introduit un des principes de [SOLID](https://fr.wikipedia.org/wiki/SOLID_%28informatique%29) en POO : la responsabilité unique ou souhaitée : un composant répond à un besoin et un seul (par exemple, un composant d'entêtes, d'autocompletion, de table pour afficher des éléments, etc) et souvent, réutilisables.

Schéma (tiré de [cet article](https://medium.com/a-lady-dev/single-page-applications-a-powerful-design-pattern-for-modern-web-apps-ec3590bb7e7a)) qui explique la différence entre les MPAs et le SPAs

![Alt MPA vs SPA](https://miro.medium.com/max/1280/0*kDBQ4srXkqALU6Vw.png)

Le propre d'une SPA est de charger l'application en une seule fois ou en différé et que cela soit transparent pour l'utilisateur de l'application : cela amène une **fluidité de navigation** et une **UX** bien supérieure à des applications "classiques".

Pour ces APIs, dans l'écosystème Django, il existe un module qui va nous permettre de développer ces dites APIs : [Django rest framework](https://www.django-rest-framework.org/) et c'est ce nous allons voir dans les prochaines parties.

### DRF initialisation <a name="drf-1"></a>

Pour l'utilisation de DRF, nous aurons besoin d'un ensemble de modules à installer et à utiliser (voir le [requirements.txt](#requirements) de l'initialisation)

Dans backend/settings.py, la partie **INSTALLED_APPS** contient les modules qui seront chargés : rest_framework mais aussi rest_framework_swagger ou drf-yasg qui nous permettra de documenter nos APIs, [django_filters](https://django-filter.readthedocs.io/en/master/) est un module tiers très puissant qui nous servira à faire des filtres de recherche par la suite et bien entendu notre module **api_library** :

```python
INSTALLED_APPS = [
    'rest_framework',
    'rest_framework_swagger',

    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'django_extensions',

    'django_filters',

    'api_library',
]
```

### APIs <a name="drf-2"></a>

Avec DRF, un triptyque permet simplement de créer une API avec : le modèle (de Django), le [**S**erializer](https://www.django-rest-framework.org/api-guide/serializers/), la [**V**iewset](https://www.django-rest-framework.org/api-guide/viewsets/), on se basera sur le plus complet le **ModelViewSet**, l'URL et optionnellement le FilterSet.


#### Serializers et viewsets <a name="drf-21"></a>  

##### Serializers <a name="drf-210"></a>

Un serializer servira à sérialiser ou désérialiser la donnée en JSON (entrées / sorties d'une API) : l'application envoie du JSON et réceptionne du JSON.

Une viewset servira pour traiter les requêtes HTTP pour les APIs : un viewset qui répondra à 80 % de nos besoins est le [ModelViewSet](https://www.django-rest-framework.org/api-guide/viewsets/#modelviewset), il traitera le GET, POST, PUT, DELETE "automatiquement" (le [CRUD](https://fr.wikipedia.org/wiki/CRUD) de nos modèles), nous verrons qu'il existe aussi d'autres classes plus restrictives (pour uniquement du GET ou du POST, etc). 

Dans api_library, créer un fichier **serializers.py** pour définir nos serializers sur nos modèles Author et Book :

```python
# api_library/serializers.py

from rest_framework import serializers

from .models import Author, Book

class AuthorSerializer(serializers.ModelSerializer):
    class Meta:
        model = Author
        fields = '__all__'

class BookSerializer(serializers.ModelSerializer):
    class Meta:
        model = Book
        fields = ['id', 'label']
``` 

la **class Meta** permet de définir le serializer (les méta-données), parmi les plus communs :

- **model** : le modèle utilisé par DRF pour sérializer / désérializer
- **fields** : les champs qui seront inclus dans le JSON, le mot clé **'__all__'** permet d'inclure tous les champs du modèle automatiquement
- **exclude** : les champs à exclure - attention, ne doit pas être utilisé en même temps que **fields**, il prendra donc tous les champs moins les **exclude**
- **read_only_fields** : les champs qui ne sont pas à "écrire" lors de l'envoi / de la réception du JSON

Lorsque l'on interrogera les Books (via http://plateform/library/books/ ou http://plateform/library/books/1), avec la pagination on aura comme résultat du serializer BookSerializer, un Book en JSON aura la forme suivante

```javascript
{
   "id": 1,
   "dt_created": "2020-02-22T15:57:26.934127",
   "dt_updated": "2020-02-22T15:57:26.934149",
   "name": "Django primer",
   "nb_pages": 150,
   "enabled": true,
   "author": 1
}
``` 

##### Nested Serializers <a name="drf-211"></a>

Comme nous pouvons le voir sur la partie précédente et selon le schéma 

**Book * ----- 1 Author**

le sérializer de Book, renverra le lien Author avec sa clé : "author": 1, ou "1" est l'id d'author qui est lié à Book (un livre a un auteur). 

Egalement, lorsque l'on demande un "Author", on pourrait avoir besoin de sa liste de livres directement sous forme d'objects.

Il peut être intéressant de renvoyer tout l'**objet Author**, pour afficher directement sur le livre toute l'information de son auteur (un nester serializer ou serializer embarqué)

On modifie le serializer BookSerializer pour ajouter l'objet Author :

```python
class BookSerializer(serializers.ModelSerializer):
    author_obj = AuthorSerializer(source='author', read_only=True)

    class Meta:
        model = Book
        fields = '__all__'
```

- **source** est à la propriété de jointure utilisé dans le modèle Book (**author** = models.ForeignKey(Author, related_name='books', null=False, on_delete=models.CASCADE)), elle est obligatoire dans notre cas car la propriété renvoyée dans le serializer se nomme **author_obj** sinon elle devient optionnelle (si on avait écrit par exemple author = AuthorSerializer(read_only=True))
- **read_only** car cela ne servira qu'à la consultation, DRF ne peut écrire directement des nested serializer en base, une autre technique sera alors utilisée

Lors du requêtage (http://plateform/library/books/ ou http://plateform/library/books/1), on aura alors le résultat suivant, avec intégrer l'auteur (propriété author_obj)

```javascript
{
    "id": 1,
    "author_obj": {
        "id": 1,
        "dt_created": "2020-02-22T15:56:55.547391",
        "dt_updated": "2020-02-22T15:56:55.547413",
        "first_name": "Olivier",
        "last_name": "DUVAL"
    },
    "dt_created": "2020-02-22T15:57:26.934127",
    "dt_updated": "2020-02-22T15:57:26.934149",
    "name": "Django primer",
    "nb_pages": 150,
    "enabled": true,
    "author": 1
}
```
A l'inverse, on pourrait avoir la **liste des livres** d'un auteur directement  dans le serializer **AuthorSerializer**, on aura alors le code suivant :

```python
class BookSimpleSerializer(serializers.ModelSerializer):
    class Meta:
        model = Book
        fields = '__all__'

class AuthorSerializer(serializers.ModelSerializer):
    books = BookSimpleSerializer(many=True, read_only=True)

    class Meta:
        model = Author
        fields = '__all__'

class BookSerializer(serializers.ModelSerializer):
    author_obj = AuthorSerializer(source='author', read_only=True)

    class Meta:
        model = Book
        fields = '__all__'

```

Dans ce cas, la création d'un autre serializer (BookSimpleSerializer) est obligatoire pour pouvoir l'utiliser dans AuthorSerializer car BookSerializer est **en-dessous** de AuthorSerializer, ceci pour éviter de la récursion à l'infinie (author -> books -> author -> books -> etc).

- **many=True** permet de répondre au rendu de plusieurs livres pour un auteur (pour rappel, la déclaration dans Book est author = models.ForeignKey(Author, related_name='**books**', null=False, on_delete=models.CASCADE)), **books** de books = BookSimpleSerializer(many=True, read_only=True) représente la relation inverse déclarée dans **related_name**.

Lorsque l'on interroge http://plateform/library/authors/1/ , on aura le JSON suivant, l'auteur avec la liste de ses livres :

```javascript
{
    "id": 1,
    "books": [
        {
            "id": 1,
            "dt_created": "2020-02-22T15:57:26.934127",
            "dt_updated": "2020-02-22T15:57:26.934149",
            "name": "Django primer",
            "nb_pages": 150,
            "enabled": true,
            "author": 1
        },
        {
            "id": 3,
            "dt_created": "2020-02-22T15:57:51.851990",
            "dt_updated": "2020-02-22T15:57:51.852012",
            "name": "Angular primer",
            "nb_pages": 325,
            "enabled": true,
            "author": 1
        }
    ],
    "dt_created": "2020-02-22T15:56:55.547391",
    "dt_updated": "2020-02-22T15:56:55.547413",
    "first_name": "Olivier",
    "last_name": "DUVAL"
}
```

**ATTENTION** : les "nested serializers" feront un appel / des appels vers l' / les entité(s) pour obtenir les données, on pensera donc à utiliser le **select_related** (pour les cardinalités __1__) ou le **prefetch_related** (pour les __many__) dans la queryset de la viewset que nous voyons ci-après. Les "nested" engendrent aussi plus de données qui transitent via l'API.

##### Viewsets <a name="drf-212"></a>

Un **viewset** est une vue qui va servir pour l'API, le plus simple étant le [ModelViewSet](https://www.django-rest-framework.org/api-guide/viewsets/#modelviewset) qui fera le CRUD : GET, POST, PUT, DELETE, en se servant d'un serializer (pour la transformation JSON des entrées et des sorties), d'une **queryset** (la requête par défaut),. Dans dans api_library, le fichier **views.py** contiendra :

```python
# api_library/views.py

from rest_framework import viewsets
from rest_framework.permissions import AllowAny

from .models import Author, Book
from .serializers import AuthorSerializer, BookSerializer

class AuthorViewSet(viewsets.ModelViewSet):
    queryset = Author.objects.prefetch_related('books').all()
    serializer_class = AuthorSerializer
    permission_classes = [AllowAny]

    
class BookViewSet(viewsets.ModelViewSet):
    queryset = Book.objects.select_related('author').all()
    serializer_class = BookSerializer
    permission_classes = [AllowAny]
``` 

- **queryset** : est la queryset par défaut utilisée pour retrouver les enregistrements, remarquez qu'on utilise les **select_related** ou **prefetch_related** pour optimiser le requêtage comme vu sur https://dev.to/zorky/django-drf-101-partie-1-2p2#optimisations
- **serializer_class** : le serializer par défaut à utiliser pour la sérialisation / dé-sérialisation des données, on peut également en préciser dynamiquement, patience, nous verrons cela dans un futur article
- **permission_classes** : les droits d'accès à l'API, ici **AllowAny** pour dire qu'elle est publique, on pourrait également configurer les permissions par défaut de toutes les viewset via le backend/settings, section REST_FRAMEWORK, cela s'appliquera à toutes les viewsets sans avoir besoin de **permission_classes** à l'intérieur. Si vous souhaitez qu'au moins les APIs aient besoin d'une authentification, il suffira d'utiliser **IsAuthenticated** à la place. Les permissions sont également personnalisables, c'est à dire que l'on peut imaginer des rôles applicatifs, rôles qui seront ensuite utilisés pour accéder à telle ou telle API (APIs de backoffice, de frontoffice, etc)

```
REST_FRAMEWORK = {
 'DEFAULT_PERMISSION_CLASSES': [
        'rest_framework.permissions.AllowAny',
    ]
...
}
```

Lorsqu'on regarde les sources de ModelViewSet, la class se compose en une multitude de "mixin" qui représente le CRUD et une pour la tuyauterie. On peut utiliser d'autre viewset génériques spécialisés autre que le ModelViewSet pour répondre à des besoins spécifiques d'API : CreateAPIView (POST uniquement), UpdateAPIView (PUT uniquement), ListAPIView (GET liste), RetrieveAPIView (GET d'un objet), DestroyAPIView (DELETE uniquement), ListCreateAPIView, RetrieveUpdateAPIView, RetrieveDestroyAPIView, RetrieveUpdateDestroyAPIView, voir l'[ensemble des views disponibles sur le site de DRF](https://www.django-rest-framework.org/api-guide/generic-views/#createapiview).

```python
class ModelViewSet(mixins.CreateModelMixin,
                   mixins.RetrieveModelMixin,
                   mixins.UpdateModelMixin,
                   mixins.DestroyModelMixin,
                   mixins.ListModelMixin,
                   GenericViewSet):
    """
    A viewset that provides default `create()`, `retrieve()`, `update()`,
    `partial_update()`, `destroy()` and `list()` actions.
    """
    pass
```

Dans **api_library**, créer un fichier **urls.py**, Les viewset sont attachées à une URL, précisées dans urls.py qui contiendra le code suivant

```python
# coding=utf-8
# api_library/urls.py

from django.conf.urls import url, include
from rest_framework import routers
from . import views

router = routers.DefaultRouter()
router.register(r'authors', views.AuthorViewSet, 'authors')
router.register(r'book', views.BookViewSet, 'books')

urlpatterns = [
    url(r'', include(router.urls)),    
]
``` 

Dans **backend**, le fichier urls.py utilisera les urls d'api_library précédemment ajoutées, et nous allons activer Swagger pour accéder et requêter notre API directement. Egalement, en mode DEBUG (en dév), on active la debug toolbar ([debug_toolbar](https://django-debug-toolbar.readthedocs.io/en/latest/index.html)) pour obtenir de l'information sur les API (notamment le SQL), l'api de notre librairie aura la forme : http://domain.ntld/library/ :

```python
# backend/urls.py
from django.contrib import admin
from django.conf import settings
from django.conf.urls import url, include
from django.conf.urls.static import static
from rest_framework_jwt.views import obtain_jwt_token, refresh_jwt_token, verify_jwt_token
from rest_framework_swagger.views import get_swagger_view

schema_view = get_swagger_view(title='Api plateforme')

urlpatterns = [
    url(r'^$', schema_view),
    url(r'library/', include('api_library.urls')),
    url(r'^admin/', admin.site.urls)
]

if settings.DEBUG:
    import debug_toolbar

    urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
    urlpatterns = [
        url(r'^__debug__/', include(debug_toolbar.urls))
    ] + urlpatterns

```

Pour résumer :

- des serializers dans api_library/serializers.py
- des views dans api_library/views.py
- des urls avec les viewsets dans api_library/urls.py
- déclaration de swagger, de la debug toolbar et inclusion des urls api_library dans backend/urls.py


Aller sur http://localhost:8000 ou http://plateform , Swagger expose les APIs authors et books que l'on peut manipuler directement, basées sur les verbes HTTP d'une architecture REST

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/m4gczkxzbvk85qmpjcbp.png)

Utilisation de /library/authors/ en GET, avec le résultat JSON 

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/w84ds4c5rykej3ekqfn5.png)

ou de /library/authors/1/ (donne moi l'auteur d'identifiant 1)

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/fx3qau1k1qbi3j4aau7w.png)

La partie **Request URL** (par ex. http://plateform/library/authors/) peut être ouverte dans un onglet à part du navigateur, on a accès alors à la **debug toolbar** et notamment la partie SQL pour voir les requêtes qui sont exécutées ou d'autres informations utiles

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/99sqvvgm0eiqknsqnui3.png)

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/6mx5smxsmkod1qv54s74.png)

Si l'on veut ouvrir la request URL avec un résultat uniquement en JSON (sans Swagger), il suffit de postfixer l'url avec ?format=json (http://plateform/library/authors/?format=json)

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/49n7c5bxivyt0w42hlp5.png)

#### Actions <a name="drf-22"></a>

Les viewsets **ModelViewSet** permettent le CRUD sur un modèle, on peut les spécialiser pour avoir des "fonctions" au sein du ModelViewSet, par exemple une url de type http://plateform/library/books/disponibles/ , /disponibles/ renverra les livres uniquement disponibles, les actions permettent de répondre spécifiquement à du fonctionnel dans le détail.

Le paramètre **method** permet de choisir le verbe HTTP sur lequel l'action répondra (get, post, put, delete), optionnellement, on peut également utiliser le **permission_classes** avec une liste de permissions, cela surchargera les permissions de la viewset.

Les actions DRF sont créés grâce au décorateur **@action**, il en existe 2 types 

- une qui va chercher l'objet d'id N, l'attribut **detail** est à True. Action qui nécessite de passer l'id de l'objet sur lequel on va créer l'action Cela permet d'utiliser la fonction utilitaire **get_object** qui se chargera de nous trouver l'objet à partir du paramètre **pk** qui représente l'id envoyé. L'action doit renvoyer un **Response**, dans l'exemple si après : trouve l'objet Entite d'id **pk** et le renvoie

```python
    @action(detail=True, methods=['get'])
    def ma_fonction_specialisee(self, request, pk=None):
          mon_entite: Entite = self.get_object()
          serializer = EntiteSerializer(mon_entite)
          return Response(data={serializer.data}, status=status.HTTP_200_OK)
```

- une "générique" où le **detail** est à False

```python
    @action(detail=False)
    def ma_fonction_specialisee(self, request):
        # traitement pour renvoyer de la donnée
        return Response(data={}, status=status.HTTP_200_OK)
```

Dans les 2, on pourra utiliser **request** pour retrouver des paramètres passés à la queystring (&param1=val1&param2=val2&...&parmN=valN), de la façon suivante, exemple pour retrouver param1, s'il n'existe pas, sa valeur est fixée à None

```python
request.query_params.get('param1', None)
```

On pourra également tester la **méthode utilisée** via **request.method**, à savoir si c'est 'GET', 'POST', 'PUT', 'DELETE'

### Pagination, filtres et recherche <a name="drf-3"></a>

Lorsque l'on développe des API ou plus généralement on interroge des listes d'objets, il est important de penser à la **pagination** (le fait de ne ramener qu'un ensemble de lignes (10 par exemple) d'un ensemble global de données), et aux **filtres** de recherche qu'on peut donner à l'utilisateur d'une application, cela contribue à l'UX.

#### Pagination <a name="pagination"></a>

La pagination doit être réalisée côté **base de données**, plus performante bien évidemment qu'une pagination effectuée sur un ensemble d'objets en "mémoire", voir plus loin pour les requêtes.

Pour ça, DRF propose 2 options :

- soit indiquer la pagination en **settings**, qui sera commune à toutes les APIs,
- soit dans le **viewset**, au cas par cas

DRF propose plusieurs type de "fournisseurs" :  **PageNumberPagination** (l'url sera de la forme &page=N&page_size=LIMIT), **LimitOffsetPagination** (l'url sera de la forme &limit=10&offset=5) et CursorPagination, je préfère le **LimitOffsetPagination** car dans le retour JSON, nous avons toutes les indications liées à la pagination, on se déplace dans les "pages" grâce au **limit** et optionnellement à l'**offset** (par défaut 0, indique le démarrage à l'enregistrement N° offset)

Par exemple, la requête http://plateform/library/authors/?limit=1&format=json retournera : on a le nombre totale en base (count: 3), la prochaine "page" avec l'offset (http://plateform/library/authors/?format=json&limit=1&offset=1) et enfin les données dans results

```json
{
  count: 3,
  next: "http://plateform/library/authors/?format=json&limit=1&offset=1",
  previous: null,
  results: [
  {
    id: 1,
    dt_created: "2020-02-18T11:57:51.608823",
    dt_updated: "2020-02-18T11:57:51.608823",
    first_name: "Olivier",
    last_name: "DUVAL"
  }
]
}
```

Dans le backend/settings.py, sur la partie **REST_FRAMEWORK**, ajouter  :

```yaml
REST_FRAMEWORK = {
'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.LimitOffsetPagination',
'PAGE_SIZE': 10,
...
}
```

ou dans le viewset désiré :

```python
from rest_framework import viewsets
from rest_framework.pagination import LimitOffsetPagination
from rest_framework.permissions import AllowAny

from .models import Author, Book
from .serializers import AuthorSerializer, BookSerializer

class AuthorViewSet(viewsets.ModelViewSet):
    queryset = Author.objects.prefetch_related('books').all()
    serializer_class = AuthorSerializer
    permission_classes = [AllowAny]

    pagination_class = LimitOffsetPagination
    page_size = 10
```

Dès lors, on peut utiliser la pagination en ajoutant les paramètres **limit** (le nombre de lignes ramené) et **offset** (à partir de quel ligne) à l'url : http://plateform/library/authors/?limit=5&offset=5 : donne moi la liste des auteurs à partir du 5ème et les 5 premiers de la liste, simple et efficace.

Pour les requêtes SQL générées, ouvrons sur http://plateform/library/authors/?limit=2&offset=1 la partie SQL de la debug toolbar, 2 requêtes pour les auteurs sont générées : 1 pour le comptage en base (count) et une autre pour la liste (selon le SGBD les instructions peuvent varier, ici, avec SQLite, nous avons un OFFSET et un LIMIT)

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/1g935ug7n9so0tuedlg8.png)


![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/y8ovk655elkzv5tkl4jw.png)


#### Filtres <a name="filtres"></a>

##### Recherche

Lorsque l'on a des listes, on a souvent besoin de rechercher des éléments. DRF a prévu le cas grâce au **SearchFilter**, ajoutons filter_backends =  (SearchFilter,) au viewset et les champs de recherche souhaités (ici "first_name" OU "last_name")

```python
# api_library/views.py

import django_filters

from rest_framework import viewsets
from rest_framework.filters import SearchFilter
from rest_framework.pagination import LimitOffsetPagination, PageNumberPagination
from rest_framework.permissions import AllowAny, IsAuthenticated

from .models import Author, Book
from .serializers import AuthorSerializer, BookSerializer

class AuthorViewSet(viewsets.ModelViewSet):
    queryset = Author.objects.prefetch_related('books').all()
    serializer_class = AuthorSerializer
    permission_classes = [AllowAny]
    filter_backends =  (SearchFilter,)

    search_fields = ['first_name', 'last_name']
```

La requête http://plateform/library/authors/?search=aur%C3%A9%20duv cherchera sur les 2 champs précisés dans **search_fields** en effectuant un LIKE et un OR en permutant toutes les possibilités : 

```SQL
SELECT "api_library_author"."id",
       "api_library_author"."dt_created",
       "api_library_author"."dt_updated",
       "api_library_author"."first_name",
       "api_library_author"."last_name"
  FROM "api_library_author"
 WHERE (("api_library_author"."first_name" LIKE '''%auré%''' ESCAPE '\' OR "api_library_author"."last_name" LIKE '''%auré%''' ESCAPE '\') AND ("api_library_author"."first_name" LIKE '''%duv%''' ESCAPE '\' OR "api_library_author"."last_name" LIKE '''%duv%''' ESCAPE '\'))
 LIMIT 10
```

Attention, c'est **accent sensitive** mais case insensitive (duv ou DUV renverront le même résultat).

##### Filtres de recherche sur des champs

Autre facilité de DRF, la possibilité d'utiliser des filtres sur des champs, par exemple, pouvoir rechercher les livres ayant 150 pages, ou le nom d'un auteur égale à "DUVAL", les filtres sont cumulables, et bien entendu, si la pagination est utilisée, elle s'intégrera à la requête.

Ajoutons à **filter_backends** la classe **DjangoFilterBackend** qui active l'usage des filtres, associé à **filterset_fields** qui précise les champs que l'on peut filter. Les filtres de base sont stricts, un **=** sera utilisé.

```python
class BookViewSet(viewsets.ModelViewSet):
    queryset = Book.objects.select_related('author').all()
    serializer_class = BookSerializer
    permission_classes = [AllowAny]
    filter_backends = (DjangoFilterBackend, SearchFilter,)
    filterset_fields = ('name', 'nb_pages', 'author', 'author__last_name', 'author__first_name',)
```

Dès lors que les filtres sont mis, on peut les voir sur swagger pour les utiliser

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/6f3wmul85qy1uh2z8xid.png)

Dès lors on peut filtrer exactement sur les champs souhaités (précisés avec **filterset_fields**), lors de l'utilisation de ces champs dans l'url, un **ET** est appliqué entre les conditions.

Par exemple, les livres de 150 pages pour l'auteur d'identifiant 1 : http://plateform/library/books/?nb_pages=150&author=1 ou les livres de 150 pages de l'auteur de nom "DUVAL" : http://plateform/library/books/?author__last_name=DUVAL&nb_pages=150

La dernière requête, en utilisant la debug toolbar donnera 

```sql
SELECT "api_library_book"."id",
       "api_library_book"."dt_created",
       "api_library_book"."dt_updated",
       "api_library_book"."name",
       "api_library_book"."nb_pages",
       "api_library_book"."author_id",
       "api_library_author"."id",
       "api_library_author"."dt_created",
       "api_library_author"."dt_updated",
       "api_library_author"."first_name",
       "api_library_author"."last_name"
  FROM "api_library_book"
 INNER JOIN "api_library_author"
    ON ("api_library_book"."author_id" = "api_library_author"."id")
 WHERE ("api_library_book"."nb_pages" = '150' AND "api_library_author"."last_name" = '''DUVAL''')
```

Comme on peut le remarquer, dans notre exemple, les filtres s'effectuent à partir du modèle **Book**, on peut alors remonter vers auteur en indiquant les champs de ce modèle grâce à **`__`** (grâce à la clé étrangère author définie dans le modèle par `author = models.ForeignKey(Author, related_name='books', null=False, on_delete=models.CASCADE))` pour filtrer sur le nom ou prénom : `author__last_name` ou `author__first_name`. Attention, cela génère une requête pour aller chercher l'entité `author` pour ses attributs `last_name` / `first_name`, un `select_related('author')` est conseillé sur la queryset de la viewset.

##### Filtres personnalisés <a name="filtres-perso"></a>

Les filtres proposés permettent déjà pas mal de recherche et filtrage. DRF et [django-filters](https://django-filter.readthedocs.io/en/master/index.html) permettent aussi d'en **créer**, la classe [FilterSet](https://django-filter.readthedocs.io/en/master/ref/filterset.html) permet cela en conjonction avec **filterset_class** que nous allons utiliser dans les viewsets.


Pour Book, on pourrait rechercher les livres entre 2 nombre de pages ou sur une liste d'auteurs (identifiants). 

Création d'un filtre personnalisé pour Book et Author, dans un nouveau fichier api_libray/filters.py

```python
from django_filters import FilterSet, filters, BaseInFilter, NumberFilter

from .models import Book, Author

class IntegerInFilter(BaseInFilter, NumberFilter):
    pass

class AuthorFilter(FilterSet):
    last_name = filters.CharFilter(lookup_expr='iexact')
    first_name = filters.CharFilter(lookup_expr='iexact')
    
    class Meta:
        model = Author
        fields = ['last_name', 'first_name', ]

class BookFilter(FilterSet):
    pages =  filters.RangeFilter(field_name='nb_pages')
    author_in = IntegerInFilter(field_name='author', lookup_expr='in')
    author_isnull = django_filters.BooleanFilter(field_name='author', lookup_expr='isnull')

    class Meta:
        model = Book
        fields = ['name', 'nb_pages', 'author', 'author__last_name', 'author__first_name', ]
```

La **partie gauche** représente la clé de l'url (pages ou author_in ou author_isnull, l'url sera pages=valeur ou author_in=1,2 ou author_isnull=true).

La **partie droite** soit l'expression de la queryset (lookup_expr="mot_cle", sera le postfix à la requête queryset.

__iexact__ donnera champ\__iexact="valeur"), soit la classe filtre spécialisée (RangeFilter dans notre exemple), si field_name n'est pas donné, il recherchera le champ de même libellé que la partie gauche (last_name par exemple).

__author_isnull__ donnera champ\__isnull=true

La **class Meta** permet de préciser le modèle sur lequel le filtre s'applique ainsi que les champs, optionnellement, les **lookup_expr** pourraient être définies dans cette partie pour chaque champ

```python
class AuthorFilter(FilterSet):
    last_name = filters.CharFilter()
    first_name = filters.CharFilter()

    class Meta:
        model = Author
        fields = {'last_name': ['iexact'],
                  'first_name': ['iexact']}
```

Pour Book, on va pouvoir rechercher les libres qui ont entre N et M pages grâce au filtre RangeFilter, l'url d'interrogation sera par exemple http://plateform/library/books/?pages_min=100&pages_max=400, on peut omettre _min ou _max, la requête sera alors >= ou <= pour comparer la valeur

Requête générée

```sql
SELECT "api_library_book"."id",
       "api_library_book"."dt_created",
       "api_library_book"."dt_updated",
       "api_library_book"."name",
       "api_library_book"."nb_pages",
       "api_library_book"."author_id",
       "api_library_author"."id",
       "api_library_author"."dt_created",
       "api_library_author"."dt_updated",
       "api_library_author"."first_name",
       "api_library_author"."last_name"
  FROM "api_library_book"
 INNER JOIN "api_library_author"
    ON ("api_library_book"."author_id" = "api_library_author"."id")
 WHERE "api_library_book"."nb_pages" BETWEEN '100' AND '400'
 LIMIT 10
```

Pouvoir retrouver les libres d'une liste d'auteurs, author_in = IntegerInFilter(field_name='author', lookup_expr='in'), IntergerInFilter permet de surchargées BaseInFilter et NumberFilter pour préciser que ce sont des entiers que l'on attend, la requête sera alors par exemple http://plateform/library/books/?author_in=1,2  

Il est à remarquer que l'on retrouve les filtres applicables sur l'interface de Swagger, en ouvrant directement les urls, par exemple http://plateform/library/books/

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/wl6ql2lhomauffmcniri.png)

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/vvvxhxzh74hyv2ekox0b.png)

On pourrait aussi définir des fonctions si les règles métiers sont un peu complexes grâce au mot clé **method**, on accède à la queryset courante ainsi que la valeur à prendre en compte, ajoutons la possibilité pour Author de faire ?by_search=oli%20duv

```python
from django.db.models import Q
from django_filters import FilterSet, filters, BaseInFilter, NumberFilter

from .models import Book, Author

class AuthorFilter(FilterSet):
    last_name = filters.CharFilter(lookup_expr='iexact')
    first_name = filters.CharFilter(lookup_expr='iexact')
    by_search = filters.CharFilter(method="get_by_search")

    def get_by_search(self, queryset, name, value):
        """
        Search mot à mot
        """
        words = value.split(' ')

        filters = Q()
        for word in words:
            filters |= Q(first_name__icontains=word) | Q(last_name__icontains=word)

        qs = queryset.filter(filters)
        return qs

    class Meta:
        model = Author
        fields = ['last_name', 'first_name',]
```


#### Tri <a name="order"></a>

DRF permet également d'effectuer des tris dynamiquement en les précisant dans l'url. Pour cela, nous utilisons la classe **OrderingFilter** que l'on va ajouter à **filter_backends** et associé à **ordering_fields** qui précise quels champs sont triables, dans l'url il suffira d'utiliser **ordering** en précisant le champ et l'ordre (- pour DESC, rien ou + pour ASC), par exemples : 

http://plateform/library/books/?ordering=name donnera un tri sur le champ **name** en tri ascendant (A -> Z)

```sql
SELECT "api_library_book"."id",
       "api_library_book"."dt_created",
       "api_library_book"."dt_updated",
       "api_library_book"."name",
       "api_library_book"."nb_pages",
       "api_library_book"."author_id",
       "api_library_author"."id",
       "api_library_author"."dt_created",
       "api_library_author"."dt_updated",
       "api_library_author"."first_name",
       "api_library_author"."last_name"
  FROM "api_library_book"
 INNER JOIN "api_library_author"
    ON ("api_library_book"."author_id" = "api_library_author"."id")
 ORDER BY "api_library_book"."name" ASC
 LIMIT 10
```

http://plateform/library/books/?ordering=-name donnera un tri sur le champ **name** en tri descendant (Z -> A)

```sql
SELECT "api_library_book"."id",
       "api_library_book"."dt_created",
       "api_library_book"."dt_updated",
       "api_library_book"."name",
       "api_library_book"."nb_pages",
       "api_library_book"."author_id",
       "api_library_author"."id",
       "api_library_author"."dt_created",
       "api_library_author"."dt_updated",
       "api_library_author"."first_name",
       "api_library_author"."last_name"
  FROM "api_library_book"
 INNER JOIN "api_library_author"
    ON ("api_library_book"."author_id" = "api_library_author"."id")
 ORDER BY "api_library_book"."name" DESC
 LIMIT 10
```

Nos viewsets deviennent 

```python
# api_library/views.py

from django_filters.rest_framework import DjangoFilterBackend

from rest_framework import viewsets
from rest_framework.filters import SearchFilter, OrderingFilter
from rest_framework.pagination import LimitOffsetPagination
from rest_framework.permissions import AllowAny

from .models import Author, Book
from .serializers import AuthorSerializer, BookSerializer

class AuthorViewSet(viewsets.ModelViewSet):
    queryset = Author.objects.prefetch_related('books').all()
    serializer_class = AuthorSerializer
    permission_classes = [AllowAny]
    filter_backends =  (DjangoFilterBackend, SearchFilter, OrderingFilter, )

    search_fields = ['first_name', 'last_name']
    filterset_fields = ('last_name',)
    ordering_fields = ('id', 'last_name',)

class BookViewSet(viewsets.ModelViewSet):
    queryset = Book.objects.select_related('author').all()
    serializer_class = BookSerializer
    permission_classes = [AllowAny]
    filter_backends = (DjangoFilterBackend, SearchFilter, OrderingFilter,)

    search_fields = ['name', 'author__first_name', 'author__last_name']
    filterset_fields = ('name', 'nb_pages', 'author', 'author__last_name', 'author__first_name',)
    ordering_fields = ('id', 'name',)
```

On va pouvoir trier Author sur l'id et le last_name, et Book sur son id et le name

Si l'on souhaite préciser un tri par défaut, il suffira d'utiliser **ordering** qui peut être une liste de champs, à ajouter à la viewset, par exemple : ordering = ['first_name', 'last_name'] sur AuthorViewSet, triera par défaut les auteurs sur le prénom et nom et non sur leur id qui est la clé par défaut du tri pour les modèles.

On aura alors la requête suivante de générée (remarquer le ORDER BY) :

```sql
SELECT "api_library_author"."id",
       "api_library_author"."dt_created",
       "api_library_author"."dt_updated",
       "api_library_author"."first_name",
       "api_library_author"."last_name"
  FROM "api_library_author"
 ORDER BY "api_library_author"."first_name" ASC, "api_library_author"."last_name" ASC
 LIMIT 10
```

on aurait pu faire un tri sur first_name ASC et last_name DESC grâce au **-** : ordering = ['first_name', '-last_name'], cela donnera

```sql
	
SELECT "api_library_author"."id",
       "api_library_author"."dt_created",
       "api_library_author"."dt_updated",
       "api_library_author"."first_name",
       "api_library_author"."last_name"
  FROM "api_library_author"
 ORDER BY "api_library_author"."first_name" ASC, "api_library_author"."last_name" DESC
 LIMIT 10
```

La **liste des APIs disponibles** pour Book et Author sera disponible sur http://plateform


Dans une prochaine partie, nous verrons à débuter une application en Angular qui utilise ces APIs.

Restez connectés !