

\begin{Shaded}
\begin{Highlighting}[]
\ExtensionTok{La}\NormalTok{ commande poetry new }\OperatorTok{\textless{}}\NormalTok{project}\OperatorTok{\textgreater{}}\NormalTok{ créera une structure par défaut relativement compréhensible:}

\NormalTok{$ }\ExtensionTok{poetry}\NormalTok{ new django{-}gecko}
\NormalTok{$ }\ExtensionTok{tree}\NormalTok{ django{-}gecko/}
\ExtensionTok{django{-}gecko/}
\NormalTok{├── }\ExtensionTok{django\_gecko}
\NormalTok{│   └── }\ExtensionTok{\_\_init\_\_.py}
\NormalTok{├── }\ExtensionTok{pyproject.toml}
\NormalTok{├── }\ExtensionTok{README.rst}
\NormalTok{└── }\ExtensionTok{tests}
\NormalTok{    ├── }\ExtensionTok{\_\_init\_\_.py}
\NormalTok{    └── }\ExtensionTok{test\_django\_gecko.py}

\ExtensionTok{2}\NormalTok{ directories, 5 files}
\end{Highlighting}
\end{Shaded}

Ceci signifie que nous avons directement (et de manière standard):

\begin{itemize}
\item
  Un répertoire django-gecko, qui porte le nom de l'application que vous
  venez de créer
\item
  Un répertoires tests, libellé selon les standards de pytest
\item
  Un fichier README.rst (qui ne contient encore rien)
\item
  Un fichier pyproject.toml, qui contient ceci:
\end{itemize}

\begin{verbatim}
[tool.poetry]
name = "django-gecko"
version = "0.1.0"
description = ""
authors = ["... <...@grimbox.be>"]

[tool.poetry.dependencies]
python = "^3.9"

[tool.poetry.dev-dependencies]
pytest = "^5.2"

[build-system]
requires = ["poetry-core>=1.0.0"]
build-backend = "poetry.core.masonry.api"
\end{verbatim}

La commande \texttt{poetry\ init} permet de générer interactivement les
fichiers nécessaires à son intégration dans un projet existant.

J'ai pour habitude de conserver mes projets dans un répertoire
\texttt{\textasciitilde{}/Sources/} et mes environnements virtuels dans
un répertoire \texttt{\textasciitilde{}/.venvs/}.

Cette séparation évite que l'environnement virtuel ne se trouve dans le
même répertoire que les sources, ou ne soit accidentellement envoyé vers
le système de gestion de versions. Elle évite également de rendre ce
répertoire "visible" - il ne s'agit au fond que d'un paramètre de
configuration lié uniquement à votre environnement de développement; les
environnements virtuels étant disposables, il n'est pas conseillé de
trop les lier au projet qui l'utilise comme base. Dans la suite de ce
chapitre, je considérerai ces mêmes répertoires, mais n'hésitez pas à
les modifier.

DANGER: Indépendamment de l'endroit où vous stockerez le répertoire
contenant cet environnement, il est primordial de \textbf{ne pas le
conserver dans votre dépôt de stockager}. Cela irait à l'encontre des
douze facteurs, cela polluera inutilement vos sources et créera des
conflits avec l'environnement des personnes qui souhaiteraient
intervenir sur le projet.

Pur créer notre répertoire de travail et notre environnement virtuel,
exécutez les commandes suivantes:

\begin{Shaded}
\begin{Highlighting}[]
\FunctionTok{mkdir}\NormalTok{ \textasciitilde{}/.venvs/}
\ExtensionTok{python}\NormalTok{ {-}m venv \textasciitilde{}/.venvs/gwift{-}venv}
\end{Highlighting}
\end{Shaded}

Ceci aura pour effet de créer un nouveau répertoire
(\texttt{\textasciitilde{}/.venvs/gwift-env/}), dans lequel vous
trouverez une installation complète de l'interpréteur Python. Votre
environnement virtuel est prêt, il n'y a plus qu'à indiquer que nous
souhaitons l'utiliser, grâce à l'une des commandes suivantes:

