613 lines
26 KiB
TeX
613 lines
26 KiB
TeX
\chapter{Démarrer un nouveau projet}
|
||
|
||
|
||
Django fonctionne sur un
|
||
\href{https://docs.djangoproject.com/en/dev/internals/release-process/}{roulement de trois versions mineures pour une version majeure}, clôturé par une version LTS (\emph{Long Term Support}).
|
||
|
||
\section{Gestion des dépendances}
|
||
|
||
Comme nous en avons déjà discuté, PIP est la solution que nous avons choisie pour la gestion de nos dépendances.
|
||
Pour installer une nouvelle librairie, vous pouvez simplement passer par la commande \texttt{pip\ install\ \textless{}my\_awesome\_library\textgreater{}}.
|
||
Dans le cas de Django, et après avoir activé l'environnement, nous pouvons à présent y installer Django. Comme expliqué ci-dessus, la librairie restera indépendante du reste du système, et ne polluera aucun autre projet. nous exécuterons donc la commande suivante:
|
||
|
||
\begin{verbatim}
|
||
$ source ~/.venvs/gwift-env/bin/activate # ou ~/.venvs/gwift-
|
||
env/Scrips/activate.bat pour Windows.
|
||
$ pip install django
|
||
Collecting django
|
||
Downloading Django-3.1.4
|
||
100% |################################|
|
||
Installing collected packages: django
|
||
Successfully installed django-3.1.4
|
||
\end{verbatim}
|
||
|
||
|
||
Ici, la commande \texttt{pip\ install\ django} récupère la \textbf{dernière version connue disponible dans les dépôts \url{https://pypi.org/}} (sauf si vous en avez définis d'autres. Mais c'est hors sujet).
|
||
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: \texttt{django-admin}, que l'on peut utiliser pour créer notre nouvel espace de travail. Par la suite, nous utiliserons \texttt{manage.py}, qui constitue un \textbf{wrapper} autour de \texttt{django-admin}.
|
||
|
||
Pour démarrer notre projet, nous lançons
|
||
\texttt{django-admin\ startproject\ gwift}:
|
||
|
||
\begin{verbatim}
|
||
$ django-admin startproject gwift
|
||
\end{verbatim}
|
||
|
||
Cette action a pour effet de créer un nouveau dossier \texttt{gwift},
|
||
dans lequel nous trouvons la structure suivante:
|
||
|
||
\begin{verbatim}
|
||
$ tree gwift
|
||
gwift
|
||
-- gwift
|
||
----- asgi.py
|
||
----- __init__.py
|
||
----- settings.py
|
||
----- urls.py
|
||
----- wsgi.py
|
||
-- manage.py
|
||
\end{verbatim}
|
||
|
||
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
|
||
(maintenance, déploiement, écriture, tests, \ldots\hspace{0pt}) puissent
|
||
se faire à partir d'un seul point d'entrée.
|
||
|
||
L'utilité de ces fichiers est définie ci-dessous:
|
||
|
||
\begin{itemize}
|
||
\item
|
||
\texttt{settings.py} contient tous les paramètres globaux à notre
|
||
projet.
|
||
\item
|
||
\texttt{urls.py} contient les variables de routes, les adresses
|
||
utilisées et les fonctions vers lesquelles elles pointent.
|
||
\item
|
||
\texttt{manage.py}, pour toutes les commandes de gestion.
|
||
\item
|
||
\texttt{asgi.py} contient la définition de l'interface
|
||
\href{https://en.wikipedia.org/wiki/Asynchronous_Server_Gateway_Interface}{ASGI},
|
||
le protocole pour la passerelle asynchrone entre votre application et
|
||
le serveur Web.
|
||
\item
|
||
\texttt{wsgi.py} contient la définition de l'interface
|
||
\href{https://en.wikipedia.org/wiki/Web_Server_Gateway_Interface}{WSGI},
|
||
qui permettra à votre serveur Web (Nginx, Apache, \ldots\hspace{0pt})
|
||
de faire un pont vers votre projet.
|
||
\end{itemize}
|
||
|
||
Indiquer qu'il est possible d'avoir plusieurs structures de dossiers et
|
||
qu'il n'y a pas de "magie" derrière toutes ces commandes.
|
||
|
||
Tant que nous y sommes, nous pouvons ajouter un répertoire dans lequel
|
||
nous stockerons les dépendances et un fichier README:
|
||
|
||
TODO
|
||
|
||
Comme nous venons d'ajouter une dépendance à notre projet, profitons-en pour créer un fichier reprenant tous les dépendances de notre projet.
|
||
Celles-ci sont normalement placées dans un fichier \texttt{requirements.txt}.
|
||
Dans un premier temps, ce fichier peut être placé directement à la racine du projet, mais on préférera rapidement le déplacer dans un sous-répertoire spécifique (\texttt{requirements}), afin de grouper les dépendances en fonction de leur environnement de destination:
|
||
|
||
\begin{itemize}
|
||
\item
|
||
\texttt{base.txt}
|
||
\item
|
||
\texttt{dev.txt}
|
||
\item
|
||
\texttt{production.txt}
|
||
\end{itemize}
|
||
|
||
Au début de chaque fichier, il suffit d'ajouter la ligne \texttt{-r\ base.txt}, puis de lancer l'installation grâce à un \texttt{pip\ install\ -r\ \textless{}nom\ du\ fichier\textgreater{}}.
|
||
De cette manière, il est tout à fait acceptable de n'installer \texttt{flake8} et \texttt{django-debug-toolbar} qu'en développement par exemple.
|
||
Dans l'immédiat, nous allons ajouter \texttt{django} dans une version égale à la version 3.2 dans le fichier \texttt{requirements/base.txt}.
|
||
|
||
\begin{verbatim}
|
||
$ echo 'django==3.2' > requirements/base.txt
|
||
$ echo '-r base.txt' > requirements/prod.txt
|
||
$ echo '-r base.txt' > requirements/dev.txt
|
||
\end{verbatim}
|
||
|
||
Une bonne pratique consiste à également placer un fichier \texttt{requirements.txt} à la racine du projet, et dans lequel nous retrouverons le contenu \texttt{-r requirements/production.txt} (notamment pour Heroku).
|
||
|
||
Prenez directement l'habitude de spécifier la version ou les versions compatibles: les librairies que vous utilisez comme dépendances évoluent, de la même manière que vos projets.
|
||
Pour être sûr et certain le code que vous avez écrit continue à fonctionner, spécifiez la version de chaque librairie de dépendances.
|
||
Entre deux versions d'une même librairie, des fonctions sont cassées, certaines signatures sont modifiées, des comportements sont altérés, etc.
|
||
Il suffit de parcourirles pages de \emph{Changements incompatibles avec les anciennes versions dans Django}
|
||
\href{https://docs.djangoproject.com/fr/3.1/releases/3.0/}{(par exemple ici pour le passage de la 3.0 à la 3.1)} pour réaliser que certaines opérations ne sont pas anodines, et que sans filet de sécurité, c'est le
|
||
mur assuré.
|
||
Avec les mécanismes d'intégration continue et de tests unitaires, nous verrons plus loin comment se prémunir d'un changement inattendu.
|
||
|
||
|
||
\includegraphics{images/django-support-lts.png}
|
||
|
||
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\ldots\hspace{0pt}) des dépendances à installer, pour peu que l'on reste sous un certain seuil.
|
||
|
||
Dans les étapes ci-dessous, nous épinglerons une version LTS afin de nous assurer une certaine sérénité d'esprit (= dont nous ne occuperons pas pendant les 3 prochaines années).
|
||
|
||
\section{Django}
|
||
|
||
Comme nous l'avons vu ci-dessus, \texttt{django-admin} permet de créer un nouveau projet.
|
||
Nous faisons ici une distinction entre un \textbf{projet} et une \textbf{application}:
|
||
|
||
\begin{itemize}
|
||
\item
|
||
\textbf{Un projet} représente l'ensemble des applications, paramètres,
|
||
pages HTML, middlewares, dépendances, etc., qui font que votre code
|
||
fait ce qu'il est sensé faire.
|
||
\item
|
||
\textbf{Une application} est un contexte d'exécution, idéalement
|
||
autonome, d'une partie du projet.
|
||
\end{itemize}
|
||
|
||
Pour \texttt{gwift}, nous aurons:
|
||
|
||
\begin{figure}
|
||
\centering
|
||
\includegraphics{images/django/django-project-vs-apps-gwift.png}
|
||
\caption{Django Projet vs Applications}
|
||
\end{figure}
|
||
|
||
\begin{enumerate}
|
||
\item
|
||
Une première application pour la gestion des listes de souhaits et des
|
||
éléments,
|
||
\item
|
||
Une deuxième application pour la gestion des utilisateurs,
|
||
\item
|
||
Voire une troisième application qui gérera les partages entre
|
||
utilisateurs et listes.
|
||
\end{enumerate}
|
||
|
||
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 \texttt{khana}, nous pourrions avoir quelque chose comme ceci:
|
||
|
||
\begin{figure}
|
||
\centering
|
||
\includegraphics{images/django/django-project-vs-apps-khana.png}
|
||
\caption{Django Project vs Applications}
|
||
\end{figure}
|
||
|
||
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 applications.
|
||
Nous pouvons clairement visualiser le principe de \textbf{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 \href{https://www.djangopackages.com/}{paquets Django} déjà disponibles:
|
||
ce sont "\emph{simplement}" de petites applications empaquetées et pouvant être réutilisées dans différents contextes (eg. \href{https://github.com/tomchristie/django-rest-framework}{Django-Rest-Framework}, \href{https://github.com/django-debug-toolbar/django-debug-toolbar}{Django-Debug-Toolbar}, ...
|
||
|
||
\subsection{manage.py}
|
||
|
||
Le fichier \texttt{manage.py} que vous trouvez à la racine de votre projet est un \textbf{wrapper} sur les commandes \texttt{django-admin}.
|
||
A partir de maintenant, nous n'utiliserons plus que celui-là pour tout
|
||
ce qui touchera à la gestion de notre projet:
|
||
|
||
\begin{itemize}
|
||
\item
|
||
\texttt{manage.py\ check} pour vérifier (en surface\ldots\hspace{0pt})
|
||
que votre projet ne rencontre aucune erreur évidente
|
||
\item
|
||
\texttt{manage.py\ check\ -\/-deploy}, pour vérifier (en surface
|
||
aussi) que l'application est prête pour un déploiement
|
||
\item
|
||
\texttt{manage.py\ runserver} pour lancer un serveur de développement
|
||
\item
|
||
\texttt{manage.py\ test} pour découvrir les tests unitaires
|
||
disponibles et les lancer.
|
||
\end{itemize}
|
||
|
||
La liste complète peut être affichée avec \texttt{manage.py\ help}. Vous
|
||
remarquerez que ces commandes sont groupées selon différentes
|
||
catégories:
|
||
|
||
\begin{itemize}
|
||
\item
|
||
\textbf{auth}: création d'un nouveau super-utilisateur, changer le mot de passe pour un utilisateur existant.
|
||
\item
|
||
\textbf{django}: vérifier la \textbf{compliance} du projet, lancer un \textbf{shell}, \textbf{dumper} les données de la base, effectuer une migration du schéma, ...
|
||
\item
|
||
\textbf{sessions}: suppressions des sessions en cours
|
||
\item
|
||
\textbf{staticfiles}: gestion des fichiers statiques et lancement du serveur de développement.
|
||
\end{itemize}
|
||
|
||
Nous verrons plus tard comment ajouter de nouvelles commandes.
|
||
|
||
Si nous démarrons la commande \texttt{python\ manage.py\ runserver},
|
||
nous verrons la sortie console suivante:
|
||
|
||
\begin{verbatim}
|
||
$ python manage.py runserver
|
||
Watching for file changes with StatReloader
|
||
Performing system checks...
|
||
System check identified no issues (0 silenced).
|
||
[...]
|
||
December 15, 2020 - 20:45:07
|
||
Django version 3.1.4, using settings 'gwift.settings'
|
||
Starting development server at http://127.0.0.1:8000/
|
||
Quit the server with CTRL-BREAK.
|
||
\end{verbatim}
|
||
|
||
Si nous nous rendons sur la page \url{http://127.0.0.1:8000} (ou \url{http://localhost:8000}) comme le propose si gentiment notre (nouveau) meilleur ami, nous verrons ceci:
|
||
|
||
\begin{figure}
|
||
\centering
|
||
\includegraphics{images/django/manage-runserver.png}
|
||
\caption{python manage.py runserver (Non, ce n'est pas Challenger)}
|
||
\end{figure}
|
||
|
||
Nous avons mis un morceau de la sortie console entre crochet \texttt{{[}\ldots{}\hspace{0pt}{]}} ci-dessus, car elle concerne les migrations.
|
||
Si vous avez suivi les étapes jusqu'ici, vous avez également dû voir un message type \texttt{You\ have\ 18\ unapplied\ migration(s).\ {[}\ldots{}\hspace{0pt}{]}\ Run\ \textquotesingle{}python\ manage.py\ migrate\textquotesingle{}\ to\ apply\ them.}
|
||
Cela concerne les migrations, et c'est un point que nous verrons un peu plus tard.
|
||
|
||
\section{Nouvelle application}
|
||
|
||
Maintenant que nous avons a vu à quoi servait \texttt{manage.py}, nous pouvons créer notre nouvelle application grâce à la commande \texttt{manage.py\ startapp\ \textless{}label\textgreater{}}.
|
||
|
||
Notre première application servira à structurer les listes de souhaits, les éléments qui les composent et les parties que chaque utilisateur pourra 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 \texttt{wish}.
|
||
|
||
C'est parti pour \texttt{manage.py\ startapp\ wish}!
|
||
|
||
\begin{verbatim}
|
||
$ python manage.py startapp wish
|
||
\end{verbatim}
|
||
|
||
Résultat? Django nous a créé un répertoire \texttt{wish}, dans lequel nous trouvons les fichiers et dossiers suivants:
|
||
|
||
\begin{itemize}
|
||
\item
|
||
\texttt{wish/init.py} pour que notre répertoire \texttt{wish} soit converti en package Python.
|
||
\item
|
||
\texttt{wish/admin.py} servira à structurer l'administration de notre application.
|
||
Chaque information peut être administrée facilement au travers d'une interface générée à la volée par le framework.
|
||
Nous y reviendrons par la suite.
|
||
\item
|
||
\texttt{wish/apps.py} qui contient la configuration de l'application et qui permet notamment de fixer un nom ou un libellé \url{https://docs.djangoproject.com/en/stable/ref/applications/}
|
||
\item
|
||
\texttt{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)
|
||
\item
|
||
\texttt{wish/models.py} représentera et structurera nos données, et est intimement lié aux migrations.
|
||
\item
|
||
\texttt{wish/tests.py} pour les tests unitaires.
|
||
\end{itemize}
|
||
|
||
Par soucis de clarté, vous pouvez déplacer ce nouveau répertoire \texttt{wish} dans votre répertoire \texttt{gwift} existant.
|
||
C'est une forme de convention.
|
||
|
||
La structure de vos répertoires devient celle-ci:
|
||
|
||
TODO
|
||
|
||
|
||
Notre application a bien été créée, et nous l'avons déplacée dans le répertoire \texttt{gwift} !
|
||
|
||
\section{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 actios à réaliser: requêtes en bases de données, construction de la page, ...
|
||
La recherche d'une solution a 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 \texttt{print()} ou des \texttt{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 cadriciels) résolvent ce problème en se basant ouvertement sur le principe de \texttt{Don’t\ repeat\ yourself} \footnote{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 est expliqué en (petits) détails ci-dessous.
|
||
|
||
\begin{figure}
|
||
\centering
|
||
\includegraphics{images/diagrams/django-how-it-works.png}
|
||
\caption{How it works}
|
||
\end{figure}
|
||
|
||
\textbf{1. Un utilisateur ou un visiteur souhaite accéder à une URL hébergée et servie par notre application}.
|
||
Ici, nous prenons l'exemple de l'URL fictive \texttt{https://gwift/wishes/91827}.
|
||
Lorsque cette URL "arrive" dans notre application, son point d'entrée se trouvera au niveau des fichiers \texttt{asgi.py} ou \texttt{wsgi.py}.
|
||
Nous verrons cette partie plus tard, et nous pouvons nous concentrer sur le chemin interne qu'elle va parcourir.
|
||
|
||
\textbf{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 \texttt{gwift/urls.py}.
|
||
|
||
\textbf{Etape 1} - Si ce n'est pas le cas, l'application n'ira pas plus loin et retournera une erreur à l'utilisateur.
|
||
|
||
\textbf{Etape 2} - Django va parcourir l'ensemble des \emph{patterns} présents dans le fichier \texttt{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 \texttt{/wishes/91827} a une correspondance au
|
||
niveau de la ligne \texttt{path("wishes/\textless{}int:wish\_id\textgreater{}} dans l'exemple ci-dessous. Django va alors appeler la fonction \footnote{Qui ne sera pas toujours une fonction. Django s'attend à trouver un \emph{callable}, c'est-à-dire n'importe quel élément qu'il peut appeler comme une fonction.} associée à ce \emph{pattern}, c'est-à-dire \texttt{wish\_details} du module \texttt{gwift.views}.
|
||
|
||
\begin{minted}{Python}
|
||
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),
|
||
]
|
||
\end{minted}
|
||
|
||
|
||
\begin{itemize}
|
||
\item
|
||
Nous importons la fonction \texttt{wish\_details} du module
|
||
\texttt{gwift.views}
|
||
\item
|
||
Champomy et cotillons! Nous avons une correspondance avec
|
||
\texttt{wishes/details/91827}
|
||
\end{itemize}
|
||
|
||
TODO: En fait, il faudrait quand même s'occuper du modèle ici.
|
||
TODO: et de la mise en place de l'administration, parce que nous en aurons besoin pour les étapes de déploiement.
|
||
|
||
Nous n'allons pas nous occuper de l'accès à la base de données pour le moment (nous nous en occuperons dans un prochain chapitre) et nous nous contenterons de remplir un canevas avec un ensemble de données.
|
||
|
||
Le module \texttt{gwift.views} qui se trouve dans le fichier \texttt{gwift/views.py} peut ressembler à ceci:
|
||
|
||
\begin{minted}{Python}
|
||
[...]
|
||
|
||
from datetime import datetime
|
||
|
||
|
||
def wishes_details(request: HttpRequest, wish_id: int) -> HttpResponse:
|
||
context = {
|
||
"user_name": "Bond,"
|
||
"user_first_name": "James",
|
||
"now": datetime.now()
|
||
}
|
||
return render(
|
||
request,
|
||
"wish_details.html",
|
||
context
|
||
)
|
||
\end{minted}
|
||
|
||
|
||
Pour résumer, cette fonction permet:
|
||
|
||
\begin{enumerate}
|
||
\def\labelenumi{\arabic{enumi}.}
|
||
\item
|
||
De construire un \emph{contexte}, qui est représenté sous la forme d'un dictionnaire associant des clés à des valeurs. Les clés sont respectivement \texttt{user\_name}, \texttt{user\_first\_name} et \texttt{now}, tandis que leurs valeurs respectives sont \texttt{Bond}, \texttt{James} et le \texttt{moment\ présent} \footnote{Non, pas celui d'Eckhart Tolle}.
|
||
\item
|
||
Nous passons ensuite ce dictionnaire à un canevas, \texttt{wish\_details.html}
|
||
\item
|
||
L'application du contexte sur le canevas nous donne un résultat.
|
||
\end{enumerate}
|
||
|
||
\begin{minted}{html}
|
||
<!-- fichier 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 {{ now }}</p>
|
||
</body>
|
||
</html>
|
||
\end{minted}
|
||
|
||
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:
|
||
|
||
\begin{minted}{html}
|
||
<!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>
|
||
\end{minted}
|
||
|
||
\begin{figure}
|
||
\centering
|
||
\includegraphics{images/django/django-first-template.png}
|
||
\caption{Résultat}
|
||
\end{figure}
|
||
|
||
\section{Configuration globale}
|
||
|
||
\subsection{setup.cfg}
|
||
|
||
→ Faire le lien avec les settings → Faire le lien avec les douze
|
||
facteurs → Construction du fichier setup.cfg
|
||
|
||
\begin{verbatim}
|
||
[flake8]
|
||
max-line-length = 100
|
||
exclude = .tox,.git,*/migrations/*,*/static/CACHE/*,docs,node_modules,venv
|
||
|
||
[pycodestyle]
|
||
max-line-length = 100
|
||
exclude = .tox,.git,*/migrations/*,*/static/CACHE/*,docs,node_modules,venv
|
||
|
||
[mypy]
|
||
python_version = 3.8
|
||
check_untyped_defs = True
|
||
ignore_missing_imports = True
|
||
warn_unused_ignores = True
|
||
warn_redundant_casts = True
|
||
warn_unused_configs = True
|
||
plugins = mypy_django_plugin.main
|
||
|
||
[mypy.plugins.django-stubs]
|
||
django_settings_module = config.settings.test
|
||
|
||
[mypy-*.migrations.*]
|
||
# Django migrations should not produce any errors:
|
||
ignore_errors = True
|
||
|
||
[coverage:run]
|
||
include = khana/*
|
||
omit = *migrations*, *tests*
|
||
plugins =
|
||
django_coverage_plugin
|
||
\end{verbatim}
|
||
|
||
\subsection{Structure finale}
|
||
|
||
Nous avons donc la structure finale pour notre environnement de travail:
|
||
|
||
\subsection{Cookie-cutter}
|
||
|
||
Pfiou! Ca en fait des commandes et du boulot pour "juste" démarrer un nouveau projet, non? Sachant qu'en plus, nous avons dû modifier des fichiers, déplacer des dossiers, ajouter des dépendances, configurer une
|
||
base de données, ...
|
||
|
||
Bonne nouvelle! Il existe des générateurs, permettant de démarrer rapidement un nouveau projet sans (trop) se prendre la tête.
|
||
Le plus connu (et le plus personnalisable) est \href{https://cookiecutter.readthedocs.io/}{Cookie-Cutter}, qui se base sur des canevas \emph{type \href{https://pypi.org/project/Jinja2/}{Jinja2}}, pour créer une
|
||
arborescence de dossiers et fichiers conformes à votre manière de travailler.
|
||
Et si vous avez la flemme de créer votre propre canevas, vous pouvez utiliser \href{https://cookiecutter-django.readthedocs.io}{ceux qui existent déjà}.
|
||
|
||
Pour démarrer, créez un environnement virtuel (comme d'habitude):
|
||
|
||
\begin{verbatim}
|
||
python -m venv .venvs\cookie-cutter-khana
|
||
.venvs\cookie-cutter-khana\Scripts\activate.bat
|
||
|
||
(cookie-cutter-khana) $ pip install cookiecutter
|
||
Collecting cookiecutter
|
||
[...]
|
||
Successfully installed Jinja2-2.11.2 MarkupSafe-1.1.1 arrow-0.17.0
|
||
binaryornot-0.4.4 certifi-2020.12.5 chardet-4.0.0 click-7.1.2 cookiecutter-
|
||
1.7.2 idna-2.10 jinja2-time-0.2.0 poyo-0.5.0 python-dateutil-2.8.1 python-
|
||
slugify-4.0.1 requests-2.25.1 six-1.15.0 text-unidecode-1.3 urllib3-1.26.2
|
||
(cookie-cutter-khana) $ cookiecutter https://github.com/pydanny/cookiecutter-
|
||
django
|
||
[...]
|
||
[SUCCESS]: Project initialized, keep up the good work!
|
||
\end{verbatim}
|
||
|
||
Si vous explorez les différents fichiers, vous trouverez beaucoup de similitudes avec la configuration que nous vous proposions ci-dessus.
|
||
En fonction de votre expérience, vous serez tenté de modifier certains paramètres, pour faire correspondre ces sources avec votre utilisation ou vos habitudes.
|
||
|
||
Il est aussi possible d'utiliser l'argument \texttt{-\/-template}, suivie d'un argument reprenant le nom de votre projet (\texttt{\textless{}my\_project\textgreater{}}), lors de l'initialisation d'un projet avec la commande \texttt{startproject} de \texttt{django-admin}, afin de calquer votre arborescence sur un projet
|
||
existant.
|
||
La \href{https://docs.djangoproject.com/en/stable/ref/django-admin/\#startproject}{documentation} à ce sujet est assez complète.
|
||
|
||
\begin{verbatim}
|
||
django-admin.py startproject --template=https://[...].zip <my_project>
|
||
\end{verbatim}
|
||
|
||
\section{Tests unitaires}
|
||
|
||
Chaque application est créée par défaut avec un fichier \textbf{tests.py}, qui inclut la classe \texttt{TestCase} depuis le package \texttt{django.test}:
|
||
|
||
On a deux choix ici:
|
||
|
||
\begin{enumerate}
|
||
\item Utiliser les librairies de test de Django
|
||
\item Utiliser Pytest
|
||
\end{enumerate}
|
||
|
||
\subsection{django.test}
|
||
|
||
\begin{listing}[H]
|
||
\begin{minted}{Python}
|
||
from django.test import TestCase
|
||
class TestModel(TestCase):
|
||
def test_str(self):
|
||
raise NotImplementedError('Not implemented yet')
|
||
\end{minted}
|
||
\end{listing}
|
||
|
||
\subsection{Pytest}
|
||
|
||
\subsection{Couverture de code}
|
||
|
||
La couverture de code est une analyse qui donne un pourcentage lié à la quantité de code couvert par les tests. Il ne s'agit pas de vérifier que le code est bien testé, mais de vérifier quelle partie du code est testée.
|
||
Le paquet coverage se charge d’évaluer le pourcentage de code couvert par les tests.
|
||
Avec pytest, il convient d’utiliser le paquet pytest-cov, suivi de la commande pytest
|
||
--cov=gwift tests/.
|
||
Si vous préférez rester avec le cadre de tests de Django, vous pouvez passer par le paquet django-coverage-plugin.
|
||
Ajoutez-le dans le fichier requirements/base.txt, et lancez une couverture de code grâce à la commande coverage.
|
||
La configuration peut se faire dans un fichier .coveragerc que vous placerez à la racine de votre projet, et qui sera lu lors de l’exécution.
|
||
|
||
|
||
\begin{verbatim}
|
||
# requirements/base.text
|
||
[...]
|
||
django_coverage_plugin
|
||
\end{verbatim}
|
||
|
||
\begin{verbatim}
|
||
# .coveragerc to control coverage.py
|
||
[run]
|
||
branch = True
|
||
omit = ../*migrations*
|
||
plugins =
|
||
django_coverage_plugin
|
||
|
||
[report]
|
||
ignore_errors = True
|
||
|
||
[html]
|
||
directory = coverage_html_report
|
||
\end{verbatim}
|
||
|
||
\begin{verbatim}
|
||
$ coverage run --source "." manage.py test
|
||
|
||
$ coverage report
|
||
|
||
Name Stmts Miss Cover
|
||
---------------------------------------------
|
||
gwift\gwift\__init__.py 0 0 100%
|
||
gwift\gwift\settings.py 17 0 100%
|
||
gwift\gwift\urls.py 5 5 0%
|
||
gwift\gwift\wsgi.py 4 4 0%
|
||
gwift\manage.py 6 0 100%
|
||
gwift\wish\__init__.py 0 0 100%
|
||
gwift\wish\admin.py 1 0 100%
|
||
gwift\wish\models.py 49 16 67%
|
||
gwift\wish\tests.py 1 1 0%
|
||
gwift\wish\views.py 6 6 0%
|
||
---------------------------------------------
|
||
TOTAL 89 32 64%
|
||
----
|
||
$ coverage html
|
||
|
||
\end{verbatim}
|
||
|
||
\subsection{Réalisation des tests}
|
||
|
||
En résumé, il est recommandé de:
|
||
|
||
\begin{enumerate}
|
||
\item
|
||
Tester que le nommage d'une URL (son attribut \texttt{name} dans les fichiers \texttt{urls.py}) corresponde à la fonction que l'on y a définie
|
||
\item
|
||
Tester que l'URL envoie bien vers l'exécution d'une fonction (et que cette fonction est celle que l'on attend)
|
||
\end{enumerate}
|
||
|
||
\subsubsection{Tests de nommage}
|
||
|
||
\begin{minted}{python}
|
||
from django.core.urlresolvers import reverse
|
||
from django.test import TestCase
|
||
|
||
class HomeTests(TestCase):
|
||
def test_home_view_status_code(self):
|
||
url = reverse("home")
|
||
response = self.client.get(url)
|
||
self.assertEquals(response.status_code, 200)
|
||
\end{minted}
|
||
|
||
\subsubsection{Tests d'URLs}
|
||
|
||
\begin{minted}{python}
|
||
from django.core.urlresolvers import reverse
|
||
from django.test import TestCase
|
||
|
||
from .views import home
|
||
|
||
class HomeTests(TestCase):
|
||
def test_home_view_status_code(self):
|
||
view = resolve("/")
|
||
self.assertEquals(view.func, home)
|
||
\end{minted}
|
||
|