Aller au contenu

Démarrer un projet Django

L’objectif de ce chapitre va être de démarrer un nouveau projet basé sur Django, en respectant les quelques points que nous avons abordés plus haut :

  • Gestion et épinglage des dépendances,
  • Utilisation de variables d’environnement,
  • Tests de base.

Nous en profiterons pour présenter brièvement quelques concepts nécessaires à l’obtention d’une application réellement basique - surtout pour préparer la prochaine partie :

Gestion et épinglage des dépendances

Une des manières recommandées par la Python Packaging Authority consiste à passer par pip ou poetry. Ce dernier présente plusieurs avantages, notamment au niveau de l’intégration et du nombre de fonctionnalités nativement présentes.

Django fonctionne sur un roulement de trois versions mineures pour une version majeure, clôturé par une version LTS (Long Term Support).

Django issues a “feature release” — one which introduces new features — roughly once every eight months. The current feature release series of Django is 5.1. Django issues “bugfix releases” — which fix bugs in one or more feature releases — roughly once each month. > As I write this, the latest bugfix release for the 5.1 feature release series is 5.1.3 (along with Django 5.0.9 for the 5.0 feature release series, and Django 4.2.16 for the 4.2 feature release series). The version number scheme is MAJOR.FEATURE.BUGFIX, where MAJOR, FEATURE, and BUGFIX are integers. The FEATURE component starts at 0, then increments to 1, then to 2, then MAJOR is incremented and FEATURE goes back to 0. BUGFIX starts at 0 with each new feature release, and increments for the bugfix releases for that feature release. Every feature release whose FEATURE component is 2 is a long-term support (“LTS”) release.

https://www.b-list.org/weblog/2024/nov/18/djangover/

This has been in effect since Django 2.0 was released, and the feature releases have been: 2.0, 2.1, 2.2 (LTS); 3.0, 3.1, 3.2 (LTS); 4.0, 4.1, 4.2 (LTS); 5.0, 5.1. Django 5.2 (LTS) is expected in April 2025, and then eight months later (if nothing is changed) will come Django 6.0.

Une bonne pratique consiste à ne viser que ces versions LTS, supportées durant trois ans, et à se prévoir une fenêtre de maintenance à intervalles reguliers. Les sauts de versions sont bien documentés, et ne nécessitent que quelques heures de travail pour passer à la suivante (par exemple ici pour le passage de la 3.0 à la 3.1).

Démarrage du projet

  • Créez un nouveau répertoire de travail (mkdir generic)
  • Déplacez l’invite de commande dans ce nouveau répertoire (cd generic)
  • Initialiser un nouvel espace pour Poetry (poetry init --no-interaction)
  • Ajoutez-y Django comme dépendance (poetry add django),
  • Démarrez votre serveur de développement (poetry run python manage.py runserver).
Fenêtre de terminal
mkdir generic && cd generic
poetry init --no-interaction --dependency django
poetry run django-admin startproject gwift .
poetry run python manage.py runserver

La commande poetry add django récupère la dernière version connue disponible dans les dépôts https://pypi.org/. Nous en avons déjà discuté : il est important de bien spécifier la version que vous souhaitez utiliser, sans quoi vous risquez de rencontrer des effets de bord.

L’installation de Django a ajouté un nouvel exécutable: django-admin, que l’on peut utiliser pour créer notre nouvel espace de travail. Par la suite, nous utiliserons plutôt la commande manage.py, qui constitue un wrapper autour de django-admin.

Pour démarrer notre projet, nous lançons poetry run django-admin startproject gwift au travers de Poetry :

Fenêtre de terminal
$ poetry run django-admin startproject gwift .

Notez bien le . à la fin de la ligne de commande, qui indique que nous installons notre nouvelle application dans le répertoire courant.

Cette action a pour effet de créer la structure de fichiers ci-dessous. Nous discuterons en annexe des différentes structures de projets possibles. Celle que nous conseillons est celle ci-dessous :

  • db.sqlite3
  • Répertoiregwift
    • __init__.py
    • asgi.py
    • settings.py
    • urls.py
    • wsgi.py
  • manage.py
  • poetry.lock
  • pyproject.toml