\begin{Shaded}
\begin{Highlighting}[]
\CommentTok{\# GNU/Linux, macOS}
\BuiltInTok{source}\NormalTok{ \textasciitilde{}/.venvs/gwift{-}venv/bin/activate}

\CommentTok{\# MS Windows, avec Cmder}
\ExtensionTok{\textasciitilde{}/.venvs/gwift{-}venv/Scripts/activate.bat}

\CommentTok{\# Pour les deux}
\KeywordTok{(}\ExtensionTok{gwift{-}env}\KeywordTok{)} \ExtensionTok{fred@aerys}\NormalTok{:\textasciitilde{}/Sources/.venvs/gwift{-}env$ }
\end{Highlighting}
\end{Shaded}

\begin{itemize}
\item
  Le terminal signale que nous sommes bien dans l'environnement
  \texttt{gwift-env}.
\end{itemize}

A présent que l'environnement est activé, tous les binaires de cet
environnement prendront le pas sur les binaires du système. De la même
manière, une variable \texttt{PATH} propre est définie et utilisée, afin
que les librairies Python y soient stockées. C'est donc dans cet
environnement virtuel que nous retrouverons le code source de Django,
ainsi que des librairies externes pour Python une fois que nous les
aurons installées.

Pour les curieux, un environnement virtuel n'est jamais qu'un répertoire
dans lequel se trouve une installation fraîche de l'interpréteur, vers
laquelle pointe les liens symboliques des binaires. Si vous recherchez
l'emplacement de l'interpréteur avec la commande \texttt{which\ python},
vous recevrez comme réponse
\texttt{/home/fred/.venvs/gwift-env/bin/python}.

Pour sortir de l'environnement virtuel, exécutez la commande
\texttt{deactivate}. Si vous pensez ne plus en avoir besoin, supprimer
le dossier. Si nécessaire, il suffira d'en créer un nouveau.

Pour gérer des versions différentes d'une même librairie, il nous suffit
de jongler avec autant d'environnements que nécessaires. Une application
nécessite une version de Django inférieure à la 2.0 ? On crée un
environnement, on l'active et on installe ce qu'il faut.

Cette technique fonctionnera autant pour un poste de développement que
sur les serveurs destinés à recevoir notre application.

Par la suite, nous considérerons que l'environnement virtuel est
toujours activé, même si \texttt{gwift-env} n'est pas indiqué.

a manière recommandée pour la gestion des dépendances consiste à les
épingler dans un fichier requirements.txt, placé à la racine du projet.
Ce fichier reprend, ligne par ligne, chaque dépendance et la version
nécessaire. Cet épinglage est cependant relativement basique, dans la
mesure où les opérateurs disponibles sont ==, ⇐ et \textgreater=.

Poetry propose un épinglage basé sur SemVer. Les contraintes qui peuvent
être appliquées aux dépendances sont plus touffues que ce que proposent
pip -r, avec la présence du curseur \^{}, qui ne modifiera pas le nombre
différent de zéro le plus à gauche:

\begin{verbatim}
^1.2.3 (où le nombre en question est 1) pourra proposer une mise à jour jusqu'à la version juste avant la version 2.0.0
^0.2.3 pourra être mise à jour jusqu'à la version juste avant 0.3.0.
...
\end{verbatim}

L'avantage est donc que l'on spécifie une version majeure - mineure -
patchée, et que l'on pourra spécifier accepter toute mise à jour jusqu'à
la prochaine version majeure - mineure patchée (non incluse 😉).

Une bonne pratique consiste également, tout comme pour npm, à intégrer
le fichier de lock (poetry.lock) dans le dépôt de sources: de cette
manière, seules les dépendances testées (et intégrées) seront
considérées sur tous les environnements de déploiement.

Il est alors nécessaire de passer par une action manuelle (poetry
update) pour mettre à jour le fichier de verrou, et assurer une mise à
jour en sécurité (seules les dépendances testées sont prises en compte)
et de qualité (tous les environnements utilisent la même version d'une
dépendance).

L'ajout d'une nouvelle dépendance à un projet se réalise grâce à la
commande \texttt{poetry\ add\ \textless{}dep\textgreater{}}:

\begin{verbatim}
$ poetry add django
Using version ^3.2.3 for Django

Updating dependencies
Resolving dependencies... (5.1s)

Writing lock file

Package operations: 8 installs, 1 update, 0 removals

  • Installing pyparsing (2.4.7)
  • Installing attrs (21.2.0)
  • Installing more-itertools (8.8.0)
  • Installing packaging (20.9)
  • Installing pluggy (0.13.1)
  • Installing py (1.10.0)
  • Installing wcwidth (0.2.5)
  • Updating django (3.2 -> 3.2.3)
  • Installing pytest (5.4.3)
\end{verbatim}

Elle est ensuite ajoutée à notre fichier \texttt{pyproject.toml}:

\begin{verbatim}
[...]

[tool.poetry.dependencies]
python = "^3.9"
Django = "^3.2.3"

[...]
\end{verbatim}

Et contrairement à \texttt{pip}, pas besoin de savoir s'il faut pointer
vers un fichier (\texttt{-r}) ou un dépôt VCS (\texttt{-e}), puisque
Poetry va tout essayer, {[}dans un certain
ordre{]}(\url{https://python-poetry.org/docs/cli/\#add}). L'avantage
également (et cela m'arrive encore souvent, ce qui fait hurler le runner
de Gitlab), c'est qu'il n'est plus nécessaire de penser à épingler la
dépendance que l'on vient d'installer parmi les fichiers de
requirements, puisqu'elles s'y ajoutent automatiquement grâce à la
commande \texttt{add}.

\hypertarget{_python_packaging_made_easy}{%
\subsubsection{Python packaging made
easy}\label{_python_packaging_made_easy}}

Cette partie dépasse mes compétences et connaissances, dans la mesure où
je n'ai jamais rien packagé ni publié sur {[}pypi.org{]}(pypi.org). Ce
n'est pas l'envie qui manque, mais les idées et la nécessité 😉. Ceci
dit, Poetry propose un ensemble de règles et une préconfiguration qui
(doivent) énormément facilite(r) la mise à disposition de librairies sur
Pypi - et rien que ça, devrait ouvrir une partie de l'écosystème.

Les chapitres 7 et 8 de {[}Expert Python Programming - Third
Edtion{]}(\#), écrit par Michal Jaworski et Tarek Ziadé en parlent très
bien:

\begin{quote}
Python packaging can be a bit overwhelming at first. The main reason for
that is the confusion about proper tools for creating Python packages.
Anyway, once you create your first package, you will se that this is as
hard as it looks. Also, knowing propre, state-of-the-art packaging helps
a lot.
\end{quote}

En gros, c'est ardu-au-début-mais-plus-trop-après. Et c'est heureusement
suivi et documenté par la PyPA
(\textbf{\href{https://github.com/pypa}{Python Packaging Authority}}).

Les étapes sont les suivantes:

\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
  Utiliser setuptools pour définir les projets et créer les
  distributions sources,
\item
  Utiliser \textbf{wheels} pour créer les paquets,
\item
  Passer par \textbf{twine} pour envoyer ces paquets vers PyPI
\item
  Définir un ensemble d'actions (voire, de plugins nécessaires - lien
  avec le VCS, etc.) dans le fichier \texttt{setup.py}, et définir les
  propriétés du projet ou de la librairie dans le fichier
  \texttt{setup.cfg}.
\end{enumerate}

Avec Poetry, deux commandes suffisent (théoriquement - puisque je n'ai
pas essayé 🤪): \texttt{poetry\ build} et \texttt{poetry\ publish}:

\begin{verbatim}
$ poetry build
Building geco (0.1.0)
  - Building sdist
  - Built geco-0.1.0.tar.gz
  - Building wheel
  - Built geco-0.1.0-py3-none-any.whl

$ tree dist/
dist/
├── geco-0.1.0-py3-none-any.whl
└── geco-0.1.0.tar.gz

0 directories, 2 files
\end{verbatim}

Ce qui est quand même 'achement plus simple que d'appréhender tout un
écosystème.

\hypertarget{_gestion_des_duxe9pendances_installation_de_django_et_cruxe9ation_dun_nouveau_projet}{%
\subsubsection{Gestion des dépendances, installation de Django et
création d'un nouveau
projet}\label{_gestion_des_duxe9pendances_installation_de_django_et_cruxe9ation_dun_nouveau_projet}}

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{Shaded}
\begin{Highlighting}[]
\NormalTok{$ }\BuiltInTok{source}\NormalTok{ \textasciitilde{}/.venvs/gwift{-}env/bin/activate }\CommentTok{\# ou \textasciitilde{}/.venvs/gwift{-}env/Scrips/activate.bat pour Windows.}
\NormalTok{$ }\ExtensionTok{pip}\NormalTok{ install django}
\ExtensionTok{Collecting}\NormalTok{ django}
    \ExtensionTok{Downloading}\NormalTok{ Django{-}3.1.4}
\ExtensionTok{100\%} \KeywordTok{|}\NormalTok{\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#}\KeywordTok{|}
\ExtensionTok{Installing}\NormalTok{ collected packages: django}
\ExtensionTok{Successfully}\NormalTok{ installed django{-}3.1.4}
\end{Highlighting}
\end{Shaded}

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{Shaded}
\begin{Highlighting}[]
\NormalTok{$ }\ExtensionTok{django{-}admin}\NormalTok{ startproject gwift}
\end{Highlighting}
\end{Shaded}

Cette action a pour effet de créer un nouveau dossier \texttt{gwift},
dans lequel nous trouvons la structure suivante:

\begin{Shaded}
\begin{Highlighting}[]
\NormalTok{$ }\ExtensionTok{tree}\NormalTok{ gwift}
\ExtensionTok{gwift}
\NormalTok{├── }\ExtensionTok{gwift}
\KeywordTok{|}   \KeywordTok{|}\NormalTok{── }\ExtensionTok{asgi.py}
\NormalTok{│   ├── }\ExtensionTok{\_\_init\_\_.py}
\NormalTok{│   ├── }\ExtensionTok{settings.py}
\NormalTok{│   ├── }\ExtensionTok{urls.py}
\NormalTok{│   └── }\ExtensionTok{wsgi.py}
\NormalTok{└── }\ExtensionTok{manage.py}
\end{Highlighting}
\end{Shaded}

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:

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{(}\ExtensionTok{gwift}\KeywordTok{)}\NormalTok{ $ }\FunctionTok{mkdir}\NormalTok{ requirements}
\KeywordTok{(}\ExtensionTok{gwift}\KeywordTok{)}\NormalTok{ $ }\FunctionTok{touch}\NormalTok{ README.md}
\KeywordTok{(}\ExtensionTok{gwift}\KeywordTok{)}\NormalTok{ $ }\ExtensionTok{tree}\NormalTok{ gwift}
\ExtensionTok{gwift}
\NormalTok{├── }\ExtensionTok{gwift}
\NormalTok{│   ├── }\ExtensionTok{asgi.py}
\NormalTok{│   ├── }\ExtensionTok{\_\_init\_\_.py}
\NormalTok{│   ├── }\ExtensionTok{settings.py}
\NormalTok{│   ├── }\ExtensionTok{urls.py}
\NormalTok{│   └── }\ExtensionTok{wsgi.py}
\NormalTok{├── }\ExtensionTok{requirements} 
\NormalTok{├── }\ExtensionTok{README.md} 
\NormalTok{└── }\ExtensionTok{manage.py}
\end{Highlighting}
\end{Shaded}

\begin{itemize}
\item
  Ici
\item
  Et là
\end{itemize}

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 strictement inférieure à la version 3.2 dans le fichier
\texttt{requirements/base.txt}.

\begin{Shaded}
\begin{Highlighting}[]
\NormalTok{$ }\BuiltInTok{echo} \StringTok{\textquotesingle{}django==3.2\textquotesingle{}} \OperatorTok{\textgreater{}}\NormalTok{ requirements/base.txt}
\NormalTok{$ }\BuiltInTok{echo} \StringTok{\textquotesingle{}{-}r base.txt\textquotesingle{}} \OperatorTok{\textgreater{}}\NormalTok{ requirements/prod.txt}
\NormalTok{$ }\BuiltInTok{echo} \StringTok{\textquotesingle{}{-}r base.txt\textquotesingle{}} \OperatorTok{\textgreater{}}\NormalTok{ requirements/dev.txt}
\end{Highlighting}
\end{Shaded}

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 parcourir
les 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.

\hypertarget{_gestion_des_diffuxe9rentes_versions_des_python}{%
\subsection{Gestion des différentes versions des
Python}\label{_gestion_des_diffuxe9rentes_versions_des_python}}

\begin{verbatim}
pyenv install 3.10
\end{verbatim}

\hypertarget{_django}{%
\subsection{Django}\label{_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}
\def\labelenumi{\arabic{enumi}.}
\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},
\ldots\hspace{0pt}).

\hypertarget{_manage_py}{%
\subsubsection{manage.py}\label{_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, \ldots\hspace{0pt}
\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{Shaded}
\begin{Highlighting}[]
\NormalTok{$ }\ExtensionTok{python}\NormalTok{ manage.py runserver}
\ExtensionTok{Watching}\NormalTok{ for file changes with StatReloader}
\ExtensionTok{Performing}\NormalTok{ system checks...}

\ExtensionTok{System}\NormalTok{ check identified no issues (0 silenced)}\ExtensionTok{.}

\NormalTok{[}\ExtensionTok{...}\NormalTok{]}

\ExtensionTok{December}\NormalTok{ 15, 2020 {-} 20:45:07}
\ExtensionTok{Django}\NormalTok{ version 3.1.4, using settings }\StringTok{\textquotesingle{}gwift.settings\textquotesingle{}}
\ExtensionTok{Starting}\NormalTok{ development server at http://127.0.0.1:8000/}
\ExtensionTok{Quit}\NormalTok{ the server with CTRL{-}BREAK.}
\end{Highlighting}
\end{Shaded}

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.

\hypertarget{_cruxe9ation_dune_nouvelle_application}{%
\subsubsection{Création d'une nouvelle
application}\label{_cruxe9ation_dune_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{Shaded}
\begin{Highlighting}[]
\NormalTok{$ }\ExtensionTok{python}\NormalTok{ manage.py startapp wish}
\end{Highlighting}
\end{Shaded}

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:

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{(}\ExtensionTok{gwift{-}env}\KeywordTok{)} \ExtensionTok{fred@aerys}\NormalTok{:\textasciitilde{}/Sources/gwift$ tree .}
\ExtensionTok{.}
\NormalTok{├── }\ExtensionTok{gwift}
\NormalTok{│   ├── }\ExtensionTok{\_\_init\_\_.py}
\NormalTok{│   ├── }\ExtensionTok{asgi.py}
\NormalTok{│   ├── }\ExtensionTok{settings.py}
\NormalTok{│   ├── }\ExtensionTok{urls.py}
\NormalTok{│   ├── }\ExtensionTok{wish} 
\NormalTok{│   │   ├── }\ExtensionTok{\_\_init\_\_.py}
\NormalTok{│   │   ├── }\ExtensionTok{admin.py}
\NormalTok{│   │   ├── }\ExtensionTok{apps.py}
\NormalTok{│   │   ├── }\ExtensionTok{migrations}
\NormalTok{│   │   │   └── }\ExtensionTok{\_\_init\_\_.py}
\NormalTok{│   │   ├── }\ExtensionTok{models.py}
\NormalTok{│   │   ├── }\ExtensionTok{tests.py}
\NormalTok{│   │   └── }\ExtensionTok{views.py}
\NormalTok{│   └── }\ExtensionTok{wsgi.py}
\NormalTok{├── }\ExtensionTok{Makefile}
\NormalTok{├── }\ExtensionTok{manage.py}
\NormalTok{├── }\ExtensionTok{README.md}
\NormalTok{├── }\ExtensionTok{requirements}
\NormalTok{│   ├── }\ExtensionTok{base.txt}
\NormalTok{│   ├── }\ExtensionTok{dev.txt}
\NormalTok{│   └── }\ExtensionTok{prod.txt}
\NormalTok{├── }\ExtensionTok{setup.cfg}
\NormalTok{└── }\ExtensionTok{tox.ini}

\ExtensionTok{5}\NormalTok{ directories, 22 files}
\end{Highlighting}
\end{Shaded}

\begin{itemize}
\item
  Notre application a bien été créée, et nous l'avons déplacée dans le
  répertoire \texttt{gwift} !
\end{itemize}

\hypertarget{_fonctionement_guxe9nuxe9ral}{%
\subsubsection{Fonctionement
général}\label{_fonctionement_guxe9nuxe9ral}}

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, \ldots\hspace{0pt} 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{Shaded}
\begin{Highlighting}[]
\ImportTok{from}\NormalTok{ django.contrib }\ImportTok{import}\NormalTok{ admin}
\ImportTok{from}\NormalTok{ django.urls }\ImportTok{import}\NormalTok{ path}

\ImportTok{from}\NormalTok{ gwift.views }\ImportTok{import}\NormalTok{ wish\_details }

\NormalTok{urlpatterns }\OperatorTok{=}\NormalTok{ [}
\NormalTok{    path(}\StringTok{\textquotesingle{}admin/\textquotesingle{}}\NormalTok{, admin.site.urls),}
\NormalTok{    path(}\StringTok{"wishes/\textless{}int:wish\_id\textgreater{}"}\NormalTok{, wish\_details), }
\NormalTok{]}
\end{Highlighting}
\end{Shaded}

\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{Shaded}
\begin{Highlighting}[]
\NormalTok{[...]}

\ImportTok{from}\NormalTok{ datetime }\ImportTok{import}\NormalTok{ datetime}


\KeywordTok{def}\NormalTok{ wishes\_details(request: HttpRequest, wish\_id: }\BuiltInTok{int}\NormalTok{) }\OperatorTok{{-}\textgreater{}}\NormalTok{ HttpResponse:}
\NormalTok{    context }\OperatorTok{=}\NormalTok{ \{}
        \StringTok{"user\_name"}\NormalTok{: }\StringTok{"Bond,"}
        \StringTok{"user\_first\_name"}\NormalTok{: }\StringTok{"James"}\NormalTok{,}
        \StringTok{"now"}\NormalTok{: datetime.now()}
\NormalTok{    \}}

    \ControlFlowTok{return}\NormalTok{ render(}
\NormalTok{        request,}
        \StringTok{"wish\_details.html"}\NormalTok{,}
\NormalTok{        context}
\NormalTok{    )}
\end{Highlighting}
\end{Shaded}

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{Shaded}
\begin{Highlighting}[]
\CommentTok{\textless{}!{-}{-} fichier wish\_details.html {-}{-}\textgreater{}}
\DataTypeTok{\textless{}!DOCTYPE }\NormalTok{html}\DataTypeTok{\textgreater{}}
\KeywordTok{\textless{}html\textgreater{}}
\KeywordTok{\textless{}head\textgreater{}}
  \KeywordTok{\textless{}title\textgreater{}}\NormalTok{Page title}\KeywordTok{\textless{}/title\textgreater{}}
\KeywordTok{\textless{}/head\textgreater{}}
\KeywordTok{\textless{}body\textgreater{}}
  \KeywordTok{\textless{}h1\textgreater{}}\NormalTok{👤 Hi!}\KeywordTok{\textless{}/h1\textgreater{}}
  \KeywordTok{\textless{}p\textgreater{}}\NormalTok{My name is \{\{ user\_name \}\}. \{\{ user\_first\_name \}\} \{\{ user\_name \}\}.}\KeywordTok{\textless{}/p\textgreater{}}
  \KeywordTok{\textless{}p\textgreater{}}\NormalTok{This page was generated at \{\{ now \}\}}\KeywordTok{\textless{}/p\textgreater{}}
\KeywordTok{\textless{}/body\textgreater{}}
\KeywordTok{\textless{}/html\textgreater{}}
\end{Highlighting}
\end{Shaded}

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{Shaded}
\begin{Highlighting}[]
\DataTypeTok{\textless{}!DOCTYPE }\NormalTok{html}\DataTypeTok{\textgreater{}}
\KeywordTok{\textless{}html\textgreater{}}
\KeywordTok{\textless{}head\textgreater{}}
  \KeywordTok{\textless{}title\textgreater{}}\NormalTok{Page title}\KeywordTok{\textless{}/title\textgreater{}}
\KeywordTok{\textless{}/head\textgreater{}}
\KeywordTok{\textless{}body\textgreater{}}
  \KeywordTok{\textless{}h1\textgreater{}}\NormalTok{👤 Hi!}\KeywordTok{\textless{}/h1\textgreater{}}
  \KeywordTok{\textless{}p\textgreater{}}\NormalTok{My name is Bond. James Bond.}\KeywordTok{\textless{}/p\textgreater{}}
  \KeywordTok{\textless{}p\textgreater{}}\NormalTok{This page was generated at 2027{-}03{-}19 19:47:38}\KeywordTok{\textless{}/p\textgreater{}}
\KeywordTok{\textless{}/body\textgreater{}}
\KeywordTok{\textless{}/html\textgreater{}}
\end{Highlighting}
\end{Shaded}

\begin{figure}
\centering
\includegraphics{images/django/django-first-template.png}
\caption{Résultat}
\end{figure}

\hypertarget{_12_facteurs_et_configuration_globale}{%
\subsubsection{12 facteurs et configuration
globale}\label{_12_facteurs_et_configuration_globale}}

→ Faire le lien avec les settings → Faire le lien avec les douze
facteurs → Construction du fichier setup.cfg

\hypertarget{_setup_cfg}{%
\subsubsection{setup.cfg}\label{_setup_cfg}}

(Repris de cookie-cutter-django)

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{[flake8]}
\DataTypeTok{max{-}line{-}length }\OtherTok{=}\StringTok{ }\DecValTok{120}
\DataTypeTok{exclude }\OtherTok{=}\StringTok{ .tox,.git,*/migrations/*,*/static/CACHE/*,docs,node\_modules,venv}

\KeywordTok{[pycodestyle]}
\DataTypeTok{max{-}line{-}length }\OtherTok{=}\StringTok{ }\DecValTok{120}
\DataTypeTok{exclude }\OtherTok{=}\StringTok{ .tox,.git,*/migrations/*,*/static/CACHE/*,docs,node\_modules,venv}

\KeywordTok{[mypy]}
\DataTypeTok{python\_version }\OtherTok{=}\StringTok{ }\FloatTok{3.8}
\DataTypeTok{check\_untyped\_defs }\OtherTok{=}\StringTok{ }\KeywordTok{True}
\DataTypeTok{ignore\_missing\_imports }\OtherTok{=}\StringTok{ }\KeywordTok{True}
\DataTypeTok{warn\_unused\_ignores }\OtherTok{=}\StringTok{ }\KeywordTok{True}
\DataTypeTok{warn\_redundant\_casts }\OtherTok{=}\StringTok{ }\KeywordTok{True}
\DataTypeTok{warn\_unused\_configs }\OtherTok{=}\StringTok{ }\KeywordTok{True}
\DataTypeTok{plugins }\OtherTok{=}\StringTok{ mypy\_django\_plugin.main}

\KeywordTok{[mypy.plugins.django{-}stubs]}
\DataTypeTok{django\_settings\_module }\OtherTok{=}\StringTok{ config.settings.test}

\KeywordTok{[mypy{-}*.migrations.*]}
\CommentTok{\# Django migrations should not produce any errors:}
\DataTypeTok{ignore\_errors }\OtherTok{=}\StringTok{ }\KeywordTok{True}

\KeywordTok{[coverage:run]}
\DataTypeTok{include }\OtherTok{=}\StringTok{ khana/*}
\DataTypeTok{omit }\OtherTok{=}\StringTok{ *migrations*, *tests*}
\DataTypeTok{plugins }\OtherTok{=}
\DataTypeTok{    django\_coverage\_plugin}
\end{Highlighting}
\end{Shaded}

\hypertarget{_structure_finale_de_notre_environnement}{%
\subsection{Structure finale de notre
environnement}\label{_structure_finale_de_notre_environnement}}

Nous avons donc la structure finale pour notre environnement de travail:

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{(}\ExtensionTok{gwift{-}env}\KeywordTok{)} \ExtensionTok{fred@aerys}\NormalTok{:\textasciitilde{}/Sources/gwift$ tree .}
\ExtensionTok{.}
\NormalTok{├── }\ExtensionTok{gwift}
\NormalTok{│   ├── }\ExtensionTok{\_\_init\_\_.py}
\NormalTok{│   ├── }\ExtensionTok{asgi.py}
\NormalTok{│   ├── }\ExtensionTok{settings.py}
\NormalTok{│   ├── }\ExtensionTok{urls.py}
\NormalTok{│   ├── }\ExtensionTok{wish} 
\NormalTok{│   │   ├── }\ExtensionTok{\_\_init\_\_.py}
\NormalTok{│   │   ├── }\ExtensionTok{admin.py}
\NormalTok{│   │   ├── }\ExtensionTok{apps.py}
\NormalTok{│   │   ├── }\ExtensionTok{migrations}
\NormalTok{│   │   │   └── }\ExtensionTok{\_\_init\_\_.py}
\NormalTok{│   │   ├── }\ExtensionTok{models.py}
\NormalTok{│   │   ├── }\ExtensionTok{tests.py}
\NormalTok{│   │   └── }\ExtensionTok{views.py}
\NormalTok{│   └── }\ExtensionTok{wsgi.py}
\NormalTok{├── }\ExtensionTok{Makefile}
\NormalTok{├── }\ExtensionTok{manage.py}
\NormalTok{├── }\ExtensionTok{README.md}
\NormalTok{├── }\ExtensionTok{requirements}
\NormalTok{│   ├── }\ExtensionTok{base.txt}
\NormalTok{│   ├── }\ExtensionTok{dev.txt}
\NormalTok{│   └── }\ExtensionTok{prod.txt}
\NormalTok{├── }\ExtensionTok{setup.cfg}
\NormalTok{└── }\ExtensionTok{tox.ini}
\end{Highlighting}
\end{Shaded}

\hypertarget{_cookie_cutter}{%
\subsection{Cookie cutter}\label{_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, \ldots\hspace{0pt}

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{Shaded}
\begin{Highlighting}[]
\NormalTok{λ }\ExtensionTok{python}\NormalTok{ {-}m venv .venvs\textbackslash{}cookie{-}cutter{-}khana}
\NormalTok{λ }\ExtensionTok{.venvs}\NormalTok{\textbackslash{}cookie{-}cutter{-}khana\textbackslash{}Scripts\textbackslash{}activate.bat}
\KeywordTok{(}\ExtensionTok{cookie{-}cutter{-}khana}\KeywordTok{)}\NormalTok{ λ }\ExtensionTok{pip}\NormalTok{ install cookiecutter}

  \ExtensionTok{Collecting}\NormalTok{ cookiecutter}
\NormalTok{  [}\ExtensionTok{...}\NormalTok{]}
  \ExtensionTok{Successfully}\NormalTok{ 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}

\KeywordTok{(}\ExtensionTok{cookie{-}cutter{-}khana}\KeywordTok{)}\NormalTok{ λ }\ExtensionTok{cookiecutter}\NormalTok{ https://github.com/pydanny/cookiecutter{-}django}

\NormalTok{  [}\ExtensionTok{...}\NormalTok{]}

\NormalTok{ [}\ExtensionTok{SUCCESS}\NormalTok{]: Project initialized, keep up the good work!}
\end{Highlighting}
\end{Shaded}

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{Shaded}
\begin{Highlighting}[]
\ExtensionTok{django{-}admin.py}\NormalTok{ startproject {-}{-}template=https://[...].zip }\OperatorTok{\textless{}}\NormalTok{my\_project}\OperatorTok{\textgreater{}}
\end{Highlighting}
\end{Shaded}

Dans ce chapitre, nous allons parler de plusieurs concepts fondamentaux
au développement rapide d'une application utilisant Django. Nous
parlerons de modélisation, de métamodèle, de migrations,
d'administration auto-générée, de traductions et de cycle de vie des
données.

Django est un framework Web qui propose une très bonne intégration des
composants et une flexibilité bien pensée: chacun des composants permet
de définir son contenu de manière poussée, en respectant des contraintes
logiques et faciles à retenir, et en gérant ses dépendances de manière
autonome. Pour un néophyte, la courbe d'apprentissage sera relativement
ardue: à côté de concepts clés de Django, il conviendra également
d'assimiler correctement les structures de données du langage Python, le
cycle de vie des requêtes HTTP et le B.A-BA des principes de sécurité.

En restant dans les sentiers battus, votre projet suivra un patron de
conception dérivé du modèle \texttt{MVC} (Modèle-Vue-Controleur), où la
variante concerne les termes utilisés: Django les nomme respectivement
Modèle-Template-Vue et leur contexte d'utilisation. Dans un
\textbf{pattern} MVC classique, la traduction immédiate du
\textbf{contrôleur} est une \textbf{vue}. Et comme nous le verrons par
la suite, la \textbf{vue} est en fait le \textbf{template}. La
principale différence avec un modèle MVC concerne le fait que la vue ne
s'occupe pas du routage des URLs; ce point est réalisé par un autre
composant, interne au framework, graĉe aux différentes routes définies
dans les fichiers \texttt{urls.py}.

\begin{itemize}
\item
  Le \textbf{modèle} (\texttt{models.py}) fait le lien avec la base de
  données et permet de définir les champs et leur type à associer à une
  table. \emph{Grosso modo}*, une table SQL correspondra à une classe
  d'un modèle Django.
\item
  La \textbf{vue} (\texttt{views.py}), qui joue le rôle de contrôleur:
  \emph{a priori}, tous les traitements, la récupération des données,
  etc. doit passer par ce composant et ne doit (pratiquement) pas être
  généré à la volée, directement à l'affichage d'une page. En d'autres
  mots, la vue sert de pont entre les données gérées par la base et
  l'interface utilisateur.
\item
  Le \textbf{template}, qui s'occupe de la mise en forme: c'est le
  composant qui s'occupe de transformer les données en un affichage
  compréhensible (avec l'aide du navigateur) pour l'utilisateur.
\end{itemize}

Pour reprendre une partie du schéma précédent, lorsqu'une requête est
émise par un utilisateur, la première étape va consister à trouver une
\emph{route} qui correspond à cette requête, c'est à dire à trouver la
correspondance entre l'URL qui est demandée par l'utilisateur et la
fonction du langage qui sera exécutée pour fournir le résultat attendu.
Cette fonction correspond au \textbf{contrôleur} et s'occupera de
construire le \textbf{modèle} correspondant.

\hypertarget{_moduxe9lisation}{%
\section{Modélisation}\label{_moduxe9lisation}}

Ce chapitre aborde la modélisation des objets et les options qui y sont
liées.

Avec Django, la modélisation est en lien direct avec la conception et le
stockage, sous forme d'une base de données relationnelle, et la manière
dont ces données s'agencent et communiquent entre elles. Cette
modélisation va ériger les premières pierres de votre édifice

\begin{quote}
\emph{Le modèle n'est qu'une grande hypothèque. Il se base sur des choix
conscients et inconscients, et dans chacun de ces choix se cachent nos
propres perceptions qui résultent de qui nous sommes, de nos
connaissances, de nos profils scientifiques et de tant d'autres choses.}

---  Aurélie Jean De l'autre côté de la machine
\end{quote}

Comme expliqué par Aurélie Jean cite:{[}other\_side{]}, "\emph{toute
modélisation reste une approximation de la réalité}". Plus tard dans ce
chapitre, nous expliquerons les bonnes pratiques à suivre pour faire
évoluer ces biais.

Django utilise un paradigme de persistence des données de type
\href{https://fr.wikipedia.org/wiki/Mapping_objet-relationnel}{ORM} -
c'est-à-dire que chaque type d'objet manipulé peut s'apparenter à une
table SQL, tout en respectant une approche propre à la programmation
orientée object. Plus spécifiquement, l'ORM de Django suit le patron de
conception
\href{https://en.wikipedia.org/wiki/Active_record_pattern}{Active
Records}, comme le font par exemple
\href{https://rubyonrails.org/}{Rails} pour Ruby ou
\href{https://docs.microsoft.com/fr-fr/ef/}{EntityFramework} pour .Net.

Le modèle de données de Django est sans doute la (seule ?) partie qui
soit tellement couplée au framework qu'un changement à ce niveau
nécessitera une refonte complète de beaucoup d'autres briques de vos
applications; là où un pattern de type
\href{https://www.martinfowler.com/eaaCatalog/repository.html}{Repository}
permettrait justement de découpler le modèle des données de l'accès à
ces mêmes données, un pattern Active Record lie de manière extrêmement
forte le modèle à sa persistence. Architecturalement, c'est sans doute
la plus grosse faiblesse de Django, à tel point que \textbf{ne pas
utiliser cette brique de fonctionnalités} peut remettre en question le
choix du framework.

Conceptuellement, c'est pourtant la manière de faire qui permettra
d'avoir quelque chose à présenter très rapidement: à partir du moment où
vous aurez un modèle de données, vous aurez accès, grâce à cet ORM à:

\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
  Des migrations de données et la possibilité de faire évoluer votre
  modèle,
\item
  Une abstraction entre votre modélisation et la manière dont les
  données sont représentées \emph{via} un moteur de base de données
  relationnelles,
\item
  Une interface d'administration auto-générée
\item
  Un mécanisme de formulaires HTML qui soit complet, pratique à
  utiliser, orienté objet et facile à faire évoluer,
\item
  Une définition des notions d'héritage (tout en restant dans une forme
  d'héritage simple).
\end{enumerate}

Comme tout ceci reste au niveau du code, cela suit également la
méthodologie des douze facteurs, concernant la minimisation des
divergences entre environnements d'exécution: comme tout se trouve au
niveau du code, il n'est plus nécessaire d'avoir un DBA qui doive
démarrer un script sur un serveur au moment de la mise à jour, de
recevoir une release note de 512 pages en PDF reprenant les
modifications ou de nécessiter l'intervention de trois équipes
différentes lors d'une modification majeure du code. Déployer une
nouvelle instance de l'application pourra être réalisé directement à
partir d'une seule et même commande.

\hypertarget{_active_records}{%
\subsection{Active Records}\label{_active_records}}

Il est important de noter que l'implémentation d'Active Records reste
une forme hybride entre une structure de données brutes et une classe:

\begin{itemize}
\item
  Une classe va exposer ses données derrière une forme d'abstraction et
  n'exposer que les fonctions qui opèrent sur ces données,
\item
  Une structure de données ne va exposer que ses champs et propriétés,
  et ne va pas avoir de functions significatives.
\end{itemize}

L'exemple ci-dessous présente trois structure de données, qui exposent
chacune leurs propres champs:

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{class}\NormalTok{ Square:}
    \KeywordTok{def} \FunctionTok{\_\_init\_\_}\NormalTok{(}\VariableTok{self}\NormalTok{, top\_left, side):}
        \VariableTok{self}\NormalTok{.top\_left }\OperatorTok{=}\NormalTok{ top\_left}
        \VariableTok{self}\NormalTok{.side }\OperatorTok{=}\NormalTok{ side}

\KeywordTok{class}\NormalTok{ Rectangle:}
    \KeywordTok{def} \FunctionTok{\_\_init\_\_}\NormalTok{(}\VariableTok{self}\NormalTok{, top\_left, height, width):}
        \VariableTok{self}\NormalTok{.top\_left }\OperatorTok{=}\NormalTok{ top\_left}
        \VariableTok{self}\NormalTok{.height }\OperatorTok{=}\NormalTok{ height}
        \VariableTok{self}\NormalTok{.width }\OperatorTok{=}\NormalTok{ width}

\KeywordTok{class}\NormalTok{ Circle:}
    \KeywordTok{def} \FunctionTok{\_\_init\_\_}\NormalTok{(}\VariableTok{self}\NormalTok{, center, radius):}
        \VariableTok{self}\NormalTok{.center }\OperatorTok{=}\NormalTok{ center}
        \VariableTok{self}\NormalTok{.radius }\OperatorTok{=}\NormalTok{ radius}
\end{Highlighting}
\end{Shaded}

Si nous souhaitons ajouter une fonctionnalité permettant de calculer
l'aire pour chacune de ces structures, nous aurons deux possibilités:

\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
  Soit ajouter une classe de \emph{visite} qui ajoute cette fonction de
  calcul d'aire
\item
  Soit modifier notre modèle pour que chaque structure hérite d'une
  classe de type \texttt{Shape}, qui implémentera elle-même ce calcul
  d'aire.
\end{enumerate}

Dans le premier cas, nous pouvons procéder de la manière suivante:

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{class}\NormalTok{ Geometry:}
\NormalTok{    PI }\OperatorTok{=} \FloatTok{3.141592653589793}

    \KeywordTok{def}\NormalTok{ area(}\VariableTok{self}\NormalTok{, shape):}
        \ControlFlowTok{if} \BuiltInTok{isinstance}\NormalTok{(shape, Square):}
            \ControlFlowTok{return}\NormalTok{ shape.side }\OperatorTok{*}\NormalTok{ shape.side}

        \ControlFlowTok{if} \BuiltInTok{isinstance}\NormalTok{(shape, Rectangle):}
            \ControlFlowTok{return}\NormalTok{ shape.height }\OperatorTok{*}\NormalTok{ shape.width}

        \ControlFlowTok{if} \BuiltInTok{isinstance}\NormalTok{(shape, Circle):}
            \ControlFlowTok{return}\NormalTok{ PI }\OperatorTok{*}\NormalTok{ shape.radius}\OperatorTok{**}\DecValTok{2}

        \ControlFlowTok{raise}\NormalTok{ NoSuchShapeException()}
\end{Highlighting}
\end{Shaded}

Dans le second cas, l'implémentation pourrait évoluer de la manière
suivante:

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{class}\NormalTok{ Shape:}
    \KeywordTok{def}\NormalTok{ area(}\VariableTok{self}\NormalTok{):}
        \ControlFlowTok{pass}

\KeywordTok{class}\NormalTok{ Square(Shape):}
    \KeywordTok{def} \FunctionTok{\_\_init\_\_}\NormalTok{(}\VariableTok{self}\NormalTok{, top\_left, side):}
        \VariableTok{self}\NormalTok{.\_\_top\_left }\OperatorTok{=}\NormalTok{ top\_left}
        \VariableTok{self}\NormalTok{.\_\_side }\OperatorTok{=}\NormalTok{ side}

    \KeywordTok{def}\NormalTok{ area(}\VariableTok{self}\NormalTok{):}
        \ControlFlowTok{return} \VariableTok{self}\NormalTok{.\_\_side }\OperatorTok{*} \VariableTok{self}\NormalTok{.\_\_side}

\KeywordTok{class}\NormalTok{ Rectangle(Shape):}
    \KeywordTok{def} \FunctionTok{\_\_init\_\_}\NormalTok{(}\VariableTok{self}\NormalTok{, top\_left, height, width):}
        \VariableTok{self}\NormalTok{.\_\_top\_left }\OperatorTok{=}\NormalTok{ top\_left}
        \VariableTok{self}\NormalTok{.\_\_height }\OperatorTok{=}\NormalTok{ height}
        \VariableTok{self}\NormalTok{.\_\_width }\OperatorTok{=}\NormalTok{ width}

    \KeywordTok{def}\NormalTok{ area(}\VariableTok{self}\NormalTok{):}
        \ControlFlowTok{return} \VariableTok{self}\NormalTok{.\_\_height }\OperatorTok{*} \VariableTok{self}\NormalTok{.\_\_width}

\KeywordTok{class}\NormalTok{ Circle(Shape):}
    \KeywordTok{def} \FunctionTok{\_\_init\_\_}\NormalTok{(}\VariableTok{self}\NormalTok{, center, radius):}
        \VariableTok{self}\NormalTok{.\_\_center }\OperatorTok{=}\NormalTok{ center}
        \VariableTok{self}\NormalTok{.\_\_radius }\OperatorTok{=}\NormalTok{ radius}

    \KeywordTok{def}\NormalTok{ area(}\VariableTok{self}\NormalTok{):}
\NormalTok{        PI }\OperatorTok{=} \FloatTok{3.141592653589793}
        \ControlFlowTok{return}\NormalTok{ PI }\OperatorTok{*} \VariableTok{self}\NormalTok{.\_\_radius}\OperatorTok{**}\DecValTok{2}
\end{Highlighting}
\end{Shaded}

Une structure de données peut être rendue abstraite au travers des
notions de programmation orientée objet.

Dans l'exemple géométrique ci-dessus, repris de
cite:{[}clean\_code(95-97){]}, l'accessibilité des champs devient
restreinte, tandis que la fonction \texttt{area()} bascule comme méthode
d'instance plutôt que de l'isoler au niveau d'un visiteur. Nous ajoutons
une abstraction au niveau des formes grâce à un héritage sur la classe
\texttt{Shape}; indépendamment de ce que nous manipulerons, nous aurons
la possibilité de calculer son aire.

Une structure de données permet de facilement gérer des champs et des
propriétés, tandis qu'une classe gère et facilite l'ajout de fonctions
et de méthodes.

Le problème d'Active Records est que chaque classe s'apparente à une
table SQL et revient donc à gérer des \emph{DTO} ou \emph{Data Transfer
Object}, c'est-à-dire des objets de correspondance pure et simple entre
les champs de la base de données et les propriétés de la programmation
orientée objet, c'est-à-dire également des classes sans fonctions. Or,
chaque classe a également la possibilité d'exposer des possibilités
d'interactions au niveau de la persistence, en
\href{https://docs.djangoproject.com/en/stable/ref/models/instances/\#django.db.models.Model.save}{enregistrant
ses propres données} ou en en autorisant leur
\href{https://docs.djangoproject.com/en/stable/ref/models/instances/\#deleting-objects}{suppression}.
Nous arrivons alors à un modèle hybride, mélangeant des structures de
données et des classes d'abstraction, ce qui restera parfaitement viable
tant que l'on garde ces principes en tête et que l'on se prépare à une
éventuelle réécriture du code.

Lors de l'analyse d'une classe de modèle, nous pouvons voir que Django
exige un héritage de la classe \texttt{django.db.models.Model}. Nous
pouvons regarder les propriétés définies dans cette classe en analysant
le fichier
\texttt{lib\textbackslash{}site-packages\textbackslash{}django\textbackslash{}models\textbackslash{}base.py}.
Outre que \texttt{models.Model} hérite de \texttt{ModelBase} au travers
de \href{https://pypi.python.org/pypi/six}{six} pour la
rétrocompatibilité vers Python 2.7, cet héritage apporte notamment les
fonctions \texttt{save()}, \texttt{clean()}, \texttt{delete()},
\ldots\hspace{0pt} En résumé, toutes les méthodes qui font qu'une
instance sait \textbf{comment} interagir avec la base de données.

\hypertarget{_types_de_champs_relations_et_cluxe9s_uxe9tranguxe8res}{%
\subsection{Types de champs, relations et clés
étrangères}\label{_types_de_champs_relations_et_cluxe9s_uxe9tranguxe8res}}

Nous l'avons vu plus tôt, Python est un langage dynamique et fortement
typé. Django, de son côté, ajoute une couche de typage statique exigé
par le lien sous-jacent avec le moteur de base de données relationnelle.
Dans le domaine des bases de données relationnelles, un point
d'attention est de toujours disposer d'une clé primaire pour nos
enregistrements. Si aucune clé primaire n'est spécifiée, Django
s'occupera d'en ajouter une automatiquement et la nommera (par
convention) \texttt{id}. Elle sera ainsi accessible autant par cette
propriété que par la propriété \texttt{pk}.

Chaque champ du modèle est donc typé et lié, soit à une primitive, soit
à une autre instance au travers de sa clé d'identification.

Grâce à toutes ces informations, nous sommes en mesure de représenter
facilement des livres liés à des catégories:

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{class}\NormalTok{ Category(models.Model):}
\NormalTok{    name }\OperatorTok{=}\NormalTok{ models.CharField(max\_length}\OperatorTok{=}\DecValTok{255}\NormalTok{)}

\KeywordTok{class}\NormalTok{ Book(models.Model):}
\NormalTok{    title }\OperatorTok{=}\NormalTok{ models.CharField(max\_length}\OperatorTok{=}\DecValTok{255}\NormalTok{)}
\NormalTok{    category }\OperatorTok{=}\NormalTok{ models.ForeignKey(Category, on\_delete}\OperatorTok{=}\NormalTok{models.CASCADE)}
\end{Highlighting}
\end{Shaded}

Par défaut, et si aucune propriété ne dispose d'un attribut
\texttt{primary\_key=True}, Django s'occupera d'ajouter un champ
\texttt{id} grâce à son héritage de la classe \texttt{models.Model}. Les
autres champs nous permettent d'identifier une catégorie
(\texttt{Category}) par un nom (\texttt{name}), tandis qu'un livre
(\texttt{Book}) le sera par ses propriétés \texttt{title} et une clé de
relation vers une catégorie. Un livre est donc lié à une catégorie,
tandis qu'une catégorie est associée à plusieurs livres.

\includegraphics{diagrams/books-foreign-keys-example.drawio.png}

A présent que notre structure dispose de sa modélisation, il nous faut
informer le moteur de base de données de créer la structure
correspondance:

\begin{verbatim}
$ python manage.py makemigrations
Migrations for 'library':
  library/migrations/0001_initial.py
    - Create model Category
    - Create model Book
\end{verbatim}

Cette étape créera un fichier différentiel, explicitant les
modifications à appliquer à la structure de données pour rester en
corrélation avec la modélisation de notre application.

Nous pouvons écrire un premier code d'initialisation de la manière
suivante:

\begin{Shaded}
\begin{Highlighting}[]
\ImportTok{from}\NormalTok{ library.models }\ImportTok{import}\NormalTok{ Book, Category}

\NormalTok{movies }\OperatorTok{=}\NormalTok{ Category.objects.create(name}\OperatorTok{=}\StringTok{"Adaptations au cinéma"}\NormalTok{)}
\NormalTok{medieval }\OperatorTok{=}\NormalTok{ Category.objects.create(name}\OperatorTok{=}\StringTok{"Médiéval{-}Fantastique"}\NormalTok{)}
\NormalTok{science\_fiction }\OperatorTok{=}\NormalTok{ Category.objects.create(name}\OperatorTok{=}\StringTok{"Sciences{-}fiction"}\NormalTok{)}
\NormalTok{computers }\OperatorTok{=}\NormalTok{ Category.objects.create(name}\OperatorTok{=}\StringTok{"Sciences Informatiques"}\NormalTok{)}

\NormalTok{books }\OperatorTok{=}\NormalTok{ \{}
    \StringTok{"Harry Potter"}\NormalTok{: movies,}
    \StringTok{"The Great Gatsby"}\NormalTok{: movies,}
    \StringTok{"Dune"}\NormalTok{: science\_fiction,}
    \StringTok{"H2G2"}\NormalTok{: science\_fiction,}
    \StringTok{"Ender\textquotesingle{}s Game"}\NormalTok{: science\_fiction,}
    \StringTok{"Le seigneur des anneaux"}\NormalTok{: medieval,}
    \StringTok{"L\textquotesingle{}Assassin Royal"}\NormalTok{, medieval,}
    \StringTok{"Clean code"}\NormalTok{: computers,}
    \StringTok{"Designing Data{-}Intensive Applications"}\NormalTok{: computers}
\NormalTok{\}}

\ControlFlowTok{for}\NormalTok{ book\_title, category }\KeywordTok{in}\NormalTok{ books.items:}
\NormalTok{    Book.objects.create(name}\OperatorTok{=}\NormalTok{book\_title, category}\OperatorTok{=}\NormalTok{category)}
\end{Highlighting}
\end{Shaded}

Nous nous rendons rapidement compte qu'un livre peut appartenir à
plusieurs catégories:

\begin{itemize}
\item
  \emph{Dune} a été adapté au cinéma en 1973 et en 2021, de même que
  \emph{Le Seigneur des Anneaux}. Ces deux titres (au moins) peuvent
  appartenir à deux catégories distinctes.
\item
  Pour \emph{The Great Gatsby}, c'est l'inverse: nous l'avons
  initialement classé comme film, mais le livre existe depuis 1925.
\item
  Nous pourrions sans doute également étoffer notre bibliothèque avec
  une catégorie spéciale "Baguettes magiques et trucs phalliques", à
  laquelle nous pourrons associer la saga \emph{Harry Potter} et ses
  dérivés.
\end{itemize}

En clair, notre modèle n'est pas adapté, et nous devons le modifier pour
qu'une occurrence puisse être liée à plusieurs catégories. Au lieu
d'utiliser un champ de type \texttt{ForeignKey}, nous utiliserons un
champ de type \texttt{ManyToMany}, c'est-à-dire qu'à présent, un livre
pourra être lié à plusieurs catégories, et qu'inversément, une même
catégorie pourra être liée à plusieurs livres.

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{class}\NormalTok{ Category(models.Model):}
\NormalTok{    name }\OperatorTok{=}\NormalTok{ models.CharField(max\_length}\OperatorTok{=}\DecValTok{255}\NormalTok{)}

\KeywordTok{class}\NormalTok{ Book(models.Model):}
\NormalTok{    title }\OperatorTok{=}\NormalTok{ models.CharField(max\_length}\OperatorTok{=}\DecValTok{255}\NormalTok{)}
\NormalTok{    category }\OperatorTok{=}\NormalTok{ models.ManyManyField(Category, on\_delete}\OperatorTok{=}\NormalTok{models.CASCADE)}
\end{Highlighting}
\end{Shaded}

Notre code d'initialisation reste par contre identique: Django s'occupe
parfaitement de gérer la transition.

\hypertarget{_accuxe8s_aux_relations}{%
\subsubsection{Accès aux relations}\label{_accuxe8s_aux_relations}}

\begin{Shaded}
\begin{Highlighting}[]
\CommentTok{\# wish/models.py}

\KeywordTok{class}\NormalTok{ Wishlist(models.Model):}
    \ControlFlowTok{pass}


\KeywordTok{class}\NormalTok{ Item(models.Model):}
\NormalTok{    wishlist }\OperatorTok{=}\NormalTok{ models.ForeignKey(Wishlist)}
\end{Highlighting}
\end{Shaded}

Depuis le code, à partir de l'instance de la classe \texttt{Item}, on
peut donc accéder à la liste en appelant la propriété \texttt{wishlist}
de notre instance. \textbf{A contrario}, depuis une instance de type
\texttt{Wishlist}, on peut accéder à tous les éléments liés grâce à
\texttt{\textless{}nom\ de\ la\ propriété\textgreater{}\_set}; ici
\texttt{item\_set}.

Lorsque vous déclarez une relation 1-1, 1-N ou N-N entre deux classes,
vous pouvez ajouter l'attribut \texttt{related\_name} afin de nommer la
relation inverse.

\begin{Shaded}
\begin{Highlighting}[]
\CommentTok{\# wish/models.py}

\KeywordTok{class}\NormalTok{ Wishlist(models.Model):}
    \ControlFlowTok{pass}


\KeywordTok{class}\NormalTok{ Item(models.Model):}
\NormalTok{    wishlist }\OperatorTok{=}\NormalTok{ models.ForeignKey(Wishlist, related\_name}\OperatorTok{=}\StringTok{\textquotesingle{}items\textquotesingle{}}\NormalTok{)}
\end{Highlighting}
\end{Shaded}

Si, dans une classe A, plusieurs relations sont liées à une classe B,
Django ne saura pas à quoi correspondra la relation inverse. Pour palier
à ce problème, nous fixons une valeur à l'attribut
\texttt{related\_name}. Par facilité (et par conventions), prenez
l'habitude de toujours ajouter cet attribut: votre modèle gagnera en
cohérence et en lisibilité. Si cette relation inverse n'est pas
nécessaire, il est possible de l'indiquer (par convention) au travers de
l'attribut \texttt{related\_name="+"}.

A partir de maintenant, nous pouvons accéder à nos propriétés de la
manière suivante:

\begin{Shaded}
\begin{Highlighting}[]
\CommentTok{\# python manage.py shell}

\OperatorTok{\textgreater{}\textgreater{}\textgreater{}} \ImportTok{from}\NormalTok{ wish.models }\ImportTok{import}\NormalTok{ Wishlist, Item}
\OperatorTok{\textgreater{}\textgreater{}\textgreater{}}\NormalTok{ wishlist }\OperatorTok{=}\NormalTok{ Wishlist.create(}\StringTok{\textquotesingle{}Liste de test\textquotesingle{}}\NormalTok{, }\StringTok{\textquotesingle{}description\textquotesingle{}}\NormalTok{)}
\OperatorTok{\textgreater{}\textgreater{}\textgreater{}}\NormalTok{ item }\OperatorTok{=}\NormalTok{ Item.create(}\StringTok{\textquotesingle{}Element de test\textquotesingle{}}\NormalTok{, }\StringTok{\textquotesingle{}description\textquotesingle{}}\NormalTok{, w)}
\OperatorTok{\textgreater{}\textgreater{}\textgreater{}}
\OperatorTok{\textgreater{}\textgreater{}\textgreater{}}\NormalTok{ item.wishlist}
\OperatorTok{\textless{}}\NormalTok{Wishlist: Wishlist }\BuiltInTok{object}\OperatorTok{\textgreater{}}
\OperatorTok{\textgreater{}\textgreater{}\textgreater{}}
\OperatorTok{\textgreater{}\textgreater{}\textgreater{}}\NormalTok{ wishlist.items.}\BuiltInTok{all}\NormalTok{()}
\NormalTok{[}\OperatorTok{\textless{}}\NormalTok{Item: Item }\BuiltInTok{object}\OperatorTok{\textgreater{}}\NormalTok{]}
\end{Highlighting}
\end{Shaded}

\hypertarget{_n1_queries}{%
\subsubsection{N+1 Queries}\label{_n1_queries}}

\hypertarget{_unicituxe9}{%
\subsection{Unicité}\label{_unicituxe9}}

\hypertarget{_indices}{%
\subsection{Indices}\label{_indices}}

\hypertarget{_conclusions}{%
\subsubsection{Conclusions}\label{_conclusions}}

Dans les examples ci-dessus, nous avons vu les relations multiples
(1-N), représentées par des clés étrangères (\textbf{ForeignKey}) d'une
classe A vers une classe B. Pour représenter d'autres types de
relations, il existe également les champs de type
\textbf{ManyToManyField}, afin de représenter une relation N-N. Il
existe également un type de champ spécial pour les clés étrangères, qui
est le Les champs de type \textbf{OneToOneField}, pour représenter une
relation 1-1.

\hypertarget{_metamoduxe8le_et_introspection}{%
\subsubsection{Metamodèle et
introspection}\label{_metamoduxe8le_et_introspection}}

Comme chaque classe héritant de \texttt{models.Model} possède une
propriété \texttt{objects}. Comme on l'a vu dans la section
\textbf{Jouons un peu avec la console}, cette propriété permet d'accéder
aux objects persistants dans la base de données, au travers d'un
\texttt{ModelManager}.

En plus de cela, il faut bien tenir compte des propriétés \texttt{Meta}
de la classe: si elle contient déjà un ordre par défaut, celui-ci sera
pris en compte pour l'ensemble des requêtes effectuées sur cette classe.

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{class}\NormalTok{ Wish(models.Model):}
\NormalTok{    name }\OperatorTok{=}\NormalTok{ models.CharField(max\_length}\OperatorTok{=}\DecValTok{255}\NormalTok{)}

    \KeywordTok{class}\NormalTok{ Meta:}
\NormalTok{        ordering }\OperatorTok{=}\NormalTok{ (}\StringTok{\textquotesingle{}name\textquotesingle{}}\NormalTok{,) }
\end{Highlighting}
\end{Shaded}

\begin{itemize}
\item
  Nous définissons un ordre par défaut, directement au niveau du modèle.
  Cela ne signifie pas qu'il ne sera pas possible de modifier cet ordre
  (la méthode \texttt{order\_by} existe et peut être chaînée à n'importe
  quel \emph{queryset}). D'où l'intérêt de tester ce type de
  comportement, dans la mesure où un \texttt{top\ 1} dans votre code
  pourrait être modifié simplement par cette petite information.
\end{itemize}

Pour sélectionner un objet au pif :
\texttt{return\ Category.objects.order\_by("?").first()}

Les propriétés de la classe Meta les plus utiles sont les suivates:

\begin{itemize}
\item
  \texttt{ordering} pour spécifier un ordre de récupération spécifique.
\item
  \texttt{verbose\_name} pour indiquer le nom à utiliser au singulier
  pour définir votre classe
\item
  \texttt{verbose\_name\_plural}, pour le pluriel.
\item
  \texttt{contraints} (Voir
  \href{https://girlthatlovestocode.com/django-model}{ici}-), par
  exemple
\end{itemize}

\begin{Shaded}
\begin{Highlighting}[]
\NormalTok{    constraints }\OperatorTok{=}\NormalTok{ [ }\CommentTok{\# constraints added}
\NormalTok{        models.CheckConstraint(check}\OperatorTok{=}\NormalTok{models.Q(year\_born\_\_lte}\OperatorTok{=}\NormalTok{datetime.date.today().year}\OperatorTok{{-}}\DecValTok{18}\NormalTok{), name}\OperatorTok{=}\StringTok{\textquotesingle{}will\_be\_of\_age\textquotesingle{}}\NormalTok{),}
\NormalTok{    ]}
\end{Highlighting}
\end{Shaded}

\hypertarget{_choix}{%
\subsubsection{Choix}\label{_choix}}

Voir \href{https://girlthatlovestocode.com/django-model}{ici}

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{class}\NormalTok{ Runner(models.Model):}

    \CommentTok{\# this is new:}
    \KeywordTok{class}\NormalTok{ Zone(models.IntegerChoices):}
\NormalTok{        ZONE\_1 }\OperatorTok{=} \DecValTok{1}\NormalTok{, }\StringTok{\textquotesingle{}Less than 3.10\textquotesingle{}}
\NormalTok{        ZONE\_2 }\OperatorTok{=} \DecValTok{2}\NormalTok{, }\StringTok{\textquotesingle{}Less than 3.25\textquotesingle{}}
\NormalTok{        ZONE\_3 }\OperatorTok{=} \DecValTok{3}\NormalTok{, }\StringTok{\textquotesingle{}Less than 3.45\textquotesingle{}}
\NormalTok{        ZONE\_4 }\OperatorTok{=} \DecValTok{4}\NormalTok{, }\StringTok{\textquotesingle{}Less than 4 hours\textquotesingle{}}
\NormalTok{        ZONE\_5 }\OperatorTok{=} \DecValTok{5}\NormalTok{, }\StringTok{\textquotesingle{}More than 4 hours\textquotesingle{}}

\NormalTok{    name }\OperatorTok{=}\NormalTok{ models.CharField(max\_length}\OperatorTok{=}\DecValTok{50}\NormalTok{)}
\NormalTok{    last\_name }\OperatorTok{=}\NormalTok{ models.CharField(max\_length}\OperatorTok{=}\DecValTok{50}\NormalTok{)}
\NormalTok{    email }\OperatorTok{=}\NormalTok{ models.EmailField()}
    \BuiltInTok{id} \OperatorTok{=}\NormalTok{ models.UUIDField(primary\_key}\OperatorTok{=}\VariableTok{True}\NormalTok{, default}\OperatorTok{=}\NormalTok{uuid.uuid4, editable}\OperatorTok{=}\VariableTok{False}\NormalTok{)}
\NormalTok{    start\_zone }\OperatorTok{=}\NormalTok{ models.PositiveSmallIntegerField(choices}\OperatorTok{=}\NormalTok{Zone.choices, default}\OperatorTok{=}\NormalTok{Zone.ZONE\_5, help\_text}\OperatorTok{=}\StringTok{"What was your best time on the marathon in last 2 years?"}\NormalTok{) }\CommentTok{\# this is new}
\end{Highlighting}
\end{Shaded}

\hypertarget{_validateurs}{%
\subsubsection{Validateurs}\label{_validateurs}}

\hypertarget{_constructeurs}{%
\subsubsection{Constructeurs}\label{_constructeurs}}

Si vous décidez de définir un constructeur sur votre modèle, ne
surchargez pas la méthode \texttt{init}: créez plutôt une méthode static
de type \texttt{create()}, en y associant les paramètres obligatoires ou
souhaités:

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{class}\NormalTok{ Wishlist(models.Model):}

    \AttributeTok{@staticmethod}
    \KeywordTok{def}\NormalTok{ create(name, description):}
\NormalTok{        w }\OperatorTok{=}\NormalTok{ Wishlist()}
\NormalTok{        w.name }\OperatorTok{=}\NormalTok{ name}
\NormalTok{        w.description }\OperatorTok{=}\NormalTok{ description}
\NormalTok{        w.save()}
        \ControlFlowTok{return}\NormalTok{ w}

\KeywordTok{class}\NormalTok{ Item(models.Model):}

    \AttributeTok{@staticmethod}
    \KeywordTok{def}\NormalTok{ create(name, description, wishlist):}
\NormalTok{        i }\OperatorTok{=}\NormalTok{ Item()}
\NormalTok{        i.name }\OperatorTok{=}\NormalTok{ name}
\NormalTok{        i.description }\OperatorTok{=}\NormalTok{ description}
\NormalTok{        i.wishlist }\OperatorTok{=}\NormalTok{ wishlist}
\NormalTok{        i.save()}
        \ControlFlowTok{return}\NormalTok{ i}
\end{Highlighting}
\end{Shaded}

Mieux encore: on pourrait passer par un \texttt{ModelManager} pour
limiter le couplage; l'accès à une information stockée en base de
données ne se ferait dès lors qu'au travers de cette instance et pas
directement au travers du modèle. De cette manière, on limite le
couplage des classes et on centralise l'accès.

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{class}\NormalTok{ ItemManager(...):}
\NormalTok{    (de mémoire, je ne sais plus exactement :}\OperatorTok{{-}}\NormalTok{))}
\end{Highlighting}
\end{Shaded}

\hypertarget{_conclusion_2}{%
\subsection{Conclusion}\label{_conclusion_2}}

Le modèle proposé par Django est un composant extrêmement performant,
mais fort couplé avec le coeur du framework. Si tous les composants
peuvent être échangés avec quelques manipulations, le cas du modèle sera
plus difficile à interchanger.

A côté de cela, il permet énormément de choses, et vous fera gagner un
temps précieux, tant en rapidité d'essais/erreurs, que de preuves de
concept.

Une possibilité peut également être de cantonner Django à un framework

\hypertarget{_querysets_managers}{%
\subsection{Querysets \& managers}\label{_querysets_managers}}

\begin{itemize}
\item
  \url{http://stackoverflow.com/questions/12681653/when-to-use-or-not-use-iterator-in-the-django-orm}
\item
  \url{https://docs.djangoproject.com/en/1.9/ref/models/querysets/\#django.db.models.query.QuerySet.iterator}
\item
  \url{http://blog.etianen.com/blog/2013/06/08/django-querysets/}
\end{itemize}

L'ORM de Django (et donc, chacune des classes qui composent votre
modèle) propose par défaut deux objets hyper importants:

\begin{itemize}
\item
  Les \texttt{managers}, qui consistent en un point d'entrée pour
  accéder aux objets persistants
\item
  Les \texttt{querysets}, qui permettent de filtrer des ensembles ou
  sous-ensemble d'objets. Les querysets peuvent s'imbriquer, pour
  ajouter d'autres filtres à des filtres existants, et fonctionnent
  comme un super jeu d'abstraction pour accéder à nos données
  (persistentes).
\end{itemize}

Ces deux propriétés vont de paire; par défaut, chaque classe de votre
modèle propose un attribut \texttt{objects}, qui correspond à un manager
(ou un gestionnaire, si vous préférez). Ce gestionnaire constitue
l'interface par laquelle vous accéderez à la base de données. Mais pour
cela, vous aurez aussi besoin d'appliquer certains requêtes ou filtres.
Et pour cela, vous aurez besoin des \texttt{querysets}, qui consistent
en des \ldots\hspace{0pt} ensembles de requêtes :-).

Si on veut connaître la requête SQL sous-jacente à l'exécution du
queryset, il suffit d'appeler la fonction str() sur la propriété
\texttt{query}:

\begin{Shaded}
\begin{Highlighting}[]
\NormalTok{queryset }\OperatorTok{=}\NormalTok{ Wishlist.objects.}\BuiltInTok{all}\NormalTok{()}

\BuiltInTok{print}\NormalTok{(queryset.query)}
\end{Highlighting}
\end{Shaded}

Conditions AND et OR sur un queryset

\begin{verbatim}
Pour un `AND`, il suffit de chaîner les conditions. ** trouver un exemple ici ** :-)
\end{verbatim}

\begin{verbatim}
Mais en gros : bidule.objects.filter(condition1, condition2)
\end{verbatim}

\begin{verbatim}
Il existe deux autres options : combiner deux querysets avec l'opérateur `&` ou combiner des Q objects avec ce même opérateur.
\end{verbatim}

Soit encore combiner des filtres:

\begin{Shaded}
\begin{Highlighting}[]
\ImportTok{from}\NormalTok{ core.models }\ImportTok{import}\NormalTok{ Wish}

\NormalTok{Wish.objects }
\NormalTok{Wish.objects.}\BuiltInTok{filter}\NormalTok{(name\_\_icontains}\OperatorTok{=}\StringTok{"test"}\NormalTok{).}\BuiltInTok{filter}\NormalTok{(name\_\_icontains}\OperatorTok{=}\StringTok{"too"}\NormalTok{) }
\end{Highlighting}
\end{Shaded}

\begin{itemize}
\item
  Ca, c'est notre manager.
\item
  Et là, on chaîne les requêtes pour composer une recherche sur tous les
  souhaits dont le nom contient (avec une casse insensible) la chaîne
  "test" et dont le nom contient la chaîne "too".
\end{itemize}

Pour un 'OR', on a deux options :

\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
  Soit passer par deux querysets, typiuqment
  \texttt{queryset1\ \textbar{}\ queryset2}
\item
  Soit passer par des \texttt{Q\ objects}, que l'on trouve dans le
  namespace \texttt{django.db.models}.
\end{enumerate}

\begin{Shaded}
\begin{Highlighting}[]
\ImportTok{from}\NormalTok{ django.db.models }\ImportTok{import}\NormalTok{ Q}

\NormalTok{condition1 }\OperatorTok{=}\NormalTok{ Q(...)}
\NormalTok{condition2 }\OperatorTok{=}\NormalTok{ Q(...)}

\NormalTok{bidule.objects.}\BuiltInTok{filter}\NormalTok{(condition1 }\OperatorTok{|}\NormalTok{ condition2)}
\end{Highlighting}
\end{Shaded}

L'opérateur inverse (\emph{NOT})

Idem que ci-dessus : soit on utilise la méthode \texttt{exclude} sur le
queryset, soit l'opérateur \texttt{\textasciitilde{}} sur un Q object;

Ajouter les sujets suivants :

\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
  Prefetch
\item
  select\_related
\end{enumerate}

\hypertarget{_gestionnaire_de_models_managers_et_opuxe9rations}{%
\subsubsection{Gestionnaire de models (managers) et
opérations}\label{_gestionnaire_de_models_managers_et_opuxe9rations}}

Chaque définition de modèle utilise un \texttt{Manager}, afin d'accéder
à la base de données et traiter nos demandes. Indirectement, une
instance de modèle ne \textbf{connait} \textbf{pas} la base de données:
c'est son gestionnaire qui a cette tâche. Il existe deux exceptions à
cette règle: les méthodes \texttt{save()} et \texttt{update()}.

\begin{itemize}
\item
  Instanciation: MyClass()
\item
  Récupération: MyClass.objects.get(pk=\ldots\hspace{0pt})
\item
  Sauvegarde : MyClass().save()
\item
  Création: MyClass.objects.create(\ldots\hspace{0pt})
\item
  Liste des enregistrements: MyClass.objects.all()
\end{itemize}

Par défaut, le gestionnaire est accessible au travers de la propriété
\texttt{objects}. Cette propriété a une double utilité:

\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
  Elle est facile à surcharger - il nous suffit de définir une nouvelle
  classe héritant de ModelManager, puis de définir, au niveau de la
  classe, une nouvelle assignation à la propriété \texttt{objects}
\item
  Il est tout aussi facile de définir d'autres propriétés présentant des
  filtres bien spécifiques.
\end{enumerate}

\hypertarget{_requuxeates}{%
\subsubsection{Requêtes}\label{_requuxeates}}

DANGER: Les requêtes sont sensibles à la casse, \textbf{même} si le
moteur de base de données ne l'est pas. C'est notamment le cas pour
Microsoft SQL Server; faire une recherche directement via les outils de
Microsoft ne retournera pas obligatoirement les mêmes résultats que les
managers, qui seront beaucoup plus tatillons sur la qualité des
recherches par rapport aux filtres paramétrés en entrée.

\hypertarget{_jointures}{%
\subsubsection{Jointures}\label{_jointures}}

Pour appliquer une jointure sur un modèle, nous pouvons passer par les
méthodes \texttt{select\_related} et \texttt{prefetch\_related}. Il faut
cependant faire \textbf{très} attention au prefetch related, qui
fonctionne en fait comme une grosse requête dans laquelle nous trouvons
un \texttt{IN\ (\ldots{}\hspace{0pt})}. Càd que Django va récupérer tous
les objets demandés initialement par le queryset, pour ensuite prendre
toutes les clés primaires, pour finalement faire une deuxième requête et
récupérer les relations externes.

Au final, si votre premier queryset est relativement grand (nous parlons
de 1000 à 2000 éléments, en fonction du moteur de base de données), la
seconde requête va planter et vous obtiendrez une exception de type
\texttt{django.db.utils.OperationalError:\ too\ many\ SQL\ variables}.

Nous pourrions penser qu'utiliser un itérateur permettrait de combiner
les deux, mais ce n'est pas le cas\ldots\hspace{0pt}

Comme l'indique la documentation:

\begin{verbatim}
Note that if you use iterator() to run the query, prefetch_related() calls will be ignored since these two optimizations do not make sense together.
\end{verbatim}

Ajouter un itérateur va en fait forcer le code à parcourir chaque
élément de la liste, pour l'évaluer. Il y aura donc (à nouveau) autant
de requêtes qu'il y a d'éléments, ce que nous cherchons à éviter.

\begin{Shaded}
\begin{Highlighting}[]
\NormalTok{informations }\OperatorTok{=}\NormalTok{ (}
  \OperatorTok{\textless{}}\NormalTok{MyObject}\OperatorTok{\textgreater{}}\NormalTok{.objects.}\BuiltInTok{filter}\NormalTok{(}\OperatorTok{\textless{}}\NormalTok{my\_criteria}\OperatorTok{\textgreater{}}\NormalTok{)}
\NormalTok{  .select\_related(}\OperatorTok{\textless{}}\NormalTok{related\_field}\OperatorTok{\textgreater{}}\NormalTok{)}
\NormalTok{  .prefetch\_related(}\OperatorTok{\textless{}}\NormalTok{related\_field}\OperatorTok{\textgreater{}}\NormalTok{)}
\NormalTok{  .iterator(chunk\_size}\OperatorTok{=}\DecValTok{1000}\NormalTok{)}
\NormalTok{)}
\end{Highlighting}
\end{Shaded}

\hypertarget{_aggregate_vs_annotate}{%
\subsection{Aggregate vs. Annotate}\label{_aggregate_vs_annotate}}

\url{https://docs.djangoproject.com/en/3.1/topics/db/aggregation/}

\hypertarget{_migrations}{%
\section{Migrations}\label{_migrations}}

Dans cette section, nous allons voir comment fonctionnent les
migrations. Lors d'une première approche, elles peuvent sembler un peu
magiques, puisqu'elles centralisent un ensemble de modifications pouvant
être répétées sur un schéma de données, en tenant compte de ce qui a
déjà été appliqué et en vérifiant quelles migrations devaient encore
l'être pour mettre l'application à niveau. Une analyse en profondeur
montrera qu'elles ne sont pas plus complexes à suivre et à comprendre
qu'un ensemble de fonctions de gestion appliquées à notre application.

La commande \texttt{sqldump}, qui nous présentera le schéma tel qu'il
sera compris.

L'intégration des migrations a été réalisée dans la version 1.7 de
Django. Avant cela, il convenait de passer par une librairie tierce
intitulée \href{https://south.readthedocs.io/en/latest}{South}.

Prenons l'exemple de notre liste de souhaits; nous nous rendons
(bêtement) compte que nous avons oublié d'ajouter un champ de
\texttt{description} à une liste. Historiquement, cette action
nécessitait l'intervention d'un administrateur système ou d'une personne
ayant accès au schéma de la base de données, à partir duquel ce-dit
utilisateur pouvait jouer manuellement un script SQL. sql Cet
enchaînement d'étapes nécessitait une bonne coordination d'équipe, mais
également une bonne confiance dans les scripts à exécuter. Et
souvenez-vous (cf. ref-à-insérer), que l'ensemble des actions doit être
répétable et automatisable.

Bref, dans les années '80, il convenait de jouer ceci après s'être
connecté au serveur de base de données:

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{ALTER} \KeywordTok{TABLE}\NormalTok{ WishList }\KeywordTok{ADD} \KeywordTok{COLUMN}\NormalTok{ Description }\DataTypeTok{nvarchar}\NormalTok{(}\FunctionTok{MAX}\NormalTok{);}
\end{Highlighting}
\end{Shaded}

Et là, nous nous rappelons qu'un utilisateur tourne sur Oracle et pas
sur MySQL, et qu'il a donc besoin de son propre script d'exécution,
parce que le type du nouveau champ n'est pas exactement le même entre
les deux moteurs différents:

\begin{Shaded}
\begin{Highlighting}[]
\CommentTok{{-}{-} Firebird}
\KeywordTok{ALTER} \KeywordTok{TABLE} \KeywordTok{Category} \KeywordTok{ALTER} \KeywordTok{COLUMN}\NormalTok{ Name }\KeywordTok{type} \DataTypeTok{varchar}\NormalTok{(}\DecValTok{2000}\NormalTok{)}

\CommentTok{{-}{-} MSSQL}
\KeywordTok{ALTER} \KeywordTok{TABLE} \KeywordTok{Category} \KeywordTok{ALTER} \KeywordTok{Column}\NormalTok{ Name }\DataTypeTok{varchar}\NormalTok{(}\DecValTok{2000}\NormalTok{)}

\CommentTok{{-}{-} Oracle}
\KeywordTok{ALTER} \KeywordTok{TABLE} \KeywordTok{Category} \KeywordTok{MODIFY}\NormalTok{ Name }\DataTypeTok{varchar2}\NormalTok{(}\DecValTok{2000}\NormalTok{)}
\end{Highlighting}
\end{Shaded}

En bref, les problèmes suivants apparaissent très rapidement:

\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
  Aucune autonomie: il est nécessaire d'avoir les compétences d'une
  personne tierce pour avancer ou de disposer des droits
  administrateurs,
\item
  Aucune automatisation possible, à moins d'écrire un programme, qu'il
  faudra également maintenir et intégrer au niveau des tests
\item
  Nécessité de maintenir autant de scripts différents qu'il y a de
  moteurs de base de données supportés
\item
  Aucune possibilité de vérifier si le script a déjà été exécuté ou non,
  à moins, à nouveau, de maintenir un programme supplémentaire.
\end{enumerate}

\hypertarget{_fonctionement_guxe9nuxe9ral_2}{%
\subsection{Fonctionement
général}\label{_fonctionement_guxe9nuxe9ral_2}}

Le moteur de migrations résout la plupart de ces soucis: le framework
embarque ses propres applications, dont les migrations, qui gèrent
elles-mêmes l'arbre de dépendances entre les modifications devant être
appliquées.

Pour reprendre un des premiers exemples, nous avions créé un modèle
contenant deux classes, qui correspondent chacun à une table dans un
modèle relationnel:

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{class}\NormalTok{ Category(models.Model):}
\NormalTok{    name }\OperatorTok{=}\NormalTok{ models.CharField(max\_length}\OperatorTok{=}\DecValTok{255}\NormalTok{)}

\KeywordTok{class}\NormalTok{ Book(models.Model):}
\NormalTok{    title }\OperatorTok{=}\NormalTok{ models.CharField(max\_length}\OperatorTok{=}\DecValTok{255}\NormalTok{)}
\NormalTok{    category }\OperatorTok{=}\NormalTok{ models.ForeignKey(Category, on\_delete}\OperatorTok{=}\NormalTok{models.CASCADE)}
\end{Highlighting}
\end{Shaded}

Nous avions ensuite modifié la clé de liaison, pour permettre d'associer
plusieurs catégories à un même livre, et inversément:

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{class}\NormalTok{ Category(models.Model):}
\NormalTok{    name }\OperatorTok{=}\NormalTok{ models.CharField(max\_length}\OperatorTok{=}\DecValTok{255}\NormalTok{)}

\KeywordTok{class}\NormalTok{ Book(models.Model):}
\NormalTok{    title }\OperatorTok{=}\NormalTok{ models.CharField(max\_length}\OperatorTok{=}\DecValTok{255}\NormalTok{)}
\NormalTok{    category }\OperatorTok{=}\NormalTok{ models.ManyManyField(Category, on\_delete}\OperatorTok{=}\NormalTok{models.CASCADE)}
\end{Highlighting}
\end{Shaded}

Chronologiquement, cela nous a donné une première migration consistant à
créer le modèle initial, suivie d'une seconde migration après que nous
ayons modifié le modèle pour autoriser des relations multiples.

migrations successives, à appliquer pour que la structure relationnelle
corresponde aux attentes du modèle Django:

\begin{Shaded}
\begin{Highlighting}[]
\CommentTok{\# library/migrations/0001\_initial.py}

\ImportTok{from}\NormalTok{ django.db }\ImportTok{import}\NormalTok{ migrations, models}
\ImportTok{import}\NormalTok{ django.db.models.deletion}


\KeywordTok{class}\NormalTok{ Migration(migrations.Migration):}

\NormalTok{    initial }\OperatorTok{=} \VariableTok{True}

\NormalTok{    dependencies }\OperatorTok{=}\NormalTok{ []}

\NormalTok{    operations }\OperatorTok{=}\NormalTok{ [}
\NormalTok{        migrations.CreateModel(  }
\NormalTok{            name}\OperatorTok{=}\StringTok{"Category"}\NormalTok{,}
\NormalTok{            fields}\OperatorTok{=}\NormalTok{[}
\NormalTok{                (}
                    \StringTok{"id"}\NormalTok{,}
\NormalTok{                    models.BigAutoField(}
\NormalTok{                        auto\_created}\OperatorTok{=}\VariableTok{True}\NormalTok{,}
\NormalTok{                        primary\_key}\OperatorTok{=}\VariableTok{True}\NormalTok{,}
\NormalTok{                        serialize}\OperatorTok{=}\VariableTok{False}\NormalTok{,}
\NormalTok{                        verbose\_name}\OperatorTok{=}\StringTok{"ID"}\NormalTok{,}
\NormalTok{                    ),}
\NormalTok{                ),}
\NormalTok{                (}\StringTok{"name"}\NormalTok{, models.CharField(max\_length}\OperatorTok{=}\DecValTok{255}\NormalTok{)),}
\NormalTok{            ],}
\NormalTok{        ),}
\NormalTok{        migrations.CreateModel(  }
\NormalTok{            name}\OperatorTok{=}\StringTok{"Book"}\NormalTok{,}
\NormalTok{            fields}\OperatorTok{=}\NormalTok{[}
\NormalTok{                (}
                    \StringTok{"id"}\NormalTok{,}
\NormalTok{                    models.BigAutoField(}
\NormalTok{                        auto\_created}\OperatorTok{=}\VariableTok{True}\NormalTok{,}
\NormalTok{                        primary\_key}\OperatorTok{=}\VariableTok{True}\NormalTok{,}
\NormalTok{                        serialize}\OperatorTok{=}\VariableTok{False}\NormalTok{,}
\NormalTok{                        verbose\_name}\OperatorTok{=}\StringTok{"ID"}\NormalTok{,}
\NormalTok{                    ),}
\NormalTok{                ),}
\NormalTok{                (}
                    \StringTok{"title"}\NormalTok{,}
\NormalTok{                    models.CharField(max\_length}\OperatorTok{=}\DecValTok{255}\NormalTok{)),}
\NormalTok{                (}
                    \StringTok{"category"}\NormalTok{,}
\NormalTok{                    models.ForeignKey(}
\NormalTok{                        on\_delete}\OperatorTok{=}\NormalTok{django.db.models.deletion.CASCADE,}
\NormalTok{                        to}\OperatorTok{=}\StringTok{"library.category"}\NormalTok{,}
\NormalTok{                    ),}
\NormalTok{                ),}
\NormalTok{            ],}
\NormalTok{        ),}
\NormalTok{    ]}
\end{Highlighting}
\end{Shaded}

\begin{itemize}
\item
  La migration crée un nouveau modèle intitulé "Category", possédant un
  champ \texttt{id} (auto-défini, puisque nous n'avons rien fait), ainsi
  qu'un champ \texttt{name} de type texte et d'une longue maximale de
  255 caractères.
\item
  Elle crée un deuxième modèle intitulé "Book", possédant trois champs:
  son identifiant auto-généré \texttt{id}, son titre \texttt{title} et
  sa relation vers une catégorie, au travers du champ \texttt{category}.
\end{itemize}

Un outil comme \href{https://sqlitebrowser.org/}{DB Browser For SQLite}
nous donne la structure suivante:

\includegraphics{images/db/migrations-0001-to-0002.png}

La représentation au niveau de la base de données est la suivante:

\includegraphics{images/db/link-book-category-fk.drawio.png}

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{class}\NormalTok{ Category(models.Model):}
\NormalTok{    name }\OperatorTok{=}\NormalTok{ models.CharField(max\_length}\OperatorTok{=}\DecValTok{255}\NormalTok{)}

\KeywordTok{class}\NormalTok{ Book(models.Model):}
\NormalTok{    title }\OperatorTok{=}\NormalTok{ models.CharField(max\_length}\OperatorTok{=}\DecValTok{255}\NormalTok{)}
\NormalTok{    category }\OperatorTok{=}\NormalTok{ models.ManyManyField(Category) }
\end{Highlighting}
\end{Shaded}

\begin{itemize}
\item
  Vous noterez que l'attribut \texttt{on\_delete} n'est plus nécessaire.
\end{itemize}

Après cette modification, la migration résultante à appliquer
correspondra à ceci. En SQL, un champ de type \texttt{ManyToMany} ne
peut qu'être représenté par une table intermédiaire. C'est qu'applique
la migration en supprimant le champ liant initialement un livre à une
catégorie et en ajoutant une nouvelle table de liaison.

\begin{Shaded}
\begin{Highlighting}[]
\CommentTok{\# library/migrations/0002\_remove\_book\_category\_book\_category.py}

\ImportTok{from}\NormalTok{ django.db }\ImportTok{import}\NormalTok{ migrations, models}


\KeywordTok{class}\NormalTok{ Migration(migrations.Migration):}

\NormalTok{    dependencies }\OperatorTok{=}\NormalTok{ [}
\NormalTok{        (}\StringTok{\textquotesingle{}library\textquotesingle{}}\NormalTok{, }\StringTok{\textquotesingle{}0001\_initial\textquotesingle{}}\NormalTok{),}
\NormalTok{    ]}

\NormalTok{    operations }\OperatorTok{=}\NormalTok{ [}
\NormalTok{        migrations.RemoveField(  }
\NormalTok{            model\_name}\OperatorTok{=}\StringTok{\textquotesingle{}book\textquotesingle{}}\NormalTok{,}
\NormalTok{            name}\OperatorTok{=}\StringTok{\textquotesingle{}category\textquotesingle{}}\NormalTok{,}
\NormalTok{        ),}
\NormalTok{        migrations.AddField(   }
\NormalTok{            model\_name}\OperatorTok{=}\StringTok{\textquotesingle{}book\textquotesingle{}}\NormalTok{,}
\NormalTok{            name}\OperatorTok{=}\StringTok{\textquotesingle{}category\textquotesingle{}}\NormalTok{,}
\NormalTok{            field}\OperatorTok{=}\NormalTok{models.ManyToManyField(to}\OperatorTok{=}\StringTok{\textquotesingle{}library.Category\textquotesingle{}}\NormalTok{),}
\NormalTok{        ),}
\NormalTok{    ]}
\end{Highlighting}
\end{Shaded}

\begin{itemize}
\item
  La migration supprime l'ancienne clé étrangère\ldots\hspace{0pt}
\item
  \ldots\hspace{0pt} et ajoute une nouvelle table, permettant de lier
  nos catégories à nos livres.
\end{itemize}

\includegraphics{images/db/migrations-0002-many-to-many.png}

Nous obtenons à présent la représentation suivante en base de données:

\includegraphics{images/db/link-book-category-m2m.drawio.png}

\hypertarget{_graph_de_duxe9pendances}{%
\subsection{Graph de dépendances}\label{_graph_de_duxe9pendances}}

Lorsqu'une migration applique une modification au schéma d'une base de
données, il est évident qu'elle ne peut pas être appliquée dans
n'importe quel ordre ou à n'importe quel moment.

Dès la création d'un nouveau projet, avec une configuration par défaut
et même sans avoir ajouté d'applications, Django proposera immédiatement
d'appliquer les migrations des applications \textbf{admin},
\textbf{auth}, \textbf{contenttypes} et \textbf{sessions}, qui font
partie du coeur du système, et qui se trouvent respectivement aux
emplacements suivants:

\begin{itemize}
\item
  \textbf{admin}: \texttt{site-packages/django/contrib/admin/migrations}
\item
  \textbf{auth}: \texttt{site-packages/django/contrib/auth/migrations}
\item
  \textbf{contenttypes}:
  \texttt{site-packages/django/contrib/contenttypes/migrations}
\item
  \textbf{sessions}:
  \texttt{site-packages/django/contrib/sessions/migrations}
\end{itemize}

Ceci est dû au fait que, toujours par défaut, ces applications sont
reprises au niveau de la configuration d'un nouveau projet, dans le
fichier \texttt{settings.py}:

\begin{Shaded}
\begin{Highlighting}[]
\NormalTok{[snip]}

\NormalTok{INSTALLED\_APPS }\OperatorTok{=}\NormalTok{ [}
    \StringTok{\textquotesingle{}django.contrib.admin\textquotesingle{}}\NormalTok{, }
    \StringTok{\textquotesingle{}django.contrib.auth\textquotesingle{}}\NormalTok{, }
    \StringTok{\textquotesingle{}django.contrib.contenttypes\textquotesingle{}}\NormalTok{, }
    \StringTok{\textquotesingle{}django.contrib.sessions\textquotesingle{}}\NormalTok{, }
    \StringTok{\textquotesingle{}django.contrib.messages\textquotesingle{}}\NormalTok{,}
    \StringTok{\textquotesingle{}django.contrib.staticfiles\textquotesingle{}}\NormalTok{,}
\NormalTok{]}

\NormalTok{[snip]}
\end{Highlighting}
\end{Shaded}

\begin{itemize}
\item
  Admin
\item
  Auth
\item
  Contenttypes
\item
  et Sessions.
\end{itemize}

Dès que nous les appliquerons, nous recevrons les messages suivants:

\begin{Shaded}
\begin{Highlighting}[]
\NormalTok{$ }\ExtensionTok{python}\NormalTok{ manage.py migrate}
\ExtensionTok{Operations}\NormalTok{ to perform:}
  \ExtensionTok{Apply}\NormalTok{ all migrations: admin, auth, contenttypes, library, sessions, world}
\ExtensionTok{Running}\NormalTok{ migrations:}
  \ExtensionTok{Applying}\NormalTok{ contenttypes.0001\_initial... OK}
  \ExtensionTok{Applying}\NormalTok{ auth.0001\_initial... OK}
  \ExtensionTok{Applying}\NormalTok{ admin.0001\_initial... OK}
  \ExtensionTok{Applying}\NormalTok{ admin.0002\_logentry\_remove\_auto\_add... OK}
  \ExtensionTok{Applying}\NormalTok{ admin.0003\_logentry\_add\_action\_flag\_choices... OK}
  \ExtensionTok{Applying}\NormalTok{ contenttypes.0002\_remove\_content\_type\_name... OK}
  \ExtensionTok{Applying}\NormalTok{ auth.0002\_alter\_permission\_name\_max\_length... OK}
  \ExtensionTok{Applying}\NormalTok{ auth.0003\_alter\_user\_email\_max\_length... OK}
  \ExtensionTok{Applying}\NormalTok{ auth.0004\_alter\_user\_username\_opts... OK}
  \ExtensionTok{Applying}\NormalTok{ auth.0005\_alter\_user\_last\_login\_null... OK}
  \ExtensionTok{Applying}\NormalTok{ auth.0006\_require\_contenttypes\_0002... OK}
  \ExtensionTok{Applying}\NormalTok{ auth.0007\_alter\_validators\_add\_error\_messages... OK}
  \ExtensionTok{Applying}\NormalTok{ auth.0008\_alter\_user\_username\_max\_length... OK}
  \ExtensionTok{Applying}\NormalTok{ auth.0009\_alter\_user\_last\_name\_max\_length... OK}
  \ExtensionTok{Applying}\NormalTok{ auth.0010\_alter\_group\_name\_max\_length... OK}
  \ExtensionTok{Applying}\NormalTok{ auth.0011\_update\_proxy\_permissions... OK}
  \ExtensionTok{Applying}\NormalTok{ auth.0012\_alter\_user\_first\_name\_max\_length... OK}
  \ExtensionTok{Applying}\NormalTok{ sessions.0001\_initial... OK}
\end{Highlighting}
\end{Shaded}

Cet ordre est défini au niveau de la propriété \texttt{dependencies},
que l'on retrouve au niveau de chaque description de migration, En
explorant les paquets qui se trouvent au niveau des répertoires et en
analysant les dépendances décrites au niveau de chaque action de
migration, on arrive au schéma suivant, qui est un graph dirigé
acyclique:

\includegraphics{images/db/migrations_auth_admin_contenttypes_sessions.png}

\hypertarget{_sous_le_capot}{%
\subsection{Sous le capot}\label{_sous_le_capot}}

Une migration consiste à appliquer un ensemble de modifications (ou
\textbf{opérations}), qui exercent un ensemble de transformations, pour
que le schéma de base de données corresponde au modèle de l'application
sous-jacente.

Les migrations (comprendre les "\emph{migrations du schéma de base de
données}") sont intimement liées à la représentation d'un contexte
fonctionnel: l'ajout d'une nouvelle information, d'un nouveau champ ou
d'une nouvelle fonction peut s'accompagner de tables de données à mettre
à jour ou de champs à étendre. Il est primordial que la structure de la
base de données corresponde à ce à quoi l'application s'attend, sans
quoi la probabilité que l'utilisateur tombe sur une erreur de type
\texttt{django.db.utils.OperationalError} est (très) grande.
Typiquement, après avoir ajouté un nouveau champ \texttt{summary} à
chacun de nos livres, et sans avoir appliqué de migrations, nous tombons
sur ceci:

\begin{verbatim}
>>> from library.models import Book
>>> Book.objects.all()
Traceback (most recent call last):
  File "~/Sources/.venvs/gwlib/lib/python3.9/site-packages/django/db/backends/utils.py", line 85, in _execute
    return self.cursor.execute(sql, params)
  File "~/Sources/.venvs/gwlib/lib/python3.9/site-packages/django/db/backends/sqlite3/base.py", line 416, in execute
    return Database.Cursor.execute(self, query, params)
sqlite3.OperationalError: no such column: library_book.summary
\end{verbatim}

Pour éviter ce type d'erreurs, il est impératif que les nouvelles
migrations soient appliquées \textbf{avant} que le code ne soit déployé;
l'idéal étant que ces deux opérations soient réalisées de manière
atomique, avec un \emph{rollback} si une anomalie était détectée.

En allant

Pour éviter ce type d'erreurs, plusieurs stratégies peuvent être
appliquées:

intégrer ici un point sur les updates db - voir designing data-intensive
applications.

Toujours dans une optique de centralisation, les migrations sont
directement embarquées au niveau du code, et doivent faire partie du
dépôt central de sources. Le développeur s'occupe de créer les
migrations en fonction des actions à entreprendre; ces migrations
peuvent être retravaillées, \emph{squashées}, \ldots\hspace{0pt} et
feront partie intégrante du processus de mise à jour de l'application.

A noter que les migrations n'appliqueront de modifications que si le
schéma est impacté. Ajouter une propriété \texttt{related\_name} sur une
ForeignKey n'engendrera aucune nouvelle action de migration, puisque ce
type d'action ne s'applique que sur l'ORM, et pas directement sur la
base de données: au niveau des tables, rien ne change. Seul le code et
le modèle sont impactés.

Une migration est donc une classe Python, présentant \emph{a minima}
deux propriétés:

\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
  \texttt{dependencies}, qui décrit les opérations précédentes devant
  obligatoirement avoir été appliquées
\item
  \texttt{operations}, qui consiste à décrire précisément ce qui doit
  être exécuté.
\end{enumerate}

Pour reprendre notre exemple d'ajout d'un champ \texttt{description} sur
le modèle \texttt{WishList}, la migration ressemblera à ceci:

\begin{Shaded}
\begin{Highlighting}[]
\ImportTok{from}\NormalTok{ django.db }\ImportTok{import}\NormalTok{ migrations, models}
\ImportTok{import}\NormalTok{ django.db.models.deletion}
\ImportTok{import}\NormalTok{ django.utils.timezone}


\KeywordTok{class}\NormalTok{ Migration(migrations.Migration):}

\NormalTok{    dependencies }\OperatorTok{=}\NormalTok{ [}
\NormalTok{        (}\StringTok{\textquotesingle{}gwift\textquotesingle{}}\NormalTok{, }\StringTok{\textquotesingle{}0004\_name\_value\textquotesingle{}}\NormalTok{),}
\NormalTok{    ]}

\NormalTok{    operations }\OperatorTok{=}\NormalTok{ [}
\NormalTok{        migrations.AddField(}
\NormalTok{            model\_name}\OperatorTok{=}\StringTok{\textquotesingle{}wishlist\textquotesingle{}}\NormalTok{,}
\NormalTok{            name}\OperatorTok{=}\StringTok{\textquotesingle{}description\textquotesingle{}}\NormalTok{,}
\NormalTok{            field}\OperatorTok{=}\NormalTok{models.TextField(default}\OperatorTok{=}\StringTok{""}\NormalTok{, null}\OperatorTok{=}\VariableTok{True}\NormalTok{)}
\NormalTok{            preserve\_default}\OperatorTok{=}\VariableTok{False}\NormalTok{,}
\NormalTok{        ),}
\NormalTok{    ]}
\end{Highlighting}
\end{Shaded}

\hypertarget{_liste_des_migrations}{%
\subsection{Liste des migrations}\label{_liste_des_migrations}}

L'option \texttt{showmigrations} de \texttt{manage.py} permet de lister
toutes les migrations du projet, et d'identifier celles qui n'auraient
pas encore été appliquées:

\begin{Shaded}
\begin{Highlighting}[]
\NormalTok{$ }\ExtensionTok{python}\NormalTok{ manage.py showmigrations}
\ExtensionTok{admin}
\NormalTok{ [}\ExtensionTok{X}\NormalTok{] 0001\_initial}
\NormalTok{ [}\ExtensionTok{X}\NormalTok{] 0002\_logentry\_remove\_auto\_add}
\NormalTok{ [}\ExtensionTok{X}\NormalTok{] 0003\_logentry\_add\_action\_flag\_choices}
\ExtensionTok{auth}
\NormalTok{ [}\ExtensionTok{X}\NormalTok{] 0001\_initial}
\NormalTok{ [}\ExtensionTok{X}\NormalTok{] 0002\_alter\_permission\_name\_max\_length}
\NormalTok{ [}\ExtensionTok{X}\NormalTok{] 0003\_alter\_user\_email\_max\_length}
\NormalTok{ [}\ExtensionTok{X}\NormalTok{] 0004\_alter\_user\_username\_opts}
\NormalTok{ [}\ExtensionTok{X}\NormalTok{] 0005\_alter\_user\_last\_login\_null}
\NormalTok{ [}\ExtensionTok{X}\NormalTok{] 0006\_require\_contenttypes\_0002}
\NormalTok{ [}\ExtensionTok{X}\NormalTok{] 0007\_alter\_validators\_add\_error\_messages}
\NormalTok{ [}\ExtensionTok{X}\NormalTok{] 0008\_alter\_user\_username\_max\_length}
\NormalTok{ [}\ExtensionTok{X}\NormalTok{] 0009\_alter\_user\_last\_name\_max\_length}
\NormalTok{ [}\ExtensionTok{X}\NormalTok{] 0010\_alter\_group\_name\_max\_length}
\NormalTok{ [}\ExtensionTok{X}\NormalTok{] 0011\_update\_proxy\_permissions}
\NormalTok{ [}\ExtensionTok{X}\NormalTok{] 0012\_alter\_user\_first\_name\_max\_length}
\ExtensionTok{contenttypes}
\NormalTok{ [}\ExtensionTok{X}\NormalTok{] 0001\_initial}
\NormalTok{ [}\ExtensionTok{X}\NormalTok{] 0002\_remove\_content\_type\_name}
\ExtensionTok{library}
\NormalTok{ [}\ExtensionTok{X}\NormalTok{] 0001\_initial}
\NormalTok{ [}\ExtensionTok{X}\NormalTok{] 0002\_remove\_book\_category\_book\_category}
\BuiltInTok{ [ ]} \ExtensionTok{0003\_book\_summary}
\ExtensionTok{sessions}
\NormalTok{ [}\ExtensionTok{X}\NormalTok{] 0001\_initial}
\end{Highlighting}
\end{Shaded}

\hypertarget{_squash}{%
\subsection{Squash}\label{_squash}}

Finalement, lorsque vous développez sur votre propre branche (cf.
\protect\hyperlink{git}{???}), vous serez peut-être tentés de créer
plusieurs migrations en fonction de l'évolution de ce que vous mettez en
place. Dans ce cas précis, il peut être intéressant d'utiliser la
méthode \texttt{squashmigrations}, qui permet \emph{d'aplatir} plusieurs
fichiers en un seul.

Nous partons dans deux migrations suivantes:

\begin{Shaded}
\begin{Highlighting}[]
\CommentTok{\# library/migrations/0002\_remove\_book\_category.py}

\ImportTok{from}\NormalTok{ django.db }\ImportTok{import}\NormalTok{ migrations, models}


\KeywordTok{class}\NormalTok{ Migration(migrations.Migration):}

\NormalTok{    dependencies }\OperatorTok{=}\NormalTok{ [}
\NormalTok{        (}\StringTok{\textquotesingle{}library\textquotesingle{}}\NormalTok{, }\StringTok{\textquotesingle{}0001\_initial\textquotesingle{}}\NormalTok{),}
\NormalTok{    ]}

\NormalTok{    operations }\OperatorTok{=}\NormalTok{ [}
\NormalTok{        migrations.RemoveField(}
\NormalTok{            model\_name}\OperatorTok{=}\StringTok{\textquotesingle{}book\textquotesingle{}}\NormalTok{,}
\NormalTok{            name}\OperatorTok{=}\StringTok{\textquotesingle{}category\textquotesingle{}}\NormalTok{,}
\NormalTok{        ),}
\NormalTok{        migrations.AddField(}
\NormalTok{            model\_name}\OperatorTok{=}\StringTok{\textquotesingle{}book\textquotesingle{}}\NormalTok{,}
\NormalTok{            name}\OperatorTok{=}\StringTok{\textquotesingle{}category\textquotesingle{}}\NormalTok{,}
\NormalTok{            field}\OperatorTok{=}\NormalTok{models.ManyToManyField(to}\OperatorTok{=}\StringTok{\textquotesingle{}library.Category\textquotesingle{}}\NormalTok{),}
\NormalTok{        ),}
\NormalTok{    ]}
\end{Highlighting}
\end{Shaded}

\begin{Shaded}
\begin{Highlighting}[]
\CommentTok{\# library/migrations/0003\_book\_summary.py}

\ImportTok{from}\NormalTok{ django.db }\ImportTok{import}\NormalTok{ migrations, models}


\KeywordTok{class}\NormalTok{ Migration(migrations.Migration):}

\NormalTok{    dependencies }\OperatorTok{=}\NormalTok{ [}
\NormalTok{        (}\StringTok{\textquotesingle{}library\textquotesingle{}}\NormalTok{, }\StringTok{\textquotesingle{}0002\_remove\_book\_category\_book\_category\textquotesingle{}}\NormalTok{),}
\NormalTok{    ]}

\NormalTok{    operations }\OperatorTok{=}\NormalTok{ [}
\NormalTok{        migrations.AddField(}
\NormalTok{            model\_name}\OperatorTok{=}\StringTok{\textquotesingle{}book\textquotesingle{}}\NormalTok{,}
\NormalTok{            name}\OperatorTok{=}\StringTok{\textquotesingle{}summary\textquotesingle{}}\NormalTok{,}
\NormalTok{            field}\OperatorTok{=}\NormalTok{models.TextField(blank}\OperatorTok{=}\VariableTok{True}\NormalTok{),}
\NormalTok{        ),}
\NormalTok{    ]}
\end{Highlighting}
\end{Shaded}

La commande
\texttt{python\ manage.py\ squashmigrations\ library\ 0002\ 0003}
appliquera une fusion entre les migrations numérotées \texttt{0002} et
\texttt{0003}:

\begin{Shaded}
\begin{Highlighting}[]
\NormalTok{$ }\ExtensionTok{python}\NormalTok{ manage.py squashmigrations library 0002 0003}
\ExtensionTok{Will}\NormalTok{ squash the following migrations:}
 \ExtensionTok{{-}}\NormalTok{ 0002\_remove\_book\_category\_book\_category}
 \ExtensionTok{{-}}\NormalTok{ 0003\_book\_summary}
\ExtensionTok{Do}\NormalTok{ you wish to proceed? [yN] y}
\ExtensionTok{Optimizing...}
  \ExtensionTok{No}\NormalTok{ optimizations possible.}
\ExtensionTok{Created}\NormalTok{ new squashed migration /home/fred/Sources/gwlib/library/migrations/0002\_remove\_book\_category\_book\_category\_squashed\_0003\_book\_summary.py}
  \ExtensionTok{You}\NormalTok{ should commit this migration but leave the old ones in place}\KeywordTok{;}
  \ExtensionTok{the}\NormalTok{ new migration will be used for new installs. Once you are sure}
  \ExtensionTok{all}\NormalTok{ instances of the codebase have applied the migrations you squashed,}
  \ExtensionTok{you}\NormalTok{ can delete them.}
\end{Highlighting}
\end{Shaded}

Dans le cas où vous développez proprement (bis), il est sauf de purement
et simplement supprimer les anciens fichiers; dans le cas où il pourrait
exister au moins une instance ayant appliqué ces migrations, les anciens
\textbf{ne peuvent surtout pas être modifiés}.

Nous avons à présent un nouveau fichier intitulé
\texttt{0002\_remove\_book\_category\_book\_category\_squashed\_0003\_book\_summary}:

\begin{Shaded}
\begin{Highlighting}[]
\NormalTok{$ }\FunctionTok{cat}\NormalTok{ library/migrations/0002\_remove\_book\_category\_book\_category\_squashed\_0003\_book\_summary.py}
\CommentTok{\# Generated by Django 4.0.3 on 2022{-}03{-}15 18:01}

\ExtensionTok{from}\NormalTok{ django.db import migrations, models}


\ExtensionTok{class}\NormalTok{ Migration(migrations.Migration)}\BuiltInTok{:}

    \ExtensionTok{replaces}\NormalTok{ = [(}\StringTok{\textquotesingle{}library\textquotesingle{}}\NormalTok{, }\StringTok{\textquotesingle{}0002\_remove\_book\_category\_book\_category\textquotesingle{}}\NormalTok{), }\KeywordTok{(}\StringTok{\textquotesingle{}library\textquotesingle{}}\NormalTok{, }\StringTok{\textquotesingle{}0003\_book\_summary\textquotesingle{}}\KeywordTok{)}\NormalTok{]}

    \ExtensionTok{dependencies}\NormalTok{ = [}
        \KeywordTok{(}\StringTok{\textquotesingle{}library\textquotesingle{}}\NormalTok{, }\StringTok{\textquotesingle{}0001\_initial\textquotesingle{}}\KeywordTok{)}\NormalTok{,}
\NormalTok{    ]}

    \ExtensionTok{operations}\NormalTok{ = [}
        \ExtensionTok{migrations.RemoveField}\NormalTok{(}
            \VariableTok{model\_name=}\StringTok{\textquotesingle{}book\textquotesingle{}}\NormalTok{,}
            \VariableTok{name=}\StringTok{\textquotesingle{}category\textquotesingle{}}\NormalTok{,}
\NormalTok{        ),}
        \ExtensionTok{migrations.AddField}\NormalTok{(}
            \VariableTok{model\_name=}\StringTok{\textquotesingle{}book\textquotesingle{}}\NormalTok{,}
            \VariableTok{name=}\StringTok{\textquotesingle{}category\textquotesingle{}}\NormalTok{,}
            \VariableTok{field=}\NormalTok{models.ManyToManyField}\VariableTok{(}\NormalTok{to}\VariableTok{=}\StringTok{\textquotesingle{}library.category\textquotesingle{}}\VariableTok{)}\NormalTok{,}
\NormalTok{        ),}
        \ExtensionTok{migrations.AddField}\NormalTok{(}
            \VariableTok{model\_name=}\StringTok{\textquotesingle{}book\textquotesingle{}}\NormalTok{,}
            \VariableTok{name=}\StringTok{\textquotesingle{}summary\textquotesingle{}}\NormalTok{,}
            \VariableTok{field=}\NormalTok{models.TextField}\VariableTok{(}\NormalTok{blank}\VariableTok{=}\NormalTok{True}\VariableTok{)}\NormalTok{,}
\NormalTok{        ),}
\NormalTok{    ]}
\end{Highlighting}
\end{Shaded}

\hypertarget{_ruxe9initialisation_dune_ou_plusieurs_migrations}{%
\subsection{Réinitialisation d'une ou plusieurs
migrations}\label{_ruxe9initialisation_dune_ou_plusieurs_migrations}}

\href{https://simpleisbetterthancomplex.com/tutorial/2016/07/26/how-to-reset-migrations.html}{reset
migrations}.

\begin{quote}
\begin{verbatim}
 En gros, soit on supprime toutes les migrations (en conservant le fichier __init__.py), soit on réinitialise proprement les migrations avec un --fake-initial (sous réserve que toutes les personnes qui utilisent déjà le projet s'y conforment... Ce qui n'est pas gagné.
Pour repartir de notre exemple ci-dessus, nous avions un modèle reprenant quelques classes, saupoudrées de propriétés décrivant nos différents champs. Pour être prise en compte par le moteur de base de données, chaque modification doit être
\end{verbatim}
\end{quote}

\hypertarget{_shell}{%
\section{Shell}\label{_shell}}

\hypertarget{_administration}{%
\section{Administration}\label{_administration}}

Woké. On va commencer par la \textbf{partie à ne \emph{surtout}
(\emph{surtout} !!) pas faire en premier dans un projet Django}. Mais on
va la faire quand même: la raison principale est que cette partie est
tellement puissante et performante, qu'elle pourrait laisser penser
qu'il est possible de réaliser une application complète rien qu'en
configurant l'administration. Mais c'est faux.

L'administration est une sorte de tour de contrôle évoluée, un
\emph{back office} sans transpirer; elle se base sur le modèle de
données programmé et construit dynamiquement les formulaires qui lui est
associé. Elle joue avec les clés primaires, étrangères, les champs et
types de champs par
\href{https://fr.wikipedia.org/wiki/Introspection}{introspection}, et
présente tout ce qu'il faut pour avoir du
\href{https://fr.wikipedia.org/wiki/CRUD}{CRUD}, c'est-à-dire tout ce
qu'il faut pour ajouter, lister, modifier ou supprimer des informations.

Son problème est qu'elle présente une courbe d'apprentissage
asymptotique. Il est \textbf{très} facile d'arriver rapidement à un bon
résultat, au travers d'un périmètre de configuration relativement
restreint. Mais quoi que vous fassiez, il y a un moment où la courbe de
paramétrage sera tellement ardue que vous aurez plus facile à développer
ce que vous souhaitez ajouter en utilisant les autres concepts de
Django.

Cette fonctionnalité doit rester dans les mains d'administrateurs ou de
gestionnaires, et dans leurs mains à eux uniquement: il n'est pas
question de donner des droits aux utilisateurs finaux (même si c'est
extrêment tentant durant les premiers tours de roues). Indépendamment de
la manière dont vous allez l'utiliser et la configurer, vous finirez par
devoir développer une "vraie" application, destinée aux utilisateurs
classiques, et répondant à leurs besoins uniquement.

Une bonne idée consiste à développer l'administration dans un premier
temps, en \textbf{gardant en tête qu'il sera nécessaire de développer
des concepts spécifiques}. Dans cet objectif, l'administration est un
outil exceptionel, qui permet de valider un modèle, de créer des objets
rapidement et de valider les liens qui existent entre eux.

C'est aussi un excellent outil de prototypage et de preuve de concept.

Elle se base sur plusieurs couches que l'on a déjà (ou on va bientôt)
aborder (suivant le sens de lecture que vous préférez):

\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
  Le modèle de données
\item
  Les validateurs
\item
  Les formulaires
\item
  Les widgets
\end{enumerate}

\hypertarget{_le_moduxe8le_de_donnuxe9es}{%
\subsection{Le modèle de données}\label{_le_moduxe8le_de_donnuxe9es}}

Comme expliqué ci-dessus, le modèle de données est constité d'un
ensemble de champs typés et de relations. L'administration permet de
décrire les données qui peuvent être modifiées, en y associant un
ensemble (basique) de permissions.

Si vous vous rappelez de l'application que nous avions créée dans la
première partie, les URLs reprenaient déjà la partie suivante:

\begin{Shaded}
\begin{Highlighting}[]
\ImportTok{from}\NormalTok{ django.contrib }\ImportTok{import}\NormalTok{ admin}
\ImportTok{from}\NormalTok{ django.urls }\ImportTok{import}\NormalTok{ path}

\ImportTok{from}\NormalTok{ gwift.views }\ImportTok{import}\NormalTok{ wish\_details}

\NormalTok{urlpatterns }\OperatorTok{=}\NormalTok{ [}
\NormalTok{    path(}\StringTok{\textquotesingle{}admin/\textquotesingle{}}\NormalTok{, admin.site.urls), }
\NormalTok{    [...]}
\NormalTok{]}
\end{Highlighting}
\end{Shaded}

\begin{itemize}
\item
  Cette URL signifie que la partie \texttt{admin} est déjà active et
  accessible à l'URL \texttt{\textless{}mon\_site\textgreater{}/admin}
\end{itemize}

C'est le seul prérequis pour cette partie.

Chaque application nouvellement créée contient par défaut un fichier
\texttt{admin.py}, dans lequel il est possible de déclarer quel ensemble
de données sera accessible/éditable. Ainsi, si nous partons du modèle
basique que nous avions détaillé plus tôt, avec des souhaits et des
listes de souhaits:

\begin{Shaded}
\begin{Highlighting}[]
\CommentTok{\# gwift/wish/models.py}

\ImportTok{from}\NormalTok{ django.db }\ImportTok{import}\NormalTok{ models}


\KeywordTok{class}\NormalTok{ WishList(models.Model):}
\NormalTok{    name }\OperatorTok{=}\NormalTok{ models.CharField(max\_length}\OperatorTok{=}\DecValTok{255}\NormalTok{)}


\KeywordTok{class}\NormalTok{ Item(models.Model):}
\NormalTok{    name }\OperatorTok{=}\NormalTok{ models.CharField(max\_length}\OperatorTok{=}\DecValTok{255}\NormalTok{)}
\NormalTok{    wishlist }\OperatorTok{=}\NormalTok{ models.ForeignKey(WishList, on\_delete}\OperatorTok{=}\NormalTok{models.CASCADE)}
\end{Highlighting}
\end{Shaded}

Nous pouvons facilement arriver au résultat suivant, en ajoutant
quelques lignes de configuration dans ce fichier \texttt{admin.py}:

\begin{Shaded}
\begin{Highlighting}[]
\ImportTok{from}\NormalTok{ django.contrib }\ImportTok{import}\NormalTok{ admin}

\ImportTok{from}\NormalTok{ .models }\ImportTok{import}\NormalTok{ Item, WishList }


\NormalTok{admin.site.register(Item) }
\NormalTok{admin.site.register(WishList)}
\end{Highlighting}
\end{Shaded}

\begin{itemize}
\item
  Nous importons les modèles que nous souhaitons gérer dans l'admin
\item
  Et nous les déclarons comme gérables. Cette dernière ligne implique
  aussi qu'un modèle pourrait ne pas être disponible du tout, ce qui
  n'activera simplement aucune opération de lecture ou modification.
\end{itemize}

Il nous reste une seule étape à réaliser: créer un nouvel utilisateur.
Pour cet exemple, notre gestion va se limiter à une gestion manuelle;
nous aurons donc besoin d'un \emph{super-utilisateur}, que nous pouvons
créer grâce à la commande \texttt{python\ manage.py\ createsuperuser}.

\begin{Shaded}
\begin{Highlighting}[]
\NormalTok{λ }\ExtensionTok{python}\NormalTok{ manage.py createsuperuser}
\ExtensionTok{Username}\NormalTok{ (leave blank to use }\StringTok{\textquotesingle{}fred\textquotesingle{}}\NormalTok{)}\BuiltInTok{:}\NormalTok{ fred}
\ExtensionTok{Email}\NormalTok{ address: fred@root.org}
\ExtensionTok{Password}\NormalTok{: ******}
\ExtensionTok{Password}\NormalTok{ (again)}\BuiltInTok{:}\NormalTok{ ******}
\ExtensionTok{Superuser}\NormalTok{ created successfully.}
\end{Highlighting}
\end{Shaded}

\begin{figure}
\centering
\includegraphics{images/django/django-site-admin.png}
\caption{Connexion au site d'administration}
\end{figure}

\begin{figure}
\centering
\includegraphics{images/django/django-site-admin-after-connection.png}
\caption{Administration}
\end{figure}

\hypertarget{_quelques_conseils_de_base}{%
\subsection{Quelques conseils de
base}\label{_quelques_conseils_de_base}}

\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
  Surchargez la méthode \texttt{str(self)} pour chaque classe que vous
  aurez définie dans le modèle. Cela permettra de construire une
  représentation textuelle pour chaque instance de votre classe. Cette
  information sera utilisée un peu partout dans le code, et donnera une
  meilleure idée de ce que l'on manipule. En plus, cette méthode est
  également appelée lorsque l'administration historisera une action (et
  comme cette étape sera inaltérable, autant qu'elle soit fixée dans le
  début).
\item
  La méthode \texttt{get\_absolute\_url(self)} retourne l'URL à laquelle
  on peut accéder pour obtenir les détails d'une instance. Par exemple:
\end{enumerate}

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{def}\NormalTok{ get\_absolute\_url(}\VariableTok{self}\NormalTok{):}
    \ControlFlowTok{return}\NormalTok{ reverse(}\StringTok{\textquotesingle{}myapp.views.details\textquotesingle{}}\NormalTok{, args}\OperatorTok{=}\NormalTok{[}\VariableTok{self}\NormalTok{.}\BuiltInTok{id}\NormalTok{])}
\end{Highlighting}
\end{Shaded}

\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
  Les attributs \texttt{Meta}:
\end{enumerate}

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{class}\NormalTok{ Meta:}
\NormalTok{    ordering }\OperatorTok{=}\NormalTok{ [}\StringTok{\textquotesingle{}{-}field1\textquotesingle{}}\NormalTok{, }\StringTok{\textquotesingle{}field2\textquotesingle{}}\NormalTok{]}
\NormalTok{    verbose\_name }\OperatorTok{=} \StringTok{\textquotesingle{}my class in singular\textquotesingle{}}
\NormalTok{    verbose\_name\_plural }\OperatorTok{=} \StringTok{\textquotesingle{}my class when is in a list!\textquotesingle{}}
\end{Highlighting}
\end{Shaded}

\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
  Le titre:

  \begin{itemize}
  \item
    Soit en modifiant le template de l'administration
  \item
    Soit en ajoutant l'assignation suivante dans le fichier
    \texttt{urls.py}:
    \texttt{admin.site.site\_header\ =\ "SuperBook\ Secret\ Area}.
  \end{itemize}
\item
  Prefetch
\end{enumerate}

\url{https://hackernoon.com/all-you-need-to-know-about-prefetching-in-django-f9068ebe1e60?gi=7da7b9d3ad64}

\url{https://medium.com/@hakibenita/things-you-must-know-about-django-admin-as-your-app-gets-bigger-6be0b0ee9614}

En gros, le problème de l'admin est que si on fait des requêtes
imbriquées, on va flinguer l'application et le chargement de la page. La
solution consiste à utiliser la propriété \texttt{list\_select\_related}
de la classe d'Admin, afin d'appliquer une jointure par défaut et et
gagner en performances.

\hypertarget{_admin_modeladmin}{%
\subsection{admin.ModelAdmin}\label{_admin_modeladmin}}

La classe \texttt{admin.ModelAdmin} que l'on retrouvera principalement
dans le fichier \texttt{admin.py} de chaque application contiendra la
définition de ce que l'on souhaite faire avec nos données dans
l'administration. Cette classe (et sa partie Meta)

\hypertarget{_laffichage}{%
\subsection{L'affichage}\label{_laffichage}}

Comme l'interface d'administration fonctionne (en trèèèès) gros comme un
CRUD auto-généré, on trouve par défaut la possibilité de :

\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
  Créer de nouveaux éléments
\item
  Lister les éléments existants
\item
  Modifier des éléments existants
\item
  Supprimer un élément en particulier.
\end{enumerate}

Les affichages sont donc de deux types: en liste et par élément.

Pour les affichages en liste, le plus simple consiste à jouer sur la
propriété \texttt{list\_display}.

Par défaut, la première colonne va accueillir le lien vers le formulaire
d'édition. On peut donc modifier ceci, voire créer de nouveaux liens
vers d'autres éléments en construisant des URLs dynamiquement.

(Insérer ici l'exemple de Medplan pour les liens vers les postgradués
:-))

Voir aussi comment personnaliser le fil d'Ariane ?

\hypertarget{_les_filtres}{%
\subsection{Les filtres}\label{_les_filtres}}

\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
  list\_filter
\item
  filter\_horizontal
\item
  filter\_vertical
\item
  date\_hierarchy
\end{enumerate}

\hypertarget{_les_permissions}{%
\subsection{Les permissions}\label{_les_permissions}}

On l'a dit plus haut, il vaut mieux éviter de proposer un accès à
l'administration à vos utilisateurs. Il est cependant possible de
configurer des permissions spécifiques pour certains groupes, en leur
autorisant certaines actions de visualisation/ajout/édition ou
suppression.

Cela se joue au niveau du \texttt{ModelAdmin}, en implémentant les
méthodes suivantes:

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{def}\NormalTok{ has\_add\_permission(}\VariableTok{self}\NormalTok{, request):}
    \ControlFlowTok{return} \VariableTok{True}

\KeywordTok{def}\NormalTok{ has\_delete\_permission(}\VariableTok{self}\NormalTok{, request):}
    \ControlFlowTok{return} \VariableTok{True}

\KeywordTok{def}\NormalTok{ has\_change\_permission(}\VariableTok{self}\NormalTok{, request):}
    \ControlFlowTok{return} \VariableTok{True}
\end{Highlighting}
\end{Shaded}

On peut accéder aux informations de l'utilisateur actuellement connecté
au travers de l'objet \texttt{request.user}.

\begin{enumerate}
\def\labelenumi{\alph{enumi}.}
\item
  NOTE: ajouter un ou deux screenshots :-)
\end{enumerate}

\hypertarget{_les_relations}{%
\subsection{Les relations}\label{_les_relations}}

\hypertarget{_les_relations_1_n}{%
\subsubsection{Les relations 1-n}\label{_les_relations_1_n}}

Les relations 1-n sont implémentées au travers de formsets (que l'on a
normalement déjà décrits plus haut). L'administration permet de les
définir d'une manière extrêmement simple, grâce à quelques propriétés.

L'implémentation consiste tout d'abord à définir le comportement du type
d'objet référencé (la relation -N), puis à inclure cette définition au
niveau du type d'objet référençant (la relation 1-).

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{class}\NormalTok{ WishInline(TabularInline):}
\NormalTok{    model }\OperatorTok{=}\NormalTok{ Wish}


\KeywordTok{class}\NormalTok{ Wishlist(admin.ModelAdmin):}
\NormalTok{    ...}
\NormalTok{    inlines }\OperatorTok{=}\NormalTok{ [WishInline]}
\NormalTok{    ...}
\end{Highlighting}
\end{Shaded}

Et voilà : l'administration d'une liste de souhaits (\emph{Wishlist})
pourra directement gérer des relations multiples vers des souhaits.

\hypertarget{_les_auto_suggestions_et_auto_compluxe9tions}{%
\subsubsection{Les auto-suggestions et
auto-complétions}\label{_les_auto_suggestions_et_auto_compluxe9tions}}

Parler de l'intégration de select2.

\hypertarget{_la_pruxe9sentation}{%
\subsection{La présentation}\label{_la_pruxe9sentation}}

Parler ici des \texttt{fieldsets} et montrer comment on peut regrouper
des champs dans des groupes, ajouter un peu de javascript,
\ldots\hspace{0pt}

\hypertarget{_les_actions_sur_des_suxe9lections}{%
\subsection{Les actions sur des
sélections}\label{_les_actions_sur_des_suxe9lections}}

Les actions permettent de partir d'une liste d'éléments, et autorisent
un utilisateur à appliquer une action sur une sélection d'éléments. Par
défaut, il existe déjà une action de \textbf{suppression}.

Les paramètres d'entrée sont :

\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
  L'instance de classe
\item
  La requête entrante
\item
  Le queryset correspondant à la sélection.
\end{enumerate}

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{def}\NormalTok{ double\_quantity(}\VariableTok{self}\NormalTok{, request, queryset):}
    \ControlFlowTok{for}\NormalTok{ obj }\KeywordTok{in}\NormalTok{ queryset.}\BuiltInTok{all}\NormalTok{():}
\NormalTok{        obj.field }\OperatorTok{+=} \DecValTok{1}
\NormalTok{        obj.save()}
\NormalTok{double\_quantity.short\_description }\OperatorTok{=} \StringTok{"Doubler la quantité des souhaits."}
\end{Highlighting}
\end{Shaded}

Et pour informer l'utilisateur de ce qui a été réalisé, on peut aussi
lui passer un petit message:

\begin{Shaded}
\begin{Highlighting}[]
\ControlFlowTok{if}\NormalTok{ rows\_updated }\OperatorTok{=} \DecValTok{0}\NormalTok{:}
    \VariableTok{self}\NormalTok{.message\_user(request, }\StringTok{"Aucun élément n\textquotesingle{}a été impacté."}\NormalTok{)}
\ControlFlowTok{else}\NormalTok{:}
    \VariableTok{self}\NormalTok{.message\_user(request, }\StringTok{"}\SpecialCharTok{\{\}}\StringTok{ élément(s) mis à jour"}\NormalTok{.}\BuiltInTok{format}\NormalTok{(rows\_updated))}
\end{Highlighting}
\end{Shaded}

\hypertarget{_la_documentation}{%
\subsection{La documentation}\label{_la_documentation}}

Nous l'avons dit plus haut, l'administration de Django a également la
possibilité de rendre accessible la documentation associée à un modèle
de données. Pour cela, il suffit de suivre les bonnes pratiques, puis
\href{https://docs.djangoproject.com/en/stable/ref/contrib/admin/admindocs/}{d'activer
la documentation à partir des URLs}:

\begin{Shaded}
\begin{Highlighting}[]

\end{Highlighting}
\end{Shaded}

\hypertarget{_forms}{%
\section{Forms}\label{_forms}}

\begin{quote}
Le form, il s'assure que l'utilisateur n'a pas encodé de conneries et
que l'ensemble reste cohérent. Il (le form) n'a pas à savoir que tu as
implémenté des closure tables dans un graph dirigé acyclique.
\end{quote}

Ou comment valider proprement des données entrantes.

\includegraphics{images/xkcd-327.png}

Quand on parle de \texttt{forms}, on ne parle pas uniquement de
formulaires Web. On pourrait considérer qu'il s'agit de leur objectif
principal, mais on peut également voir un peu plus loin: on peut en fait
voir les \texttt{forms} comme le point d'entrée pour chaque donnée
arrivant dans notre application: il s'agit en quelque sorte d'un
ensemble de règles complémentaires à celles déjà présentes au niveau du
modèle.

L'exemple le plus simple est un fichier \texttt{.csv}: la lecture de ce
fichier pourrait se faire de manière très simple, en récupérant les
valeurs de chaque colonne et en l'introduisant dans une instance du
modèle.

Mauvaise idée. On peut proposer trois versions d'un même code, de la
version simple (lecture du fichier csv et jonglage avec les indices de
colonnes), puis une version plus sophistiquée (et plus lisible, à base
de
\href{https://docs.python.org/3/library/csv.html\#csv.DictReader}{DictReader}),
et la version + à base de form.

Les données fournies par un utilisateur \textbf{doivent}
\textbf{toujours} être validées avant introduction dans la base de
données. Notre base de données étant accessible ici par l'ORM, la
solution consiste à introduire une couche supplémentaire de validation.

Le flux à suivre est le suivant:

\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
  Création d'une instance grâce à un dictionnaire
\item
  Validation des données et des informations reçues
\item
  Traitement, si la validation a réussi.
\end{enumerate}

Ils jouent également deux rôles importants:

\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
  Valider des données, en plus de celles déjà définies au niveau du
  modèle
\item
  Contrôler le rendu à appliquer aux champs.
\end{enumerate}

Ils agissent come une glue entre l'utilisateur et la modélisation de vos
structures de données.

\hypertarget{_flux_de_validation}{%
\subsection{Flux de validation}\label{_flux_de_validation}}

\textbar{} .Validation \textbar{} .is\_valid \textbar{} .clean\_fields ↓
.clean\_fields\_machin

A compléter ;-)

\hypertarget{_duxe9pendance_avec_le_moduxe8le}{%
\subsection{Dépendance avec le
modèle}\label{_duxe9pendance_avec_le_moduxe8le}}

Un \textbf{form} peut dépendre d'une autre classe Django. Pour cela, il
suffit de fixer l'attribut \texttt{model} au niveau de la
\texttt{class\ Meta} dans la définition.

\begin{Shaded}
\begin{Highlighting}[]
\ImportTok{from}\NormalTok{ django }\ImportTok{import}\NormalTok{ forms}

\ImportTok{from}\NormalTok{ wish.models }\ImportTok{import}\NormalTok{ Wishlist}

\KeywordTok{class}\NormalTok{ WishlistCreateForm(forms.ModelForm):}
    \KeywordTok{class}\NormalTok{ Meta:}
\NormalTok{        model }\OperatorTok{=}\NormalTok{ Wishlist}
\NormalTok{        fields }\OperatorTok{=}\NormalTok{ (}\StringTok{\textquotesingle{}name\textquotesingle{}}\NormalTok{, }\StringTok{\textquotesingle{}description\textquotesingle{}}\NormalTok{)}
\end{Highlighting}
\end{Shaded}

De cette manière, notre form dépendra automatiquement des champs déjà
déclarés dans la classe \texttt{Wishlist}. Cela suit le principe de
\texttt{DRY\ \textless{}don’t\ repeat\ yourself\textgreater{}\textasciigrave{}\_,\ et\ évite\ qu’une\ modification\ ne\ pourrisse\ le\ code:\ en\ testant\ les\ deux\ champs\ présent\ dans\ l’attribut\ \textasciigrave{}fields},
nous pourrons nous assurer de faire évoluer le formulaire en fonction du
modèle sur lequel il se base.

\hypertarget{_rendu_et_affichage}{%
\subsection{Rendu et affichage}\label{_rendu_et_affichage}}

Le formulaire permet également de contrôler le rendu qui sera appliqué
lors de la génération de la page. Si les champs dépendent du modèle sur
lequel se base le formulaire, ces widgets doivent être initialisés dans
l'attribut \texttt{Meta}. Sinon, ils peuvent l'être directement au
niveau du champ.

\begin{Shaded}
\begin{Highlighting}[]
\ImportTok{from}\NormalTok{ datetime }\ImportTok{import}\NormalTok{ date}

\ImportTok{from}\NormalTok{ django }\ImportTok{import}\NormalTok{ forms}

\ImportTok{from}\NormalTok{ .models }\ImportTok{import}\NormalTok{ Accident}


\KeywordTok{class}\NormalTok{ AccidentForm(forms.ModelForm):}
    \KeywordTok{class}\NormalTok{ Meta:}
\NormalTok{        model }\OperatorTok{=}\NormalTok{ Accident}
\NormalTok{        fields }\OperatorTok{=}\NormalTok{ (}\StringTok{\textquotesingle{}gymnast\textquotesingle{}}\NormalTok{, }\StringTok{\textquotesingle{}educative\textquotesingle{}}\NormalTok{, }\StringTok{\textquotesingle{}date\textquotesingle{}}\NormalTok{, }\StringTok{\textquotesingle{}information\textquotesingle{}}\NormalTok{)}
\NormalTok{        widgets }\OperatorTok{=}\NormalTok{ \{}
            \StringTok{\textquotesingle{}date\textquotesingle{}}\NormalTok{ : forms.TextInput(}
\NormalTok{                     attrs}\OperatorTok{=}\NormalTok{\{}
                        \StringTok{\textquotesingle{}class\textquotesingle{}}\NormalTok{ : }\StringTok{\textquotesingle{}form{-}control\textquotesingle{}}\NormalTok{,}
                        \StringTok{\textquotesingle{}data{-}provide\textquotesingle{}}\NormalTok{ : }\StringTok{\textquotesingle{}datepicker\textquotesingle{}}\NormalTok{,}
                        \StringTok{\textquotesingle{}data{-}date{-}format\textquotesingle{}}\NormalTok{ : }\StringTok{\textquotesingle{}dd/mm/yyyy\textquotesingle{}}\NormalTok{,}
                        \StringTok{\textquotesingle{}placeholder\textquotesingle{}}\NormalTok{ : date.today().strftime(}\StringTok{"}\SpecialCharTok{\%d}\StringTok{/\%m/\%Y"}\NormalTok{)}
\NormalTok{                     \}),}
            \StringTok{\textquotesingle{}information\textquotesingle{}}\NormalTok{ : forms.Textarea(}
\NormalTok{                            attrs}\OperatorTok{=}\NormalTok{\{}
                                \StringTok{\textquotesingle{}class\textquotesingle{}}\NormalTok{ : }\StringTok{\textquotesingle{}form{-}control\textquotesingle{}}\NormalTok{,}
                                \StringTok{\textquotesingle{}placeholder\textquotesingle{}}\NormalTok{ : }\StringTok{\textquotesingle{}Context (why, where, ...)\textquotesingle{}}
\NormalTok{                            \})}
\NormalTok{        \}}
\end{Highlighting}
\end{Shaded}

\hypertarget{_squelette_par_duxe9faut}{%
\subsection{Squelette par défaut}\label{_squelette_par_duxe9faut}}

On a d'un côté le \{\{ form.as\_p \}\} ou \{\{ form.as\_table \}\}, mais
il y a beaucoup mieux que ça ;-) Voir les templates de Vitor et en
passant par \texttt{widget-tweaks}.

\hypertarget{_crispy_forms}{%
\subsection{Crispy-forms}\label{_crispy_forms}}

Comme on l'a vu à l'instant, les forms, en Django, c'est le bien. Cela
permet de valider des données reçues en entrée et d'afficher (très)
facilement des formulaires à compléter par l'utilisateur.

Par contre, c'est lourd. Dès qu'on souhaite peaufiner un peu
l'affichage, contrôler parfaitement ce que l'utilisateur doit remplir,
modifier les types de contrôleurs, les placer au pixel près,
\ldots\hspace{0pt} Tout ça demande énormément de temps. Et c'est là
qu'intervient
\href{http://django-crispy-forms.readthedocs.io/en/latest/}{Django-Crispy-Forms}.
Cette librairie intègre plusieurs frameworks CSS (Bootstrap, Foundation
et uni-form) et permet de contrôler entièrement le \textbf{layout} et la
présentation.

(c/c depuis le lien ci-dessous)

Pour chaque champ, crispy-forms va :

\begin{itemize}
\item
  utiliser le \texttt{verbose\_name} comme label.
\item
  vérifier les paramètres \texttt{blank} et \texttt{null} pour savoir si
  le champ est obligatoire.
\item
  utiliser le type de champ pour définir le type de la balise
  \texttt{\textless{}input\textgreater{}}.
\item
  récupérer les valeurs du paramètre \texttt{choices} (si présent) pour
  la balise \texttt{\textless{}select\textgreater{}}.
\end{itemize}

\url{http://dotmobo.github.io/django-crispy-forms.html}

\hypertarget{_en_conclusion}{%
\subsection{En conclusion}\label{_en_conclusion}}

\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
  Toute donnée entrée par l'utilisateur \textbf{doit} passer par une
  instance de \texttt{form}.
\item
  euh ?
\end{enumerate}

\hypertarget{_authentification}{%
\section{Authentification}\label{_authentification}}

Comme on l'a vu dans la partie sur le modèle, nous souhaitons que le
créateur d'une liste puisse retrouver facilement les éléments qu'il aura
créé. Ce dont nous n'avons pas parlé cependant, c'est la manière dont
l'utilisateur va pouvoir créer son compte et s'authentifier. La
\href{https://docs.djangoproject.com/en/stable/topics/auth/}{documentation}
est très complète, nous allons essayer de la simplifier au maximum.
Accrochez-vous, le sujet peut être complexe.

\hypertarget{_muxe9canisme_dauthentification}{%
\subsection{Mécanisme
d'authentification}\label{_muxe9canisme_dauthentification}}

On peut schématiser le flux d'authentification de la manière suivante :

En gros:

\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
  La personne accède à une URL qui est protégée (voir les décorateurs
  @login\_required et le mixin LoginRequiredMixin)
\item
  Le framework détecte qu'il est nécessaire pour la personne de se
  connecter (grâce à un paramètre type LOGIN\_URL)
\item
  Le framework présente une page de connexion ou un mécanisme d'accès
  pour la personne (template à définir)
\item
  Le framework récupère les informations du formulaire, et les transmets
  aux différents backends d'authentification, dans l'ordre
\item
  Chaque backend va appliquer la méthode \texttt{authenticate} en
  cascade, jusqu'à ce qu'un backend réponde True ou qu'aucun ne réponde
\item
  La réponse de la méthode authenticate doit être une instance d'un
  utilisateur, tel que définit parmi les paramètres généraux de
  l'application.
\end{enumerate}

En résumé (bis):

\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
  Une personne souhaite se connecter;
\item
  Les backends d'authentification s'enchaîne jusqu'à trouver une bonne
  correspondance. Si aucune correspondance n'est trouvée, on envoie la
  personne sur les roses.
\item
  Si OK, on retourne une instance de type current\_user, qui pourra être
  utilisée de manière uniforme dans l'application.
\end{enumerate}

Ci-dessous, on définit deux backends différents pour mieux comprendre
les différentes possibilités:

\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
  Une authentification par jeton
\item
  Une authentification LDAP
\end{enumerate}

\begin{Shaded}
\begin{Highlighting}[]
\ImportTok{from}\NormalTok{ datetime }\ImportTok{import}\NormalTok{ datetime}

\ImportTok{from}\NormalTok{ django.contrib.auth }\ImportTok{import}\NormalTok{ backends, get\_user\_model}
\ImportTok{from}\NormalTok{ django.db.models }\ImportTok{import}\NormalTok{ Q}

\ImportTok{from}\NormalTok{ accounts.models }\ImportTok{import}\NormalTok{ Token  }


\NormalTok{UserModel }\OperatorTok{=}\NormalTok{ get\_user\_model()}


\KeywordTok{class}\NormalTok{ TokenBackend(backends.ModelBackend):}
    \KeywordTok{def}\NormalTok{ authenticate(}\VariableTok{self}\NormalTok{, request, username}\OperatorTok{=}\VariableTok{None}\NormalTok{, password}\OperatorTok{=}\VariableTok{None}\NormalTok{, }\OperatorTok{**}\NormalTok{kwargs):}
        \CommentTok{"""Authentifie l\textquotesingle{}utilisateur sur base d\textquotesingle{}un jeton qu\textquotesingle{}il a reçu.}

\CommentTok{        On regarde la date de validité de chaque jeton avant d\textquotesingle{}autoriser l\textquotesingle{}accès.}
\CommentTok{        """}
\NormalTok{        token }\OperatorTok{=}\NormalTok{ kwargs.get(}\StringTok{"token"}\NormalTok{, }\VariableTok{None}\NormalTok{)}

\NormalTok{        current\_token }\OperatorTok{=}\NormalTok{ Token.objects.}\BuiltInTok{filter}\NormalTok{(token}\OperatorTok{=}\NormalTok{token, validity\_date\_\_gte}\OperatorTok{=}\NormalTok{datetime.now()).first()}

        \ControlFlowTok{if}\NormalTok{ current\_token:}
\NormalTok{            user }\OperatorTok{=}\NormalTok{ current\_token.user}

\NormalTok{            current\_token.last\_used\_date }\OperatorTok{=}\NormalTok{ datetime.now()}
\NormalTok{            current\_token.save()}

            \ControlFlowTok{return}\NormalTok{ user}

        \ControlFlowTok{return} \VariableTok{None}
\end{Highlighting}
\end{Shaded}

\begin{itemize}
\item
  Sous-entend qu'on a bien une classe qui permet d'accéder à ces jetons
  ;-)
\end{itemize}

\begin{Shaded}
\begin{Highlighting}[]
\ImportTok{from}\NormalTok{ django.contrib.auth }\ImportTok{import}\NormalTok{ backends, get\_user\_model}

\ImportTok{from}\NormalTok{ ldap3 }\ImportTok{import}\NormalTok{ Server, Connection, ALL}
\ImportTok{from}\NormalTok{ ldap3.core.exceptions }\ImportTok{import}\NormalTok{ LDAPPasswordIsMandatoryError}

\ImportTok{from}\NormalTok{ config }\ImportTok{import}\NormalTok{ settings}


\NormalTok{UserModel }\OperatorTok{=}\NormalTok{ get\_user\_model()}


\KeywordTok{class}\NormalTok{ LdapBackend(backends.ModelBackend):}
    \CommentTok{"""Implémentation du backend LDAP pour la connexion des utilisateurs à l\textquotesingle{}Active Directory.}
\CommentTok{    """}
    \KeywordTok{def}\NormalTok{ authenticate(}\VariableTok{self}\NormalTok{, request, username}\OperatorTok{=}\VariableTok{None}\NormalTok{, password}\OperatorTok{=}\VariableTok{None}\NormalTok{, }\OperatorTok{**}\NormalTok{kwargs):}
        \CommentTok{"""Authentifie l\textquotesingle{}utilisateur au travers du serveur LDAP.}
\CommentTok{        """}

\NormalTok{        ldap\_server }\OperatorTok{=}\NormalTok{ Server(settings.LDAP\_SERVER, get\_info}\OperatorTok{=}\NormalTok{ALL)}
\NormalTok{        ldap\_connection }\OperatorTok{=}\NormalTok{ Connection(ldap\_server, user}\OperatorTok{=}\NormalTok{username, password}\OperatorTok{=}\NormalTok{password)}

        \ControlFlowTok{try}\NormalTok{:}
            \ControlFlowTok{if} \KeywordTok{not}\NormalTok{ ldap\_connection.bind():}
                \ControlFlowTok{raise} \PreprocessorTok{ValueError}\NormalTok{(}\StringTok{"Login ou mot de passe incorrect"}\NormalTok{)}
        \ControlFlowTok{except}\NormalTok{ (LDAPPasswordIsMandatoryError, }\PreprocessorTok{ValueError}\NormalTok{) }\ImportTok{as}\NormalTok{ ldap\_exception:}
            \ControlFlowTok{raise}\NormalTok{ ldap\_exception}

\NormalTok{        user, \_ }\OperatorTok{=}\NormalTok{ UserModel.objects.get\_or\_create(username}\OperatorTok{=}\NormalTok{username)}
\end{Highlighting}
\end{Shaded}

On peut résumer le mécanisme d'authentification de la manière suivante:

\begin{itemize}
\item
  Si vous voulez modifier les informations liées à un utilisateur,
  orientez-vous vers la modification du modèle. Comme nous le verrons
  ci-dessous, il existe trois manières de prendre ces modifications en
  compte. Voir également
  \href{https://docs.djangoproject.com/en/stable/topics/auth/customizing/}{ici}.
\item
  Si vous souhaitez modifier la manière dont l'utilisateur se connecte,
  alors vous devrez modifier le \textbf{backend}.
\end{itemize}

\hypertarget{_modification_du_moduxe8le}{%
\subsection{Modification du modèle}\label{_modification_du_moduxe8le}}

Dans un premier temps, Django a besoin de manipuler
\href{https://docs.djangoproject.com/en/1.9/ref/contrib/auth/\#user-model}{des
instances de type \texttt{django.contrib.auth.User}}. Cette classe
implémente les champs suivants:

\begin{itemize}
\item
  \texttt{username}
\item
  \texttt{first\_name}
\item
  \texttt{last\_name}
\item
  \texttt{email}
\item
  \texttt{password}
\item
  \texttt{date\_joined}.
\end{itemize}

D'autres champs, comme les groupes auxquels l'utilisateur est associé,
ses permissions, savoir s'il est un super-utilisateur,
\ldots\hspace{0pt} sont moins pertinents pour le moment. Avec les
quelques champs déjà définis ci-dessus, nous avons de quoi identifier
correctement nos utilisateurs. Inutile d'implémenter nos propres
classes, puisqu'elles existent déjà :-)

Si vous souhaitez ajouter un champ, il existe trois manières de faire.

\hypertarget{_extension_du_moduxe8le_existant}{%
\subsection{Extension du modèle
existant}\label{_extension_du_moduxe8le_existant}}

Le plus simple consiste à créer une nouvelle classe, et à faire un lien
de type \texttt{OneToOne} vers la classe
\texttt{django.contrib.auth.User}. De cette manière, on ne modifie rien
à la manière dont Django authentife ses utlisateurs: tout ce qu'on fait,
c'est un lien vers une table nouvellement créée, comme on l'a déjà vu au
point {[}\ldots\hspace{0pt}voir l'héritage de modèle{]}. L'avantage de
cette méthode, c'est qu'elle est extrêmement flexible, et qu'on garde
les mécanismes Django standard. Le désavantage, c'est que pour avoir
toutes les informations de notre utilisateur, on sera obligé d'effectuer
une jointure sur le base de données, ce qui pourrait avoir des
conséquences sur les performances.

\hypertarget{_substitution}{%
\subsection{Substitution}\label{_substitution}}

Avant de commencer, sachez que cette étape doit être effectuée
\textbf{avant la première migration}. Le plus simple sera de définir une
nouvelle classe héritant de \texttt{django.contrib.auth.User} et de
spécifier la classe à utiliser dans votre fichier de paramètres. Si ce
paramètre est modifié après que la première migration ait été effectuée,
il ne sera pas pris en compte. Tenez-en compte au moment de modéliser
votre application.

\begin{Shaded}
\begin{Highlighting}[]
\NormalTok{AUTH\_USER\_MODEL }\OperatorTok{=} \StringTok{\textquotesingle{}myapp.MyUser\textquotesingle{}}
\end{Highlighting}
\end{Shaded}

Notez bien qu'il ne faut pas spécifier le package \texttt{.models} dans
cette injection de dépendances: le schéma à indiquer est bien
\texttt{\textless{}nom\ de\ l’application\textgreater{}.\textless{}nom\ de\ la\ classe\textgreater{}}.

\hypertarget{_backend}{%
\subsubsection{Backend}\label{_backend}}

\hypertarget{_templates}{%
\subsubsection{Templates}\label{_templates}}

Ce qui n'existe pas par contre, ce sont les vues. Django propose donc
tout le mécanisme de gestion des utilisateurs, excepté le visuel (hors
administration). En premier lieu, ces paramètres sont fixés dans le
fichier `settings
\textless{}\url{https://docs.djangoproject.com/en/1.8/ref/settings/\#auth\%3E\%60_}.
On y trouve par exemple les paramètres suivants:

\begin{itemize}
\item
  \texttt{LOGIN\_REDIRECT\_URL}: si vous ne spécifiez pas le paramètre
  \texttt{next}, l'utilisateur sera automatiquement redirigé vers cette
  page.
\item
  \texttt{LOGIN\_URL}: l'URL de connexion à utiliser. Par défaut,
  l'utilisateur doit se rendre sur la page \texttt{/accounts/login}.
\end{itemize}

\hypertarget{_social_authentification}{%
\subsubsection{Social-Authentification}\label{_social_authentification}}

Voir ici : \href{https://github.com/omab/python-social-auth}{python
social auth}

\hypertarget{_un_petit_mot_sur_oauth}{%
\subsubsection{Un petit mot sur OAuth}\label{_un_petit_mot_sur_oauth}}

OAuth est un standard libre définissant un ensemble de méthodes à
implémenter pour l'accès (l'autorisation) à une API. Son fonctionnement
se base sur un système de jetons (Tokens), attribués par le possesseur
de la ressource à laquelle un utilisateur souhaite accéder.

Le client initie la connexion en demandant un jeton au serveur. Ce jeton
est ensuite utilisée tout au long de la connexion, pour accéder aux
différentes ressources offertes par ce serveur. `wikipedia
\textless{}\url{http://en.wikipedia.org/wiki/OAuth\%3E\%60_}.

Une introduction à OAuth est
\href{http://hueniverse.com/oauth/guide/intro/}{disponible ici}. Elle
introduit le protocole comme étant une \texttt{valet\ key}, une clé que
l'on donne à la personne qui va garer votre voiture pendant que vous
profitez des mondanités. Cette clé donne un accès à votre voiture, tout
en bloquant un ensemble de fonctionnalités. Le principe du protocole est
semblable en ce sens: vous vous réservez un accès total à une API,
tandis que le système de jetons permet d'identifier une personne, tout
en lui donnant un accès restreint à votre application.

L'utilisation de jetons permet notamment de définir une durée
d'utilisation et une portée d'utilisation. L'utilisateur d'un service A
peut par exemple autoriser un service B à accéder à des ressources qu'il
possède, sans pour autant révéler son nom d'utilisateur ou son mot de
passe.

L'exemple repris au niveau du
\href{http://hueniverse.com/oauth/guide/workflow/}{workflow} est le
suivant : un utilisateur(trice), Jane, a uploadé des photos sur le site
faji.com (A). Elle souhaite les imprimer au travers du site beppa.com
(B). Au moment de la commande, le site beppa.com envoie une demande au
site faji.com pour accéder aux ressources partagées par Jane. Pour cela,
une nouvelle page s'ouvre pour l'utilisateur, et lui demande
d'introduire sa "pièce d'identité". Le site A, ayant reçu une demande de
B, mais certifiée par l'utilisateur, ouvre alors les ressources et lui
permet d'y accéder.

\begin{Shaded}
\begin{Highlighting}[]
\NormalTok{INSTALLED\_APPS }\OperatorTok{=}\NormalTok{ [}
    \StringTok{"django.contrib..."}
\NormalTok{]}
\end{Highlighting}
\end{Shaded}

peut être splitté en plusieurs parties:

\begin{Shaded}
\begin{Highlighting}[]
\NormalTok{INSTALLED\_APPS }\OperatorTok{=}\NormalTok{ [}

\NormalTok{]}

\NormalTok{THIRD\_PARTIES }\OperatorTok{=}\NormalTok{ [}

\NormalTok{]}

\NormalTok{MY\_APPS }\OperatorTok{=}\NormalTok{ [}

\NormalTok{]}
\end{Highlighting}
\end{Shaded}

\hypertarget{_context_processors}{%
\section{\texorpdfstring{\emph{Context
Processors}}{Context Processors}}\label{_context_processors}}

Mise en pratique: un \emph{context processor} sert \emph{grosso-modo} à
peupler l'ensemble des données transmises des vues aux templates avec
des données communes. Un context processor est un peu l'équivalent d'un
middleware, mais entre les données et les templates, là où le middleware
va s'occuper des données relatives aux réponses et requêtes elles-mêmes.

\begin{Shaded}
\begin{Highlighting}[]
\CommentTok{\# core/context\_processors.py}

\ImportTok{import}\NormalTok{ subprocess}

\KeywordTok{def}\NormalTok{ git\_describe(request) }\OperatorTok{{-}\textgreater{}} \BuiltInTok{str}\NormalTok{:}
    \ControlFlowTok{return}\NormalTok{ \{}
        \StringTok{"git\_describe"}\NormalTok{: subprocess.check\_output(}
\NormalTok{            [}\StringTok{"git"}\NormalTok{, }\StringTok{"describe"}\NormalTok{, }\StringTok{"{-}{-}always"}\NormalTok{]}
\NormalTok{        ).strip(),}
        \StringTok{"git\_date"}\NormalTok{: subprocess.check\_output(}
\NormalTok{            [}\StringTok{"git"}\NormalTok{, }\StringTok{"show"}\NormalTok{, }\StringTok{"{-}s"}\NormalTok{, }\VerbatimStringTok{r"{-}{-}format=}\SpecialCharTok{\%c}\VerbatimStringTok{d"}\NormalTok{, }\VerbatimStringTok{r"{-}{-}date=format:}\SpecialCharTok{\%d}\VerbatimStringTok{{-}\%m{-}\%Y"}\NormalTok{]}
\NormalTok{        ),}
\NormalTok{    \}}
\end{Highlighting}
\end{Shaded}

Ceci aura pour effet d'ajouter les deux variables \texttt{git\_describe}
et \texttt{git\_date} dans tous les contextes de tous les templates de
l'application.

\begin{Shaded}
\begin{Highlighting}[]
\NormalTok{TEMPLATES }\OperatorTok{=}\NormalTok{ [}
\NormalTok{    \{}
        \StringTok{\textquotesingle{}BACKEND\textquotesingle{}}\NormalTok{: }\StringTok{\textquotesingle{}django.template.backends.django.DjangoTemplates\textquotesingle{}}\NormalTok{,}
        \StringTok{\textquotesingle{}DIRS\textquotesingle{}}\NormalTok{: [os.path.join(BASE\_DIR, }\StringTok{"templates"}\NormalTok{),],}
        \StringTok{\textquotesingle{}APP\_DIRS\textquotesingle{}}\NormalTok{: }\VariableTok{True}\NormalTok{,}
        \StringTok{\textquotesingle{}OPTIONS\textquotesingle{}}\NormalTok{: \{}
            \StringTok{\textquotesingle{}context\_processors\textquotesingle{}}\NormalTok{: [}
                \StringTok{\textquotesingle{}django.template.context\_processors.debug\textquotesingle{}}\NormalTok{,}
                \StringTok{\textquotesingle{}django.template.context\_processors.request\textquotesingle{}}\NormalTok{,}
                \StringTok{\textquotesingle{}django.contrib.auth.context\_processors.auth\textquotesingle{}}\NormalTok{,}
                \StringTok{\textquotesingle{}django.contrib.messages.context\_processors.messages\textquotesingle{}}\NormalTok{,}
                \StringTok{"core.context\_processors.git\_describe"}
\NormalTok{            ],}
\NormalTok{        \},}
\NormalTok{    \},}
\NormalTok{]}
\end{Highlighting}
\end{Shaded}

\hypertarget{_tests}{%
\subsection{Tests}\label{_tests}}

\begin{quote}
Tests are part of the system.

---  Robert C. Martin Clean Architecture
\end{quote}

\hypertarget{_types_de_tests}{%
\subsubsection{Types de tests}\label{_types_de_tests}}

Les \textbf{tests unitaires} ciblent typiquement une seule fonction,
classe ou méthode, de manière isolée, en fournissant au développeur
l'assurance que son code réalise ce qu'il en attend. Pour plusieurs
raisons (et notamment en raison de performances), les tests unitaires
utilisent souvent des données stubbées - pour éviter d'appeler le "vrai"
service.

\begin{quote}
The aim of a unit test is to show that a single part of the application
does what programmer intends it to.
\end{quote}

Les \textbf{tests d'acceptance} vérifient que l'application fonctionne
comme convenu, mais à un plus haut niveau (fonctionnement correct d'une
API, validation d'une chaîne d'actions effectuées par un humain,
\ldots\hspace{0pt}).

\begin{quote}
The objective of acceptance tests is to prove that our application does
what the customer meant it to.
\end{quote}

Les \textbf{tests d'intégration} vérifient que l'application coopère
correctement avec les systèmes périphériques.

De manière plus générale, si nous nous rendons compte que les tests sont
trop compliqués à écrire ou nous coûtent trop de temps, c'est sans doute
que l'architecture de la solution n'est pas adaptée et que les
composants sont couplés les uns aux autres. Dans ces cas, il sera
nécessaire de refactoriser le code, afin que chaque module puisse être
testé indépendamment des autres. cite:{[}clean\_architecture{]}

\begin{quote}
Martin Fowler observes that, in general, "a ten minute build {[}and test
process{]} is perfectly within reason\ldots\hspace{0pt} {[}We first{]}
do the compilation and run tests that are more localized unit tests with
the database completely stubbed out. Such tests can run very fast,
keeping within the ten minutes guideline. However any bugs that involve
larger scale intercations, particularly those involving the real
database, won't be found. The second stage build runs a different suite
of tests {[}acceptance tests{]} that do hit the real database and
involve more end-to-end behavior. This suite may take a couple of hours
to run.

---  Robert C. Martin Clean Architecture
\end{quote}

Au final, le plus important est de toujours corréler les phases de tests
indépendantes du reste du travail (de développement, ici), en
l'automatisant au plus près de sa source de création.

En résumé, il est recommandé de:

\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\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}

TODO: Voir comment configurer une \texttt{memoryDB} pour l'exécution des
tests.

\hypertarget{_tests_de_nommage}{%
\subsubsection{Tests de nommage}\label{_tests_de_nommage}}

\begin{Shaded}
\begin{Highlighting}[]
\ImportTok{from}\NormalTok{ django.core.urlresolvers }\ImportTok{import}\NormalTok{ reverse}
\ImportTok{from}\NormalTok{ django.test }\ImportTok{import}\NormalTok{ TestCase}


\KeywordTok{class}\NormalTok{ HomeTests(TestCase):}
    \KeywordTok{def}\NormalTok{ test\_home\_view\_status\_code(}\VariableTok{self}\NormalTok{):}
\NormalTok{        url }\OperatorTok{=}\NormalTok{ reverse(}\StringTok{"home"}\NormalTok{)}
\NormalTok{        response }\OperatorTok{=} \VariableTok{self}\NormalTok{.client.get(url)}
        \VariableTok{self}\NormalTok{.assertEquals(response.status\_code, }\DecValTok{200}\NormalTok{)}
\end{Highlighting}
\end{Shaded}

\hypertarget{_tests_durls}{%
\subsubsection{Tests d'urls}\label{_tests_durls}}

\begin{Shaded}
\begin{Highlighting}[]
\ImportTok{from}\NormalTok{ django.core.urlresolvers }\ImportTok{import}\NormalTok{ reverse}
\ImportTok{from}\NormalTok{ django.test }\ImportTok{import}\NormalTok{ TestCase}

\ImportTok{from}\NormalTok{ .views }\ImportTok{import}\NormalTok{ home}


\KeywordTok{class}\NormalTok{ HomeTests(TestCase):}
    \KeywordTok{def}\NormalTok{ test\_home\_view\_status\_code(}\VariableTok{self}\NormalTok{):}
\NormalTok{        view }\OperatorTok{=}\NormalTok{ resolve(}\StringTok{"/"}\NormalTok{)}
        \VariableTok{self}\NormalTok{.assertEquals(view.func, home)}
\end{Highlighting}
\end{Shaded}

\hypertarget{_conclusions_2}{%
\section{Conclusions}\label{_conclusions_2}}

\begin{quote}
To be effective, a software system must be deployable. The higher the
cost of deployements, the less useful the system is. A goal of a
software architecture, then, should be to make a system that can be
easily deployed with a single action. Unfortunately, deployment strategy
is seldom considered during initial development. This leads to
architectures that may be make the system easy to develop, but leave it
very difficult to deploy.

---  Robert C. Martin Clean Architecture
\end{quote}

Il y a une raison très simple à aborder le déploiement dès maintenant: à
trop attendre et à peaufiner son développement en local, on en oublie
que sa finalité sera de se retrouver exposé et accessible depuis un
serveur. Il est du coup probable d'oublier une partie des désidérata, de
zapper une fonctionnalité essentielle ou simplement de passer énormément
de temps à adapter les sources pour qu'elles puissent être mises à
disposition sur un environnement en particulier, une fois que leur
développement aura été finalisé, testé et validé. Un bon déploiement ne
doit pas dépendre de dizaines de petits scripts éparpillés sur le
disque. L'objectif est qu'il soit rapide et fiable. Ceci peut être
atteint au travers d'un partitionnement correct, incluant le fait que le
composant principal s'assure que chaque sous-composant est correctement
démarré intégré et supervisé.

Aborder le déploiement dès le début permet également de rédiger dès le
début les procédures d'installation, de mises à jour et de sauvegardes.
A la fin de chaque intervalle de développement, les fonctionnalités
auront dû avoir été intégrées, testées, fonctionnelles et un code
propre, démontrable dans un environnement similaire à un environnement
de production, et créées à partir d'un tronc commun au développement
cite:{[}devops\_handbook{]}.

Déploier une nouvelle version sera aussi simple que de récupérer la
dernière archive depuis le dépôt, la placer dans le bon répertoire,
appliquer des actions spécifiques (et souvent identiques entre deux
versions), puis redémarrer les services adéquats, et la procédure
complète se résumera à quelques lignes d'un script bash.

\begin{quote}
Because value is created only when our services are running into
production, we must ensure that we are not only delivering fast flow,
but that our deployments can also be performed without causing chaos and
disruptions such as service outages, service impairments, or security or
compliance failures.

---  DevOps Handbook Introduction
\end{quote}

Le serveur que django met à notre disposition \emph{via} la commande
\texttt{runserver} est extrêmement pratique, mais il est uniquement
prévu pour la phase développement: en production, il est inutile de
passer par du code Python pour charger des fichiers statiques (feuilles
de style, fichiers JavaScript, images, \ldots\hspace{0pt}). De même,
Django propose par défaut une base de données SQLite, qui fonctionne
parfaitement dès lors que l'on connait ses limites et que l'on se limite
à un utilisateur à la fois. En production, il est légitime que la base
de donnée soit capable de supporter plusieurs utilisateurs et connexions
simultanés. En restant avec les paramètres par défaut, il est plus que
probable que vous rencontriez rapidement des erreurs de verrou parce
qu'un autre processus a déjà pris la main pour écrire ses données. En
bref, vous avez quelque chose qui fonctionne, qui répond à un besoin,
mais qui va attirer la grogne de ses utilisateurs pour des problèmes de
latences, pour des erreurs de verrou ou simplement parce que le serveur
répondra trop lentement.

L'objectif de cette partie est de parcourir les différentes possibilités
qui s'offrent à nous en termes de déploiement, tout en faisant en sorte
que le code soit le moins couplé possible à sa destination de
production. L'objectif est donc de faire en sorte qu'une même
application puisse être hébergées par plusieurs hôtes sans avoir à subir
de modifications. Nous vous renvoyons vers les 12-facteurs dont nous
avons déjà parlé et qui vous énormément nous aider, puisque ce sont des
variables d'environnement qui vont réellement piloter le câblage entre
l'application, ses composants et son hébergeur.

RedHat proposait récemment un article intitulé \emph{*What Is IaaS*},
qui présentait les principales différences entre types d'hébergement.

\begin{figure}
\centering
\includegraphics{images/deployment/iaas_focus-paas-saas-diagram.png}
\caption{L'infrastructure en tant que service, cc. \emph{RedHat Cloud
Computing}}
\end{figure}

Ainsi, on trouve:

\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
  Le déploiment \emph{on-premises} ou \emph{on-site}
\item
  Les \emph{Infrastructures as a service} ou \emph{IaaSIaaS}
\item
  Les \emph{Platforms as a service} ou \emph{PaaSPaaS}
\item
  Les \emph{Softwares as a service} ou \emph{SaaSSaaS}, ce dernier point
  nous concernant moins, puisque c'est nous qui développons le logiciel.
\end{enumerate}

Dans cette partie, nous aborderons les points suivants:

\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
  Définir l'infrastructure et les composants nécessaires à notre
  application
\item
  Configurer l'hôte qui hébergera l'application et y déployer notre
  application: dans une machine physique, virtuelle ou dans un
  container. Nous aborderons aussi les déploiements via Ansible et Salt.
  A ce stade, nous aurons déjà une application disponible.
\item
  Configurer les outils nécessaires à la bonne exécution de ce code et
  de ses fonctionnalités: les différentes méthodes de supervision de
  l'application, comment analyser les fichiers de logs, comment
  intercepter correctement une erreur si elle se présente et comment
  remonter correctement l'information.
\end{enumerate}

\hypertarget{_infrastructure_composants}{%
\section{Infrastructure \&
composants}\label{_infrastructure_composants}}

Pour une mise ne production, le standard \emph{de facto} est le suivant:

\begin{itemize}
\item
  Nginx comme reverse proxy
\item
  HAProxy pour la distribution de charge
\item
  Gunicorn ou Uvicorn comme serveur d'application
\item
  Supervisor pour le monitoring
\item
  PostgreSQL ou MySQL/MariaDB comme bases de données.
\item
  Celery et RabbitMQ pour l'exécution de tâches asynchrones
\item
  Redis / Memcache pour la mise à en cache (et pour les sessions ? A
  vérifier).
\item
  Sentry, pour le suivi des bugs
\end{itemize}

Si nous schématisons l'infrastructure et le chemin parcouru par une
requête, nous pourrions arriver à la synthèse suivante:

\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
  L'utilisateur fait une requête via son navigateur (Firefox ou Chrome)
\item
  Le navigateur envoie une requête http, sa version, un verbe (GET,
  POST, \ldots\hspace{0pt}), un port et éventuellement du contenu
\item
  Le firewall du serveur (Debian GNU/Linux, CentOS, \ldots\hspace{0pt})
  vérifie si la requête peut être prise en compte
\item
  La requête est transmise à l'application qui écoute sur le port
  (probablement 80 ou 443; et \emph{a priori} Nginx)
\item
  Elle est ensuite transmise par socket et est prise en compte par un
  des \emph{workers} (= un processus Python) instancié par Gunicorn. Si
  l'un de ces travailleurs venait à planter, il serait automatiquement
  réinstancié par Supervisord.
\item
  Qui la transmet ensuite à l'un de ses \emph{workers} (= un processus
  Python).
\item
  Après exécution, une réponse est renvoyée à l'utilisateur.
\end{enumerate}

\includegraphics{images/diagrams/architecture.png}

\hypertarget{_reverse_proxy}{%
\subsection{Reverse proxy}\label{_reverse_proxy}}

Le principe du \textbf{proxy inverse} est de pouvoir rediriger du trafic
entrant vers une application hébergée sur le système. Il serait tout à
fait possible de rendre notre application directement accessible depuis
l'extérieur, mais le proxy a aussi l'intérêt de pouvoir élever la
sécurité du serveur (SSL) et décharger le serveur applicatif grâce à un
mécanisme de cache ou en compressant certains résultats \footnote{\url{https://fr.wikipedia.org/wiki/Proxy_inverse}}

\hypertarget{_load_balancer}{%
\subsection{Load balancer}\label{_load_balancer}}

\hypertarget{_workers}{%
\subsection{Workers}\label{_workers}}

\hypertarget{_supervision_des_processus}{%
\subsection{Supervision des
processus}\label{_supervision_des_processus}}

\hypertarget{_base_de_donnuxe9es_2}{%
\subsection{Base de données}\label{_base_de_donnuxe9es_2}}

\hypertarget{_tuxe2ches_asynchrones}{%
\subsection{Tâches asynchrones}\label{_tuxe2ches_asynchrones}}

\hypertarget{_mise_en_cache}{%
\subsection{Mise en cache}\label{_mise_en_cache}}

\hypertarget{_code_source}{%
\section{Code source}\label{_code_source}}

Au niveau logiciel (la partie mise en subrillance ci-dessus), la requête
arrive dans les mains du processus Python, qui doit encore

\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
  effectuer le routage des données,
\item
  trouver la bonne fonction à exécuter,
\item
  récupérer les données depuis la base de données,
\item
  effectuer le rendu ou la conversion des données,
\item
  et renvoyer une réponse à l'utilisateur.
\end{enumerate}

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,
\ldots\hspace{0pt}

\hypertarget{_outils_de_supervision_et_de_mise_uxe0_disposition}{%
\section{Outils de supervision et de mise à
disposition}\label{_outils_de_supervision_et_de_mise_uxe0_disposition}}

\hypertarget{_logs}{%
\subsection{Logs}\label{_logs}}

\hypertarget{_logging}{%
\section{Logging}\label{_logging}}

La structure des niveaux de journaux est essentielle.

\begin{quote}
When deciding whether a message should be ERROR or WARN, imagine being
woken up at 4 a.m. Low printer toner is not an ERROR.

---  Dan North former ToughtWorks consultant
\end{quote}

\begin{itemize}
\item
  \textbf{DEBUG}: Il s'agit des informations qui concernent tout ce qui
  peut se passer durant l'exécution de l'application. Généralement, ce
  niveau est désactivé pour une application qui passe en production,
  sauf s'il est nécessaire d'isoler un comportement en particulier,
  auquel cas il suffit de le réactiver temporairement.
\item
  \textbf{INFO}: Enregistre les actions pilotées par un utilisateur -
  Démarrage de la transaction de paiement, \ldots\hspace{0pt}
\item
  \textbf{WARN}: Regroupe les informations qui pourraient
  potentiellement devenir des erreurs.
\item
  \textbf{ERROR}: Indique les informations internes - Erreur lors de
  l'appel d'une API, erreur interne, \ldots\hspace{0pt}
\item
  \textbf{FATAL} (ou \textbf{EXCEPTION}): \ldots\hspace{0pt}
  généralement suivie d'une terminaison du programme ;-) - Bind raté
  d'un socket, etc.
\end{itemize}

La configuration des \emph{loggers} est relativement simple, un peu plus
complexe si nous nous penchons dessus, et franchement complète si nous
creusons encore. Il est ainsi possible de définir des formattages,
gestionnaires (\emph{handlers}) et loggers distincts, en fonction de nos
applications.

Sauf que comme nous l'avons vu avec les 12 facteurs, nous devons traiter
les informations de notre application comme un flux d'évènements. Il
n'est donc pas réellement nécessaire de chipoter la configuration,
puisque la seule classe qui va réellement nous intéresser concerne les
\texttt{StreamHandler}. La configuration que nous allons utiliser est
celle-ci:

\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
  Formattage: à définir - mais la variante suivante est complète,
  lisible et pratique:
  \texttt{\{levelname\}\ \{asctime\}\ \{module\}\ \{process:d\}\ \{thread:d\}\ \{message\}}
\item
  Handler: juste un, qui définit un \texttt{StreamHandler}
\item
  Logger: pour celui-ci, nous avons besoin d'un niveau (\texttt{level})
  et de savoir s'il faut propager les informations vers les
  sous-paquets, auquel cas il nous suffira de fixer la valeur de
  \texttt{propagate} à \texttt{True}.
\end{enumerate}

\begin{Shaded}
\begin{Highlighting}[]
\NormalTok{LOGGING }\OperatorTok{=}\NormalTok{ \{}
    \StringTok{\textquotesingle{}version\textquotesingle{}}\NormalTok{: }\DecValTok{1}\NormalTok{,}
    \StringTok{\textquotesingle{}disable\_existing\_loggers\textquotesingle{}}\NormalTok{: }\VariableTok{False}\NormalTok{,}
    \StringTok{\textquotesingle{}formatters\textquotesingle{}}\NormalTok{: \{}
        \StringTok{\textquotesingle{}verbose\textquotesingle{}}\NormalTok{: \{}
            \StringTok{\textquotesingle{}format\textquotesingle{}}\NormalTok{: }\StringTok{\textquotesingle{}}\SpecialCharTok{\{levelname\}}\StringTok{ }\SpecialCharTok{\{asctime\}}\StringTok{ }\SpecialCharTok{\{module\}}\StringTok{ }\SpecialCharTok{\{process:d\}}\StringTok{ }\SpecialCharTok{\{thread:d\}}\StringTok{ }\SpecialCharTok{\{message\}}\StringTok{\textquotesingle{}}\NormalTok{,}
\NormalTok{        \},}
        \StringTok{\textquotesingle{}simple\textquotesingle{}}\NormalTok{: \{}
            \StringTok{\textquotesingle{}format\textquotesingle{}}\NormalTok{: }\StringTok{\textquotesingle{}}\SpecialCharTok{\{levelname\}}\StringTok{ }\SpecialCharTok{\{asctime\}}\StringTok{ }\SpecialCharTok{\{module\}}\StringTok{ }\SpecialCharTok{\{message\}}\StringTok{\textquotesingle{}}\NormalTok{,}
\NormalTok{        \},}
\NormalTok{    \},}
    \StringTok{\textquotesingle{}handlers\textquotesingle{}}\NormalTok{: \{}
        \StringTok{\textquotesingle{}console\textquotesingle{}}\NormalTok{: \{}
            \StringTok{\textquotesingle{}level\textquotesingle{}}\NormalTok{: }\StringTok{\textquotesingle{}DEBUG\textquotesingle{}}\NormalTok{,}
            \StringTok{\textquotesingle{}class\textquotesingle{}}\NormalTok{: }\StringTok{\textquotesingle{}logging.StreamHandler\textquotesingle{}}\NormalTok{,}
            \StringTok{\textquotesingle{}formatter\textquotesingle{}}\NormalTok{: }\StringTok{"verbose"}
\NormalTok{        \}}
\NormalTok{    \},}
    \StringTok{\textquotesingle{}loggers\textquotesingle{}}\NormalTok{: \{}
        \StringTok{\textquotesingle{}khana\textquotesingle{}}\NormalTok{: \{}
            \StringTok{\textquotesingle{}handlers\textquotesingle{}}\NormalTok{: [}\StringTok{\textquotesingle{}console\textquotesingle{}}\NormalTok{],}
            \StringTok{\textquotesingle{}level\textquotesingle{}}\NormalTok{: env(}\StringTok{"LOG\_LEVEL"}\NormalTok{, default}\OperatorTok{=}\StringTok{"DEBUG"}\NormalTok{),}
            \StringTok{\textquotesingle{}propagate\textquotesingle{}}\NormalTok{: }\VariableTok{True}\NormalTok{,}
\NormalTok{        \},}
\NormalTok{    \}}
\NormalTok{\}}
\end{Highlighting}
\end{Shaded}

Pour utiliser nos loggers, il suffit de copier le petit bout de code
suivant:

\begin{Shaded}
\begin{Highlighting}[]
\ImportTok{import}\NormalTok{ logging}

\NormalTok{logger }\OperatorTok{=}\NormalTok{ logging.getLogger(}\VariableTok{\_\_name\_\_}\NormalTok{)}

\NormalTok{logger.debug(}\StringTok{\textquotesingle{}helloworld\textquotesingle{}}\NormalTok{)}
\end{Highlighting}
\end{Shaded}

\href{https://docs.djangoproject.com/en/stable/topics/logging/\#examples}{Par
exemples}.

\hypertarget{_logging_2}{%
\subsection{Logging}\label{_logging_2}}

\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
  Sentry via sentry\_sdk
\item
  Nagios
\item
  LibreNMS
\item
  Zabbix
\end{enumerate}

Il existe également \href{https://munin-monitoring.org}{Munin},
\href{https://www.elastic.co}{Logstash, ElasticSearch et Kibana
(ELK-Stack)} ou \href{https://www.fluentd.org}{Fluentd}.

\hypertarget{_muxe9thode_de_duxe9ploiement}{%
\section{Méthode de déploiement}\label{_muxe9thode_de_duxe9ploiement}}

Nous allons détailler ci-dessous trois méthodes de déploiement:

\begin{itemize}
\item
  Sur une machine hôte, en embarquant tous les composants sur un même
  serveur. Ce ne sera pas idéal, puisqu'il ne sera pas possible de
  configurer un \emph{load balancer}, de routeur plusieurs basées de
  données, mais ce sera le premier cas de figure.
\item
  Dans des containers, avec Docker-Compose.
\item
  Sur une \textbf{Plateforme en tant que Service} (ou plus simplement,
  \textbf{PaaSPaaS}), pour faire abstraction de toute la couche de
  configuration du serveur.
\end{itemize}

\hypertarget{_duxe9ploiement_sur_debian}{%
\section{Déploiement sur Debian}\label{_duxe9ploiement_sur_debian}}

La première étape pour la configuration de notre hôte consiste à définir
les utilisateurs et groupes de droits. Il est faut absolument éviter de
faire tourner une application en tant qu'utilisateur \textbf{root}, car
la moindre faille pourrait avoir des conséquences catastrophiques.

Une fois que ces utilisateurs seront configurés, nous pourrons passer à
l'étape de configuration, qui consistera à:

\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
  Déployer les sources
\item
  Démarrer un serveur implémentant une interface WSGI (\textbf{Web
  Server Gateway Interface}), qui sera chargé de créer autant de petits
  lutins travailleurs que nous le désirerons.
\item
  Démarrer un superviseur, qui se chargera de veiller à la bonne santé
  de nos petits travailleurs, et en créer de nouveaux s'il le juge
  nécessaire
\item
  Configurer un proxy inverse, qui s'occupera d'envoyer les requêtes
  d'un utilisateur externe à la machine hôte vers notre serveur
  applicatif, qui la communiquera à l'un des travailleurs.
\end{enumerate}

La machine hôte peut être louée chez Digital Ocean, Scaleway, OVH,
Vultr, \ldots\hspace{0pt} Il existe des dizaines d'hébergements typés
VPS (\textbf{Virtual Private Server}). A vous de choisir celui qui vous
convient \footnote{Personnellement, j'ai un petit faible pour Hetzner
  Cloud}.

\begin{Shaded}
\begin{Highlighting}[]
\ExtensionTok{apt}\NormalTok{ update}
\ExtensionTok{groupadd}\NormalTok{ {-}{-}system webapps }
\ExtensionTok{groupadd}\NormalTok{ {-}{-}system gunicorn\_sockets }
\ExtensionTok{useradd}\NormalTok{ {-}{-}system {-}{-}gid webapps {-}{-}shell /bin/bash {-}{-}home /home/gwift gwift }
\FunctionTok{mkdir}\NormalTok{ {-}p /home/gwift }
\FunctionTok{chown}\NormalTok{ gwift:webapps /home/gwift }
\end{Highlighting}
\end{Shaded}

\begin{itemize}
\item
  On ajoute un groupe intitulé \texttt{webapps}
\item
  On crée un groupe pour les communications via sockets
\item
  On crée notre utilisateur applicatif; ses applications seront placées
  dans le répertoire \texttt{/home/gwift}
\item
  On crée le répertoire home/gwift
\item
  On donne les droits sur le répertoire /home/gwift
\end{itemize}

\hypertarget{_installation_des_duxe9pendances_systuxe8mes}{%
\subsection{Installation des dépendances
systèmes}\label{_installation_des_duxe9pendances_systuxe8mes}}

La version 3.6 de Python se trouve dans les dépôts officiels de CentOS.
Si vous souhaitez utiliser une version ultérieure, il suffit de
l'installer en parallèle de la version officiellement supportée par
votre distribution.

Pour CentOS, vous avez donc deux possibilités :

\begin{Shaded}
\begin{Highlighting}[]
\ExtensionTok{yum}\NormalTok{ install python36 {-}y}
\end{Highlighting}
\end{Shaded}

Ou passer par une installation alternative:

\begin{Shaded}
\begin{Highlighting}[]
\FunctionTok{sudo}\NormalTok{ yum {-}y groupinstall }\StringTok{"Development Tools"}
\FunctionTok{sudo}\NormalTok{ yum {-}y install openssl{-}devel bzip2{-}devel libffi{-}devel}

\FunctionTok{wget}\NormalTok{ https://www.python.org/ftp/python/3.8.2/Python{-}3.8.2.tgz}
\BuiltInTok{cd}\NormalTok{ Python{-}3.8*/}
\ExtensionTok{./configure}\NormalTok{ {-}{-}enable{-}optimizations}
\FunctionTok{sudo}\NormalTok{ make altinstall }
\end{Highlighting}
\end{Shaded}

\begin{itemize}
\item
  \textbf{Attention !} Le paramètre \texttt{altinstall} est primordial.
  Sans lui, vous écraserez l'interpréteur initialement supporté par la
  distribution, et cela pourrait avoir des effets de bord non souhaités.
\end{itemize}

\hypertarget{_installation_de_la_base_de_donnuxe9es}{%
\subsection{Installation de la base de
données}\label{_installation_de_la_base_de_donnuxe9es}}

On l'a déjà vu, Django se base sur un pattern type
\href{https://www.martinfowler.com/eaaCatalog/activeRecord.html}{ActiveRecords}
pour la gestion de la persistance des données et supporte les principaux
moteurs de bases de données connus:

\begin{itemize}
\item
  SQLite (en natif, mais Django 3.0 exige une version du moteur
  supérieure ou égale à la 3.8)
\item
  MariaDB (en natif depuis Django 3.0),
\item
  PostgreSQL au travers de psycopg2 (en natif aussi),
\item
  Microsoft SQLServer grâce aux drivers {[}\ldots\hspace{0pt}à
  compléter{]}
\item
  Oracle via
  \href{https://oracle.github.io/python-cx_Oracle/}{cx\_Oracle}.
\end{itemize}

Chaque pilote doit être utilisé précautionneusement ! Chaque version de
Django n'est pas toujours compatible avec chacune des versions des
pilotes, et chaque moteur de base de données nécessite parfois une
version spécifique du pilote. Par ce fait, vous serez parfois bloqué sur
une version de Django, simplement parce que votre serveur de base de
données se trouvera dans une version spécifique (eg. Django 2.3 à cause
d'un Oracle 12.1).

Ci-dessous, quelques procédures d'installation pour mettre un serveur à
disposition. Les deux plus simples seront MariaDB et PostgreSQL, qu'on
couvrira ci-dessous. Oracle et Microsoft SQLServer se trouveront en
annexes.

\hypertarget{_postgresql}{%
\subsubsection{PostgreSQL}\label{_postgresql}}

On commence par installer PostgreSQL.

Par exemple, dans le cas de debian, on exécute la commande suivante:

\begin{Shaded}
\begin{Highlighting}[]
\VariableTok{$$}\NormalTok{$ }\ExtensionTok{aptitude}\NormalTok{ install postgresql postgresql{-}contrib}
\end{Highlighting}
\end{Shaded}

Ensuite, on crée un utilisateur pour la DB:

\begin{Shaded}
\begin{Highlighting}[]
\VariableTok{$$}\NormalTok{$ }\FunctionTok{su}\NormalTok{ {-} postgres}
\ExtensionTok{postgres@gwift}\NormalTok{:\textasciitilde{}$ createuser {-}{-}interactive {-}P}
\ExtensionTok{Enter}\NormalTok{ name of role to add: gwift\_user}
\ExtensionTok{Enter}\NormalTok{ password for new role:}
\ExtensionTok{Enter}\NormalTok{ it again:}
\ExtensionTok{Shall}\NormalTok{ the new role be a superuser? (y/n) }\ExtensionTok{n}
\ExtensionTok{Shall}\NormalTok{ the new role be allowed to create databases? (y/n) }\ExtensionTok{n}
\ExtensionTok{Shall}\NormalTok{ the new role be allowed to create more new roles? (y/n) }\ExtensionTok{n}
\ExtensionTok{postgres@gwift}\NormalTok{:\textasciitilde{}$}
\end{Highlighting}
\end{Shaded}

Finalement, on peut créer la DB:

\begin{Shaded}
\begin{Highlighting}[]
\ExtensionTok{postgres@gwift}\NormalTok{:\textasciitilde{}$ createdb {-}{-}owner gwift\_user gwift}
\ExtensionTok{postgres@gwift}\NormalTok{:\textasciitilde{}$ exit}
\BuiltInTok{logout}
\VariableTok{$$}\NormalTok{$}
\end{Highlighting}
\end{Shaded}

penser à inclure un bidule pour les backups.

\hypertarget{_mariadb}{%
\subsubsection{MariaDB}\label{_mariadb}}

Idem, installation, configuration, backup, tout ça. A copier de
grimboite, je suis sûr d'avoir des notes là-dessus.

\hypertarget{_microsoft_sql_server}{%
\subsubsection{Microsoft SQL Server}\label{_microsoft_sql_server}}

\hypertarget{_oracle}{%
\subsubsection{Oracle}\label{_oracle}}

\hypertarget{_pruxe9paration_de_lenvironnement_utilisateur}{%
\subsection{Préparation de l'environnement
utilisateur}\label{_pruxe9paration_de_lenvironnement_utilisateur}}

\begin{Shaded}
\begin{Highlighting}[]
\FunctionTok{su}\NormalTok{ {-} gwift}
\FunctionTok{cp}\NormalTok{ /etc/skel/.bashrc .}
\FunctionTok{cp}\NormalTok{ /etc/skel/.bash\_profile .}
\FunctionTok{ssh{-}keygen}
\FunctionTok{mkdir}\NormalTok{ bin}
\FunctionTok{mkdir}\NormalTok{ .venvs}
\FunctionTok{mkdir}\NormalTok{ webapps}
\ExtensionTok{python3.6}\NormalTok{ {-}m venv .venvs/gwift}
\BuiltInTok{source}\NormalTok{ .venvs/gwift/bin/activate}
\BuiltInTok{cd}\NormalTok{ /home/gwift/webapps}
\FunctionTok{git}\NormalTok{ clone ...}
\end{Highlighting}
\end{Shaded}

La clé SSH doit ensuite être renseignée au niveau du dépôt, afin de
pouvoir y accéder.

A ce stade, on devrait déjà avoir quelque chose de fonctionnel en
démarrant les commandes suivantes:

\begin{Shaded}
\begin{Highlighting}[]
\CommentTok{\# en tant qu\textquotesingle{}utilisateur \textquotesingle{}gwift\textquotesingle{}}

\BuiltInTok{source}\NormalTok{ .venvs/gwift/bin/activate}
\ExtensionTok{pip}\NormalTok{ install {-}U pip}
\ExtensionTok{pip}\NormalTok{ install {-}r requirements/base.txt}
\ExtensionTok{pip}\NormalTok{ install gunicorn}
\BuiltInTok{cd}\NormalTok{ webapps/gwift}
\ExtensionTok{gunicorn}\NormalTok{ config.wsgi:application {-}{-}bind localhost:3000 {-}{-}settings=config.settings\_production}
\end{Highlighting}
\end{Shaded}

\hypertarget{_configuration_de_lapplication}{%
\subsection{Configuration de
l'application}\label{_configuration_de_lapplication}}

\begin{Shaded}
\begin{Highlighting}[]
\VariableTok{SECRET\_KEY=}\OperatorTok{\textless{}}\KeywordTok{set} \ExtensionTok{your}\NormalTok{ secret key here}\OperatorTok{\textgreater{}} 
\VariableTok{ALLOWED\_HOSTS=}\ExtensionTok{*}
\VariableTok{STATIC\_ROOT=}\NormalTok{/var/www/gwift/static}
\VariableTok{DATABASE=} 
\end{Highlighting}
\end{Shaded}

\begin{itemize}
\item
  La variable \texttt{SECRET\_KEY} est notamment utilisée pour le
  chiffrement des sessions.
\item
  On fait confiance à django\_environ pour traduire la chaîne de
  connexion à la base de données.
\end{itemize}

\hypertarget{_cruxe9ation_des_ruxe9pertoires_de_logs}{%
\subsection{Création des répertoires de
logs}\label{_cruxe9ation_des_ruxe9pertoires_de_logs}}

\begin{verbatim}
mkdir -p /var/www/gwift/static
\end{verbatim}

\hypertarget{_cruxe9ation_du_ruxe9pertoire_pour_le_socket}{%
\subsection{Création du répertoire pour le
socket}\label{_cruxe9ation_du_ruxe9pertoire_pour_le_socket}}

Dans le fichier \texttt{/etc/tmpfiles.d/gwift.conf}:

\begin{verbatim}
D /var/run/webapps 0775 gwift gunicorn_sockets -
\end{verbatim}

Suivi de la création par systemd :

\begin{verbatim}
systemd-tmpfiles --create
\end{verbatim}

\hypertarget{_gunicorn}{%
\subsection{Gunicorn}\label{_gunicorn}}

\begin{Shaded}
\begin{Highlighting}[]
\CommentTok{\#!/bin/bash}

\CommentTok{\# defines settings for gunicorn}
\VariableTok{NAME=}\StringTok{"gwift"}
\VariableTok{DJANGODIR=}\NormalTok{/home/gwift/webapps/gwift}
\VariableTok{SOCKFILE=}\NormalTok{/var/run/webapps/gunicorn\_gwift.sock}
\VariableTok{USER=}\NormalTok{gwift}
\VariableTok{GROUP=}\NormalTok{gunicorn\_sockets}
\VariableTok{NUM\_WORKERS=}\NormalTok{5}
\VariableTok{DJANGO\_SETTINGS\_MODULE=}\NormalTok{config.settings\_production}
\VariableTok{DJANGO\_WSGI\_MODULE=}\NormalTok{config.wsgi}

\BuiltInTok{echo} \StringTok{"Starting }\VariableTok{$NAME}\StringTok{ as }\KeywordTok{\textasciigrave{}}\FunctionTok{whoami}\KeywordTok{\textasciigrave{}}\StringTok{"}

\BuiltInTok{source}\NormalTok{ /home/gwift/.venvs/gwift/bin/activate}
\BuiltInTok{cd} \VariableTok{$DJANGODIR}
\BuiltInTok{export} \VariableTok{DJANGO\_SETTINGS\_MODULE=$DJANGO\_SETTINGS\_MODULE}
\BuiltInTok{export} \VariableTok{PYTHONPATH=$DJANGODIR}\NormalTok{:}\VariableTok{$PYTHONPATH}

\BuiltInTok{exec}\NormalTok{ gunicorn }\VariableTok{$\{DJANGO\_WSGI\_MODULE\}}\NormalTok{:application }\KeywordTok{\textbackslash{}}
\ExtensionTok{{-}{-}name} \VariableTok{$NAME} \KeywordTok{\textbackslash{}}
\ExtensionTok{{-}{-}workers} \VariableTok{$NUM\_WORKERS} \KeywordTok{\textbackslash{}}
\ExtensionTok{{-}{-}user} \VariableTok{$USER} \KeywordTok{\textbackslash{}}
\ExtensionTok{{-}{-}bind}\NormalTok{=unix:}\VariableTok{$SOCKFILE} \KeywordTok{\textbackslash{}}
\ExtensionTok{{-}{-}log{-}level}\NormalTok{=debug }\KeywordTok{\textbackslash{}}
\ExtensionTok{{-}{-}log{-}file}\NormalTok{={-}}
\end{Highlighting}
\end{Shaded}

\hypertarget{_supervision_keep_alive_et_autoreload}{%
\subsection{Supervision, keep-alive et
autoreload}\label{_supervision_keep_alive_et_autoreload}}

Pour la supervision, on passe par Supervisor. Il existe d'autres
superviseurs,

\begin{Shaded}
\begin{Highlighting}[]
\ExtensionTok{yum}\NormalTok{ install supervisor {-}y}
\end{Highlighting}
\end{Shaded}

On crée ensuite le fichier \texttt{/etc/supervisord.d/gwift.ini}:

\begin{Shaded}
\begin{Highlighting}[]
\NormalTok{[}\ExtensionTok{program}\NormalTok{:gwift]}
\VariableTok{command=}\NormalTok{/home/gwift/bin/start\_gunicorn.sh}
\VariableTok{user=}\NormalTok{gwift}
\VariableTok{stdout\_logfile=}\NormalTok{/var/log/gwift/gwift.log}
\VariableTok{autostart=}\NormalTok{true}
\VariableTok{autorestart=}\NormalTok{unexpected}
\VariableTok{redirect\_stdout=}\NormalTok{true}
\VariableTok{redirect\_stderr=}\NormalTok{true}
\end{Highlighting}
\end{Shaded}

Et on crée les répertoires de logs, on démarre supervisord et on vérifie
qu'il tourne correctement:

\begin{Shaded}
\begin{Highlighting}[]
\FunctionTok{mkdir}\NormalTok{ /var/log/gwift}
\FunctionTok{chown}\NormalTok{ gwift:nagios /var/log/gwift}

\ExtensionTok{systemctl}\NormalTok{ enable supervisord}
\ExtensionTok{systemctl}\NormalTok{ start supervisord.service}
\ExtensionTok{systemctl}\NormalTok{ status supervisord.service}
\NormalTok{● }\ExtensionTok{supervisord.service}\NormalTok{ {-} Process Monitoring and Control Daemon}
   \ExtensionTok{Loaded}\NormalTok{: loaded (/usr/lib/systemd/system/supervisord.service}\KeywordTok{;} \ExtensionTok{enabled}\KeywordTok{;} \ExtensionTok{vendor}\NormalTok{ preset: disabled)}
   \ExtensionTok{Active}\NormalTok{: active (running) }\ExtensionTok{since}\NormalTok{ Tue 2019{-}12{-}24 10:08:09 CET}\KeywordTok{;} \ExtensionTok{10s}\NormalTok{ ago}
  \ExtensionTok{Process}\NormalTok{: 2304 ExecStart=/usr/bin/supervisord {-}c /etc/supervisord.conf (code=exited, status=0/SUCCESS)}
 \ExtensionTok{Main}\NormalTok{ PID: 2310 (supervisord)}
   \ExtensionTok{CGroup}\NormalTok{: /system.slice/supervisord.service}
\NormalTok{           ├─}\ExtensionTok{2310}\NormalTok{ /usr/bin/python /usr/bin/supervisord {-}c /etc/supervisord.conf}
\NormalTok{           ├─}\ExtensionTok{2313}\NormalTok{ /home/gwift/.venvs/gwift/bin/python3 /home/gwift/.venvs/gwift/bin/gunicorn config.wsgi:...}
\NormalTok{           ├─}\ExtensionTok{2317}\NormalTok{ /home/gwift/.venvs/gwift/bin/python3 /home/gwift/.venvs/gwift/bin/gunicorn config.wsgi:...}
\NormalTok{           ├─}\ExtensionTok{2318}\NormalTok{ /home/gwift/.venvs/gwift/bin/python3 /home/gwift/.venvs/gwift/bin/gunicorn config.wsgi:...}
\NormalTok{           ├─}\ExtensionTok{2321}\NormalTok{ /home/gwift/.venvs/gwift/bin/python3 /home/gwift/.venvs/gwift/bin/gunicorn config.wsgi:...}
\NormalTok{           ├─}\ExtensionTok{2322}\NormalTok{ /home/gwift/.venvs/gwift/bin/python3 /home/gwift/.venvs/gwift/bin/gunicorn config.wsgi:...}
\NormalTok{           └─}\ExtensionTok{2323}\NormalTok{ /home/gwift/.venvs/gwift/bin/python3 /home/gwift/.venvs/gwift/bin/gunicorn config.wsgi:...}
\FunctionTok{ls}\NormalTok{ /var/run/webapps/}
\end{Highlighting}
\end{Shaded}

On peut aussi vérifier que l'application est en train de tourner, à
l'aide de la commande \texttt{supervisorctl}:

\begin{Shaded}
\begin{Highlighting}[]
\VariableTok{$$}\NormalTok{$ }\ExtensionTok{supervisorctl}\NormalTok{ status gwift}
\ExtensionTok{gwift}\NormalTok{                            RUNNING    pid 31983, uptime 0:01:00}
\end{Highlighting}
\end{Shaded}

Et pour gérer le démarrage ou l'arrêt, on peut passer par les commandes
suivantes:

\begin{Shaded}
\begin{Highlighting}[]
\VariableTok{$$}\NormalTok{$ }\ExtensionTok{supervisorctl}\NormalTok{ stop gwift}
\ExtensionTok{gwift}\NormalTok{: stopped}
\ExtensionTok{root@ks3353535}\NormalTok{:/etc/supervisor/conf.d\# supervisorctl start gwift}
\ExtensionTok{gwift}\NormalTok{: started}
\ExtensionTok{root@ks3353535}\NormalTok{:/etc/supervisor/conf.d\# supervisorctl restart gwift}
\ExtensionTok{gwift}\NormalTok{: stopped}
\ExtensionTok{gwift}\NormalTok{: started}
\end{Highlighting}
\end{Shaded}

\hypertarget{_configuration_du_firewall_et_ouverture_des_ports}{%
\subsection{Configuration du firewall et ouverture des
ports}\label{_configuration_du_firewall_et_ouverture_des_ports}}

\begin{verbatim}
et 443 (HTTPS).
\end{verbatim}

\begin{verbatim}
firewall-cmd --permanent --zone=public --add-service=http 
firewall-cmd --permanent --zone=public --add-service=https 
firewall-cmd --reload
\end{verbatim}

\begin{itemize}
\item
  On ouvre le port 80, uniquement pour autoriser une connexion HTTP,
  mais qui sera immédiatement redirigée vers HTTPS
\item
  Et le port 443 (forcément).
\end{itemize}

\hypertarget{_installation_dnginx}{%
\subsection{Installation d'Nginx}\label{_installation_dnginx}}

\begin{verbatim}
yum install nginx -y
usermod -a -G gunicorn_sockets nginx
\end{verbatim}

On configure ensuite le fichier \texttt{/etc/nginx/conf.d/gwift.conf}:

\begin{verbatim}
upstream gwift_app {
        server unix:/var/run/webapps/gunicorn_gwift.sock fail_timeout=0;
}

server {
        listen 80;
        server_name <server_name>;
        root /var/www/gwift;
        error_log /var/log/nginx/gwift_error.log;
        access_log /var/log/nginx/gwift_access.log;

        client_max_body_size 4G;
        keepalive_timeout 5;

        gzip on;
        gzip_comp_level 7;
        gzip_proxied any;
        gzip_types gzip_types text/plain text/css text/xml text/javascript application/x-javascript application/xml;


        location /static/ { 
                access_log off;
                expires 30d;
                add_header Pragma public;
                add_header Cache-Control "public";
                add_header Vary "Accept-Encoding";
                try_files $uri $uri/ =404;
        }

        location / {
                proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 
                proxy_set_header Host $http_host;
                proxy_redirect off;

                proxy_pass http://gwift_app;
        }
}
\end{verbatim}

\begin{itemize}
\item
  Ce répertoire sera complété par la commande \texttt{collectstatic} que
  l'on verra plus tard. L'objectif est que les fichiers ne demandant
  aucune intelligence soit directement servis par Nginx. Cela évite
  d'avoir un processus Python (relativement lent) qui doive être
  instancié pour servir un simple fichier statique.
\item
  Afin d'éviter que Django ne reçoive uniquement des requêtes provenant
  de 127.0.0.1
\end{itemize}

\hypertarget{_mise_uxe0_jour}{%
\subsection{Mise à jour}\label{_mise_uxe0_jour}}

Script de mise à jour.

\begin{Shaded}
\begin{Highlighting}[]
\FunctionTok{su}\NormalTok{ {-} }\OperatorTok{\textless{}}\NormalTok{user}\OperatorTok{\textgreater{}}
\BuiltInTok{source}\NormalTok{ \textasciitilde{}/.venvs/}\OperatorTok{\textless{}}\NormalTok{app}\OperatorTok{\textgreater{}}\NormalTok{/bin/activate}
\BuiltInTok{cd}\NormalTok{ \textasciitilde{}/webapps/}\OperatorTok{\textless{}}\NormalTok{app}\OperatorTok{\textgreater{}}
\FunctionTok{git}\NormalTok{ fetch}
\FunctionTok{git}\NormalTok{ checkout vX.Y.Z}
\ExtensionTok{pip}\NormalTok{ install {-}U requirements/prod.txt}
\ExtensionTok{python}\NormalTok{ manage.py migrate}
\ExtensionTok{python}\NormalTok{ manage.py collectstatic}
\BuiltInTok{kill}\NormalTok{ {-}HUP }\KeywordTok{\textasciigrave{}}\FunctionTok{ps}\NormalTok{ {-}C gunicorn fch {-}o pid }\KeywordTok{|} \FunctionTok{head}\NormalTok{ {-}n 1}\KeywordTok{\textasciigrave{}} 
\end{Highlighting}
\end{Shaded}

\begin{itemize}
\item
  \url{https://stackoverflow.com/questions/26902930/how-do-i-restart-gunicorn-hup-i-dont-know-masterpid-or-location-of-pid-file}
\end{itemize}

\hypertarget{_configuration_des_sauvegardes}{%
\subsection{Configuration des
sauvegardes}\label{_configuration_des_sauvegardes}}

Les sauvegardes ont été configurées avec borg:
\texttt{yum\ install\ borgbackup}.

C'est l'utilisateur gwift qui s'en occupe.

\begin{verbatim}
mkdir -p /home/gwift/borg-backups/
cd /home/gwift/borg-backups/
borg init gwift.borg -e=none
borg create gwift.borg::{now} ~/bin ~/webapps
\end{verbatim}

Et dans le fichier crontab :

\begin{verbatim}
0 23 * * * /home/gwift/bin/backup.sh
\end{verbatim}

\hypertarget{_rotation_des_jounaux}{%
\subsection{Rotation des jounaux}\label{_rotation_des_jounaux}}

\begin{Shaded}
\begin{Highlighting}[]
\ExtensionTok{/var/log/gwift/*}\NormalTok{ \{}
        \ExtensionTok{weekly}
        \ExtensionTok{rotate}\NormalTok{ 3}
        \FunctionTok{size}\NormalTok{ 10M}
        \ExtensionTok{compress}
        \ExtensionTok{delaycompress}
\NormalTok{\}}
\end{Highlighting}
\end{Shaded}

Puis on démarre logrotate avec \# logrotate -d /etc/logrotate.d/gwift
pour vérifier que cela fonctionne correctement.

\hypertarget{_ansible}{%
\subsection{Ansible}\label{_ansible}}

TODO

\hypertarget{_duxe9ploiement_sur_heroku}{%
\section{Déploiement sur Heroku}\label{_duxe9ploiement_sur_heroku}}

\href{https://www.heroku.com}{Heroku} est une \emph{Plateform As A
Service} paas, où vous choisissez le \emph{service} dont vous avez
besoin (une base de données, un service de cache, un service applicatif,
\ldots\hspace{0pt}), vous lui envoyer les paramètres nécessaires et le
tout démarre gentiment sans que vous ne deviez superviser l'hôte. Ce
mode démarrage ressemble énormément aux 12 facteurs dont nous avons déjà
parlé plus tôt - raison de plus pour que notre application soit
directement prête à y être déployée, d'autant plus qu'il ne sera pas
possible de modifier un fichier une fois qu'elle aura démarré: si vous
souhaitez modifier un paramètre, cela reviendra à couper l'actuelle et
envoyer de nouveaux paramètres et recommencer le déploiement depuis le
début.

\begin{figure}
\centering
\includegraphics{images/deployment/heroku.png}
\caption{Invest in apps, not ops. Heroku handles the hard stuff ---
patching and upgrading, 24/7 ops and security, build systems, failovers,
and more --- so your developers can stay focused on building great
apps.}
\end{figure}

Pour un projet de type "hobby" et pour l'exemple de déploiement
ci-dessous, il est tout à fait possible de s'en sortir sans dépenser un
kopek, afin de tester nos quelques idées ou mettre rapidement un
\emph{Most Valuable Product} en place. La seule contrainte consistera à
pouvoir héberger des fichiers envoyés par vos utilisateurs - ceci pourra
être fait en configurant un \emph{bucket compatible S3}, par exemple
chez Amazon, Scaleway ou OVH.

Le fonctionnement est relativement simple: pour chaque application,
Heroku crée un dépôt Git qui lui est associé. Il suffit donc d'envoyer
les sources de votre application vers ce dépôt pour qu'Heroku les
interprête comme étant une nouvelle version, déploie les nouvelles
fonctionnalités - sous réserve que tous les tests passent correctement -
et les mettent à disposition. Dans un fonctionnement plutôt manuel,
chaque déploiement est initialisé par le développeur ou par un membre de
l'équipe. Dans une version plus automatisée, chacun de ces déploiements
peut être placé en fin de \emph{pipeline}, lorsque tous les tests
unitaires et d'intégration auront été réalisés.

Au travers de la commande \texttt{heroku\ create}, vous associez donc
une nouvelle référence à votre code source, comme le montre le contenu
du fichier \texttt{.git/config} ci-dessous:

\begin{Shaded}
\begin{Highlighting}[]
\NormalTok{$ }\ExtensionTok{heroku}\NormalTok{ create}
\ExtensionTok{Creating}\NormalTok{ app... done, ⬢ young{-}temple{-}86098}
\ExtensionTok{https}\NormalTok{://young{-}temple{-}86098.herokuapp.com/ }\KeywordTok{|} \ExtensionTok{https}\NormalTok{://git.heroku.com/young{-}temple{-}86098.git}

\NormalTok{$ }\FunctionTok{cat}\NormalTok{ .git/config}
\NormalTok{[}\ExtensionTok{core}\NormalTok{]}
        \ExtensionTok{repositoryformatversion}\NormalTok{ = 0}
        \ExtensionTok{filemode}\NormalTok{ = false}
        \ExtensionTok{bare}\NormalTok{ = false}
        \ExtensionTok{logallrefupdates}\NormalTok{ = true}
        \ExtensionTok{symlinks}\NormalTok{ = false}
        \ExtensionTok{ignorecase}\NormalTok{ = true}
\NormalTok{[}\ExtensionTok{remote} \StringTok{"heroku"}\NormalTok{]}
        \ExtensionTok{url}\NormalTok{ = https://git.heroku.com/still{-}thicket{-}66406.git}
        \ExtensionTok{fetch}\NormalTok{ = +refs/heads/*:refs/remotes/heroku/*}
\end{Highlighting}
\end{Shaded}

IMPORTANT:

\begin{verbatim}
Pour définir de quel type d'application il s'agit, Heroku nécessite un minimum de configuration.
Celle-ci se limite aux deux fichiers suivants:

* Déclarer un fichier `Procfile` qui va simplement décrire le fichier à passer au protocole WSGI
* Déclarer un fichier `requirements.txt` (qui va éventuellement chercher ses propres dépendances dans un sous-répertoire, avec l'option `-r`)
\end{verbatim}

Après ce paramétrage, il suffit de pousser les changements vers ce
nouveau dépôt grâce à la commande \texttt{git\ push\ heroku\ master}.

Heroku propose des espaces de déploiements, mais pas d'espace de
stockage. Il est possible d'y envoyer des fichiers utilisateurs
(typiquement, des media personnalisés), mais ceux-ci seront perdus lors
du redémarrage du container. Il est donc primordial de configurer
correctement l'hébergement des fichiers média, de préférences sur un
stockage compatible S3. s3

Prêt à vous lancer ? Commencez par créer un compte:
\url{https://signup.heroku.com/python}.

\hypertarget{_configuration_du_compte_heroku}{%
\subsection{Configuration du compte
Heroku}\label{_configuration_du_compte_heroku}}

+ Récupération des valeurs d'environnement pour les réutiliser
ci-dessous.

Vous aurez peut-être besoin d'un coup de pouce pour démarrer votre
première application; heureusement, la documentation est super bien
faite:

\begin{figure}
\centering
\includegraphics{images/deployment/heroku-new-app.png}
\caption{Heroku: Commencer à travailler avec un langage}
\end{figure}

Installez ensuite la CLI (\emph{Command Line Interface}) en suivant
\href{https://devcenter.heroku.com/articles/heroku-cli}{la documentation
suivante}.

Au besoin, cette CLI existe pour:

\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
  macOS, \emph{via} `brew `
\item
  Windows, grâce à un
  \href{https://cli-assets.heroku.com/heroku-x64.exe}{binaire x64} (la
  version 32 bits existe aussi, mais il est peu probable que vous en
  ayez besoin)
\item
  GNU/Linux, via un script Shell
  \texttt{curl\ https://cli-assets.heroku.com/install.sh\ \textbar{}\ sh}
  ou sur \href{https://snapcraft.io/heroku}{SnapCraft}.
\end{enumerate}

Une fois installée, connectez-vous:

\begin{Shaded}
\begin{Highlighting}[]
\NormalTok{$ }\ExtensionTok{heroku}\NormalTok{ login}
\end{Highlighting}
\end{Shaded}

Et créer votre application:

\begin{Shaded}
\begin{Highlighting}[]
\NormalTok{$ }\ExtensionTok{heroku}\NormalTok{ create}
\ExtensionTok{Creating}\NormalTok{ app... done, ⬢ young{-}temple{-}86098}
\ExtensionTok{https}\NormalTok{://young{-}temple{-}86098.herokuapp.com/ }\KeywordTok{|} \ExtensionTok{https}\NormalTok{://git.heroku.com/young{-}temple{-}86098.git}
\end{Highlighting}
\end{Shaded}

\begin{figure}
\centering
\includegraphics{images/deployment/heroku-app-created.png}
\caption{Notre application est à présent configurée!}
\end{figure}

Ajoutons lui une base de données, que nous sauvegarderons à intervalle
régulier:

\begin{Shaded}
\begin{Highlighting}[]
\NormalTok{$ }\ExtensionTok{heroku}\NormalTok{ addons:create heroku{-}postgresql:hobby{-}dev}
\ExtensionTok{Creating}\NormalTok{ heroku{-}postgresql:hobby{-}dev on ⬢ still{-}thicket{-}66406... free}
\ExtensionTok{Database}\NormalTok{ has been created and is available}
\NormalTok{ ! }\ExtensionTok{This}\NormalTok{ database is empty. If upgrading, you can transfer}
\NormalTok{ ! }\ExtensionTok{data}\NormalTok{ from another database with pg:copy}
\ExtensionTok{Created}\NormalTok{ postgresql{-}clear{-}39693 as DATABASE\_URL}
\ExtensionTok{Use}\NormalTok{ heroku addons:docs heroku{-}postgresql to view documentation}

\NormalTok{$ }\ExtensionTok{heroku}\NormalTok{ pg:backups schedule {-}{-}at }\StringTok{\textquotesingle{}14:00 Europe/Brussels\textquotesingle{}}\NormalTok{ DATABASE\_URL}
\ExtensionTok{Scheduling}\NormalTok{ automatic daily backups of postgresql{-}clear{-}39693 at 14:00 Europe/Brussels... done}
\end{Highlighting}
\end{Shaded}

TODO: voir comment récupérer le backup de la db :-p

\begin{Shaded}
\begin{Highlighting}[]
\CommentTok{\# Copié/collé de https://cookiecutter{-}django.readthedocs.io/en/latest/deployment{-}on{-}heroku.html}
\ExtensionTok{heroku}\NormalTok{ create {-}{-}buildpack https://github.com/heroku/heroku{-}buildpack{-}python}

\ExtensionTok{heroku}\NormalTok{ addons:create heroku{-}redis:hobby{-}dev}

\ExtensionTok{heroku}\NormalTok{ addons:create mailgun:starter}

\ExtensionTok{heroku}\NormalTok{ config:set PYTHONHASHSEED=random}

\ExtensionTok{heroku}\NormalTok{ config:set WEB\_CONCURRENCY=4}

\ExtensionTok{heroku}\NormalTok{ config:set DJANGO\_DEBUG=False}
\ExtensionTok{heroku}\NormalTok{ config:set DJANGO\_SETTINGS\_MODULE=config.settings.production}
\ExtensionTok{heroku}\NormalTok{ config:set DJANGO\_SECRET\_KEY=}\StringTok{"}\VariableTok{$(}\ExtensionTok{openssl}\NormalTok{ rand {-}base64 64}\VariableTok{)}\StringTok{"}

\CommentTok{\# Generating a 32 character{-}long random string without any of the visually similar characters "IOl01":}
\ExtensionTok{heroku}\NormalTok{ config:set DJANGO\_ADMIN\_URL=}\StringTok{"}\VariableTok{$(}\ExtensionTok{openssl}\NormalTok{ rand {-}base64 4096 }\KeywordTok{|} \FunctionTok{tr}\NormalTok{ {-}dc }\StringTok{\textquotesingle{}A{-}HJ{-}NP{-}Za{-}km{-}z2{-}9\textquotesingle{}} \KeywordTok{|} \FunctionTok{head}\NormalTok{ {-}c 32}\VariableTok{)}\StringTok{/"}

\CommentTok{\# Set this to your Heroku app url, e.g. \textquotesingle{}bionic{-}beaver{-}28392.herokuapp.com\textquotesingle{}}
\ExtensionTok{heroku}\NormalTok{ config:set DJANGO\_ALLOWED\_HOSTS=}

\CommentTok{\# Assign with AWS\_ACCESS\_KEY\_ID}
\ExtensionTok{heroku}\NormalTok{ config:set DJANGO\_AWS\_ACCESS\_KEY\_ID=}

\CommentTok{\# Assign with AWS\_SECRET\_ACCESS\_KEY}
\ExtensionTok{heroku}\NormalTok{ config:set DJANGO\_AWS\_SECRET\_ACCESS\_KEY=}

\CommentTok{\# Assign with AWS\_STORAGE\_BUCKET\_NAME}
\ExtensionTok{heroku}\NormalTok{ config:set DJANGO\_AWS\_STORAGE\_BUCKET\_NAME=}

\FunctionTok{git}\NormalTok{ push heroku master}

\ExtensionTok{heroku}\NormalTok{ run python manage.py createsuperuser}

\ExtensionTok{heroku}\NormalTok{ run python manage.py check {-}{-}deploy}

\ExtensionTok{heroku}\NormalTok{ open}
\end{Highlighting}
\end{Shaded}

\hypertarget{_configuration}{%
\subsection{Configuration}\label{_configuration}}

Pour qu'Heroku comprenne le type d'application à démarrer, ainsi que les
commandes à exécuter pour que tout fonctionne correctement. Pour un
projet Django, cela comprend, à placer à la racine de votre projet:

\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
  Un fichier \texttt{requirements.txt} (qui peut éventuellement faire
  appel à un autre fichier, \textbf{via} l'argument \texttt{-r})
\item
  Un fichier \texttt{Procfile} ({[}sans
  extension{]}(\url{https://devcenter.heroku.com/articles/procfile)}!),
  qui expliquera la commande pour le protocole WSGI.
\end{enumerate}

Dans notre exemple:

\begin{verbatim}
# requirements.txt
django==3.2.8
gunicorn
boto3
django-storages
\end{verbatim}

\begin{Shaded}
\begin{Highlighting}[]
\CommentTok{\# Procfile}
\ExtensionTok{release}\NormalTok{: python3 manage.py migrate}
\ExtensionTok{web}\NormalTok{: gunicorn gwift.wsgi}
\end{Highlighting}
\end{Shaded}

\hypertarget{_huxe9bergement_s3}{%
\subsection{Hébergement S3}\label{_huxe9bergement_s3}}

Pour cette partie, nous allons nous baser sur
l'\href{https://www.scaleway.com/en/object-storage/}{Object Storage de
Scaleway}. Ils offrent 75GB de stockage et de transfert par mois, ce qui
va nous laisser suffisament d'espace pour jouer un peu 😉.

\includegraphics{images/deployment/scaleway-object-storage-bucket.png}

L'idée est qu'au moment de la construction des fichiers statiques,
Django aille simplement les héberger sur un espace de stockage
compatible S3. La complexité va être de configurer correctement les
différents points de terminaison. Pour héberger nos fichiers sur notre
\textbf{bucket} S3, il va falloir suivre et appliquer quelques étapes
dans l'ordre:

\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
  Configurer un bucket compatible S3 - je parlais de Scaleway, mais il y
  en a - \textbf{littéralement} - des dizaines.
\item
  Ajouter la librairie \texttt{boto3}, qui s'occupera de "parler" avec
  ce type de protocole
\item
  Ajouter la librairie \texttt{django-storage}, qui va elle s'occuper de
  faire le câblage entre le fournisseur (\textbf{via} \texttt{boto3}) et
  Django, qui s'attend à ce qu'on lui donne un moteur de gestion
  \textbf{via} la clé
  {[}\texttt{DJANGO\_STATICFILES\_STORAGE}{]}(\url{https://docs.djangoproject.com/en/3.2/ref/settings/\#std:setting-STATICFILES_STORAGE}).
\end{enumerate}

La première étape consiste à se rendre dans {[}la console
Scaleway{]}(\url{https://console.scaleway.com/project/credentials}),
pour gérer ses identifiants et créer un jeton.

\includegraphics{images/deployment/scaleway-api-key.png}

Selon la documentation de
\href{https://django-storages.readthedocs.io/en/latest/backends/amazon-S3.html\#settings}{django-storages},
de
\href{https://boto3.amazonaws.com/v1/documentation/api/latest/index.html}{boto3}
et de
\href{https://www.scaleway.com/en/docs/tutorials/deploy-saas-application/}{Scaleway},
vous aurez besoin des clés suivantes au niveau du fichier
\texttt{settings.py}:

\begin{Shaded}
\begin{Highlighting}[]
\NormalTok{AWS\_ACCESS\_KEY\_ID }\OperatorTok{=}\NormalTok{ os.getenv(}\StringTok{\textquotesingle{}ACCESS\_KEY\_ID\textquotesingle{}}\NormalTok{)}
\NormalTok{AWS\_SECRET\_ACCESS\_KEY }\OperatorTok{=}\NormalTok{ os.getenv(}\StringTok{\textquotesingle{}SECRET\_ACCESS\_KEY\textquotesingle{}}\NormalTok{)}
\NormalTok{AWS\_STORAGE\_BUCKET\_NAME }\OperatorTok{=}\NormalTok{ os.getenv(}\StringTok{\textquotesingle{}AWS\_STORAGE\_BUCKET\_NAME\textquotesingle{}}\NormalTok{)}
\NormalTok{AWS\_S3\_REGION\_NAME }\OperatorTok{=}\NormalTok{ os.getenv(}\StringTok{\textquotesingle{}AWS\_S3\_REGION\_NAME\textquotesingle{}}\NormalTok{)}

\NormalTok{AWS\_DEFAULT\_ACL }\OperatorTok{=} \StringTok{\textquotesingle{}public{-}read\textquotesingle{}}
\NormalTok{AWS\_LOCATION }\OperatorTok{=} \StringTok{\textquotesingle{}static\textquotesingle{}}
\NormalTok{AWS\_S3\_SIGNATURE\_VERSION }\OperatorTok{=} \StringTok{\textquotesingle{}s3v4\textquotesingle{}}

\NormalTok{AWS\_S3\_HOST }\OperatorTok{=} \StringTok{\textquotesingle{}s3.}\SpecialCharTok{\%s}\StringTok{.scw.cloud\textquotesingle{}} \OperatorTok{\%}\NormalTok{ (AWS\_S3\_REGION\_NAME,)}
\NormalTok{AWS\_S3\_ENDPOINT\_URL }\OperatorTok{=} \StringTok{\textquotesingle{}https://}\SpecialCharTok{\%s}\StringTok{\textquotesingle{}} \OperatorTok{\%}\NormalTok{ (AWS\_S3\_HOST, )}

\NormalTok{DEFAULT\_FILE\_STORAGE }\OperatorTok{=} \StringTok{\textquotesingle{}storages.backends.s3boto3.S3Boto3Storage\textquotesingle{}}
\NormalTok{STATICFILES\_STORAGE }\OperatorTok{=} \StringTok{\textquotesingle{}storages.backends.s3boto3.S3ManifestStaticStorage\textquotesingle{}}

\NormalTok{STATIC\_URL }\OperatorTok{=} \StringTok{\textquotesingle{}}\SpecialCharTok{\%s}\StringTok{/}\SpecialCharTok{\%s}\StringTok{/\textquotesingle{}} \OperatorTok{\%}\NormalTok{ (AWS\_S3\_ENDPOINT\_URL, AWS\_LOCATION)}

\CommentTok{\# General optimization for faster delivery}
\NormalTok{AWS\_IS\_GZIPPED }\OperatorTok{=} \VariableTok{True}
\NormalTok{AWS\_S3\_OBJECT\_PARAMETERS }\OperatorTok{=}\NormalTok{ \{}
    \StringTok{\textquotesingle{}CacheControl\textquotesingle{}}\NormalTok{: }\StringTok{\textquotesingle{}max{-}age=86400\textquotesingle{}}\NormalTok{,}
\NormalTok{\}}
\end{Highlighting}
\end{Shaded}

Configurez-les dans la console d'administration d'Heroku:

\includegraphics{images/deployment/heroku-vars-reveal.png}

Lors de la publication, vous devriez à présent avoir la sortie suivante,
qui sera confirmée par le \textbf{bucket}:

\begin{Shaded}
\begin{Highlighting}[]
\ExtensionTok{remote}\NormalTok{: {-}{-}{-}{-}{-}}\OperatorTok{\textgreater{}}\NormalTok{ $ python manage.py collectstatic {-}{-}noinput}
\ExtensionTok{remote}\NormalTok{:        128 static files copied, 156 post{-}processed.}
\end{Highlighting}
\end{Shaded}

\includegraphics{images/deployment/gwift-cloud-s3.png}

Sources complémentaires:

\begin{itemize}
\item
  {[}How to store Django static and media files on S3 in
  production{]}(\url{https://coderbook.com/@marcus/how-to-store-django-static-and-media-files-on-s3-in-production/})
\item
  {[}Using Django and
  Boto3{]}(\url{https://www.simplecto.com/using-django-and-boto3-with-scaleway-object-storage/})
\end{itemize}

\hypertarget{_docker_compose}{%
\subsection{Docker-Compose}\label{_docker_compose}}

(c/c Ced' - 2020-01-24)

Ça y est, j'ai fait un test sur mon portable avec docker et cookiecutter
pour django.

D'abords, après avoir installer docker-compose et les dépendances sous
debian, tu dois t'ajouter dans le groupe docker, sinon il faut être root
pour utiliser docker. Ensuite, j'ai relancé mon pc car juste relancé un
shell n'a pas suffit pour que je puisse utiliser docker avec mon compte.

Bon après c'est facile, un petit virtualenv pour cookiecutter, suivit
d'une installation du template django. Et puis j'ai suivi sans t
\url{https://cookiecutter-django.readthedocs.io/en/latest/developing-locally-docker.html}

Alors, il télécharge les images, fait un petit update, installe les
dépendances de dev, install les requirement pip \ldots\hspace{0pt}

Du coup, ça prend vite de la place: image.png

L'image de base python passe de 179 à 740 MB. Et là j'en ai pour presque
1,5 GB d'un coup.

Mais par contre, j'ai un python 3.7 direct et postgres 10 sans rien
faire ou presque.

La partie ci-dessous a été reprise telle quelle de
\href{https://cookiecutter-django.readthedocs.io/en/latest/deployment-with-docker.html}{la
documentation de cookie-cutter-django}.

le serveur de déploiement ne doit avoir qu'un accès en lecture au dépôt
source.

On peut aussi passer par fabric, ansible, chef ou puppet.

\hypertarget{_autres_outils}{%
\section{Autres outils}\label{_autres_outils}}

Voir aussi devpi, circus, uswgi, statsd.

See \url{https://mattsegal.dev/nginx-django-reverse-proxy-config.html}

\hypertarget{_ressources}{%
\section{Ressources}\label{_ressources}}

\begin{itemize}
\item
  \url{https://zestedesavoir.com/tutoriels/2213/deployer-une-application-django-en-production/}
\item
  \href{https://docs.djangoproject.com/fr/3.0/howto/deployment/}{Déploiement}.
\item
  Let's Encrypt !
\end{itemize}

Nous avons fait exprès de reprendre l'acronyme d'une \emph{Services
Oriented Architecture} pour cette partie. L'objectif est de vous mettre
la puce à l'oreille quant à la finalité du développement: que
l'utilisateur soit humain, bot automatique ou client Web, l'objectif est
de fournir des applications résilientes, disponibles et accessibles.

Dans cette partie, nous aborderons les vues, la mise en forme, la mise
en page, la définition d'une interface REST, la définition d'une
interface GraphQL et le routage d'URLs.

\hypertarget{_application_programming_interface}{%
\section{Application Programming
Interface}\label{_application_programming_interface}}

\url{https://news.ycombinator.com/item?id=30221016\&utm_term=comment} vs
Django Rest Framework

Expliquer pourquoi une API est intéressante/primordiale/la première
chose à réaliser/le cadet de nos soucis.

Voir peut-être aussi
\url{https://christophergs.com/python/2021/12/04/fastapi-ultimate-tutorial/}

Au niveau du modèle, nous allons partir de quelque chose de très simple:
des personnes, des contrats, des types de contrats, et un service
d'affectation. Quelque chose comme ceci:

\begin{Shaded}
\begin{Highlighting}[]
\CommentTok{\# models.py}

\ImportTok{from}\NormalTok{ django.db }\ImportTok{import}\NormalTok{ models}


\KeywordTok{class}\NormalTok{ People(models.Model):}
\NormalTok{    CIVILITY\_CHOICES }\OperatorTok{=}\NormalTok{ (}
\NormalTok{        (}\StringTok{"M"}\NormalTok{, }\StringTok{"Monsieur"}\NormalTok{),}
\NormalTok{        (}\StringTok{"Mme"}\NormalTok{, }\StringTok{"Madame"}\NormalTok{),}
\NormalTok{        (}\StringTok{"Dr"}\NormalTok{, }\StringTok{"Docteur"}\NormalTok{),}
\NormalTok{        (}\StringTok{"Pr"}\NormalTok{, }\StringTok{"Professeur"}\NormalTok{),}
\NormalTok{        (}\StringTok{""}\NormalTok{, }\StringTok{""}\NormalTok{)}
\NormalTok{    )}

\NormalTok{    last\_name }\OperatorTok{=}\NormalTok{ models.CharField(max\_length}\OperatorTok{=}\DecValTok{255}\NormalTok{)}
\NormalTok{    first\_name }\OperatorTok{=}\NormalTok{ models.CharField(max\_length}\OperatorTok{=}\DecValTok{255}\NormalTok{)}
\NormalTok{    civility }\OperatorTok{=}\NormalTok{ models.CharField(}
\NormalTok{        max\_length}\OperatorTok{=}\DecValTok{3}\NormalTok{,}
\NormalTok{        choices}\OperatorTok{=}\NormalTok{CIVILITY\_CHOICES,}
\NormalTok{        default}\OperatorTok{=}\StringTok{""}
\NormalTok{    )}

    \KeywordTok{def} \FunctionTok{\_\_str\_\_}\NormalTok{(}\VariableTok{self}\NormalTok{):}
        \ControlFlowTok{return} \StringTok{"}\SpecialCharTok{\{\}}\StringTok{, }\SpecialCharTok{\{\}}\StringTok{"}\NormalTok{.}\BuiltInTok{format}\NormalTok{(}\VariableTok{self}\NormalTok{.last\_name, }\VariableTok{self}\NormalTok{.first\_name)}


\KeywordTok{class}\NormalTok{ Service(models.Model):}
\NormalTok{    label }\OperatorTok{=}\NormalTok{ models.CharField(max\_length}\OperatorTok{=}\DecValTok{255}\NormalTok{)}

    \KeywordTok{def} \FunctionTok{\_\_str\_\_}\NormalTok{(}\VariableTok{self}\NormalTok{):}
        \ControlFlowTok{return} \VariableTok{self}\NormalTok{.label}


\KeywordTok{class}\NormalTok{ ContractType(models.Model):}
\NormalTok{    label }\OperatorTok{=}\NormalTok{ models.CharField(max\_length}\OperatorTok{=}\DecValTok{255}\NormalTok{)}
\NormalTok{    short\_label }\OperatorTok{=}\NormalTok{ models.CharField(max\_length}\OperatorTok{=}\DecValTok{50}\NormalTok{)}

    \KeywordTok{def} \FunctionTok{\_\_str\_\_}\NormalTok{(}\VariableTok{self}\NormalTok{):}
        \ControlFlowTok{return} \VariableTok{self}\NormalTok{.short\_label}


\KeywordTok{class}\NormalTok{ Contract(models.Model):}
\NormalTok{    people }\OperatorTok{=}\NormalTok{ models.ForeignKey(People, on\_delete}\OperatorTok{=}\NormalTok{models.CASCADE)}
\NormalTok{    date\_begin }\OperatorTok{=}\NormalTok{ models.DateField()}
\NormalTok{    date\_end }\OperatorTok{=}\NormalTok{ models.DateField(blank}\OperatorTok{=}\VariableTok{True}\NormalTok{, null}\OperatorTok{=}\VariableTok{True}\NormalTok{)}
\NormalTok{    contract\_type }\OperatorTok{=}\NormalTok{ models.ForeignKey(ContractType, on\_delete}\OperatorTok{=}\NormalTok{models.CASCADE)}
\NormalTok{    service }\OperatorTok{=}\NormalTok{ models.ForeignKey(Service, on\_delete}\OperatorTok{=}\NormalTok{models.CASCADE)}

    \KeywordTok{def} \FunctionTok{\_\_str\_\_}\NormalTok{(}\VariableTok{self}\NormalTok{):}
        \ControlFlowTok{if} \VariableTok{self}\NormalTok{.date\_end }\KeywordTok{is} \KeywordTok{not} \VariableTok{None}\NormalTok{:}
            \ControlFlowTok{return} \StringTok{"A partir du }\SpecialCharTok{\{\}}\StringTok{, jusqu\textquotesingle{}au }\SpecialCharTok{\{\}}\StringTok{, dans le service }\SpecialCharTok{\{\}}\StringTok{ (}\SpecialCharTok{\{\}}\StringTok{)"}\NormalTok{.}\BuiltInTok{format}\NormalTok{(}
                \VariableTok{self}\NormalTok{.date\_begin,}
                \VariableTok{self}\NormalTok{.date\_end,}
                \VariableTok{self}\NormalTok{.service,}
                \VariableTok{self}\NormalTok{.contract\_type}
\NormalTok{            )}

        \ControlFlowTok{return} \StringTok{"A partir du }\SpecialCharTok{\{\}}\StringTok{, à durée indéterminée, dans le service }\SpecialCharTok{\{\}}\StringTok{ (}\SpecialCharTok{\{\}}\StringTok{)"}\NormalTok{.}\BuiltInTok{format}\NormalTok{(}
                \VariableTok{self}\NormalTok{.date\_begin,}
                \VariableTok{self}\NormalTok{.service,}
                \VariableTok{self}\NormalTok{.contract\_type}
\NormalTok{            )}
\end{Highlighting}
\end{Shaded}

\includegraphics{images/rest/models.png}

\hypertarget{_configuration_2}{%
\section{Configuration}\label{_configuration_2}}

La configuration des points de terminaison de notre API est relativement
touffue. Il convient de:

\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
  Configurer les sérialiseurs, càd. les champs que nous souhaitons
  exposer au travers de l'API,
\item
  Configurer les vues, càd le comportement de chacun des points de
  terminaison,
\item
  Configurer les points de terminaison eux-mêmes, càd les URLs
  permettant d'accéder aux ressources.
\item
  Et finalement ajouter quelques paramètres au niveau de notre
  application.
\end{enumerate}

\hypertarget{_suxe9rialiseurs}{%
\subsection{Sérialiseurs}\label{_suxe9rialiseurs}}

\begin{Shaded}
\begin{Highlighting}[]
\CommentTok{\# serializers.py}

\ImportTok{from}\NormalTok{ django.contrib.auth.models }\ImportTok{import}\NormalTok{ User, Group}
\ImportTok{from}\NormalTok{ rest\_framework }\ImportTok{import}\NormalTok{ serializers}

\ImportTok{from}\NormalTok{ .models }\ImportTok{import}\NormalTok{ People, Contract, Service}


\KeywordTok{class}\NormalTok{ PeopleSerializer(serializers.HyperlinkedModelSerializer):}
    \KeywordTok{class}\NormalTok{ Meta:}
\NormalTok{        model }\OperatorTok{=}\NormalTok{ People}
\NormalTok{        fields }\OperatorTok{=}\NormalTok{ (}\StringTok{"last\_name"}\NormalTok{, }\StringTok{"first\_name"}\NormalTok{, }\StringTok{"contract\_set"}\NormalTok{)}


\KeywordTok{class}\NormalTok{ ContractSerializer(serializers.HyperlinkedModelSerializer):}
    \KeywordTok{class}\NormalTok{ Meta:}
\NormalTok{        model }\OperatorTok{=}\NormalTok{ Contract}
\NormalTok{        fields }\OperatorTok{=}\NormalTok{ (}\StringTok{"date\_begin"}\NormalTok{, }\StringTok{"date\_end"}\NormalTok{, }\StringTok{"service"}\NormalTok{)}


\KeywordTok{class}\NormalTok{ ServiceSerializer(serializers.HyperlinkedModelSerializer):}
    \KeywordTok{class}\NormalTok{ Meta:}
\NormalTok{        model }\OperatorTok{=}\NormalTok{ Service}
\NormalTok{        fields }\OperatorTok{=}\NormalTok{ (}\StringTok{"name"}\NormalTok{,)}
\end{Highlighting}
\end{Shaded}

\hypertarget{_vues}{%
\subsection{Vues}\label{_vues}}

\begin{Shaded}
\begin{Highlighting}[]
\CommentTok{\# views.py}

\ImportTok{from}\NormalTok{ django.contrib.auth.models }\ImportTok{import}\NormalTok{ User, Group}
\ImportTok{from}\NormalTok{ rest\_framework }\ImportTok{import}\NormalTok{ viewsets}
\ImportTok{from}\NormalTok{ rest\_framework }\ImportTok{import}\NormalTok{ permissions}

\ImportTok{from}\NormalTok{ .models }\ImportTok{import}\NormalTok{ People, Contract, Service}
\ImportTok{from}\NormalTok{ .serializers }\ImportTok{import}\NormalTok{ PeopleSerializer, ContractSerializer, ServiceSerializer}


\KeywordTok{class}\NormalTok{ PeopleViewSet(viewsets.ModelViewSet):}
\NormalTok{    queryset }\OperatorTok{=}\NormalTok{ People.objects.}\BuiltInTok{all}\NormalTok{()}
\NormalTok{    serializer\_class }\OperatorTok{=}\NormalTok{ PeopleSerializer}
\NormalTok{    permission\_class }\OperatorTok{=}\NormalTok{ [permissions.IsAuthenticated]}


\KeywordTok{class}\NormalTok{ ContractViewSet(viewsets.ModelViewSet):}
\NormalTok{    queryset }\OperatorTok{=}\NormalTok{ Contract.objects.}\BuiltInTok{all}\NormalTok{()}
\NormalTok{    serializer\_class }\OperatorTok{=}\NormalTok{ ContractSerializer}
\NormalTok{    permission\_class }\OperatorTok{=}\NormalTok{ [permissions.IsAuthenticated]}


\KeywordTok{class}\NormalTok{ ServiceViewSet(viewsets.ModelViewSet):}
\NormalTok{    queryset }\OperatorTok{=}\NormalTok{ Service.objects.}\BuiltInTok{all}\NormalTok{()}
\NormalTok{    serializer\_class }\OperatorTok{=}\NormalTok{ ServiceSerializer}
\NormalTok{    permission\_class }\OperatorTok{=}\NormalTok{ [permissions.IsAuthenticated]}
\end{Highlighting}
\end{Shaded}

\hypertarget{_urls}{%
\subsection{URLs}\label{_urls}}

\begin{Shaded}
\begin{Highlighting}[]
\CommentTok{\# urls.py}

\ImportTok{from}\NormalTok{ django.contrib }\ImportTok{import}\NormalTok{ admin}
\ImportTok{from}\NormalTok{ django.urls }\ImportTok{import}\NormalTok{ path, include}

\ImportTok{from}\NormalTok{ rest\_framework }\ImportTok{import}\NormalTok{ routers}

\ImportTok{from}\NormalTok{ core }\ImportTok{import}\NormalTok{ views}


\NormalTok{router }\OperatorTok{=}\NormalTok{ routers.DefaultRouter()}
\NormalTok{router.register(}\VerbatimStringTok{r"people"}\NormalTok{, views.PeopleViewSet)}
\NormalTok{router.register(}\VerbatimStringTok{r"contracts"}\NormalTok{, views.ContractViewSet)}
\NormalTok{router.register(}\VerbatimStringTok{r"services"}\NormalTok{, views.ServiceViewSet)}

\NormalTok{urlpatterns }\OperatorTok{=}\NormalTok{ [}
\NormalTok{    path(}\StringTok{"api/v1/"}\NormalTok{, include(router.urls)),}
\NormalTok{    path(}\StringTok{\textquotesingle{}admin/\textquotesingle{}}\NormalTok{, admin.site.urls),}
\NormalTok{]}
\end{Highlighting}
\end{Shaded}

\hypertarget{_paramuxe8tres}{%
\subsection{Paramètres}\label{_paramuxe8tres}}

\begin{Shaded}
\begin{Highlighting}[]
\CommentTok{\# settings.py}

\NormalTok{INSTALLED\_APPS }\OperatorTok{=}\NormalTok{ [}
\NormalTok{    ...}
    \StringTok{"rest\_framework"}\NormalTok{,}
\NormalTok{    ...}
\NormalTok{]}

\NormalTok{...}

\NormalTok{REST\_FRAMEWORK }\OperatorTok{=}\NormalTok{ \{}
    \StringTok{\textquotesingle{}DEFAULT\_PAGINATION\_CLASS\textquotesingle{}}\NormalTok{: }\StringTok{\textquotesingle{}rest\_framework.pagination.PageNumberPagination\textquotesingle{}}\NormalTok{,}
    \StringTok{\textquotesingle{}PAGE\_SIZE\textquotesingle{}}\NormalTok{: }\DecValTok{10}
\NormalTok{\}}
\end{Highlighting}
\end{Shaded}

A ce stade, en nous rendant sur l'URL
\texttt{http://localhost:8000/api/v1}, nous obtiendrons ceci:

\includegraphics{images/rest/api-first-example.png}

\hypertarget{_moduxe8les_et_relations}{%
\section{Modèles et relations}\label{_moduxe8les_et_relations}}

Plus haut, nous avons utilisé une relation de type
\texttt{HyperlinkedModelSerializer}. C'est une bonne manière pour
autoriser des relations entre vos instances à partir de l'API, mais il
faut reconnaître que cela reste assez limité. Pour palier à ceci, il
existe {[}plusieurs manières de représenter ces
relations{]}(\url{https://www.django-rest-framework.org/api-guide/relations/}):
soit \textbf{via} un hyperlien, comme ci-dessus, soit en utilisant les
clés primaires, soit en utilisant l'URL canonique permettant d'accéder à
la ressource. La solution la plus complète consiste à intégrer la
relation directement au niveau des données sérialisées, ce qui nous
permet de passer de ceci (au niveau des contrats):

\begin{Shaded}
\begin{Highlighting}[]
\FunctionTok{\{}
    \DataTypeTok{"count"}\FunctionTok{:} \DecValTok{1}\FunctionTok{,}
    \DataTypeTok{"next"}\FunctionTok{:} \KeywordTok{null}\FunctionTok{,}
    \DataTypeTok{"previous"}\FunctionTok{:} \KeywordTok{null}\FunctionTok{,}
    \DataTypeTok{"results"}\FunctionTok{:} \OtherTok{[}
        \FunctionTok{\{}
            \DataTypeTok{"last\_name"}\FunctionTok{:} \StringTok{"Bond"}\FunctionTok{,}
            \DataTypeTok{"first\_name"}\FunctionTok{:} \StringTok{"James"}\FunctionTok{,}
            \DataTypeTok{"contract\_set"}\FunctionTok{:} \OtherTok{[}
                \StringTok{"http://localhost:8000/api/v1/contracts/1/"}\OtherTok{,}
                \StringTok{"http://localhost:8000/api/v1/contracts/2/"}
            \OtherTok{]}
        \FunctionTok{\}}
    \OtherTok{]}
\FunctionTok{\}}
\end{Highlighting}
\end{Shaded}

à ceci:

\begin{Shaded}
\begin{Highlighting}[]
\FunctionTok{\{}
    \DataTypeTok{"count"}\FunctionTok{:} \DecValTok{1}\FunctionTok{,}
    \DataTypeTok{"next"}\FunctionTok{:} \KeywordTok{null}\FunctionTok{,}
    \DataTypeTok{"previous"}\FunctionTok{:} \KeywordTok{null}\FunctionTok{,}
    \DataTypeTok{"results"}\FunctionTok{:} \OtherTok{[}
        \FunctionTok{\{}
            \DataTypeTok{"last\_name"}\FunctionTok{:} \StringTok{"Bond"}\FunctionTok{,}
            \DataTypeTok{"first\_name"}\FunctionTok{:} \StringTok{"James"}\FunctionTok{,}
            \DataTypeTok{"contract\_set"}\FunctionTok{:} \OtherTok{[}
                \FunctionTok{\{}
                    \DataTypeTok{"date\_begin"}\FunctionTok{:} \StringTok{"2019{-}01{-}01"}\FunctionTok{,}
                    \DataTypeTok{"date\_end"}\FunctionTok{:} \KeywordTok{null}\FunctionTok{,}
                    \DataTypeTok{"service"}\FunctionTok{:} \StringTok{"http://localhost:8000/api/v1/services/1/"}
                \FunctionTok{\}}\OtherTok{,}
                \FunctionTok{\{}
                    \DataTypeTok{"date\_begin"}\FunctionTok{:} \StringTok{"2009{-}01{-}01"}\FunctionTok{,}
                    \DataTypeTok{"date\_end"}\FunctionTok{:} \StringTok{"2021{-}01{-}01"}\FunctionTok{,}
                    \DataTypeTok{"service"}\FunctionTok{:} \StringTok{"http://localhost:8000/api/v1/services/1/"}
                \FunctionTok{\}}
            \OtherTok{]}
        \FunctionTok{\}}
    \OtherTok{]}
\FunctionTok{\}}
\end{Highlighting}
\end{Shaded}

La modification se limite à \textbf{surcharger} la propriété, pour
indiquer qu'elle consiste en une instance d'un des sérialiseurs
existants. Nous passons ainsi de ceci

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{class}\NormalTok{ ContractSerializer(serializers.HyperlinkedModelSerializer):}
    \KeywordTok{class}\NormalTok{ Meta:}
\NormalTok{        model }\OperatorTok{=}\NormalTok{ Contract}
\NormalTok{        fields }\OperatorTok{=}\NormalTok{ (}\StringTok{"date\_begin"}\NormalTok{, }\StringTok{"date\_end"}\NormalTok{, }\StringTok{"service"}\NormalTok{)}


\KeywordTok{class}\NormalTok{ PeopleSerializer(serializers.HyperlinkedModelSerializer):}

    \KeywordTok{class}\NormalTok{ Meta:}
\NormalTok{        model }\OperatorTok{=}\NormalTok{ People}
\NormalTok{        fields }\OperatorTok{=}\NormalTok{ (}\StringTok{"last\_name"}\NormalTok{, }\StringTok{"first\_name"}\NormalTok{, }\StringTok{"contract\_set"}\NormalTok{)}
\end{Highlighting}
\end{Shaded}

à ceci:

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{class}\NormalTok{ ContractSerializer(serializers.HyperlinkedModelSerializer):}
    \KeywordTok{class}\NormalTok{ Meta:}
\NormalTok{        model }\OperatorTok{=}\NormalTok{ Contract}
\NormalTok{        fields }\OperatorTok{=}\NormalTok{ (}\StringTok{"date\_begin"}\NormalTok{, }\StringTok{"date\_end"}\NormalTok{, }\StringTok{"service"}\NormalTok{)}


\KeywordTok{class}\NormalTok{ PeopleSerializer(serializers.HyperlinkedModelSerializer):}
\NormalTok{    contract\_set }\OperatorTok{=}\NormalTok{ ContractSerializer(many}\OperatorTok{=}\VariableTok{True}\NormalTok{, read\_only}\OperatorTok{=}\VariableTok{True}\NormalTok{)}

    \KeywordTok{class}\NormalTok{ Meta:}
\NormalTok{        model }\OperatorTok{=}\NormalTok{ People}
\NormalTok{        fields }\OperatorTok{=}\NormalTok{ (}\StringTok{"last\_name"}\NormalTok{, }\StringTok{"first\_name"}\NormalTok{, }\StringTok{"contract\_set"}\NormalTok{)}
\end{Highlighting}
\end{Shaded}

Nous ne faisons donc bien que redéfinir la propriété
\texttt{contract\_set} et indiquons qu'il s'agit à présent d'une
instance de \texttt{ContractSerializer}, et qu'il est possible d'en
avoir plusieurs. C'est tout.

\hypertarget{_filtres_et_recherches}{%
\section{Filtres et recherches}\label{_filtres_et_recherches}}

A ce stade, nous pouvons juste récupérer des informations présentes dans
notre base de données, mais à part les parcourir, il est difficile d'en
faire quelque chose.

Il est possible de jouer avec les URLs en définissant une nouvelle route
ou avec les paramètres de l'URL, ce qui demanderait alors de programmer
chaque cas possible - sans que le consommateur ne puisse les déduire
lui-même. Une solution élégante consiste à autoriser le consommateur à
filtrer les données, directement au niveau de l'API. Ceci peut être
fait. Il existe deux manières de restreindre l'ensemble des résultats
retournés:

\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
  Soit au travers d'une recherche, qui permet d'effectuer une recherche
  textuelle, globale et par ensemble à un ensemble de champs,
\item
  Soit au travers d'un filtre, ce qui permet de spécifier une valeur
  précise à rechercher.
\end{enumerate}

Dans notre exemple, la première possibilité sera utile pour rechercher
une personne répondant à un ensemble de critères. Typiquement,
\texttt{/api/v1/people/?search=raymond\ bond} ne nous donnera aucun
résultat, alors que \texttt{/api/v1/people/?search=james\ bond} nous
donnera le célèbre agent secret (qui a bien entendu un contrat chez
nous\ldots\hspace{0pt}).

Le second cas permettra par contre de préciser que nous souhaitons
disposer de toutes les personnes dont le contrat est ultérieur à une
date particulière.

Utiliser ces deux mécanismes permet, pour Django-Rest-Framework, de
proposer immédiatement les champs, et donc d'informer le consommateur
des possibilités:

\includegraphics{images/rest/drf-filters-and-searches.png}

\hypertarget{_recherches}{%
\subsection{Recherches}\label{_recherches}}

La fonction de recherche est déjà implémentée au niveau de
Django-Rest-Framework, et aucune dépendance supplémentaire n'est
nécessaire. Au niveau du \texttt{viewset}, il suffit d'ajouter deux
informations:

\begin{Shaded}
\begin{Highlighting}[]
\NormalTok{...}
\ImportTok{from}\NormalTok{ rest\_framework }\ImportTok{import}\NormalTok{ filters, viewsets}
\NormalTok{...}

\KeywordTok{class}\NormalTok{ PeopleViewSet(viewsets.ModelViewSet):}
\NormalTok{    ...}
\NormalTok{    filter\_backends }\OperatorTok{=}\NormalTok{ [filters.SearchFilter]}
\NormalTok{    search\_fields }\OperatorTok{=}\NormalTok{ [}\StringTok{"last\_name"}\NormalTok{, }\StringTok{"first\_name"}\NormalTok{]}
\NormalTok{    ...}
\end{Highlighting}
\end{Shaded}

\hypertarget{_filtres}{%
\subsection{Filtres}\label{_filtres}}

Nous commençons par installer {[}le paquet
\texttt{django-filter}{]}(\url{https://www.django-rest-framework.org/api-guide/filtering/\#djangofilterbackend})
et nous l'ajoutons parmi les applications installées:

\begin{Shaded}
\begin{Highlighting}[]
\NormalTok{λ }\ExtensionTok{pip}\NormalTok{ install django{-}filter}
\ExtensionTok{Collecting}\NormalTok{ django{-}filter}
  \ExtensionTok{Downloading}\NormalTok{ django\_filter{-}2.4.0{-}py3{-}none{-}any.whl (73 kB)}
     \KeywordTok{|}\NormalTok{████████████████████████████████}\KeywordTok{|} \ExtensionTok{73}\NormalTok{ kB 2.6 MB/s}
\ExtensionTok{Requirement}\NormalTok{ already satisfied: Django}\OperatorTok{\textgreater{}}\NormalTok{=2.2 in c:\textbackslash{}users\textbackslash{}fred\textbackslash{}sources\textbackslash{}.venvs\textbackslash{}rps\textbackslash{}lib\textbackslash{}site{-}packages (from django{-}filter) }\KeywordTok{(}\ExtensionTok{3.1.7}\KeywordTok{)}
\ExtensionTok{Requirement}\NormalTok{ already satisfied: asgiref}\OperatorTok{\textless{}}\NormalTok{4,}\OperatorTok{\textgreater{}}\NormalTok{=3.2.10 in c:\textbackslash{}users\textbackslash{}fred\textbackslash{}sources\textbackslash{}.venvs\textbackslash{}rps\textbackslash{}lib\textbackslash{}site{-}packages (from Django}\OperatorTok{\textgreater{}}\NormalTok{=2.2{-}}\OperatorTok{\textgreater{}}\NormalTok{django{-}filter) }\KeywordTok{(}\ExtensionTok{3.3.1}\KeywordTok{)}
\ExtensionTok{Requirement}\NormalTok{ already satisfied: sqlparse}\OperatorTok{\textgreater{}}\NormalTok{=0.2.2 in c:\textbackslash{}users\textbackslash{}fred\textbackslash{}sources\textbackslash{}.venvs\textbackslash{}rps\textbackslash{}lib\textbackslash{}site{-}packages (from Django}\OperatorTok{\textgreater{}}\NormalTok{=2.2{-}}\OperatorTok{\textgreater{}}\NormalTok{django{-}filter) }\KeywordTok{(}\ExtensionTok{0.4.1}\KeywordTok{)}
\ExtensionTok{Requirement}\NormalTok{ already satisfied: pytz in c:\textbackslash{}users\textbackslash{}fred\textbackslash{}sources\textbackslash{}.venvs\textbackslash{}rps\textbackslash{}lib\textbackslash{}site{-}packages (from Django}\OperatorTok{\textgreater{}}\NormalTok{=2.2{-}}\OperatorTok{\textgreater{}}\NormalTok{django{-}filter) }\KeywordTok{(}\ExtensionTok{2021.1}\KeywordTok{)}
\ExtensionTok{Installing}\NormalTok{ collected packages: django{-}filter}
\ExtensionTok{Successfully}\NormalTok{ installed django{-}filter{-}2.4.0}
\end{Highlighting}
\end{Shaded}

Une fois l'installée réalisée, il reste deux choses à faire:

\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
  Ajouter \texttt{django\_filters} parmi les applications installées:
\item
  Configurer la clé \texttt{DEFAULT\_FILTER\_BACKENDS} à la valeur
  \texttt{{[}\textquotesingle{}django\_filters.rest\_framework.DjangoFilterBackend\textquotesingle{}{]}}.
\end{enumerate}

Vous avez suivi les étapes ci-dessus, il suffit d'adapter le fichier
\texttt{settings.py} de la manière suivante:

\begin{Shaded}
\begin{Highlighting}[]
\NormalTok{REST\_FRAMEWORK }\OperatorTok{=}\NormalTok{ \{}
    \StringTok{\textquotesingle{}DEFAULT\_PAGINATION\_CLASS\textquotesingle{}}\NormalTok{: }\StringTok{\textquotesingle{}rest\_framework.pagination.PageNumberPagination\textquotesingle{}}\NormalTok{,}
    \StringTok{\textquotesingle{}PAGE\_SIZE\textquotesingle{}}\NormalTok{: }\DecValTok{10}\NormalTok{,}
    \StringTok{\textquotesingle{}DEFAULT\_FILTER\_BACKENDS\textquotesingle{}}\NormalTok{: [}\StringTok{\textquotesingle{}django\_filters.rest\_framework.DjangoFilterBackend\textquotesingle{}}\NormalTok{]}
\NormalTok{\}}
\end{Highlighting}
\end{Shaded}

Au niveau du viewset, il convient d'ajouter ceci:

\begin{Shaded}
\begin{Highlighting}[]
\NormalTok{...}
\ImportTok{from}\NormalTok{ django\_filters.rest\_framework }\ImportTok{import}\NormalTok{ DjangoFilterBackend}
\ImportTok{from}\NormalTok{ rest\_framework }\ImportTok{import}\NormalTok{ viewsets}
\NormalTok{...}

\KeywordTok{class}\NormalTok{ PeopleViewSet(viewsets.ModelViewSet):}
\NormalTok{    ...}
\NormalTok{    filter\_backends }\OperatorTok{=}\NormalTok{ [DjangoFilterBackend]}
\NormalTok{    filterset\_fields }\OperatorTok{=}\NormalTok{ (}\StringTok{\textquotesingle{}last\_name\textquotesingle{}}\NormalTok{,)}
\NormalTok{    ...}
\end{Highlighting}
\end{Shaded}

A ce stade, nous avons deux problèmes:

\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
  Le champ que nous avons défini au niveau de la propriété
  \texttt{filterset\_fields} exige une correspondance exacte. Ainsi,
  \texttt{/api/v1/people/?last\_name=Bon} ne retourne rien, alors que
  \texttt{/api/v1/people/?last\_name=Bond} nous donnera notre agent
  secret préféré.
\item
  Il n'est pas possible d'aller appliquer un critère de sélection sur la
  propriété d'une relation. Notre exemple proposant rechercher
  uniquement les relations dans le futur (ou dans le passé) tombe à
  l'eau.
\end{enumerate}

Pour ces deux points, nous allons définir un nouveau filtre, en
surchargeant une nouvelle classe dont la classe mère serait de type
\texttt{django\_filters.FilterSet}.

TO BE CONTINUED.

A noter qu'il existe un paquet
{[}Django-Rest-Framework-filters{]}(\url{https://github.com/philipn/django-rest-framework-filters}),
mais il est déprécié depuis Django 3.0, puisqu'il se base sur
\texttt{django.utils.six} qui n'existe à présent plus. Il faut donc le
faire à la main (ou patcher le paquet\ldots\hspace{0pt}).

\hypertarget{_urls_et_espaces_de_noms}{%
\section{URLs et espaces de noms}\label{_urls_et_espaces_de_noms}}

La gestion des URLs permet \textbf{grosso modo} d'assigner une adresse
paramétrée ou non à une fonction Python. La manière simple consiste à
modifier le fichier \texttt{gwift/settings.py} pour y ajouter nos
correspondances. Par défaut, le fichier ressemble à ceci:

\begin{Shaded}
\begin{Highlighting}[]
\CommentTok{\# gwift/urls.py}

\ImportTok{from}\NormalTok{ django.conf.urls }\ImportTok{import}\NormalTok{ include, url}
\ImportTok{from}\NormalTok{ django.contrib }\ImportTok{import}\NormalTok{ admin}

\NormalTok{urlpatterns }\OperatorTok{=}\NormalTok{ [}
\NormalTok{    url(}\VerbatimStringTok{r\textquotesingle{}\^{}admin/\textquotesingle{}}\NormalTok{, include(admin.site.urls)),}
\NormalTok{]}
\end{Highlighting}
\end{Shaded}

La variable \texttt{urlpatterns} associe un ensemble d'adresses à des
fonctions. Dans le fichier \textbf{nu}, seul le \textbf{pattern}
\texttt{admin} est défini, et inclut toutes les adresses qui sont
définies dans le fichier \texttt{admin.site.urls}.

Django fonctionne avec des \textbf{expressions rationnelles} simplifiées
(des \textbf{expressions régulières} ou \textbf{regex}) pour trouver une
correspondance entre une URL et la fonction qui recevra la requête et
retournera une réponse. Nous utilisons l'expression \texttt{\^{}\$} pour
déterminer la racine de notre application, mais nous pourrions appliquer
d'autres regroupements (\texttt{/home},
\texttt{users/\textless{}profile\_id\textgreater{}},
\texttt{articles/\textless{}year\textgreater{}/\textless{}month\textgreater{}/\textless{}day\textgreater{}},
\ldots\hspace{0pt}). Chaque \textbf{variable} déclarée dans l'expression
régulière sera apparenté à un paramètre dans la fonction correspondante.
Ainsi,

\begin{Shaded}
\begin{Highlighting}[]
\CommentTok{\# admin.site.urls.py}
\end{Highlighting}
\end{Shaded}

Pour reprendre l'exemple où on en était resté:

\begin{Shaded}
\begin{Highlighting}[]
\CommentTok{\# gwift/urls.py}

\ImportTok{from}\NormalTok{ django.conf.urls }\ImportTok{import}\NormalTok{ include, url}
\ImportTok{from}\NormalTok{ django.contrib }\ImportTok{import}\NormalTok{ admin}

\ImportTok{from}\NormalTok{ wish }\ImportTok{import}\NormalTok{ views }\ImportTok{as}\NormalTok{ wish\_views}

\NormalTok{urlpatterns }\OperatorTok{=}\NormalTok{ [}
\NormalTok{    url(}\VerbatimStringTok{r\textquotesingle{}\^{}admin/\textquotesingle{}}\NormalTok{, include(admin.site.urls)),}
\NormalTok{    url(}\VerbatimStringTok{r\textquotesingle{}\^{}$\textquotesingle{}}\NormalTok{, wish\_views.wishlists, name}\OperatorTok{=}\StringTok{\textquotesingle{}wishlists\textquotesingle{}}\NormalTok{),}
\NormalTok{]}
\end{Highlighting}
\end{Shaded}

Dans la mesure du possible, essayez toujours de \textbf{nommer} chaque
expression. Cela permettra notamment de les retrouver au travers de la
fonction \texttt{reverse}, mais permettra également de simplifier vos
templates.

A présent, on doit tester que l'URL racine de notre application mène
bien vers la fonction \texttt{wish\_views.wishlists}.

Sauf que les pages \texttt{about} et \texttt{help} existent également.
Pour implémenter ce type de précédence, il faudrait implémenter les URLs
de la manière suivante:

\begin{verbatim}
| about
| help
| <user>
\end{verbatim}

Mais cela signifie aussi que les utilisateurs \texttt{about} et
\texttt{help} (s'ils existent\ldots\hspace{0pt}) ne pourront jamais
accéder à leur profil. Une dernière solution serait de maintenir une
liste d'authorité des noms d'utilisateur qu'il n'est pas possible
d'utiliser.

D'où l'importance de bien définir la séquence de déinition de ces
routes, ainsi que des espaces de noms.

Note sur les namespaces.

De là, découle une autre bonne pratique: l'utilisation de
\emph{breadcrumbs}
(\url{https://stackoverflow.com/questions/826889/how-to-implement-breadcrumbs-in-a-django-template})
ou de guidelines de navigation.

\hypertarget{_reverse}{%
\subsection{Reverse}\label{_reverse}}

En associant un nom ou un libellé à chaque URL, il est possible de
récupérer sa \textbf{traduction}. Cela implique par contre de ne plus
toucher à ce libellé par la suite\ldots\hspace{0pt}

Dans le fichier \texttt{urls.py}, on associe le libellé
\texttt{wishlists} à l'URL \texttt{r\textquotesingle{}\^{}\$}
(c'est-à-dire la racine du site):

\begin{Shaded}
\begin{Highlighting}[]
\ImportTok{from}\NormalTok{ wish.views }\ImportTok{import}\NormalTok{ WishListList}

\NormalTok{urlpatterns }\OperatorTok{=}\NormalTok{ [}
\NormalTok{    url(}\VerbatimStringTok{r\textquotesingle{}\^{}admin/\textquotesingle{}}\NormalTok{, include(admin.site.urls)),}
\NormalTok{    url(}\VerbatimStringTok{r\textquotesingle{}\^{}$\textquotesingle{}}\NormalTok{, WishListList.as\_view(), name}\OperatorTok{=}\StringTok{\textquotesingle{}wishlists\textquotesingle{}}\NormalTok{),}
\NormalTok{]}
\end{Highlighting}
\end{Shaded}

De cette manière, dans nos templates, on peut à présent construire un
lien vers la racine avec le tags suivant:

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{\textless{}a}\OtherTok{ href=}\StringTok{"\{\% url \textquotesingle{}wishlists\textquotesingle{} \%\}"}\KeywordTok{\textgreater{}}\NormalTok{\{\{ yearvar \}\} Archive}\KeywordTok{\textless{}/a\textgreater{}}
\end{Highlighting}
\end{Shaded}

De la même manière, on peut également récupérer l'URL de destination
pour n'importe quel libellé, de la manière suivante:

\begin{Shaded}
\begin{Highlighting}[]
\ImportTok{from}\NormalTok{ django.core.urlresolvers }\ImportTok{import}\NormalTok{ reverse\_lazy}

\NormalTok{wishlists\_url }\OperatorTok{=}\NormalTok{ reverse\_lazy(}\StringTok{\textquotesingle{}wishlists\textquotesingle{}}\NormalTok{)}
\end{Highlighting}
\end{Shaded}

\hypertarget{_i18n_l10n}{%
\section{i18n / l10n}\label{_i18n_l10n}}

La localisation (\emph{l10n}) et l'internationalization (\emph{i18n})
sont deux concepts proches, mais différents:

\begin{itemize}
\item
  Internationalisation: \emph{Preparing the software for localization.
  Usually done by developers.}
\item
  Localisation: \emph{Writing the translations and local formats.
  Usually done by translators.}
\end{itemize}

L'internationalisation est donc le processus permettant à une
application d'accepter une forme de localisation. La seconde ne va donc
pas sans la première, tandis que la première ne fait qu'autoriser la
seconde.

\hypertarget{_arborescences}{%
\subsection{Arborescences}\label{_arborescences}}

\begin{Shaded}
\begin{Highlighting}[]

\end{Highlighting}
\end{Shaded}

\begin{Shaded}
\begin{Highlighting}[]
\CommentTok{\# \textless{}app\textgreater{}/management/commands/rebuild.py}

\CommentTok{"""This command manages Closure Tables implementation}

\CommentTok{It adds new levels and cleans links between entities.}
\CommentTok{This way, it\textquotesingle{}s relatively easy to fetch an entire tree with just one tiny request.}

\CommentTok{"""}

\ImportTok{from}\NormalTok{ django.core.management.base }\ImportTok{import}\NormalTok{ BaseCommand}

\ImportTok{from}\NormalTok{ rps.structure.models }\ImportTok{import}\NormalTok{ Entity, EntityTreePath}


\KeywordTok{class}\NormalTok{ Command(BaseCommand):}
    \KeywordTok{def}\NormalTok{ handle(}\VariableTok{self}\NormalTok{, }\OperatorTok{*}\NormalTok{args, }\OperatorTok{**}\NormalTok{options):}
\NormalTok{        entities }\OperatorTok{=}\NormalTok{ Entity.objects.}\BuiltInTok{all}\NormalTok{()}

        \ControlFlowTok{for}\NormalTok{ entity }\KeywordTok{in}\NormalTok{ entities:}
\NormalTok{            breadcrumb }\OperatorTok{=}\NormalTok{ [node }\ControlFlowTok{for}\NormalTok{ node }\KeywordTok{in}\NormalTok{ entity.breadcrumb()]}

\NormalTok{            tree }\OperatorTok{=} \BuiltInTok{set}\NormalTok{(EntityTreePath.objects.}\BuiltInTok{filter}\NormalTok{(descendant}\OperatorTok{=}\NormalTok{entity))}

            \ControlFlowTok{for}\NormalTok{ idx, node }\KeywordTok{in} \BuiltInTok{enumerate}\NormalTok{(breadcrumb):}
\NormalTok{                tree\_path, \_ }\OperatorTok{=}\NormalTok{ EntityTreePath.objects.get\_or\_create(}
\NormalTok{                    ancestor}\OperatorTok{=}\NormalTok{node, descendant}\OperatorTok{=}\NormalTok{entity, weight}\OperatorTok{=}\NormalTok{idx }\OperatorTok{+} \DecValTok{1}
\NormalTok{                )}

                \ControlFlowTok{if}\NormalTok{ tree\_path }\KeywordTok{in}\NormalTok{ tree:}
\NormalTok{                    tree.remove(tree\_path)}

            \ControlFlowTok{for}\NormalTok{ tree\_path }\KeywordTok{in}\NormalTok{ tree:}
\NormalTok{                tree\_path.delete()}
\end{Highlighting}
\end{Shaded}

\hypertarget{_conclusions_3}{%
\section{Conclusions}\label{_conclusions_3}}

De part son pattern \texttt{MVT}, Django ne fait pas comme les autres
frameworks.

Pour commencer, nous allons nous concentrer sur la création d'un site ne
contenant qu'une seule application, même si en pratique le site
contiendra déjà plusieurs applications fournies pas django, comme nous
le verrons plus loin.

Don't make me think, or why I switched from JS SPAs to Ruby On Rails
\url{https://news.ycombinator.com/item?id=30206989\&utm_term=comment}

Pour prendre un exemple concret, nous allons créer un site permettant de
gérer des listes de souhaits, que nous appellerons \texttt{gwift} (pour
\texttt{GiFTs\ and\ WIshlisTs} :)).

La première chose à faire est de définir nos besoins du point de vue de
l'utilisateur, c'est-à-dire ce que nous souhaitons qu'un utilisateur
puisse faire avec l'application.

Ensuite, nous pourrons traduire ces besoins en fonctionnalités et
finalement effectuer le développement.

\hypertarget{_gwift}{%
\section{Gwift}\label{_gwift}}

\begin{figure}
\centering
\includegraphics{images/django/django-project-vs-apps-gwift.png}
\caption{Gwift}
\end{figure}

\hypertarget{_besoins_utilisateurs}{%
\section{Besoins utilisateurs}\label{_besoins_utilisateurs}}

Nous souhaitons développer un site où un utilisateur donné peut créer
une liste contenant des souhaits et où d'autres utilisateurs,
authentifiés ou non, peuvent choisir les souhaits à la réalisation
desquels ils souhaitent participer.

Il sera nécessaire de s'authentifier pour :

\begin{itemize}
\item
  Créer une liste associée à l'utilisateur en cours
\item
  Ajouter un nouvel élément à une liste
\end{itemize}

Il ne sera pas nécessaire de s'authentifier pour :

\begin{itemize}
\item
  Faire une promesse d'offre pour un élément appartenant à une liste,
  associée à un utilisateur.
\end{itemize}

L'utilisateur ayant créé une liste pourra envoyer un email directement
depuis le site aux personnes avec qui il souhaite partager sa liste, cet
email contenant un lien permettant d'accéder à cette liste.

A chaque souhait, on pourrait de manière facultative ajouter un prix.
Dans ce cas, le souhait pourrait aussi être subdivisé en plusieurs
parties, de manière à ce que plusieurs personnes puissent participer à
sa réalisation.

Un souhait pourrait aussi être réalisé plusieurs fois. Ceci revient à
dupliquer le souhait en question.

\hypertarget{_besoins_fonctionnels}{%
\section{Besoins fonctionnels}\label{_besoins_fonctionnels}}

\hypertarget{_gestion_des_utilisateurs}{%
\subsection{Gestion des utilisateurs}\label{_gestion_des_utilisateurs}}

Pour gérer les utilisateurs, nous allons faire en sorte de surcharger ce
que Django propose: par défaut, on a une la possibilité de gérer des
utilisateurs (identifiés par une adresse email, un nom, un prénom,
\ldots\hspace{0pt}) mais sans plus.

Ce qu'on peut souhaiter, c'est que l'utilisateur puisse s'authentifier
grâce à une plateforme connue (Facebook, Twitter, Google, etc.), et
qu'il puisse un minimum gérer son profil.

\hypertarget{_gestion_des_listes}{%
\subsection{Gestion des listes}\label{_gestion_des_listes}}

\hypertarget{_moduxe8lisation}{%
\subsubsection{Modèlisation}\label{_moduxe8lisation}}

Les données suivantes doivent être associées à une liste:

\begin{itemize}
\item
  un identifiant
\item
  un identifiant externe (un GUID, par exemple)
\item
  un nom
\item
  une description
\item
  le propriétaire, associé à l'utilisateur qui l'aura créée
\item
  une date de création
\item
  une date de modification
\end{itemize}

\hypertarget{_fonctionnalituxe9s}{%
\subsubsection{Fonctionnalités}\label{_fonctionnalituxe9s}}

\begin{itemize}
\item
  Un utilisateur authentifié doit pouvoir créer, modifier, désactiver et
  supprimer une liste dont il est le propriétaire
\item
  Un utilisateur doit pouvoir associer ou retirer des souhaits à une
  liste dont il est le propriétaire
\item
  Il faut pouvoir accéder à une liste, avec un utilisateur authentifier
  ou non, \textbf{via} son identifiant externe
\item
  Il faut pouvoir envoyer un email avec le lien vers la liste, contenant
  son identifiant externe
\item
  L'utilisateur doit pouvoir voir toutes les listes qui lui
  appartiennent
\end{itemize}

\hypertarget{_gestion_des_souhaits}{%
\subsection{Gestion des souhaits}\label{_gestion_des_souhaits}}

\hypertarget{_moduxe9lisation_2}{%
\subsubsection{Modélisation}\label{_moduxe9lisation_2}}

Les données suivantes peuvent être associées à un souhait:

\begin{itemize}
\item
  un identifiant
\item
  identifiant de la liste
\item
  un nom
\item
  une description
\item
  le propriétaire
\item
  une date de création
\item
  une date de modification
\item
  une image, afin de représenter l'objet ou l'idée
\item
  un nombre (1 par défaut)
\item
  un prix facultatif
\item
  un nombre de part, facultatif également, si un prix est fourni.
\end{itemize}

\hypertarget{_fonctionnalituxe9s_2}{%
\subsubsection{Fonctionnalités}\label{_fonctionnalituxe9s_2}}

\begin{itemize}
\item
  Un utilisateur authentifié doit pouvoir créer, modifier, désactiver et
  supprimer un souhait dont il est le propriétaire.
\item
  On ne peut créer un souhait sans liste associée
\item
  Il faut pouvoir fractionner un souhait uniquement si un prix est
  donné.
\item
  Il faut pouvoir accéder à un souhait, avec un utilisateur authentifié
  ou non.
\item
  Il faut pouvoir réaliser un souhait ou une partie seulement, avec un
  utilisateur authentifié ou non.
\item
  Un souhait en cours de réalisation et composé de différentes parts ne
  peut plus être modifié.
\item
  Un souhait en cours de réalisation ou réalisé ne peut plus être
  supprimé.
\item
  On peut modifier le nombre de fois qu'un souhait doit être réalisé
  dans la limite des réalisations déjà effectuées.
\end{itemize}

\hypertarget{_gestion_des_ruxe9alisations_de_souhaits}{%
\subsection{Gestion des réalisations de
souhaits}\label{_gestion_des_ruxe9alisations_de_souhaits}}

\hypertarget{_moduxe9lisation_3}{%
\subsubsection{Modélisation}\label{_moduxe9lisation_3}}

Les données suivantes peuvent être associées à une réalisation de
souhait:

\begin{itemize}
\item
  identifiant du souhait
\item
  identifiant de l'utilisateur si connu
\item
  identifiant de la personne si utilisateur non connu
\item
  un commentaire
\item
  une date de réalisation
\end{itemize}

\hypertarget{_fonctionnalituxe9s_3}{%
\subsubsection{Fonctionnalités}\label{_fonctionnalituxe9s_3}}

\begin{itemize}
\item
  L'utilisateur doit pouvoir voir si un souhait est réalisé, en partie
  ou non. Il doit également avoir un pourcentage de complétion sur la
  possibilité de réalisation de son souhait, entre 0\% et 100\%.
\item
  L'utilisateur doit pouvoir voir la ou les personnes ayant réalisé un
  souhait.
\item
  Il y a autant de réalisation que de parts de souhait réalisées ou de
  nombre de fois que le souhait est réalisé.
\end{itemize}

\hypertarget{_gestion_des_personnes_ruxe9alisants_les_souhaits_et_qui_ne_sont_pas_connues}{%
\subsection{Gestion des personnes réalisants les souhaits et qui ne sont
pas
connues}\label{_gestion_des_personnes_ruxe9alisants_les_souhaits_et_qui_ne_sont_pas_connues}}

\hypertarget{_moduxe9lisation_4}{%
\subsubsection{Modélisation}\label{_moduxe9lisation_4}}

Les données suivantes peuvent être associées à une personne réalisant un
souhait:

\begin{itemize}
\item
  un identifiant
\item
  un nom
\item
  une adresse email facultative
\end{itemize}

\hypertarget{_fonctionnalituxe9s_4}{%
\subsubsection{Fonctionnalités}\label{_fonctionnalituxe9s_4}}

Modélisation

L'ORM de Django permet de travailler uniquement avec une définition de
classes, et de faire en sorte que le lien avec la base de données soit
géré uniquement de manière indirecte, par Django lui-même. On peut
schématiser ce comportement par une classe = une table.

Comme on l'a vu dans la description des fonctionnalités, on va
\textbf{grosso modo} avoir besoin des éléments suivants:

\begin{itemize}
\item
  Des listes de souhaits
\item
  Des éléments qui composent ces listes
\item
  Des parts pouvant composer chacun de ces éléments
\item
  Des utilisateurs pour gérer tout ceci.
\end{itemize}

Nous proposons dans un premier temps d'éluder la gestion des
utilisateurs, et de simplement se concentrer sur les fonctionnalités
principales. Cela nous donne ceci:

\begin{enumerate}
\def\labelenumi{\alph{enumi}.}
\item
  code-block:: python

\begin{verbatim}
# wish/models.py
\end{verbatim}

\begin{verbatim}
from django.db import models
\end{verbatim}

\begin{verbatim}
class Wishlist(models.Model):
    pass
\end{verbatim}

\begin{verbatim}
class Item(models.Model):
    pass
\end{verbatim}

\begin{verbatim}
class Part(models.Model):
    pass
\end{verbatim}
\end{enumerate}

Les classes sont créées, mais vides. Entrons dans les détails.

Listes de souhaits

Comme déjà décrit précédemment, les listes de souhaits peuvent
s'apparenter simplement à un objet ayant un nom et une description. Pour
rappel, voici ce qui avait été défini dans les spécifications:

\begin{itemize}
\item
  un identifiant
\item
  un identifiant externe
\item
  un nom
\item
  une description
\item
  une date de création
\item
  une date de modification
\end{itemize}

Notre classe \texttt{Wishlist} peut être définie de la manière suivante:

\begin{enumerate}
\def\labelenumi{\alph{enumi}.}
\item
  code-block:: python

\begin{verbatim}
# wish/models.py
\end{verbatim}

\begin{verbatim}
class Wishlist(models.Model):
\end{verbatim}

\begin{verbatim}
name = models.CharField(max_length=255)
description = models.TextField()
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
external_id = models.UUIDField(unique=True, default=uuid.uuid4, editable=False)
\end{verbatim}
\end{enumerate}

Que peut-on constater?

\begin{itemize}
\item
  Que s'il n'est pas spécifié, un identifiant \texttt{id} sera
  automatiquement généré et accessible dans le modèle. Si vous souhaitez
  malgré tout spécifier que ce soit un champ en particulier qui devienne
  la clé primaire, il suffit de l'indiquer grâce à l'attribut
  \texttt{primary\_key=True}.
\item
  Que chaque type de champs (\texttt{DateTimeField}, \texttt{CharField},
  \texttt{UUIDField}, etc.) a ses propres paramètres d'initialisation.
  Il est intéressant de les apprendre ou de se référer à la
  documentation en cas de doute.
\end{itemize}

Au niveau de notre modélisation:

\begin{itemize}
\item
  La propriété \texttt{created\_at} est gérée automatiquement par Django
  grâce à l'attribut \texttt{auto\_now\_add}: de cette manière, lors
  d'un \textbf{ajout}, une valeur par défaut ("\textbf{maintenant}")
  sera attribuée à cette propriété.
\item
  La propriété \texttt{updated\_at} est également gérée automatique,
  cette fois grâce à l'attribut \texttt{auto\_now} initialisé à
  \texttt{True}: lors d'une \textbf{mise à jour}, la propriété se verra
  automatiquement assigner la valeur du moment présent. Cela ne permet
  évidemment pas de gérer un historique complet et ne nous dira pas
  \textbf{quels champs} ont été modifiés, mais cela nous conviendra dans
  un premier temps.
\item
  La propriété \texttt{external\_id} est de type \texttt{UUIDField}.
  Lorsqu'une nouvelle instance sera instanciée, cette propriété prendra
  la valeur générée par la fonction \texttt{uuid.uuid4()}. \textbf{A
  priori}, chacun des types de champs possède une propriété
  \texttt{default}, qui permet d'initialiser une valeur sur une nouvelle
  instance.
\end{itemize}

Souhaits

Nos souhaits ont besoin des propriétés suivantes:

\begin{itemize}
\item
  un identifiant
\item
  l'identifiant de la liste auquel le souhait est lié
\item
  un nom
\item
  une description
\item
  le propriétaire
\item
  une date de création
\item
  une date de modification
\item
  une image permettant de le représenter.
\item
  un nombre (1 par défaut)
\item
  un prix facultatif
\item
  un nombre de part facultatif, si un prix est fourni.
\end{itemize}

Après implémentation, cela ressemble à ceci:

\begin{enumerate}
\def\labelenumi{\alph{enumi}.}
\item
  code-block:: python

\begin{verbatim}
# wish/models.py
\end{verbatim}

\begin{verbatim}
class Wish(models.Model):
\end{verbatim}

\begin{verbatim}
wishlist = models.ForeignKey(Wishlist)
name = models.CharField(max_length=255)
description = models.TextField()
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
picture = models.ImageField()
numbers_available = models.IntegerField(default=1)
number_of_parts = models.IntegerField(null=True)
estimated_price = models.DecimalField(max_digits=19, decimal_places=2,
                                        null=True)
\end{verbatim}
\end{enumerate}

A nouveau, que peut-on constater ?

\begin{itemize}
\item
  Les clés étrangères sont gérées directement dans la déclaration du
  modèle. Un champ de type `ForeignKey
  \textless{}\url{https://docs.djangoproject.com/en/1.8/ref/models/fields/\#django.db.models.ForeignKey\%3E\%60_}
  permet de déclarer une relation 1-N entre deux classes. Dans la même
  veine, une relation 1-1 sera représentée par un champ de type
  `OneToOneField
  \textless{}\url{https://docs.djangoproject.com/en/1.8/topics/db/examples/one_to_one/\%3E\%60}\emph{,
  alors qu'une relation N-N utilisera un `ManyToManyField
  \textless{}\url{https://docs.djangoproject.com/en/1.8/topics/db/examples/many_to_many/\%3E\%60}}.
\item
  L'attribut \texttt{default} permet de spécifier une valeur initiale,
  utilisée lors de la construction de l'instance. Cet attribut peut
  également être une fonction.
\item
  Pour rendre un champ optionnel, il suffit de lui ajouter l'attribut
  \texttt{null=True}.
\item
  Comme cité ci-dessus, chaque champ possède des attributs spécifiques.
  Le champ \texttt{DecimalField} possède par exemple les attributs
  \texttt{max\_digits} et \texttt{decimal\_places}, qui nous permettra
  de représenter une valeur comprise entre 0 et plus d'un milliard (avec
  deux chiffres décimaux).
\item
  L'ajout d'un champ de type \texttt{ImageField} nécessite
  l'installation de \texttt{pillow} pour la gestion des images. Nous
  l'ajoutons donc à nos pré-requis, dans le fichier
  \texttt{requirements/base.txt}.
\end{itemize}

Parts

Les parts ont besoins des propriétés suivantes:

\begin{itemize}
\item
  un identifiant
\item
  identifiant du souhait
\item
  identifiant de l'utilisateur si connu
\item
  identifiant de la personne si utilisateur non connu
\item
  un commentaire
\item
  une date de réalisation
\end{itemize}

Elles constituent la dernière étape de notre modélisation et représente
la réalisation d'un souhait. Il y aura autant de part d'un souhait que
le nombre de souhait à réaliser fois le nombre de part.

Elles permettent à un utilisateur de participer au souhait émis par un
autre utilisateur. Pour les modéliser, une part est liée d'un côté à un
souhait, et d'autre part à un utilisateur. Cela nous donne ceci:

\begin{enumerate}
\def\labelenumi{\alph{enumi}.}
\item
  code-block:: python

\begin{verbatim}
from django.contrib.auth.models import User
\end{verbatim}

\begin{verbatim}
class WishPart(models.Model):
\end{verbatim}

\begin{verbatim}
wish = models.ForeignKey(Wish)
user = models.ForeignKey(User, null=True)
unknown_user = models.ForeignKey(UnknownUser, null=True)
comment = models.TextField(null=True, blank=True)
done_at = models.DateTimeField(auto_now_add=True)
\end{verbatim}
\end{enumerate}

La classe \texttt{User} référencée au début du snippet correspond à
l'utilisateur qui sera connecté. Ceci est géré par Django. Lorsqu'une
requête est effectuée et est transmise au serveur, cette information
sera disponible grâce à l'objet \texttt{request.user}, transmis à chaque
fonction ou \textbf{Class-based-view}. C'est un des avantages d'un
framework tout intégré: il vient \textbf{batteries-included} et beaucoup
de détails ne doivent pas être pris en compte. Pour le moment, nous nous
limiterons à ceci. Par la suite, nous verrons comment améliorer la
gestion des profils utilisateurs, comment y ajouter des informations et
comment gérer les cas particuliers.

La classe \texttt{UnknownUser} permet de représenter un utilisateur non
enregistré sur le site et est définie au point suivant.

Utilisateurs inconnus

\begin{enumerate}
\def\labelenumi{\alph{enumi}.}
\item
  todo:: je supprimerais pour que tous les utilisateurs soient gérés au
  même endroit.
\end{enumerate}

Pour chaque réalisation d'un souhait par quelqu'un, il est nécessaire de
sauver les données suivantes, même si l'utilisateur n'est pas enregistré
sur le site:

\begin{itemize}
\item
  un identifiant
\item
  un nom
\item
  une adresse email. Cette adresse email sera unique dans notre base de
  données, pour ne pas créer une nouvelle occurence si un même
  utilisateur participe à la réalisation de plusieurs souhaits.
\end{itemize}

Ceci nous donne après implémentation:

\begin{enumerate}
\def\labelenumi{\alph{enumi}.}
\item
  code-block:: python

\begin{verbatim}
class UnkownUser(models.Model):
\end{verbatim}

\begin{verbatim}
name = models.CharField(max_length=255)
email = models.CharField(email = models.CharField(max_length=255, unique=True)
\end{verbatim}
\end{enumerate}

\hypertarget{_tests_unitaires_2}{%
\section{Tests unitaires}\label{_tests_unitaires_2}}

\hypertarget{_pourquoi_sennuyer_uxe0_uxe9crire_des_tests}{%
\subsection{Pourquoi s'ennuyer à écrire des
tests?}\label{_pourquoi_sennuyer_uxe0_uxe9crire_des_tests}}

Traduit grossièrement depuis un article sur `https://medium.com
\textless{}\url{https://medium.com/javascript-scene/what-every-unit-test-needs-f6cd34d9836d\#.kfyvxyb21\%3E\%60_}:

\begin{verbatim}
Vos tests sont la première et la meilleure ligne de défense contre les défauts de programmation. Ils sont
\end{verbatim}

\begin{verbatim}
Les tests unitaires combinent de nombreuses fonctionnalités, qui en fait une arme secrète au service d'un développement réussi:
\end{verbatim}

\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
  Aide au design: écrire des tests avant d'écrire le code vous donnera
  une meilleure perspective sur le design à appliquer aux API.
\item
  Documentation (pour les développeurs): chaque description d'un test
\item
  Tester votre compréhension en tant que développeur:
\item
  Assurance qualité: des tests, 5.
\end{enumerate}

\hypertarget{_why_bother_with_test_discipline}{%
\subsection{Why Bother with Test
Discipline?}\label{_why_bother_with_test_discipline}}

Your tests are your first and best line of defense against software
defects. Your tests are more important than linting \& static analysis
(which can only find a subclass of errors, not problems with your actual
program logic). Tests are as important as the implementation itself (all
that matters is that the code meets the requirement --- how it's
implemented doesn't matter at all unless it's implemented poorly).

Unit tests combine many features that make them your secret weapon to
application success:

\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
  Design aid: Writing tests first gives you a clearer perspective on the
  ideal API design.
\item
  Feature documentation (for developers): Test descriptions enshrine in
  code every implemented feature requirement.
\item
  Test your developer understanding: Does the developer understand the
  problem enough to articulate in code all critical component
  requirements?
\item
  Quality Assurance: Manual QA is error prone. In my experience, it's
  impossible for a developer to remember all features that need testing
  after making a change to refactor, add new features, or remove
  features.
\item
  Continuous Delivery Aid: Automated QA affords the opportunity to
  automatically prevent broken builds from being deployed to production.
\end{enumerate}

Unit tests don't need to be twisted or manipulated to serve all of those
broad-ranging goals. Rather, it is in the essential nature of a unit
test to satisfy all of those needs. These benefits are all side-effects
of a well-written test suite with good coverage.

\hypertarget{_what_are_you_testing}{%
\subsection{What are you testing?}\label{_what_are_you_testing}}

\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
  What component aspect are you testing?
\item
  What should the feature do? What specific behavior requirement are you
  testing?
\end{enumerate}

\hypertarget{_couverture_de_code_2}{%
\subsection{Couverture de code}\label{_couverture_de_code_2}}

On a vu au chapitre 1 qu'il était possible d'obtenir une couverture de
code, c'est-à-dire un pourcentage.

\hypertarget{_comment_tester}{%
\subsection{Comment tester ?}\label{_comment_tester}}

Il y a deux manières d'écrire les tests: soit avant, soit après
l'implémentation. Oui, idéalement, les tests doivent être écrits à
l'avance. Entre nous, on ne va pas râler si vous faites l'inverse,
l'important étant que vous le fassiez. Une bonne métrique pour vérifier
l'avancement des tests est la couverture de code.

Pour l'exemple, nous allons écrire la fonction
\texttt{percentage\_of\_completion} sur la classe \texttt{Wish}, et nous
allons spécifier les résultats attendus avant même d'implémenter son
contenu. Prenons le cas où nous écrivons la méthode avant son test:

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{class}\NormalTok{ Wish(models.Model):}

\NormalTok{    [...]}

    \AttributeTok{@property}
    \KeywordTok{def}\NormalTok{ percentage\_of\_completion(}\VariableTok{self}\NormalTok{):}
        \CommentTok{"""}
\CommentTok{        Calcule le pourcentage de complétion pour un élément.}
\CommentTok{        """}
\NormalTok{        number\_of\_linked\_parts }\OperatorTok{=}\NormalTok{ WishPart.objects.}\BuiltInTok{filter}\NormalTok{(wish}\OperatorTok{=}\VariableTok{self}\NormalTok{).count()}
\NormalTok{        total }\OperatorTok{=} \VariableTok{self}\NormalTok{.number\_of\_parts }\OperatorTok{*} \VariableTok{self}\NormalTok{.numbers\_available}
\NormalTok{        percentage }\OperatorTok{=}\NormalTok{ (number\_of\_linked\_parts }\OperatorTok{/}\NormalTok{ total)}
        \ControlFlowTok{return}\NormalTok{ percentage }\OperatorTok{*} \DecValTok{100}
\end{Highlighting}
\end{Shaded}

Lancez maintenant la couverture de code. Vous obtiendrez ceci:

\begin{verbatim}
$ coverage run --source "." src/manage.py test wish
$ coverage report

Name                             Stmts   Miss Branch BrPart  Cover
------------------------------------------------------------------
src\gwift\__init__.py                0      0      0      0   100%
src\gwift\settings\__init__.py       4      0      0      0   100%
src\gwift\settings\base.py          14      0      0      0   100%
src\gwift\settings\dev.py            8      0      2      0   100%
src\manage.py                        6      0      2      1    88%
src\wish\__init__.py                 0      0      0      0   100%
src\wish\admin.py                    1      0      0      0   100%
src\wish\models.py                  36      5      0      0    88%
------------------------------------------------------------------
TOTAL                               69      5      4      1    93%
\end{verbatim}

Si vous générez le rapport HTML avec la commande \texttt{coverage\ html}
et que vous ouvrez le fichier
\texttt{coverage\_html\_report/src\_wish\_models\_py.html}, vous verrez
que les méthodes en rouge ne sont pas testées. \textbf{A contrario}, la
couverture de code atteignait \textbf{98\%} avant l'ajout de cette
nouvelle méthode.

Pour cela, on va utiliser un fichier \texttt{tests.py} dans notre
application \texttt{wish}. \textbf{A priori}, ce fichier est créé
automatiquement lorsque vous initialisez une nouvelle application.

\begin{Shaded}
\begin{Highlighting}[]
\ImportTok{from}\NormalTok{ django.test }\ImportTok{import}\NormalTok{ TestCase}

\KeywordTok{class}\NormalTok{ TestWishModel(TestCase):}
    \KeywordTok{def}\NormalTok{ test\_percentage\_of\_completion(}\VariableTok{self}\NormalTok{):}
        \CommentTok{"""}
\CommentTok{        Vérifie que le pourcentage de complétion d\textquotesingle{}un souhait}
\CommentTok{        est correctement calculé.}

\CommentTok{        Sur base d\textquotesingle{}un souhait, on crée quatre parts et on vérifie}
\CommentTok{        que les valeurs s\textquotesingle{}étalent correctement sur 25\%, 50\%, 75\% et 100\%.}
\CommentTok{        """}
\NormalTok{        wishlist }\OperatorTok{=}\NormalTok{ Wishlist(name}\OperatorTok{=}\StringTok{\textquotesingle{}Fake WishList\textquotesingle{}}\NormalTok{,}
\NormalTok{                            description}\OperatorTok{=}\StringTok{\textquotesingle{}This is a faked wishlist\textquotesingle{}}\NormalTok{)}
\NormalTok{        wishlist.save()}

\NormalTok{        wish }\OperatorTok{=}\NormalTok{ Wish(wishlist}\OperatorTok{=}\NormalTok{wishlist,}
\NormalTok{                    name}\OperatorTok{=}\StringTok{\textquotesingle{}Fake Wish\textquotesingle{}}\NormalTok{,}
\NormalTok{                    description}\OperatorTok{=}\StringTok{\textquotesingle{}This is a faked wish\textquotesingle{}}\NormalTok{,}
\NormalTok{                    number\_of\_parts}\OperatorTok{=}\DecValTok{4}\NormalTok{)}
\NormalTok{        wish.save()}

\NormalTok{        part1 }\OperatorTok{=}\NormalTok{ WishPart(wish}\OperatorTok{=}\NormalTok{wish, comment}\OperatorTok{=}\StringTok{\textquotesingle{}part1\textquotesingle{}}\NormalTok{)}
\NormalTok{        part1.save()}
        \VariableTok{self}\NormalTok{.assertEqual(}\DecValTok{25}\NormalTok{, wish.percentage\_of\_completion)}

\NormalTok{        part2 }\OperatorTok{=}\NormalTok{ WishPart(wish}\OperatorTok{=}\NormalTok{wish, comment}\OperatorTok{=}\StringTok{\textquotesingle{}part2\textquotesingle{}}\NormalTok{)}
\NormalTok{        part2.save()}
        \VariableTok{self}\NormalTok{.assertEqual(}\DecValTok{50}\NormalTok{, wish.percentage\_of\_completion)}

\NormalTok{        part3 }\OperatorTok{=}\NormalTok{ WishPart(wish}\OperatorTok{=}\NormalTok{wish, comment}\OperatorTok{=}\StringTok{\textquotesingle{}part3\textquotesingle{}}\NormalTok{)}
\NormalTok{        part3.save()}
        \VariableTok{self}\NormalTok{.assertEqual(}\DecValTok{75}\NormalTok{, wish.percentage\_of\_completion)}

\NormalTok{        part4 }\OperatorTok{=}\NormalTok{ WishPart(wish}\OperatorTok{=}\NormalTok{wish, comment}\OperatorTok{=}\StringTok{\textquotesingle{}part4\textquotesingle{}}\NormalTok{)}
\NormalTok{        part4.save()}
        \VariableTok{self}\NormalTok{.assertEqual(}\DecValTok{100}\NormalTok{, wish.percentage\_of\_completion)}
\end{Highlighting}
\end{Shaded}

L'attribut \texttt{@property} sur la méthode
\texttt{percentage\_of\_completion()} va nous permettre d'appeler
directement la méthode \texttt{percentage\_of\_completion()} comme s'il
s'agissait d'une propriété de la classe, au même titre que les champs
\texttt{number\_of\_parts} ou \texttt{numbers\_available}. Attention que
ce type de méthode contactera la base de données à chaque fois qu'elle
sera appelée. Il convient de ne pas surcharger ces méthodes de
connexions à la base: sur de petites applications, ce type de
comportement a très peu d'impacts, mais ce n'est plus le cas sur de
grosses applications ou sur des méthodes fréquemment appelées. Il
convient alors de passer par un mécanisme de \textbf{cache}, que nous
aborderons plus loin.

En relançant la couverture de code, on voit à présent que nous arrivons
à 99\%:

\begin{verbatim}
$ coverage run --source='.' src/manage.py test wish; coverage report; coverage html;
.
----------------------------------------------------------------------
Ran 1 test in 0.006s

OK
Creating test database for alias 'default'...
Destroying test database for alias 'default'...
Name                             Stmts   Miss Branch BrPart  Cover
------------------------------------------------------------------
src\gwift\__init__.py                0      0      0      0   100%
src\gwift\settings\__init__.py       4      0      0      0   100%
src\gwift\settings\base.py          14      0      0      0   100%
src\gwift\settings\dev.py            8      0      2      0   100%
src\manage.py                        6      0      2      1    88%
src\wish\__init__.py                 0      0      0      0   100%
src\wish\admin.py                    1      0      0      0   100%
src\wish\models.py                  34      0      0      0   100%
src\wish\tests.py                   20      0      0      0   100%
------------------------------------------------------------------
TOTAL                               87      0      4      1    99%
\end{verbatim}

En continuant de cette manière (ie. Ecriture du code et des tests,
vérification de la couverture de code), on se fixe un objectif idéal dès
le début du projet. En prenant un développement en cours de route,
fixez-vous comme objectif de ne jamais faire baisser la couverture de
code.

\hypertarget{_quelques_liens_utiles}{%
\subsection{Quelques liens utiles}\label{_quelques_liens_utiles}}

\begin{itemize}
\item
  `Django factory boy
  \textless{}\url{https://github.com/rbarrois/django-factory_boy/tree/v1.0.0\%3E\%60_}
\end{itemize}

\hypertarget{_refactoring}{%
\section{Refactoring}\label{_refactoring}}

On constate que plusieurs classes possèdent les mêmes propriétés
\texttt{created\_at} et \texttt{updated\_at}, initialisées aux mêmes
valeurs. Pour gagner en cohérence, nous allons créer une classe dans
laquelle nous définirons ces deux champs, et nous ferons en sorte que
les classes \texttt{Wishlist}, \texttt{Item} et \texttt{Part} en
héritent. Django gère trois sortes d'héritage:

\begin{itemize}
\item
  L'héritage par classe abstraite
\item
  L'héritage classique
\item
  L'héritage par classe proxy.
\end{itemize}

\hypertarget{_classe_abstraite}{%
\subsection{Classe abstraite}\label{_classe_abstraite}}

L'héritage par classe abstraite consiste à déterminer une classe mère
qui ne sera jamais instanciée. C'est utile pour définir des champs qui
se répèteront dans plusieurs autres classes et surtout pour respecter le
principe de DRY. Comme la classe mère ne sera jamais instanciée, ces
champs seront en fait dupliqués physiquement, et traduits en SQL, dans
chacune des classes filles.

\begin{Shaded}
\begin{Highlighting}[]
\CommentTok{\# wish/models.py}

\KeywordTok{class}\NormalTok{ AbstractModel(models.Model):}
    \KeywordTok{class}\NormalTok{ Meta:}
\NormalTok{        abstract }\OperatorTok{=} \VariableTok{True}

\NormalTok{    created\_at }\OperatorTok{=}\NormalTok{ models.DateTimeField(auto\_now\_add}\OperatorTok{=}\VariableTok{True}\NormalTok{)}
\NormalTok{    updated\_at }\OperatorTok{=}\NormalTok{ models.DateTimeField(auto\_now}\OperatorTok{=}\VariableTok{True}\NormalTok{)}


\KeywordTok{class}\NormalTok{ Wishlist(AbstractModel):}
    \ControlFlowTok{pass}


\KeywordTok{class}\NormalTok{ Item(AbstractModel):}
    \ControlFlowTok{pass}


\KeywordTok{class}\NormalTok{ Part(AbstractModel):}
    \ControlFlowTok{pass}
\end{Highlighting}
\end{Shaded}

En traduisant ceci en SQL, on aura en fait trois tables, chacune
reprenant les champs \texttt{created\_at} et \texttt{updated\_at}, ainsi
que son propre identifiant:

\begin{Shaded}
\begin{Highlighting}[]
\CommentTok{{-}{-}$ python manage.py sql wish}
\ControlFlowTok{BEGIN}\NormalTok{;}
\KeywordTok{CREATE} \KeywordTok{TABLE} \OtherTok{"wish\_wishlist"}\NormalTok{ (}
    \OtherTok{"id"} \DataTypeTok{integer} \KeywordTok{NOT} \KeywordTok{NULL} \KeywordTok{PRIMARY} \KeywordTok{KEY}\NormalTok{ AUTOINCREMENT,}
    \OtherTok{"created\_at"}\NormalTok{ datetime }\KeywordTok{NOT} \KeywordTok{NULL}\NormalTok{,}
    \OtherTok{"updated\_at"}\NormalTok{ datetime }\KeywordTok{NOT} \KeywordTok{NULL}
\NormalTok{)}
\NormalTok{;}
\KeywordTok{CREATE} \KeywordTok{TABLE} \OtherTok{"wish\_item"}\NormalTok{ (}
    \OtherTok{"id"} \DataTypeTok{integer} \KeywordTok{NOT} \KeywordTok{NULL} \KeywordTok{PRIMARY} \KeywordTok{KEY}\NormalTok{ AUTOINCREMENT,}
    \OtherTok{"created\_at"}\NormalTok{ datetime }\KeywordTok{NOT} \KeywordTok{NULL}\NormalTok{,}
    \OtherTok{"updated\_at"}\NormalTok{ datetime }\KeywordTok{NOT} \KeywordTok{NULL}
\NormalTok{)}
\NormalTok{;}
\KeywordTok{CREATE} \KeywordTok{TABLE} \OtherTok{"wish\_part"}\NormalTok{ (}
    \OtherTok{"id"} \DataTypeTok{integer} \KeywordTok{NOT} \KeywordTok{NULL} \KeywordTok{PRIMARY} \KeywordTok{KEY}\NormalTok{ AUTOINCREMENT,}
    \OtherTok{"created\_at"}\NormalTok{ datetime }\KeywordTok{NOT} \KeywordTok{NULL}\NormalTok{,}
    \OtherTok{"updated\_at"}\NormalTok{ datetime }\KeywordTok{NOT} \KeywordTok{NULL}
\NormalTok{)}
\NormalTok{;}

\KeywordTok{COMMIT}\NormalTok{;}
\end{Highlighting}
\end{Shaded}

\hypertarget{_huxe9ritage_classique}{%
\subsection{Héritage classique}\label{_huxe9ritage_classique}}

L'héritage classique est généralement déconseillé, car il peut
introduire très rapidement un problème de performances: en reprenant
l'exemple introduit avec l'héritage par classe abstraite, et en omettant
l'attribut \texttt{abstract\ =\ True}, on se retrouvera en fait avec
quatre tables SQL:

\begin{itemize}
\item
  Une table \texttt{AbstractModel}, qui reprend les deux champs
  \texttt{created\_at} et \texttt{updated\_at}
\item
  Une table \texttt{Wishlist}
\item
  Une table \texttt{Item}
\item
  Une table \texttt{Part}.
\end{itemize}

A nouveau, en analysant la sortie SQL de cette modélisation, on obtient
ceci:

\begin{Shaded}
\begin{Highlighting}[]
\CommentTok{{-}{-}$ python manage.py sql wish}

\ControlFlowTok{BEGIN}\NormalTok{;}
\KeywordTok{CREATE} \KeywordTok{TABLE} \OtherTok{"wish\_abstractmodel"}\NormalTok{ (}
    \OtherTok{"id"} \DataTypeTok{integer} \KeywordTok{NOT} \KeywordTok{NULL} \KeywordTok{PRIMARY} \KeywordTok{KEY}\NormalTok{ AUTOINCREMENT,}
    \OtherTok{"created\_at"}\NormalTok{ datetime }\KeywordTok{NOT} \KeywordTok{NULL}\NormalTok{,}
    \OtherTok{"updated\_at"}\NormalTok{ datetime }\KeywordTok{NOT} \KeywordTok{NULL}
\NormalTok{)}
\NormalTok{;}
\KeywordTok{CREATE} \KeywordTok{TABLE} \OtherTok{"wish\_wishlist"}\NormalTok{ (}
    \OtherTok{"abstractmodel\_ptr\_id"} \DataTypeTok{integer} \KeywordTok{NOT} \KeywordTok{NULL} \KeywordTok{PRIMARY} \KeywordTok{KEY} \KeywordTok{REFERENCES} \OtherTok{"wish\_abstractmodel"}\NormalTok{ (}\OtherTok{"id"}\NormalTok{)}
\NormalTok{)}
\NormalTok{;}
\KeywordTok{CREATE} \KeywordTok{TABLE} \OtherTok{"wish\_item"}\NormalTok{ (}
    \OtherTok{"abstractmodel\_ptr\_id"} \DataTypeTok{integer} \KeywordTok{NOT} \KeywordTok{NULL} \KeywordTok{PRIMARY} \KeywordTok{KEY} \KeywordTok{REFERENCES} \OtherTok{"wish\_abstractmodel"}\NormalTok{ (}\OtherTok{"id"}\NormalTok{)}
\NormalTok{)}
\NormalTok{;}
\KeywordTok{CREATE} \KeywordTok{TABLE} \OtherTok{"wish\_part"}\NormalTok{ (}
    \OtherTok{"abstractmodel\_ptr\_id"} \DataTypeTok{integer} \KeywordTok{NOT} \KeywordTok{NULL} \KeywordTok{PRIMARY} \KeywordTok{KEY} \KeywordTok{REFERENCES} \OtherTok{"wish\_abstractmodel"}\NormalTok{ (}\OtherTok{"id"}\NormalTok{)}
\NormalTok{)}
\NormalTok{;}

\KeywordTok{COMMIT}\NormalTok{;}
\end{Highlighting}
\end{Shaded}

Le problème est que les identifiants seront définis et incrémentés au
niveau de la table mère. Pour obtenir les informations héritées, nous
seront obligés de faire une jointure. En gros, impossible d'obtenir les
données complètes pour l'une des classes de notre travail de base sans
effectuer un \textbf{join} sur la classe mère.

Dans ce sens, cela va encore\ldots\hspace{0pt} Mais imaginez que vous
définissiez une classe \texttt{Wishlist}, de laquelle héritent les
classes \texttt{ChristmasWishlist} et \texttt{EasterWishlist}: pour
obtenir la liste complètes des listes de souhaits, il vous faudra faire
une jointure \textbf{externe} sur chacune des tables possibles, avant
même d'avoir commencé à remplir vos données. Il est parfois nécessaire
de passer par cette modélisation, mais en étant conscient des risques
inhérents.

\hypertarget{_classe_proxy}{%
\subsection{Classe proxy}\label{_classe_proxy}}

Lorsqu'on définit une classe de type \textbf{proxy}, on fait en sorte
que cette nouvelle classe ne définisse aucun nouveau champ sur la classe
mère. Cela ne change dès lors rien à la traduction du modèle de données
en SQL, puisque la classe mère sera traduite par une table, et la classe
fille ira récupérer les mêmes informations dans la même table: elle ne
fera qu'ajouter ou modifier un comportement dynamiquement, sans ajouter
d'emplacements de stockage supplémentaires.

Nous pourrions ainsi définir les classes suivantes:

\begin{Shaded}
\begin{Highlighting}[]
\CommentTok{\# wish/models.py}

\KeywordTok{class}\NormalTok{ Wishlist(models.Model):}
\NormalTok{    name }\OperatorTok{=}\NormalTok{ models.CharField(max\_length}\OperatorTok{=}\DecValTok{255}\NormalTok{)}
\NormalTok{    description }\OperatorTok{=}\NormalTok{ models.CharField(max\_length}\OperatorTok{=}\DecValTok{2000}\NormalTok{)}
\NormalTok{    expiration\_date }\OperatorTok{=}\NormalTok{ models.DateField()}

    \AttributeTok{@staticmethod}
    \KeywordTok{def}\NormalTok{ create(}\VariableTok{self}\NormalTok{, name, description, expiration\_date}\OperatorTok{=}\VariableTok{None}\NormalTok{):}
\NormalTok{        wishlist }\OperatorTok{=}\NormalTok{ Wishlist()}
\NormalTok{        wishlist.name }\OperatorTok{=}\NormalTok{ name}
\NormalTok{        wishlist.description }\OperatorTok{=}\NormalTok{ description}
\NormalTok{        wishlist.expiration\_date }\OperatorTok{=}\NormalTok{ expiration\_date}
\NormalTok{        wishlist.save()}
        \ControlFlowTok{return}\NormalTok{ wishlist}

\KeywordTok{class}\NormalTok{ ChristmasWishlist(Wishlist):}
    \KeywordTok{class}\NormalTok{ Meta:}
\NormalTok{        proxy }\OperatorTok{=} \VariableTok{True}

    \AttributeTok{@staticmethod}
    \KeywordTok{def}\NormalTok{ create(}\VariableTok{self}\NormalTok{, name, description):}
\NormalTok{        christmas }\OperatorTok{=}\NormalTok{ datetime(current\_year, }\DecValTok{12}\NormalTok{, }\DecValTok{31}\NormalTok{)}
\NormalTok{        w }\OperatorTok{=}\NormalTok{ Wishlist.create(name, description, christmas)}
\NormalTok{        w.save()}


\KeywordTok{class}\NormalTok{ EasterWishlist(Wishlist):}
    \KeywordTok{class}\NormalTok{ Meta:}
\NormalTok{        proxy }\OperatorTok{=} \VariableTok{True}

    \AttributeTok{@staticmethod}
    \KeywordTok{def}\NormalTok{ create(}\VariableTok{self}\NormalTok{, name, description):}
\NormalTok{        expiration\_date }\OperatorTok{=}\NormalTok{ datetime(current\_year, }\DecValTok{4}\NormalTok{, }\DecValTok{1}\NormalTok{)}
\NormalTok{        w }\OperatorTok{=}\NormalTok{ Wishlist.create(name, description, expiration\_date)}
\NormalTok{        w.save()}
\end{Highlighting}
\end{Shaded}

Gestion des utilisateurs

Dans les spécifications, nous souhaitions pouvoir associer un
utilisateur à une liste (\textbf{le propriétaire}) et un utilisateur à
une part (\textbf{le donateur}). Par défaut, Django offre une gestion
simplifiée des utilisateurs (pas de connexion LDAP, pas de double
authentification, \ldots\hspace{0pt}): juste un utilisateur et un mot de
passe. Pour y accéder, un paramètre par défaut est défini dans votre
fichier de settings: \texttt{AUTH\_USER\_MODEL}.

\hypertarget{_khana}{%
\section{Khana}\label{_khana}}

Khana est une application de suivi d'apprentissage pour des élèves ou
étudiants. Nous voulons pouvoir:

\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
  Lister les élèves
\item
  Faire des listes de présence pour les élèves
\item
  Pouvoir planifier ses cours
\item
  Pouvoir suivre l'apprentissage des élèves, les liens qu'ils ont entre
  les éléments à apprendre:
\item
  pour écrire une phrase, il faut pouvoir écrire des mots, connaître la
  grammaire, et connaître la conjugaison
\item
  pour écrire des mots, il faut savoir écrire des lettres
\item
  \ldots\hspace{0pt}
\end{enumerate}

Plusieurs professeurs s'occupent d'une même classe; il faut pouvoir
écrire des notes, envoyer des messages aux autres professeurs, etc.

Il faut également pouvoir définir des dates de contrôle, voir combien de
semaines il reste pour s'assurer d'avoir vu toute la matiètre.

Et pouvoir encoder les points des contrôles.

\begin{figure}
\centering
\includegraphics{images/django/django-project-vs-apps-khana.png}
\caption{Khana}
\end{figure}

Unresolved directive in part-5-go-live/\_index.adoc -
include::legacy/\_main.adoc{[}{]}

\hypertarget{_glossaire}{%
\section{Glossaire}\label{_glossaire}}

\begin{description}
\item[http]
\emph{HyperText Transfer Protocol}, ou plus généralement le protocole
utilisé (et détourné) pour tout ce qui touche au \textbf{World Wide
Web}. Il existe beaucoup d'autres protocoles d'échange de données, comme
\href{https://fr.wikipedia.org/wiki/Gopher}{Gopher},
\href{https://fr.wikipedia.org/wiki/File_Transfer_Protocol}{FTP} ou
\href{https://fr.wikipedia.org/wiki/Simple_Mail_Transfer_Protocol}{SMTP}.
\item[IaaS]
\emph{Infrastructure as a Service}, où un tiers vous fournit des
machines (généralement virtuelles) que vous devrez ensuite gérer en bon
père de famille. L'IaaS propose souvent une API, qui vous permet
d'intégrer la durée de vie de chaque machine dans vos flux - en créant,
augmentant, détruisant une machine lorsque cela s'avère nécessaire.
\item[MVC]
Le modèle \emph{Model-View-Controler} est un patron de conception
autorisant un faible couplage entre la gestion des données (le
\emph{Modèle}), l'affichage et le traitement de celles (la \emph{Vue})
et la glue entre ces deux composants (au travers du \emph{Contrôleur}).
\href{https://en.wikipedia.org/wiki/Model\%E2\%80\%93view\%E2\%80\%93controller}{Wikipédia}
\item[ORM]
\emph{Object Relational Mapper}, où une instance est directement (ou à
proximité) liée à un mode de persistance de données.
\item[PaaS]
\emph{Platform as a Service}, qui consiste à proposer les composants
d'une plateforme (Redis, PostgreSQL, \ldots\hspace{0pt}) en libre
service et disponibles à la demande (quoiqu'après avoir communiqué son
numéro de carte de crédit\ldots\hspace{0pt}).
\item[POO]
La \emph{Programmation Orientée Objet} est un paradigme de programmation
informatique. Elle consiste en la définition et l'interaction de briques
logicielles appelées objets ; un objet représente un concept, une idée
ou toute entité du monde physique, comme une voiture, une personne ou
encore une page d'un livre. Il possède une structure interne et un
comportement, et il sait interagir avec ses pairs. Il s'agit donc de
représenter ces objets et leurs relations ; l'interaction entre les
objets via leurs relations permet de concevoir et réaliser les
fonctionnalités attendues, de mieux résoudre le ou les problèmes. Dès
lors, l'étape de modélisation revêt une importance majeure et nécessaire
pour la POO. C'est elle qui permet de transcrire les éléments du réel
sous forme virtuelle.
\href{https://fr.wikipedia.org/wiki/Programmation_orient\%C3\%A9e_objet}{Wikipédia}
\item[S3]
Amazon \emph{Simple Storage Service} consiste en un système
d'hébergement de fichiers, quels qu'ils soient. Il peut s'agir de
fichiers de logs, de données applications, de fichiers média envoyés par
vos utilisateurs, de vidéos et images ou de données de sauvegardes.
\end{description}

\textbf{\url{https://aws.amazon.com/fr/s3/}.}

\includegraphics{images/amazon-s3-arch.png}

\hypertarget{_bibliographie}{%
\section{Bibliographie}\label{_bibliographie}}

bibliography::{[}{]}

\end{document}
