2022-04-27 19:33:47 +02:00
\chapter { Application Programming Interface}
\url { https://news.ycombinator.com/item?id=30221016\& utm_ term=comment} vs
2022-06-03 21:10:05 +02:00
Django Rest Framework vs Marshmallow
2022-04-27 19:33:47 +02:00
2022-05-13 11:26:09 +02:00
Expliquer pourquoi une API est intéressante/primordiale/la première chose à réaliser/le cadet de nos soucis:
\begin { itemize}
\item
Intéressante: ouverture
\item Primordiale: services
\item La première chose à réaliser: mobile-first
\item Le cadet de nos soucis: monolithique (cf. Rework)
\end { itemize}
2022-04-27 19:33:47 +02:00
Voir peut-être aussi
\url { https://christophergs.com/python/2021/12/04/fastapi-ultimate-tutorial/}
2022-05-01 19:45:53 +02:00
Au niveau du modèle, nous allons partir de quelque chose de très simple: des personnes, des contrats, des types de contrats, et un service d'affectation.
Quelque chose comme ceci:
2022-04-27 19:33:47 +02:00
\begin { minted} { python}
# models.py
from django.db import models
class People(models.Model):
CIVILITY_ CHOICES = (
("M", "Monsieur"),
("Mme", "Madame"),
("Dr", "Docteur"),
("Pr", "Professeur"),
("", "")
)
last_ name = models.CharField(max_ length=255)
first_ name = models.CharField(max_ length=255)
civility = models.CharField(
max_ length=3,
choices=CIVILITY_ CHOICES,
default=""
)
def _ _ str_ _ (self):
return "{ } , { } ".format(self.last_ name, self.first_ name)
class Service(models.Model):
label = models.CharField(max_ length=255)
def _ _ str_ _ (self):
return self.label
class ContractType(models.Model):
label = models.CharField(max_ length=255)
short_ label = models.CharField(max_ length=50)
def _ _ str_ _ (self):
return self.short_ label
class Contract(models.Model):
people = models.ForeignKey(People, on_ delete=models.CASCADE)
date_ begin = models.DateField()
date_ end = models.DateField(blank=True, null=True)
contract_ type = models.ForeignKey(ContractType, on_ delete=models.CASCADE)
service = models.ForeignKey(Service, on_ delete=models.CASCADE)
2022-05-01 19:45:53 +02:00
2022-04-27 19:33:47 +02:00
def _ _ str_ _ (self):
if self.date_ end is not None:
return "A partir du { } , jusqu'au { } , dans le service { } ({ } )".format(
self.date_ begin,
self.date_ end,
self.service,
self.contract_ type
)
return "A partir du { } , à durée indéterminée, dans le service { } ({ } )".format(
self.date_ begin,
self.service,
self.contract_ type
)
\end { minted}
\includegraphics { images/rest/models.png}
\section { Mise en place}
2022-06-03 20:50:18 +02:00
La configuration des points de terminaison de notre API est relativement touffue.
Pour cette raison, il convient de s'infliger à suivre une structure qui soit similaire pour chaque point de terminaison \cite [Predictability, Rule \#1] { django_ for_ startup_ founders} .
Il convient de:
\begin { enumerate}
\item
\textbf { Spécifier les permissions}
\item
\textbf { Copier et assainir les éléments communiqués en entrée vers des variables locales}
\item
\textbf { Valider les données d'entrée}
\item
\textbf { Enforce business requirements}
\item
\textbf { Perform busines logic}
\item
\textbf { Retourner une réponse HTTP}
\end { enumerate}
-> Répartir les responsabilités selon les composants ci-dessous
2022-04-27 19:33:47 +02:00
\begin { enumerate}
2022-05-01 19:45:53 +02:00
\item
Configurer les sérialiseurs, càd. les champs que nous souhaitons exposer au travers de l'API,
\item
Configurer les vues, càd le comportement de chacun des points de terminaison,
\item
Configurer les points de terminaison eux-mêmes, càd les URLs permettant d'accéder aux ressources.
\item
Et finalement ajouter quelques paramètres au niveau de notre application.
2022-04-27 19:33:47 +02:00
\end { enumerate}
\subsection { Serialiseurs}
2022-06-03 20:50:18 +02:00
Les sérialiseurs agissent litérallement comme des \texttt { forms} , mais au niveau de l'API.
Ils se basent sur un modèle, définit au niveau de la \texttt { class Meta} , permettent de choisir les champs qui seront sérialisés, définissent différentes méthodes d'accès à des propriétés spécifiques et des méthodes de validation.
Tout comme les forms.
Par exemple:
2022-04-27 19:33:47 +02:00
\begin { minted} { python}
# serializers.py
2022-05-01 19:45:53 +02:00
2022-04-27 19:33:47 +02:00
from django.contrib.auth.models import User, Group
from rest_ framework import serializers
2022-05-01 19:45:53 +02:00
2022-04-27 19:33:47 +02:00
from .models import People, Contract, Service
class PeopleSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = People
fields = ("last_ name", "first_ name", "contract_ set")
2022-05-01 19:45:53 +02:00
2022-04-27 19:33:47 +02:00
class ContractSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Contract
fields = ("date_ begin", "date_ end", "service")
class ServiceSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Service
fields = ("name",)
\end { minted}
\subsection { Vues}
\begin { minted} { python}
# views.py
from django.contrib.auth.models import User, Group
from rest_ framework import viewsets
from rest_ framework import permissions
from .models import People, Contract, Service
from .serializers import PeopleSerializer, ContractSerializer, ServiceSerializer
2022-05-01 19:45:53 +02:00
2022-04-27 19:33:47 +02:00
class PeopleViewSet(viewsets.ModelViewSet):
queryset = People.objects.all()
serializer_ class = PeopleSerializer
permission_ class = [permissions.IsAuthenticated]
2022-05-01 19:45:53 +02:00
2022-04-27 19:33:47 +02:00
class ContractViewSet(viewsets.ModelViewSet):
queryset = Contract.objects.all()
serializer_ class = ContractSerializer
permission_ class = [permissions.IsAuthenticated]
2022-05-01 19:45:53 +02:00
2022-04-27 19:33:47 +02:00
class ServiceViewSet(viewsets.ModelViewSet):
queryset = Service.objects.all()
serializer_ class = ServiceSerializer
permission_ class = [permissions.IsAuthenticated]
\end { minted}
\subsection { URLs}
\begin { minted} { python}
# urls.py
from django.contrib import admin
from django.urls import path, include
from rest_ framework import routers
2022-05-01 19:45:53 +02:00
2022-04-27 19:33:47 +02:00
from core import views
2022-05-01 19:45:53 +02:00
2022-04-27 19:33:47 +02:00
router = routers.DefaultRouter()
router.register(r"people", views.PeopleViewSet)
router.register(r"contracts", views.ContractViewSet)
router.register(r"services", views.ServiceViewSet)
2022-05-01 19:45:53 +02:00
2022-04-27 19:33:47 +02:00
urlpatterns = [
path("api/v1/", include(router.urls)),
path('admin/', admin.site.urls),
]
\end { minted}
\begin { minted} { python}
# settings.py
INSTALLED_ APPS = [
...
"rest_ framework",
...
]
...
REST_ FRAMEWORK = {
'DEFAULT_ PAGINATION_ CLASS':
'rest_ framework.pagination.PageNumberPagination',
'PAGE_ SIZE': 10
}
\end { minted}
\subsection { Résultat}
En nous rendant sur l'URL \texttt { http://localhost:8000/api/v1} , nous obtiendrons ceci:
\includegraphics { images/rest/api-first-example.png}
\section { Modéles et relations}
Plus haut, nous avons utilisé une relation de type \texttt { HyperlinkedModelSerializer} . C'est une bonne manière pour autoriser des relations entre vos instances à partir de l'API, mais il faut reconnaître que cela reste assez limité. Pour palier à ceci, il existe { [} plusieurs manières de représenter ces
2022-05-01 19:45:53 +02:00
\url { https://www.django-rest-framework.org/api-guide/relations/} :
2022-04-27 19:33:47 +02:00
\begin { enumerate}
2022-05-01 19:45:53 +02:00
\item Soit \textbf { via} un hyperlien, comme ci-dessus,
\item Soit en utilisant les clés primaires, soit en utilisant l'URL canonique permettant d'accéder à la ressource.
2022-04-27 19:33:47 +02:00
\end { enumerate}
La solution la plus complète consiste à intégrer la relation directement au niveau des données sérialisées, ce qui nous permet de passer de ceci (au niveau des contrats):
\begin { minted} { js}
{
"count": 1,
"next": null,
"previous": null,
"results": [
{
"last_ name": "Bond",
"first_ name": "James",
"contract_ set": [
"http://localhost:8000/api/v1/contracts/1/",
"http://localhost:8000/api/v1/contracts/2/"
]
}
]
}
\end { minted}
à ceci:
\begin { minted} { js}
{
"count": 1,
"next": null,
"previous": null,
"results": [
{
"last_ name": "Bond",
"first_ name": "James",
"contract_ set": [
{
"date_ begin": "2019-01-01",
"date_ end": null,
"service": "http://localhost:8000/api/v1/services/1/"
} ,
{
"date_ begin": "2009-01-01",
"date_ end": "2021-01-01",
"service": "http://localhost:8000/api/v1/services/1/"
}
]
}
]
}
\end { minted}
La modification se limite à \textbf { surcharger} la propriété, pour
indiquer qu'elle consiste en une instance d'un des sérialiseurs
existants. Nous passons ainsi de ceci
\begin { minted} { python}
class ContractSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Contract
fields = ("date_ begin", "date_ end", "service")
class PeopleSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = People
fields = ("last_ name", "first_ name", "contract_ set")
\end { minted}
à ceci:
\begin { minted} { python}
class ContractSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Contract
fields = ("date_ begin", "date_ end", "service")
class PeopleSerializer(serializers.HyperlinkedModelSerializer):
contract_ set = ContractSerializer(many=True, read_ only=True)
class Meta:
model = People
fields = ("last_ name", "first_ name", "contract_ set")
\end { minted}
2022-05-11 20:01:39 +02:00
Nous ne faisons donc bien que redéfinir la propriété \texttt { contract\_ set} et indiquons qu'il s'agit à présent d'une instance de \texttt { ContractSerializer} , et qu'il est possible d'en avoir plusieurs. C'est tout.