WIP django
This commit is contained in:
parent
ebae45132e
commit
f9b7688be0
|
@ -22,6 +22,8 @@ jobs:
|
|||
export deploy_key=""
|
||||
python3 -m pip install --user --upgrade pip
|
||||
python3 -m pip install --user mdtoreveal
|
||||
PATH="$HOME/.local/bin/:$PATH" make -C initiation rsync
|
||||
PATH="$HOME/.local/bin/:$PATH" make -C avancé rsync
|
||||
PATH="$HOME/.local/bin/:$PATH" make -C drf-initiation rsync
|
||||
export PATH="$HOME/.local/bin/:$PATH"
|
||||
make -C initiation rsync
|
||||
make -C avancé rsync
|
||||
make -C drf-initiation rsync
|
||||
make -C django-initiation rsync
|
||||
|
|
|
@ -0,0 +1,33 @@
|
|||
.PHONY: help
|
||||
help:
|
||||
@echo "Usage:"
|
||||
@echo " make static # to build static version."
|
||||
@echo " make test # to run tests."
|
||||
@echo " make rsync # rsync to prod"
|
||||
@echo " make clean"
|
||||
|
||||
.PHONY: static
|
||||
static: output/index.html
|
||||
|
||||
.PHONY: check
|
||||
test:
|
||||
python test.py *.md
|
||||
|
||||
%.html: %.md
|
||||
mdtoreveal $< --output $@
|
||||
|
||||
output/index.md: django.md
|
||||
mkdir -p output
|
||||
cat $< > $@
|
||||
|
||||
.PHONY: rsync
|
||||
rsync: static
|
||||
rsync -vah --delete output/ mdk_fr@mdk.fr:/var/www/mdk.fr/django-initiation/
|
||||
|
||||
.PHONY: clean
|
||||
clean:
|
||||
rm -fr output/
|
||||
|
||||
.PHONY: serve
|
||||
serve:
|
||||
python -m http.server -d output/
|
|
@ -0,0 +1,27 @@
|
|||
# Contenu
|
||||
|
||||
Side project : https://github.com/JulienPalard/drf-demos
|
||||
|
||||
- Revue des bases et consolidation Python et Django
|
||||
- Présentation des types d'API, approfondissement de REST/HATEOAS
|
||||
- Rappels autour de la sémantique HTTP
|
||||
- django-rest-framework: La sérialisation
|
||||
- django-rest-framework: Les différents types de vues
|
||||
- django-rest-framework: Les permissions
|
||||
- django-rest-framework: Les relations
|
||||
- Les tests et la maintenabilité
|
||||
|
||||
|
||||
# Objectifs pédagogiques
|
||||
|
||||
- Pouvoir démarrer rapidement un projet DRF.
|
||||
- Savoir designer une API.
|
||||
- Implémenter un service headless en utilisant DRF.
|
||||
|
||||
|
||||
# TP
|
||||
|
||||
- Projet 1 : API « Horloge parlante ».
|
||||
- Projet 2 : API « memcached ».
|
||||
- Projet 3 : API « file system ».
|
||||
- Projet 4 : API « uptime manager ».
|
|
@ -0,0 +1,945 @@
|
|||
# Django Initiation
|
||||
|
||||
par
|
||||
|
||||
Julien Palard <julien@palard.fr>
|
||||
|
||||
https://mdk.fr
|
||||
|
||||
::: notes
|
||||
|
||||
Introduce yourself!
|
||||
|
||||
|
||||
# Django
|
||||
|
||||
Django est une infrastructure d'applications web populaire et robuste.
|
||||
|
||||
> The web framework for perfectionists with deadlines.
|
||||
|
||||
|
||||
## Django : qui l'utilise ?
|
||||
|
||||
- Instagram, Pineterest, Mozilla, Disqus, BitBucket, …
|
||||
|
||||
![](static/stats.png)
|
||||
|
||||
|
||||
# Les bonnes bases : Python
|
||||
|
||||
On travaillera toujours dans un `venv` :
|
||||
|
||||
```python
|
||||
python -m pip install django
|
||||
```
|
||||
|
||||
# La théorie — Projet
|
||||
|
||||
Pour démarrer un projet, une commande :
|
||||
|
||||
```python
|
||||
django-admin startproject project
|
||||
```
|
||||
|
||||
## La théorie — App
|
||||
|
||||
Une fois dans le projet, pour créer une application, une commande :
|
||||
|
||||
```python
|
||||
python manage.py startapp watch
|
||||
```
|
||||
|
||||
::: notes
|
||||
|
||||
(et ajout dans `settings.py`)
|
||||
|
||||
|
||||
## La théorie — Modèle
|
||||
|
||||
Un « modèle » est la description d'une table.
|
||||
|
||||
Ça rappelle un ORM, mais ça permet beaucoup plus de choses en Django.
|
||||
|
||||
::: notes
|
||||
|
||||
- admin
|
||||
- forms
|
||||
- serializers (API)
|
||||
- class based views
|
||||
|
||||
|
||||
## La théorie — Modèle
|
||||
|
||||
Par exemple :
|
||||
|
||||
```python
|
||||
class Website(models.Model):
|
||||
host = models.CharField(max_length=512)
|
||||
is_up = models.BooleanField(null=True, blank=True)
|
||||
last_check = models.DateTimeField(auto_now_add=True)
|
||||
```
|
||||
|
||||
::: notes
|
||||
|
||||
Prendre le temps d'expliquer les fields (leur relation avec la DB), et
|
||||
les differents endroits ou Django peut reutiliser cette information
|
||||
(widgets, validation, ...).
|
||||
|
||||
|
||||
## La théorie — Première interface d'admin
|
||||
|
||||
En une ligne, pourquoi pas :
|
||||
|
||||
```python
|
||||
admin.site.register(Website)
|
||||
```
|
||||
|
||||
|
||||
## La théorie — la DB
|
||||
|
||||
- PostgreSQL
|
||||
- MySQL
|
||||
- sqlite
|
||||
- ...
|
||||
|
||||
::: notes
|
||||
|
||||
Leur faire croire 2 secondes qu'on va devoir s'installer et se
|
||||
configurer un serveur de base de donnée :D
|
||||
|
||||
|
||||
## La théorie — La DB
|
||||
|
||||
```bash
|
||||
python manage.py makemigrations
|
||||
python manage.py migrate
|
||||
```
|
||||
|
||||
::: notes
|
||||
|
||||
On expliquera plus tard, leur dire que ça crée la DB et que le but
|
||||
maintenant c'est surtout d'aller tester ça :)
|
||||
|
||||
|
||||
## La théorie — L'interface d'administration
|
||||
|
||||
On a une DB, mais pas encore d'utilisateur admin dedans :
|
||||
|
||||
```bash
|
||||
python manage.py createsuperuser
|
||||
```
|
||||
|
||||
## La théorie — L'interface d'administration
|
||||
|
||||
On a terminé, on peut démarrer le serveur :
|
||||
|
||||
```bash
|
||||
python manage.py runserver
|
||||
```
|
||||
|
||||
# La pratique
|
||||
|
||||
```bash
|
||||
django-admin startproject project
|
||||
cd project
|
||||
python manage.py startapp watch
|
||||
```
|
||||
|
||||
::: notes
|
||||
|
||||
Biiien prendre le temps d'expliquer l'arborescence, de se promener,
|
||||
d'y lire les commentaires.
|
||||
|
||||
|
||||
## La pratique
|
||||
|
||||
Ajout de l'app `watch` dans `project/settings.py` :
|
||||
|
||||
```python
|
||||
INSTALLED_APPS = [
|
||||
"watch",
|
||||
...,
|
||||
...,
|
||||
]
|
||||
```
|
||||
|
||||
|
||||
## Les modèles
|
||||
|
||||
On va mettre celui-ci dans `watch/models.py`.
|
||||
|
||||
```python
|
||||
class Website(models.Model):
|
||||
host = models.CharField(max_length=512)
|
||||
is_up = models.BooleanField(null=True, blank=True)
|
||||
last_check = models.DateTimeField(auto_now_add=True)
|
||||
```
|
||||
|
||||
|
||||
## Première interface d'admin
|
||||
|
||||
Et ça dans `watch/admin.py`.
|
||||
|
||||
```python
|
||||
from watch.models import Website
|
||||
|
||||
|
||||
admin.site.register(Website)
|
||||
```
|
||||
|
||||
|
||||
## Création de la DB
|
||||
|
||||
```bash
|
||||
python manage.py makemigrations
|
||||
python manage.py migrate
|
||||
```
|
||||
|
||||
::: notes
|
||||
|
||||
Expliquer les deux étapes.
|
||||
|
||||
|
||||
## L'interface d'administration
|
||||
|
||||
On a une DB, mais pas encore d'utilisateur admin dedans :
|
||||
|
||||
```bash
|
||||
python manage.py createsuperuser
|
||||
```
|
||||
|
||||
## L'interface d'administration
|
||||
|
||||
On a terminé, on peut essayer maintenant ?
|
||||
|
||||
```bash
|
||||
python manage.py runserver
|
||||
```
|
||||
::: notes
|
||||
|
||||
Leur faire faire ça dans un **autre** shell.
|
||||
|
||||
puis les laisser jouer avec l'interface d'admin, créer quelques sites...
|
||||
|
||||
|
||||
## L'interface d'administration
|
||||
|
||||
Les modèles, leurs `fields` ne servent donc pas qu'a l'ORM, cette
|
||||
interface d'admin nous à demandé une ligne de code.
|
||||
|
||||
::: notes
|
||||
|
||||
Si ce n'est pas déjà fait, leur faire ajouter des `__str__`.
|
||||
|
||||
|
||||
## Astuce
|
||||
|
||||
On peut passer beaucoup de temps à peaufiner l'interface d'admin,
|
||||
repoussez ça après avoir livré une première version.
|
||||
|
||||
|
||||
# Manipulation des modèles
|
||||
|
||||
## Mise en pratique
|
||||
|
||||
Créez le modèle `Check` avec les champs `is_up`, `date`, `website`, et
|
||||
`message`.
|
||||
|
||||
::: notes
|
||||
|
||||
Pour le champ `website` vous aurez besoin d'un `models.ForeignKey`, RTFM.
|
||||
|
||||
|
||||
## L'admin
|
||||
|
||||
Ajoutez une interface d'admin pour ce modèle, et ajoutez à la main quelques « *checks* ».
|
||||
|
||||
|
||||
## L'ORM
|
||||
|
||||
C'est l'occasion de sortir un `python manage.py shell`.
|
||||
|
||||
```pycon
|
||||
>>> from watch.models import Website
|
||||
>>> Website.objects.all()
|
||||
```
|
||||
|
||||
Essayer `.all`, `.filter`, `.get`, `.order_by`, et les slices.
|
||||
|
||||
|
||||
# Les URLs
|
||||
|
||||
Changons de sujet et rajoutons des URLs et des vues.
|
||||
|
||||
|
||||
## Les URLs
|
||||
|
||||
Dans `urls.py` on va se rajouter une URL pour la page d'accueil :
|
||||
|
||||
```python
|
||||
from votre_app import views
|
||||
|
||||
urlpatterns = [
|
||||
...
|
||||
...
|
||||
path("", views.index, name=index),
|
||||
]
|
||||
```
|
||||
|
||||
## Les vues
|
||||
|
||||
Dans `views.py` de l'app :
|
||||
|
||||
```python
|
||||
from django.http import HttpResponse
|
||||
|
||||
def index(request):
|
||||
html = "<html><body><h1>Website Watcher</h1></body></html>"
|
||||
return HttpResponse(html)
|
||||
```
|
||||
|
||||
|
||||
::: notes
|
||||
|
||||
C'est bien mais écrire du HTML dans du Python c'est pas élégant.
|
||||
|
||||
|
||||
## Les vues
|
||||
|
||||
```python
|
||||
from django.http import HttpResponse
|
||||
|
||||
def index(request):
|
||||
return render(request, "watch/index.html")
|
||||
```
|
||||
|
||||
|
||||
## Les templates
|
||||
|
||||
Dans `watch/templates/watch/index.html` ...
|
||||
|
||||
Mais pourquoi `watch` deux fois ??
|
||||
|
||||
|
||||
## Les templates
|
||||
|
||||
Dans `watch/templates/watch/index.html` :
|
||||
|
||||
```html
|
||||
<!DOCTYPE html>
|
||||
<html lang="fr">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>Website Watch</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Website Watch</h1>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
## Les vues
|
||||
|
||||
Et si on ajoutais de la données provenant de la DB dans le template ?
|
||||
|
||||
|
||||
## Les vues
|
||||
|
||||
```python
|
||||
from django.http import HttpResponse
|
||||
|
||||
def index(request):
|
||||
return render(request, "watch/index.html", {"websites": Website.objects.all()})
|
||||
```
|
||||
|
||||
## Les templates
|
||||
|
||||
```html
|
||||
<body>
|
||||
<h1>Website Watch</h1>
|
||||
<ul>
|
||||
{% for website in websites %}
|
||||
<li>{{ website.host }} {% if website.is_up %}✓{% else %}✗{% endif %}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</body>
|
||||
```
|
||||
|
||||
|
||||
## La Debug Toolbar
|
||||
|
||||
# Les tests
|
||||
|
||||
## Les fixtures
|
||||
|
||||
|
||||
|
||||
# L'ORM
|
||||
|
||||
## Les *Managers*
|
||||
|
||||
Exposent les opérations de requête de base de donnée, c'est le `.objects`.
|
||||
|
||||
Ces opérations (des méthodes) renvoient des `queryset`s.
|
||||
|
||||
## Les *Queryset*
|
||||
|
||||
Introduction aux concepts Manager et Queryset, RelatedManager
|
||||
Méthodes simples de requêtage en base de données
|
||||
|
||||
|
||||
# Les URLS
|
||||
|
||||
## path
|
||||
## include
|
||||
## namespaces
|
||||
|
||||
# Les vues
|
||||
## Function based views
|
||||
## Class based views
|
||||
|
||||
# Templates (Jour 2)
|
||||
## filters, tags
|
||||
## extends
|
||||
|
||||
# Forms
|
||||
## Forms fields
|
||||
## Widgets
|
||||
## ModelForm
|
||||
## Validation
|
||||
|
||||
# Modèles
|
||||
## Relations
|
||||
## ForeignKey
|
||||
## ManyToManyField
|
||||
## OneToOneField
|
||||
## InlineModeladmin
|
||||
|
||||
# Users
|
||||
## Groups
|
||||
## Permissions
|
||||
## Authentication
|
||||
How to protect views
|
||||
|
||||
# Static assets (day 3)
|
||||
# Deployment
|
||||
## gunicorn
|
||||
|
||||
# Les bonnes bases : Python
|
||||
|
||||
`*args, **kwargs`
|
||||
|
||||
|
||||
## Les bonnes bases : Python
|
||||
|
||||
La MRO.
|
||||
|
||||
|
||||
## 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]
|
||||
```
|
Binary file not shown.
After Width: | Height: | Size: 82 KiB |
|
@ -0,0 +1,40 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
import argparse
|
||||
from string import ascii_letters, digits
|
||||
import re
|
||||
import doctest
|
||||
from tempfile import NamedTemporaryFile
|
||||
import importlib.util
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("file", nargs="+")
|
||||
parser.add_argument("-v", "--verbose", action="store_true")
|
||||
parser.add_argument("-d", "--debug", action="store_true")
|
||||
args = parser.parse_args()
|
||||
|
||||
for file_to_test in args.file:
|
||||
with open(file_to_test) as f:
|
||||
source = f.read()
|
||||
|
||||
with NamedTemporaryFile(mode="w", suffix=".py") as f:
|
||||
for example_match in re.finditer("```python.*?```", source, re.S):
|
||||
example = example_match.group()
|
||||
example = "\n".join(example.split("\n")[1:-1])
|
||||
lineno = source[:example_match.start()].count("\n") + 1
|
||||
function_name = ''.join(letter if letter in ascii_letters + digits else '_' for letter in file_to_test[:-3]) + "_line_" + str(lineno)
|
||||
if example.startswith(">>> "):
|
||||
if '"""' in example:
|
||||
f.write(f"""def _{function_name}():\n r'''""" + example + """\n'''\n\n""")
|
||||
else:
|
||||
f.write(f'''def _{function_name}():\n r"""''' + example + '''\n"""\n\n''')
|
||||
else:
|
||||
f.write(example + "\n\n")
|
||||
f.flush()
|
||||
if args.debug:
|
||||
with open(f.name) as py_source:
|
||||
print(py_source.read())
|
||||
spec = importlib.util.spec_from_file_location("to_test", f.name)
|
||||
to_test = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(to_test)
|
||||
doctest.testmod(to_test, verbose=args.verbose)
|
Loading…
Reference in New Issue