16 KiB
Django Initiation
par
Julien Palard julien@palard.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, …
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
:
python -m pip install django
La théorie — Projet
Pour démarrer un projet, une commande :
django-admin startproject project
La théorie — App
Une fois dans le projet, pour créer une application, une commande :
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 :
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 :
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
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 :
python manage.py createsuperuser
Terminé
On a terminé, on peut démarrer le serveur :
python manage.py runserver
La pratique
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
:
INSTALLED_APPS = [
"watch",
...,
...,
]
Les modèles
On va mettre celui-ci dans watch/models.py
.
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
.
from watch.models import Website
admin.site.register(Website)
Création de la DB
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 :
python manage.py createsuperuser
Terminé
On a terminé, on peut essayer maintenant ?
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 ».
::: 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 :
@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 :
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 :
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 :
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 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
:
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
Mieux :
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
:
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="utf-8" />
<title>Website Watch</title>
</head>
<body>
<h1>Website Watch</h1>
</body>
</html>
::: 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 ajoutais de la données provenant de la DB dans le template ?
Les vues
...
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
class WebsiteListView(ListView):
model = Website
Les templates
<body>
<h1>Website Watch</h1>
<ul>
{% for website in websites %}
<li>{{ website.host }} {% if website.is_up %}✓{% else %}✗{% endif %}</li>
{% endfor %}
</ul>
</body>
Les templates
Ça fonctionne, mais on ne veux 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
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
.
>>> 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, il sont accessible via
l'attribut objects
d'un modèle.
Ses opérations (des méthodes) renvoient des queryset
s.
In [2]: Website.objects
Out[2]: <django.db.models.manager.Manager at 0x7fa77a9a1500>
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
In [3]: Website.objects.all()
Out[3]: <QuerySet [<Website: mdk.fr>]>
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]: <QuerySet [<Check: mdk.fr is up>, <Check: mdk.fr is up>]>
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 :
class WebsiteForm(forms.Form):
host = forms.CharField(label='Website hostname',
max_length=512)
Forms
On le donne au template :
return render(
request,
"watch/index.html",
{
"websites": Website.objects.all(),
"form": WebsiteForm,
},
)
Forms
On l'affiche dans le template :
<form action="/" method="post">
{% csrf_token %}
{{ form }}
<input type="submit" value="Add">
</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 :
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 <input
à un
<textarea
ou vice-versa.
Validation
Une instance de formulaire a une méthode is_valid
:
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ées via l'admin :
./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 :
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 :
python manage.py test
Les tests
Tous nos tests seront des instances de TestCase
:
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 :
./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 le protocole wsgi
, un standard en Python.
Deployment
Attention, runserver
c'est bien en dev, mais ça n'est pas voué à partir en 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 :
pip install gunicorn
gunicorn -w 16 project.wsgi
Deployment
gunicorn
est bien derrière un nginx
qui va s'occuper, entre autre,
de la décapsulation HTTPS, ou de délivrer vos fichiers statiques sans
passer par Python.
Deployment
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 :
class User(django.contrib.auth.models.AbstractUser):
...
et :
AUTH_USER_MODEL = ...
La configuration de Django
include local_settings.py
django-configurations
django-environ
Resources
Pour Django on avait : https://ccbv.co.uk/ (Memo: « Classy Class-Based-View »).