# DRF Initiation par Julien Palard https://mdk.fr ::: notes Introduce yourself! Et juste pour doctest: ```python 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é : ```bash $ 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 ```bash 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 ```bash python -m pip install django-debug-toolbar ``` L'ajouter dans `settings.py` et `urls.py`. ## Un compte administrateur ```bash ./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 : ```python class User(django.contrib.auth.models.AbstractUser): ... ``` et : ```python AUTH_USER_MODEL = ... ``` ## Les URLs ```python urlpatterns = [ path("admin/", admin.site.urls), path("__debug__/", include(debug_toolbar.urls)), ] ``` ## Les vues ```python 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` : ```python class User(django.contrib.auth.models.AbstractUser): ... ``` ## Les modèles Créez un modèle `Domain` : ```python 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 : ```python admin.site.register(Domain) ``` ## Testez ## Les bonnes bases : DRF ```bash 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 : ```text PUT /workon/user/1 PUT /user -d '{"name": "Alan"}' PUT /workon/user/2 PUT /user -d '{"name": "Ada"}' ``` ## Sans état Mais : ```text 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 ```text 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 : ```json { "@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 ```python 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` ```python class DateSerializer(serializers.BaseSerializer): def to_representation(self, instance): return instance.isoformat() ``` ## Pratique Implémentez un `FileSerializer` prenant un `Path` de `pathlib` et renvoyant : ```json { "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 ```python class IsOwner(permissions.BasePermission): def has_object_permission(self, request, view, obj): return request.user == obj.owner ``` ## HyperLinkedModelSerializer ```python class DomainSerializer(HyperlinkedModelSerializer): class Meta: model = Domain fields = ["domain", "is_up", "checks_url", "url"] ``` ## ViewSets ```python class DomainViewSet(ModelViewSet): queryset = Domain.objects.all() serializer_class = DomainSerializer permission_classes = [IsOwner] ```