formations/django-initiation/django.md

16 KiB
Raw Blame History

Django Initiation

par

Julien Palard julien@palard.fr

https://mdk.fr

notes:

Introduce yourself!

Django

Django est une infrastructure dapplications web populaire et robuste.

The web framework for perfectionists with deadlines.

Django : qui lutilise ?

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 dune 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 dexpliquer les fields (leur relation avec la DB), et les differents endroits ou Django peut reutiliser cette information (widgets, validation, ...).

Première interface dadmin

En une ligne, pourquoi pas :

admin.site.register(Website)

La théorie — la DB

  • PostgreSQL
  • MySQL
  • sqlite
  • ...

notes:

Leur faire croire 2 secondes quon va devoir sinstaller et se configurer un serveur de base de donnée :D

Leur expliquer que sqlite est utilisé dans les applications : pas besoin dinstaller un postgresql pour utiliser Firefox, pourtant Firefox a besoin dune 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 cest surtout daller tester ça :)

Linterface dadministration

On a une DB, mais pas encore dutilisateur 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 dexpliquer larborescence, de se promener, dy lire les commentaires.

La pratique

Ajout de lapp 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 dadmin

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 dabord.

Linterface dadministration

On a une DB, mais pas encore dutilisateur 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 linterface dadmin, créer quelques sites...

Linterface dadministration

Les modèles, leurs fields ne servent donc pas qua lORM, cette interface dadmin nous a demandé une ligne de code.

notes:

Si ce nest pas déjà fait, leur faire ajouter des __str__.

Astuce

On peut passer beaucoup de temps à peaufiner linterface dadmin, 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 dun models.ForeignKey, RTFM.

Ladmin

Ajoutez une interface dadmin pour ce modèle, et ajoutez à la main quelques « checks ».

notes:

Les faire tester ça.

Personalisons

Dans chaque modèle, un __str__ aide ladmin à être lisible.

Personalisons

Dans admin.py on peut préciser les colonnes quon 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 daccueil :

from watch import views

urlpatterns = [
   ...,
   ...,
   path("", views.index, name="index"),
]

notes:

Cest un path, un chemin, cest 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:

Cest 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 daccueil de linterface dadmin ou de la page daccueil de votre application ?

namespaces

Avec les espaces de nommage, on a donc :

  • admin:index
  • watch:index

sans ambiguité.

notes:

Utiliez-en, cest 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:

Cest bien mais écrire du HTML dans du Python cest 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 ajoutait de la donnée 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 lORM en passant.

Les vues

Digression :

Il existe aussi des vues basées sur des classes, pouvant sappuyer 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 veut pas répéter lentê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

Lajouter dans settings.py et urls.py.

LORM

LORM

Cest loccasion 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, ils sont accessibles via lattribut objects dun modèle.

Ses opérations (des méthodes) renvoient des querysets.

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 cest un "lazy select" : cest 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 ladmin, 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 laffiche 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 dun 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 cest douter.

notes:

Ou pas. Avoir les tests qui passent avant de pousser, avant de merger une PR, avant de mettre en prod cest 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 dutiliser les données que vous avez crée via ladmin :

./manage.py dumpdata -o watch/fixtures/initial.json

notes:

Créez le dossier dabord ;)

Les fixtures

Profitez-en pour indiquer aux collègues dans le README quils 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 na aucune importance, que seul le succès de limport compte. Ici cest le contraire, le nom du fichier doit commencer par test pour être trouvé.

Les tests

Les tests sexé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 dassertions :

  • assertTemplateUsed
  • assertRedirects
  • assertHTMLEqual
  • assertInHTML
  • ...

Les tests

Personnellement habitué à pytest jabuse 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 dusage, 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 lURL /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 les protocoles wsgi (synchrone) et asgi (asynchrone), des classiques en Python.

Deployment

Attention, runserver cest bien en dev, mais ça nest pas fait pour la prod.

notes:

Sérieusement.

Deployment

runserver cest un peu comme un groupe éléctrogène :

  • Cest 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, dautres 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 soccuper, entre autres, 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 lobjet User, même si on pense ne pas en avoir besoin :

class User(django.contrib.auth.models.AbstractUser):
    ...

et :

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 »).