9.2 KiB
DRF Initiation
par
Julien Palard julien@palard.fr
::: notes
Introduce yourself!
Et juste pour doctest:
import django.contrib.auth.models
Les bonnes bases : Python
*args, **kwargs
Les bonnes bases : Python
La MRO.
Les bonnes bases : Python
On travaille dans un venv.
Les bonnes bases : Python
La gestion des dépendances avec pip-compile
.
Pratique
Rédiger un script, en ligne de commande, permettant de tester si un site internet est en bonne santé :
$ python checkurl.py mdk.fr
Redirection HTTPS: OK
Status: OK (200)
Response time: OK (0.125s < 1s)
Certificate: OK (expires in 68 days)
HSTS: OK (max-age=63072000; always)
::: notes
versionnez !
Les bonnes bases : Django
python -m pip install Django
django-admin startproject demo
cd demo
./manage.py migrate
::: notes
Leur faire faire le tour du propriétaire.
Vocabulaire
Dans Django on va avoir des models
, des vues
, et des urls
.
::: notes
Peut être aussi des templates, et l'admin.
Debug toolbar
python -m pip install django-debug-toolbar
L'ajouter dans settings.py
et urls.py
.
Un compte administrateur
./manage.py createsuperuser
Vous devez maintenant avoir une interface d'administration qui fonctionne, avec la Debug Toolbar à droite.
Bonnes pratiques
On versionne et on prend le temps de poser un .gitignore
.
Bonnes pratiques
On en mettra le moins possible dans le dossier du projet, on utilisera des applications pour le reste du code.
Bonnes pratiques
Une bonne gestion des dépendances avec pip-tools
.
Bonnes pratiques
On surcharge l'objet User
, même si on pense ne pas en avoir besoin :
class User(django.contrib.auth.models.AbstractUser):
...
et :
AUTH_USER_MODEL = ...
Les URLs
urlpatterns = [
path("admin/", admin.site.urls),
path("__debug__/", include(debug_toolbar.urls)),
]
Les vues
def index(request):
return HttpResponse("Hello world")
Pratique
Faire une page d'accueil pour votre Django.
Les modèles
Désambiguons makemigrations
et migrate
d'abord.
Les modèles
Personalisez le modèle User
:
class User(django.contrib.auth.models.AbstractUser):
...
Les modèles
Créez un modèle Domain
:
class Domain(models.Model):
domain = models.CharField(max_length=253)
is_up = models.BooleanField(null=True, blank=True)
L'admin
Indiquez l'existance du modèle domaine à l'admin :
admin.site.register(Domain)
Testez
Les bonnes bases : DRF
python -m pip install djangorestframework
# Ajouter l'app rest_framework
vocabulaire
Dans DRF on va avoir des serializers
, des routers
, des views
et des permissions
.
Le routage de Django
La requête parcourre les urlpatterns
du projet, c'est donc à lui
d'inclure les urlpatterns
des différentes applications.
Resources
Pour Django on avait : https://ccbv.co.uk/ (Memo: « Classy Class-Based-View »).
Pour DRF on a : https://www.cdrf.co/ (Memo: « Classy DRF »).
Les media types
Pour représenter des données on utilise un media type il existe plusieurs écoles :
- Tout est liste (
Collection+JSON
, ...). - Tout est article (
atom+xml
, ...). - Snowflakes (
application/json
).
(On ne parle donc pas de RPC, on parle de représentation).
Les media types
Certains media type sont plus « tout terrain » que d'autres :
JSON-LD
, avec Hydra : le plus générique.HAL
: Pour la lecture seule.application/problem+json
application/json-patch+json
application/json-home
- ...
::: notes
Pensez au web actuel : il mélange text/html, application/javascript, text/css, ...
Mais qu'est-ce que REST ?
C'est un ensemble de contraintes :
- Client-Serveur
- Sans état
- Une URI identifie une resource
- Les resources sont manipulées via leurs représentations
- ...
::: notes
Pensez au web actuel pour chaque contrainte : ça marche.
Client-Serveur
✓
Sans état
Attention à l'interprétation : on ne parle pas d'un site statique pour autant.
Le serveur a un état, et cet état est amené à changer (un PUT
, un
POST
, un DELETE
vont typiquement changer quelque chose).
Sans état
Quand on dit « stateless » on pense au niveau d'une requête :
L'interprétation d'une requête ne doit pas dépendre des requêtes précédentes.
C'est tout.
::: notes
Prendre l'exemple du client qui s'endort, puis qui revient 8h plus tard pour terminer. Ou de plusieurs backends derrière un LB.
Sans état
Donc pas de :
PUT /workon/user/1
PUT /user -d '{"name": "Alan"}'
PUT /workon/user/2
PUT /user -d '{"name": "Ada"}'
Sans état
Mais :
PUT /users/1 -d '{"name": "Alan"}'
PUT /users/2 -d '{"name": "Ada"}'
::: notes
Si le serveur a oublié la première requête quand elle arrive, pas de souci.
Si les deux requêtes sont gérées par des serveurs différents, pas de souci.
Coopération avec les caches intermédiaires
C'est surtout respecter la sémantique HTTP.
Avec HTTPS les problèmes causés par des proxy inconnus, éventuellement ne respectant pas la sémantique HTTP ont disparu.
::: notes
Attention, certains réseaux, de fait, ne respectent pas la sémantique HTTP : un POST pourraît être exceptionnellement rejoué, sur un réseau mobile, lors du roaming.
Une URI identifie une resource
Cool URIs don't change.
REST ne nous impose pas des URL sémantiques / expressives.
Cependant les humains les apprécient, une URL bien choisie c'est comme un nom de variable bien choisi, c'est agréable.
::: notes
La slide n'en parle pas mais bien en parler:
- Une URI == une resource.
- Une resource == une URI.
Manipulation par la représentation
GET /users/1
{"name": "Alan", "birthdate": "1912-06-23"}
PUT /users/1 -d '{"name": "Alan Turing", "birthdate": "1912-06-23"}'
::: notes
Faire une parenthèse sur les etags, If-Match
, If-None-Match
.
Messages auto-descriptifs
Toutes les informations nécessaires à l'interprétation du message doivent être dans le message.
Je n'ai rien contre un lien vers la doc.
HATEOAS
C'est celui qui fait peur.
TL;DR: data + interactions
::: notes
Prendre l'exemple d'une boutique avec le bouton "acheter" qui n'est présent que s'il y a du stock.
HATEOAS
Le mauvais exemple :
{
"@id": "/products/123",
"name": "Brioche",
"in_stock": false,
"buy": {
"@id": "/cart/",
"@type": "hydra/CreateResourceOperation",
"method": "POST",
"expects": {"@id": "/products/123"}
}
}
::: notes
Ce n'est pas vraiment du JSON-LD+Hydra, mais ça loge das la slide...
HATEOAS
support on building Hypermedia APIs with REST framework is planned for a future version.
En parlant de sémantique HTTP
TL;DR
GET / HEAD / OPTIONS
safe
idempotent
PUT
idempotent
DELETE
idempotent
POST
La configuration de Django
Trois solutions
include local_settings.py
django-configurations
django-environ
Et si on revenait à DRF !?
Les serialiseurs
Leur rôle est de transformer un objet Python en un objet Python facilement sérialisable (en JSON typiquement).
Il va donc, par exemple transformer objet datetime en chaîne, puisque JSON ne spécifie pas de représentation pour les dates.
::: notes
Et vice versa.
Les URLs
Pour commencer : aucune différence avec Django.
Les vues
from rest_framework.decorators import api_view
from rest_framework.response import Response
@api_view(["GET"])
def hello(request):
return Response({"Hello": "world."})
Pratique
Avec juste un path dans urlpatterns
et une vue, faites une API qui
donne l'heure :
$ curl 0:8000/horloge/
{"datetime":"2021-05-31T12:24:04.534708"}
Les autres méthodes
@api_view(["GET", "PUT", "DELETE"])
Pratique
Sur une autre url
, par exemple /cache/
, implémentez un
memcached
, testez-le avec curl
.
Serializers
- serializers.BaseSerializer
- serializers.ModelSerializer
BaseSerializer
to_representation
/ to_internal_value
class DateSerializer(serializers.BaseSerializer):
def to_representation(self, instance):
return instance.isoformat()
Pratique
Implémentez un FileSerializer
prenant un Path
de pathlib
et
renvoyant :
{
"self": "http://127.0.0.1:8000/files/.",
"name": "drf-demo",
"path": ".",
"size": 4096,
"ctime": "2021-05-30", "mtime": "2021-05-30", "atime": "2021-04-22",
"mode": "0o40755",
"is_dir": true,
"files": [
{
Permissions
class IsOwner(permissions.BasePermission):
def has_object_permission(self, request, view, obj):
return request.user == obj.owner
HyperLinkedModelSerializer
class DomainSerializer(HyperlinkedModelSerializer):
class Meta:
model = Domain
fields = ["domain", "is_up", "checks_url", "url"]
ViewSets
class DomainViewSet(ModelViewSet):
queryset = Domain.objects.all()
serializer_class = DomainSerializer
permission_classes = [IsOwner]