C’est dans ce répertoire que vont vivre tous les fichiers liés au projet. Le but est de faire en sorte que toutes les opérations de maintenance, déploiement, écriture et tests, puissent toutes être réalisées ou exécutées partir d’un unique point d’entrée.

L’utilité de ces fichiers est définie ci-dessous :

  • settings.py contient tous les paramètres globaux à notre projet,
  • urls.py contient les variables de routes, les adresses utilisées et les fonctions vers lesquelles elles pointent,
  • manage.py, pour toutes les commandes de gestion,
  • asgi.py contient la définition de l’interface ASGI, le protocole pour la passerelle asynchrone entre votre application et le serveur Web,
  • wsgi.py contient la définition de l’interface WSGI, qui permettra à votre serveur Web (Nginx, Apache, …) de faire un pont vers votre projet.

La version utilisée sera une bonne indication à prendre en considération pour nos dépendances, puisqu’en visant une version particulière, nous ne devrons pratiquement pas nous soucier (bon, un peu quand même, mais nous le verrons plus tard…) des dépendances à installer, pour peu que l’on reste sous un certain seuil.

Django

Comme nous l’avons vu ci-dessus, django-admin permet de créer un nouveau projet. Nous faisons ici une distinction entre un projet et une application:

  • Un projet représente l’ensemble des applications, paramètres, middlewares, dépendances, …, qui font que votre code fait ce qu’il est sensé faire. Il s’agit grosso modo d’un câblage de tous les composants entre eux.
  • Une application est un contexte d’exécution (vues, comportements, pages HTML, …), idéalement autonome, d’une partie du projet. Une application est supposée avoir une portée de réutilisation, même s’il ne sera pas toujours possible de viser une généricité parfaite.

Pour gwift, nous aurons :

.Projet Django vs Applications

  • Une première application pour la gestion des listes de souhaits et des éléments,
  • Une deuxième application pour la gestion des utilisateurs,
  • Voire une troisième application qui gérera les partages entre utilisateurs et listes.

Nous voyons également que la gestion des listes de souhaits et éléments aura besoin de la gestion des utilisateurs - elle n’est pas autonome -, tandis que la gestion des utilisateurs n’a aucune autre dépendance qu’elle-même.

Pour khana, nous pourrions avoir quelque chose comme ceci :

.Django Project vs Applications

En rouge, vous pouvez voir quelque chose que nous avons déjà vu: la gestion des utilisateurs et la possibilité qu’ils auront de communiquer entre eux. Ceci pourrait être commun aux deux projets. Nous pouvons clairement visualiser le principe de contexte pour une application : celle-ci viendra avec son modèle, ses tests, ses vues et son paramétrage et pourrait ainsi être réutilisée dans un autre projet. C’est en ça que consistent les paquets Django déjà disponibles: ce sont “simplement” de petites applications empaquetées et pouvant être réutilisées dans différents contextes (eg. Django-Rest-Framework, Django-Debug-Toolbar, …).

Le projet s’occupe donc principalement d’appliquer une couche de glue entre différentes applications, là où l’application est sensée composer une entité autonome et réutilisable.

Commandes de gestion

Le fichier manage.py que vous trouvez à la racine de votre projet est un wrapper sur les commandes django-admin. A partir de maintenant, nous n’utiliserons plus que celui-là pour tout ce qui touchera à la gestion de notre projet :

  • manage.py check pour vérifier (en surface…) que votre projet ne rencontre aucune erreur évidente
  • manage.py check --deploy, pour vérifier (en surface aussi) que l’application est prête pour un déploiement
  • manage.py runserver pour lancer un serveur de développement
  • manage.py test pour découvrir les tests unitaires disponibles et les lancer.

