## Django Initiation par Julien Palard
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 - National Geographic - [Washington Post](https://www.djangoproject.com/weblog/2005/dec/08/congvotes/) - ...
## Vocabulaire Dans Django on va avoir principalement des `models`, des `vues`, des `templates`, et des `urls`.
## Les bonnes bases On travaillera toujours dans un `venv` : ```bash python -m pip install django ```
## La théorie — Projet Pour démarrer un projet, une commande : ```bash django-admin startproject project ```
## La théorie — App Une fois dans le projet, pour créer une application, une commande : ```bash 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, ...).
## 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 Leur expliquer que sqlite est utilisé dans les applications : pas besoin d’installer un postgresql pour utiliser Firefox, pourtant Firefox a besoin d’une base de donnée.
## 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 :)
## L’interface d’administration On a une DB, mais pas encore d’utilisateur admin dedans : ```bash python manage.py createsuperuser ```
## Terminé 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.
## Les modèles Désambiguons `makemigrations` et `migrate` d’abord.
## L’interface d’administration On a une DB, mais pas encore d’utilisateur admin dedans : ```bash python manage.py createsuperuser ```
## Terminé 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 a 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* ». notes: Les faire tester ça.
## Personalisons Dans chaque modèle, un `__str__` aide l’admin à être lisible.
## Personalisons Dans `admin.py` on peut préciser les colonnes qu’on veut afficher : ```python @admin.register(Website) class WebsiteAdmin(admin.ModelAdmin): list_display = ("host", "is_up", "last_check") @admin.register(Check) class CheckAdmin(admin.ModelAdmin): list_display = ("website", "date", "is_up", "message") ```
## Les URLs & les vues Changons complètement de sujet : les URLs et des vues.
## Les URLs Dans `project/urls.py` on va se rajouter une URL pour la page d’accueil : ```python from watch import views urlpatterns = [ ..., ..., path("", views.index, name="index"), ] ``` notes: C’est un `path`, un chemin, c’est le chemin vide.
## Les URLs On aurait pu rajouter : ```python path("about", views.about, name="about"), path("help", views.help, name="help"), ... ```
## include Petite parenthèse, on aurait pu mettre un `urlpatterns` dans `watch/urls.py`, et les inclure dans `project/urls.py` en utilisant : ```python from django.urls import include [...] path("", include("watch.urls")), ``` notes: C’est pratique pour « ancrer » un ensemble de chemin sous un autre chemin : pour se faire une hierarchie.
## namespaces Les espaces de nommage permettent de désambiguer les urls nommées : `index` est le nom de la page d’accueil de l’interface d’admin ou de la page d’accueil de votre application ?
## namespaces Avec les espaces de nommage, on a donc : - `admin:index` - `watch:index` sans ambiguité. notes: Utiliez-en, c’est bien.
## Les vues Dans `watch/views.py` : ```python from django.http import HttpResponse def index(request): html = "
Website Watcher
" return HttpResponse(html) ``` notes: C’est bien mais écrire du HTML dans du Python c’est pas élégant.
## Les vues Mieux : ```python from django.http import HttpResponse from django.shortcuts import render def index(request): return render(request, "watch/index.html") ```
## Les templates Django va chercher `watch/index.html` dans tous les dossiers de templates, dont `watch/templates/`.
## Les templates Donc dans `watch/templates/watch/index.html` : ```html
Website Watch
Website Watch
``` notes: La création du dossier `templates/` est typiquement quelque chose que `runserver` ne voit pas, il faut le redémarrer.
## Les vues Et si on ajoutait de la donnée provenant de la DB dans le template ?
## Les vues ```python ... from watch.models import Website def index(request): return render(request, "watch/index.html", {"websites": Website.objects.all()}) ``` notes: Ne pas oublier les imports… Premier apperçu de l’ORM en passant.
## Les vues Digression : Il existe aussi des vues basées sur des classes, pouvant s’appuyer sur des modèles.
## Les vues ```python class WebsiteListView(ListView): model = Website ```
## Les templates ```html
Website Watch
{% for website in websites %}
{{ website.host }} {% if website.is_up %}✓{% else %}✗{% endif %}
{% endfor %}
```
## Les templates Ça fonctionne, mais on ne veut pas répéter l’entête HTML à chaque page…
## Les templates En utilisant `extends`, on peut réutiliser des templates.
## La Debug Toolbar 
## La Debug Toolbar ```bash python -m pip install django-debug-toolbar ``` L’ajouter dans `settings.py` et `urls.py`.
## L’ORM
## 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 *Managers* Les *managers* représentent une table, ils sont accessibles via l’attribut `objects` d’un modèle. Ses opérations (des méthodes) renvoient des `queryset`s. ```pycon In [2]: Website.objects Out[2]:
```
## Les instances de modèles Les instances de modèles représentent une ligne de la table.
## Les *Queryset* Représentent un ensemble de lignes de la base de donnée. Ils ont les mêmes méthodes que les *managers* : - filter - get - order_by - ...
## Les *Queryset* ```pycon In [3]: Website.objects.all() Out[3]:
]> ```
## Les *Queryset* Pour ceux qui ont fait du SQL c’est un "lazy select" : c’est un `SELECT` qui ne s’éxécutera que si nécessaire.
## Les *RelatedManager* Sont des managers, mais sur les relations : ``` In [4]: Website.objects.get(host="mdk.fr").check_set.all() Out[4]:
,
]> ```
## Forms Comme pour l’admin, Django peut utiliser les informations des modèles pour vous aider à générer des formulaires.
## Forms fields Créer un formulaire ressemble à créer un modèle, on décrit les champs : ```python class WebsiteForm(forms.Form): host = forms.CharField(label="Website hostname", max_length=512) ```
## Forms On le donne au template : ```python return render( request, "watch/index.html", { "websites": Website.objects.all(), "form": WebsiteForm, }, ) ```
## Forms On l’affiche dans le template : ```html
{% csrf_token %} {{ form }}
```
## Forms Mais là on a pas utilisé les informations données par les modèles…
## ModelForm Avec un `ModelForm` on ne répète pas les attributs fields : ```python class WebsiteForm(forms.ModelForm): class Meta: model = Website fields = ("host",) ```
## Widgets Le rendu HTML d’un champ de formulaire est appelé un `Widget`, il est possible de le changer, par exemple si vous préférez un `
## Validation Une instance de formulaire a une méthode `is_valid` : ```python if request.method == "POST": form = WebsiteForm(request.POST) if form.is_valid(): Website.objects.create(host=form.cleaned_data["host"]) ``` notes: Django peut donc vérifier que les champs non-empty sont bien pas vides, que les longuers sont respectées, etc…
## Les tests Tester c’est douter. notes: Ou pas. Avoir les tests qui passent avant de pousser, avant de merger une PR, avant de mettre en prod c’est un véritable confort.
## Les fixtures Pour tester on va avoir besoin de données de test. On appelera ça des « fixtures ».
## Les fixtures Le moyen simple de créer des fixtures est d’utiliser les données que vous avez crée via l’admin : ```bash ./manage.py dumpdata -o watch/fixtures/initial.json ``` notes: Créez le dossier d’abord ;)
## Les fixtures Profitez-en pour indiquer aux collègues dans le README qu’ils peuvent les charger aussi : ```bash git clone … cd project ./manage.py migrate ./manage.py loaddata initial ```
## Les tests Par défaut Django utilise la bibliothèque native `unittest`, on placera les tests de nos applications dans le dossier `tests` ou le fichier `tests.py` des applications. notes: Habituellement on dit que le nom du fichier n’a aucune importance, que seul le succès de l’import compte. Ici c’est le contraire, le nom du fichier doit commencer par `test` pour être trouvé.
## Les tests Les tests s’exécutent via : ```bash python manage.py test ```
## Les tests Tous nos tests seront des instances de `TestCase` : ```python from django.test import TestCase class WatchTestCase(TestCase): fixtures = ["initial"] def test_can_access_home(self): ... ```
## Les tests Pour vérifier que tout se passe bien `unittest` et `Django` nous fournissent une collection d’assertions : - assertTemplateUsed - assertRedirects - assertHTMLEqual - assertInHTML - ...
## Les tests Personnellement habitué à pytest j’abuse de `assert`. Attention, bien que ça fonctionne, les messages ne sont pas aussi lisibles.
## Les relations entres modèles On a déjà fait une `ForeignKey`, mais il existe aussi : - OneToOneField - ManyToManyField notes: Parler des cas d’usage, et parler de `thrue`.
## Users - Groups - Permissions - Authentication
## Static assets Chaque application peut déposer des fichiers dans son dossier `/static/`.
## Static assets En dev ils seront tous accessibles via l’URL `/static/`.
## Static assets En prod cependant il y a beaucoup plus efficace pour les servir : ```bash ./manage.py collectstatic ``` Puis configurez votre serveur HTTP (`nginx`/`apache2`) pour servir le dossier généré sans passer par Python.
## Deployment En parlant de prod, comment mettre un Django en prod ?
## Deployment Django utilise les protocoles `wsgi` (synchrone) et `asgi` (asynchrone), des classiques en Python.
## Deployment Attention, `runserver` c’est bien en dev, mais ça n’est pas fait pour la prod. notes: Sérieusement.
## Deployment `runserver` c’est un peu comme un groupe éléctrogène : - C’est vrai que ça fonctionne. - Ça dépanne quand on est seul dessus, chez soi. - Mais on alimente pas un quartier ou une ville avec. notes: Trouver mieux :D
## Deployment Nginx et Apache2 gèrent `wsgi`, d’autres serveurs aussi, probablement.
## Deployment Mais en production on ne veut pas juste une instance de Django, on en veut plusieurs, pour traiter plus de requêtes, pour ça on peut utiliser `gunicorn`, `uwsgi`, …
## Deployment `gunicorn` est un bon point de départ : ```bash pip install gunicorn gunicorn -w 16 project.wsgi ```
## Deployment `gunicorn` est bien derrière un `nginx` qui va s’occuper, entre autres, de la décapsulation HTTPS, ou de délivrer vos fichiers statiques sans passer par Python.
## Deployment ```nginx server { listen 443 http2 ssl; server_name example.com; include snippets/letsencrypt-example.com.conf; location /static { alias /opt/example.com/static/; } location / { proxy_pass http://unix:/run/example.com/wsgi.sock; proxy_set_header Host $host; proxy_set_header X-Forwarded-Protocol $scheme; } } ```
## Bonnes pratiques Pas de `order_by` / `fiter` / … dans les vues, rangez ça dans les modèles (dans des *managers* personalisés). Ça permet de nommer et de réutiliser.
## Bonnes pratiques La gestion des dépendances avec `pip-compile`.
## Bonnes pratiques Versionnez !!
## Bonnes pratiques Prennez 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 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 = ... ``` notes: Enfin, si on est **100% sûrs** que notre application ne sera pas réutilisée dans un contexte où une autre app redéfinit l'object `User`…
## La configuration de Django - `include local_settings.py` - `django-configurations` - `django-environ`
## Resources - La doc officielle. - Pour Django : https://ccbv.co.uk/ (Memo : « Classy Class-Based-View »). - Pour Django Rest Framework : https://www.cdrf.co (Memo : « Classy Django REST Framework »).