La liste complète peut être affichée avec manage.py help. Vous remarquerez que ces commandes sont groupées selon différentes catégories:

  • auth: création d’un nouveau super-utilisateur, changer le mot de passe pour un utilisateur existant,
  • django: vérifier la conformité du projet, lancer un shell, dumper les données de la base, effectuer une migration du schéma, … Ce sont des commandes d’administration générale.
  • sessions: suppressions des sessions en cours
  • staticfiles: gestion des fichiers statiques et lancement du serveur de développement.

Chaque section correspond à une application. En analysant le code, ces applications peuvent être trouvées sous django.contrib. Nous verrons plus tard comment ajouter de nouvelles commandes.

Si nous démarrons la commande poetry run python manage.py runserver, nous verrons la sortie console suivante :

Fenêtre de terminal
$ poetry run python manage.py runserver
Watching for file changes with StatReloader
Performing system checks...
System check identified no issues (0 silenced).
[...]
Starting development server at http://127.0.0.1:8000/
Quit the server with CTRL-BREAK.

Si nous nous rendons sur la page http://127.0.0.1:8000 (ou http://localhost:8000) comme le propose si gentiment notre (nouveau) meilleur ami, nous verrons ceci :

.python manage.py runserver (Non, ce n’est pas Challenger)

Minimal Viable Application

Maintenant que nous avons a vu à quoi servait manage.py, nous pouvons créer notre nouvelle application grâce à la commande poetry run python manage.py startapp <label>.

Notre première application servira à structurer des listes de souhaits, les éléments qui les composent et les pourcentages de participation que chaque utilisateur aura souhaité offrir. De manière générale, essayez de trouver un nom éloquent, court et qui résume bien ce que fait l’application. Pour nous, ce sera donc wish.

Fenêtre de terminal
$ cd gwift/wish
$ poetry run python manage.py startapp wish gwift/wish

Nous commençons par créer le répertoire dans lequel la nouvelle application sera créée, Nous exécutons ensuite le scaffolding de création de cette application, dans le répertoire pré-cité. Cette deuxième étape est également discutée en annexe, dans la section des différentes structures de projets.

Django nous a créé un répertoire wish, dans lequel nous trouvons les fichiers et dossiers suivants :

  • wish/__init__.py pour que notre répertoire wish soit converti en package Python,
  • wish/admin.py servira à structurer l’administration de notre application. Chaque information peut être gérée facilement au travers d’une interface générée à la volée par le framework. Nous y reviendrons par la suite.
  • wish/apps.py qui contient la configuration de l’application et qui permet notamment de fixer un nom ou un libellé https://docs.djangoproject.com/en/stable/ref/applications/
  • wish/migrations/ est le dossier dans lequel seront stockées toutes les différentes migrations de notre application (= toutes les modifications que nous apporterons aux données que nous souhaiterons manipuler)
  • wish/models.py représentera et structurera nos données. Ce modèle est intimement lié aux migrations.
  • wish/tests.py pour les tests unitaires.

La structure de vos répertoires devient celle-ci :

  • db.sqlite3
  • Répertoiregwift
    • __init__.py
    • asgi.py
    • settings.py
    • urls.py
    • Répertoirewish
      • __init__.py
      • admin.py
      • apps.py
      • Répertoiremigrations
        • __init__.py
        • models.py
        • tests.py
        • views.py
      • wsgi.py
    • manage.py
    • poetry.lock
    • pyproject.toml

C’est dans ce répertoire que nous retrouvons les fichiers composant notre application.

Fonctionnement général

Le métier de programmeur est devenu de plus en plus complexe. Il y a 20 ans, nous pouvions nous contenter d’une simple page PHP dans laquelle nous mixions l’ensemble des actions à réaliser : requêtes en bases de données, construction de la page, … La recherche d’une solution à un problème n’était pas spécialement plus complexe - dans la mesure où le rendu des enregistrements en direct n’était finalement qu’une forme un chouia plus évoluée du print() ou des System.out.println() - mais c’était l’évolutivité des applications qui en prenait un coup : une grosse partie des tâches étaient dupliquées entre les différentes pages, et l’ajout d’une nouvelle fonctionnalité était relativement ardue.

Django (et d’autres frameworks) résolvent ce problème en se basant ouvertement sur le principe de Don’t repeat yourself (((DRY))) Chaque morceau de code ne doit apparaitre qu’une seule fois, afin de limiter au maximum la redite (et donc, l’application d’un même correctif à différents endroits).

Le chemin parcouru par une requête peut être repris schématiquement comme si un utilisateur ou un visiteur souhaite accéder à une URL hébergée et servie par notre application. Nous prendrons l’exemple de l’URL fictive https://gwift/wishes/91827. Lorsque cette URL “arrive” dans notre application, son point d’entrée se trouvera au niveau des fichiers asgi.py ou wsgi.py. Nous verrons cette partie plus tard, et nous pouvons nous concentrer sur le chemin interne qu’elle va parcourir.

Etape 0 - La première étape consiste à vérifier que cette URL répond à un schéma que nous avons défini dans le fichier gwift/urls.py. Ce fichier reprendra

config/urls.py
from django.contrib import admin
from django.urls import path
from gwift.views import wish_details
urlpatterns = [
path('admin/', admin.site.urls),
path("wishes/<int:wish_id>", wish_details),
]

Etape 1 - Si ce n’est pas le cas, l’application n’ira pas plus loin et retournera une erreur à l’utilisateur.

Etape 2 - Django va parcourir l’ensemble des patterns présents dans le fichier urls.py et s’arrêtera sur le premier qui correspondra à la requête qu’il a reçue. Ce cas est relativement trivial : la requête /wishes/91827 a une correspondance au niveau de la ligne path("wishes/<int:wish_id> dans l’exemple ci-dessous. Django va alors appeler la fonction correspondant au deuxième paramètre que nous trouvons dans l’appel à la fonction path(), c’est-à-dire ici la fonction wish_details, du module gwift.views.

  • Nous importons la fonction wish_details du module gwift.views,
  • Champomy et cotillons ! Nous avons une correspondance avec wishes/details/91827.

Le module gwift.views qui se trouve dans le fichier gwift/views.py peut ressembler à ceci:

gwift/views.py
[...]
from datetime import datetime
def wishes_details(request: HttpRequest, wish_id: int) -> HttpResponse:
context = {
"user_name": "Bond,"
"user_first_name": "James",
"generated_at": datetime.now()
}
return render(
request,
"wish_details.html",
context
)

Pour résumer, cette fonction permet :

  • De construire un contexte, qui est représenté sous la forme d’un dictionnaire associant des clés à des valeurs. Les clés sont respectivement user_name, user_first_name et now, tandis que leurs valeurs respectives sont Bond, James et le moment présent footnote:[Non, pas celui d’Eckhart Tolle].
  • Nous passons ensuite ce dictionnaire à un canevas, wish_details.html, que l’on trouve normalement dans le répertoire templates de notre projet, ou dans le répertoire templates propre à notre application.
  • L’application du contexte sur le canevas au travers de la fonction render nous donne un résultat formaté.
templates/wish_details.html
<!DOCTYPE html>
<html>
<head>
<title>Page title</title>
</head>
<body>
<h1>Hi!</h1>
<p>My name is {{ user_name }}. {{ user_first_name }} {{ user_name }}.</p>
<p>This page was generated at {{ generated_at }}</p>
</body>
</html>

Après application de notre contexte sur ce template, nous obtiendrons ce document, qui sera renvoyé au navigateur de l’utilisateur qui aura fait la requête initiale :

<!DOCTYPE html>
<html>
<head>
<title>Page title</title>
</head>
<body>
<h1>Hi!</h1>
<p>My name is Bond. James Bond.</p>
<p>This page was generated at 2027-03-19 19:47:38</p>
</body>
</html>

Conclusions

Comme nous l’avons vu dans la première partie, Django est un framework complet, intégrant tous les mécanismes nécessaires à la bonne évolution d’une application. Il est possible de démarrer petit, et de suivre l’évolution des besoins en fonction de la charge estimée ou ressentie, d’ajouter un mécanisme de mise en cache, des logiciels de suivi, …