Code review and rewriting #67

Closed
Sulley wants to merge 2 commits from code-review into master
27 changed files with 1817 additions and 2391 deletions

View File

@ -1,6 +1,7 @@
\chapter{Administration}
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. C'est faux.
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.
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.
@ -22,19 +23,16 @@ 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
\item Le modèle de données
\item Les validateurs
\item Les formulaires
\item Les widgets
\end{enumerate}
\section{Le modèle de données}
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.
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:
@ -147,7 +145,6 @@ class Meta:
\end{minted}
\begin{enumerate}
\item
Le titre:
@ -155,9 +152,7 @@ class Meta:
\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}.
Soit en ajoutant l'assignation suivante dans le fichier \texttt{urls.py} : \texttt{admin.site.site\_header\ =\ "SuperBook\ Secret\ Area}.
\end{itemize}
\item
Prefetch
@ -167,23 +162,16 @@ class Meta:
\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.
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.
\subsection{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)
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)
\subsection{L'affichage}
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 :
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}.}

View File

@ -4,15 +4,13 @@
\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.
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:
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{minted}{python}
# models.py
@ -89,17 +87,13 @@ touffue. Il convient de:
\begin{enumerate}
\item
Configurer les sérialiseurs, càd. les champs que nous souhaitons
exposer au travers de l'API,
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,
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.
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.
Et finalement ajouter quelques paramètres au niveau de notre application.
\end{enumerate}
\subsection{Serialiseurs}

View File

@ -24,7 +24,6 @@ Les principes SOLID, introduit par Robert C. Martin dans les années 2000 pour o
\end{enumerate}
Des équivalents à ces directives existent au niveau des composants, puis au niveau architectural:
\begin{enumerate}
\item
Reuse/release équivalence principle,
@ -40,14 +39,12 @@ Des équivalents à ces directives existent au niveau des composants, puis au ni
\subsection{Single Responsility Principle}
Le principe de responsabilité unique conseille de disposer de concepts ou domaines d'activité qui ne s'occupent chacun que d'une et une seule
chose.
Le principe de responsabilité unique conseille de disposer de concepts ou domaines d'activité qui ne s'occupent chacun que d'une et une seule chose.
Ceci rejoint (un peu) la \href{https://en.wikipedia.org/wiki/Unix_philosophy}{Philosophie Unix}, documentée par Doug McIlroy et qui demande de "\emph{faire une seule chose, mais de le faire bien}" \cite{unix_philosophy}.
Selon ce principe, une classe ou un élément de programmation ne doit donc pas avoir plus d'une seule raison de changer.
Plutôt que de centraliser le maximum de code à un seul endroit ou dans une seule classe par convenance ou commodité \footnote{Aussi appelé
\emph{God-Like object}}, le principe de responsabilité unique suggère que chaque classe soit responsable d'un et un seul concept.
Plutôt que de centraliser le maximum de code à un seul endroit ou dans une seule classe par convenance ou commodité \footnote{Aussi appelé \emph{God-Like object}}, le principe de responsabilité unique suggère que chaque classe soit responsable d'un et un seul concept.
Une manière de voir les choses consiste à différencier les acteurs ou les intervenants: imaginez disposer d'une classe représentant des données de membres du personnel; ces données pourraient être demandées par trois acteurs:
@ -342,7 +339,8 @@ Petit exemple pratique: si nous définissons une méthode \texttt{make\_some\_no
\end{listing}
Le principe de substitution de Liskov suggère qu'une classe doit toujours pouvoir être considérée comme une instance de sa classe parente, et \textbf{doit pouvoir s'y substituer}.
Dans notre exemple, cela signifie que nous pourrons tout à fait accepter qu'un lion se comporte comme un canard et adore manger des plantes, insectes, graines, algues et du poisson. Miam !
Dans notre exemple, cela signifie que nous pourrons tout à fait accepter qu'un lion se comporte comme un canard et adore manger des plantes, insectes, graines, algues et du poisson.
Miam !
Nous vous laissons tester la structure ci-dessus en glissant une antilope dans la boite à goûter du lion, ce qui nous donnera quelques trucs bizarres (et un lion atteint de botulisme).
Pour revenir à nos exemples de rendus de documents, nous aurions pu faire hériter notre \texttt{MarkdownRenderer} de la classe \texttt{XmlRenderer}:
@ -396,7 +394,7 @@ Si nous décidons à un moment d'ajouter une méthode d'entête au niveau de not
import markdown
return markdown.markdown(document.content)
\end{minted}
\caption{... et il a mal à l'entête}
\caption{\ldots~ et il a mal à l'entête}
\end{listing}
Le code ci-dessus ne porte pas à conséquence \footnote{Pas immédiatement, en tout cas...}, mais dès que nous invoquerons la méthode \texttt{header()} sur une instance de type \texttt{MarkdownRenderer}, nous obtiendrons un bloc de déclaration XML (\texttt{\textless{}?xml\ version\ =\ "1.0"?\textgreater{}}) pour un fichier Markdown, ce qui n'aura aucun sens.
@ -690,14 +688,12 @@ Pour créer un nouveau \emph{middleware}, il nous suffirait d'implémenter de no
Dans d'autres projets écrits en Python, ce type de mécanisme peut être implémenté relativement facilement en utilisant les modules \href{https://docs.python.org/3/library/importlib.html}{importlib} et la fonction \texttt{getattr}.
Un autre exemple concerne les bases de données: pour garder un maximum de flexibilité, Django ajoute une couche d'abstraction en permettant de
spécifier le moteur de base de données que vous souhaiteriez utiliser, qu'il s'agisse d'SQLite, MSSQL, Oracle, PostgreSQL ou MySQL/MariaDB \footnote{\url{http://howfuckedismydatabase.com/}}.
Un autre exemple concerne les bases de données: pour garder un maximum de flexibilité, Django ajoute une couche d'abstraction en permettant de spécifier le moteur de base de données que vous souhaiteriez utiliser, qu'il s'agisse d'SQLite, MSSQL, Oracle, PostgreSQL ou MySQL/MariaDB \footnote{\url{http://howfuckedismydatabase.com/}}.
D'un point de vue architectural, nous ne devons pas nous soucier de la manière dont les données sont stockées, s'il s'agit d'un disque magnétique, de mémoire vive, ... en fait, on ne devrait même pas savoir s'il y a un disque du tout.
D'un point de vue architectural, nous ne devons pas nous soucier de la manière dont les données sont stockées, s'il s'agit d'un disque magnétique, de mémoire vive, \ldots~ en fait, on ne devrait même pas savoir s'il y a un disque du tout.
Et Django le fait très bien pour nous.
En termes architecturaux, ce principe autorise une définition des frontières, et en permettant une séparation claire en inversant le flux
de dépendances et en faisant en sorte que les règles métiers n'aient aucune connaissance des interfaces graphiques qui les exploitent ou desmoteurs de bases de données qui les stockent.
En termes architecturaux, ce principe autorise une définition des frontières, et en permettant une séparation claire en inversant le flux de dépendances et en faisant en sorte que les règles métiers n'aient aucune connaissance des interfaces graphiques qui les exploitent ou desmoteurs de bases de données qui les stockent.
Ceci autorise une forme d'immunité entre les composants.
\section{Composants}
@ -750,7 +746,7 @@ Il est nécessaire de projeter la capacité d'adaptation, en minimisant la maint
Un des problèmes est qu'à la première demande, l'architecture pourrait avoir pris une mauvaise direction, sans aucune malléabilité.
Une bonne architecture va rendre le système facile à lire, facile à développer, facile à maintenir et facile à déployer, l'objectif ultime étant de minimiser le coût de maintenance et de maximiser la productivité des développeurs.
Un des autres objectifs d'une bonne architecture consiste à se garder le plus d'options possibles, et à se concentrer sur les détails (le type de base de données, la conception concrète, ...) le plus tard possible, tout en conservant la politique principale en ligne de mire.
Un des autres objectifs d'une bonne architecture consiste à se garder le plus d'options possibles, et à se concentrer sur les détails (le type de base de données, la conception concrète, \ldots) le plus tard possible, tout en conservant la politique principale en ligne de mire.
Ceci permet de délayer les choix techniques à «~plus tard~», ce qui permet également de concrétiser ces choix en ayant le plus d'informations possibles \cite[pp.137-141]{clean_architecture}
Derrière une bonne architecture, il y a aussi un investissement quant aux ressources qui seront nécessaires à faire évoluer l'application: ne
@ -786,37 +782,25 @@ yet still not know how the system is delivered.
--- Robert C. Martin Clean Architecture
\end{quote}
Le point soulevé ci-dessous est qu'un framework n'est qu'un outil, et
pas une obligation de structuration. L'idée est que le framework doit se
conformer à la définition de l'application, et non l'inverse. Dans le
cadre de l'utilisation de Django, c'est un point critique à prendre en
considération: une fois que vous aurez fait ce choix, vous aurez
extrêmement difficile à faire machine arrière:
Le point soulevé ci-dessous est qu'un framework n'est qu'un outil, et pas une obligation de structuration.
L'idée est que le framework doit se conformer à la définition de l'application, et non l'inverse.
Dans le cadre de l'utilisation de Django, c'est un point critique à prendre en considération: une fois que vous aurez fait ce choix, vous aurez extrêmement difficile à faire machine arrière:
\begin{itemize}
\item
Votre modèle métier sera largement couplé avec le type de base de
données (relationnelle, indépendamment
Votre modèle métier sera largement couplé avec le type de base de données (relationnelle, indépendamment
\item
Votre couche de présentation sera surtout disponible au travers d'un
navigateur
Votre couche de présentation sera surtout disponible au travers d'un navigateur
\item
Les droits d'accès et permissions seront en grosse partie gérés par le
frameworks
Les droits d'accès et permissions seront en grosse partie gérés par le frameworks
\item
La sécurité dépendra de votre habilité à suivre les versions
\item
Et les fonctionnalités complémentaires (que vous n'aurez pas voulu/eu
le temps de développer) dépendront de la bonne volonté de la
communauté
Et les fonctionnalités complémentaires (que vous n'aurez pas voulu/eu le temps de développer) dépendront de la bonne volonté de la communauté
\end{itemize}
Le point à comprendre ici n'est pas que "Django, c'est mal", mais qu'une
fois que vous aurez défini la politique, les règles métiers, les données
critiques et entités, et que vous aurez fait le choix de développer en
âme et conscience votre nouvelle création en utilisant Django, vous
serez bon gré mal gré, contraint de continuer avec. Cette décision ne
sera pas irrévocable, mais difficile à contourner.
Le point à comprendre ici n'est pas que "Django, c'est mal", mais qu'une fois que vous aurez défini la politique, les règles métiers, les données critiques et entités, et que vous aurez fait le choix de développer en âme et conscience votre nouvelle création en utilisant Django, vous serez bon gré mal gré, contraint de continuer avec.
Cette décision ne sera pas irrévocable, mais difficile à contourner.
\begin{quote}
At some point in their history most DevOps organizations were hobbled by
@ -832,11 +816,7 @@ to survice, but also to thrise and win in the marketplace.
\cite[182]{devops_handbook}
\end{quote}
Ceci dit, Django compense ses contraintes en proposant énormément de
flexibilité et de fonctionnalités \textbf{out-of-the-box}, c'est-à-dire
que vous pourrez sans doute avancer vite et bien jusqu'à un point de
rupture, puis revoir la conception et réinvestir à ce moment-là, mais en
toute connaissance de cause.
Ceci dit, Django compense ses contraintes en proposant énormément de flexibilité et de fonctionnalités \textbf{out-of-the-box}, c'est-à-dire que vous pourrez sans doute avancer vite et bien jusqu'à un point de rupture, puis revoir la conception et réinvestir à ce moment-là, mais en toute connaissance de cause.
\begin{quote}
When any of the external parts of the system become obsolete, such as
@ -846,30 +826,25 @@ elements with a minimum of fuss.
--- Robert C. Martin Clean Architecture
\end{quote}
Avec Django, la difficulté à se passer du framework va consister à
basculer vers «~autre chose~» et a remplacer chacune des tentacules qui
aura pousser partout dans l'application.
Avec Django, la difficulté à se passer du framework va consister à basculer vers «~autre chose~» et a remplacer chacune des tentacules qui aura pousser partout dans l'application.
A noter que les services et les «~architectures orientées services~» ne
sont jamais qu'une définition d'implémentation des frontières, dans la
mesure où un service n'est jamais qu'une fonction appelée au travers
d'un protocole (rest, soap, \ldots\hspace{0pt}). Une application
monolotihique sera tout aussi fonctionnelle qu'une application découpée
en microservices. \cite[p. 243]{clean_architecture}
A noter que les services et les «~architectures orientées services~» ne sont jamais qu'une définition d'implémentation des frontières, dans la mesure où un service n'est jamais qu'une fonction appelée au travers d'un protocole (rest, soap, \ldots\hspace{0pt}).
Une application monolotihique sera tout aussi fonctionnelle qu'une application découpée en microservices. \cite[p. 243]{clean_architecture}
\section{Inversion de dépendances}
Dans la partie SOLID, nous avons évoqué plusieurs principes de
développement. Django est un framework qui évolue, et qui a pu présenter
certains problèmes liés à l'un de ces principes.
Dans la partie SOLID, nous avons évoqué plusieurs principes de développement.
Django est un framework qui évolue, et qui a pu présenter certains problèmes liés à l'un de ces principes.
Les \href{https://docs.djangoproject.com/en/2.0/releases/2.0/}{Releases Notes} de Django 2.0 date de décembre 2017; parmi ces notes, l'une d'elles cite l'abandon du support d'\href{https://docs.djangoproject.com/en/2.0/releases/2.0/\#dropped-support-for-oracle-11-2}{Oracle 11.2}.
En substance, cela signifie que le framework se chargeait lui-même de construire certaines parties de requêtes, qui deviennent non fonctionnelles dès lors que l'on met le framework ou le moteur de base de données à jour.
Réécrit, cela signifie que:
\begin{enumerate}
\item Si vos données sont stockées dans un moteur géré par Oracle 11.2, vous serez limité à une version 1.11 de Django
\item Tandis que si votre moteur est géré par une version ultérieure, le framework pourra être mis à jour.
\item
Si vos données sont stockées dans un moteur géré par Oracle 11.2, vous serez limité à une version 1.11 de Django
\item
Tandis que si votre moteur est géré par une version ultérieure, le framework pourra être mis à jour.
\end{enumerate}
Nous sommes dans un cas concret d'inversion de dépendances ratée: le framework (et encore moins vos politiques et règles métiers) ne devraient pas avoir connaissance du moteur de base de données.
@ -902,9 +877,12 @@ Avec cette approche, les composants seront déjà découplés au mieux.
Les composants peuvent être découpés au niveau:
\begin{itemize}
\item \textbf{Du code source}, via des modules, paquets, dépendances, ...
\item \textbf{Du déploiement ou de l'exécution}, au travers de dll, jar, linked libraries, ..., voire au travers de threads ou de processus locaux.
\item \textbf{Via la mise à disposition de nouveaux services}, lorsqu'il est plus intuitif de contacter un nouveau point de terminaison que d'intégrer de force de nouveaux concepts dans une base de code existante.
\item
\textbf{Du code source}, via des modules, paquets, dépendances, \ldots
\item
\textbf{Du déploiement ou de l'exécution}, au travers de dll, jar, linked libraries, \ldots, voire au travers de threads ou de processus locaux.
\item
\textbf{Via la mise à disposition de nouveaux services}, lorsqu'il est plus intuitif de contacter un nouveau point de terminaison que d'intégrer de force de nouveaux concepts dans une base de code existante.
\end{itemize}
Cette section se base sur deux ressources principales \cite{maintainable_software} \cite{clean_code}, qui répartissent un ensemble de conseils parmi quatre niveaux de composants:
@ -916,11 +894,12 @@ Cette section se base sur deux ressources principales \cite{maintainable_softwar
\item Et des conseils plus généraux.
\end{itemize}
\subsection{Au niveau des méthodes et fonctions}
\subsection{Au niveau des méthodes et fonctions}
\begin{itemize}
\item
\textbf{Gardez vos méthodes/fonctions courtes}. Pas plus de 15 lignes, en comptant les commentaires.
\textbf{Gardez vos méthodes/fonctions courtes}.
Pas plus de 15 lignes, en comptant les commentaires.
Des exceptions sont possibles, mais dans une certaine mesure uniquement (pas plus de 6.9\% de plus de 60 lignes; pas plus de 22.3\% de plus de 30 lignes, au plus 43.7\% de plus de 15 lignes et au moins 56.3\% en dessous de 15 lignes).
Oui, c'est dur à tenir, mais faisable.
\item
@ -929,13 +908,13 @@ A nouveau, si une méthode présente une complexité cyclomatique de 15, la sép
\item
\textbf{N'écrivez votre code qu'une seule fois: évitez les duplications, copie, etc.}: imaginez qu'un bug soit découvert dans une fonction; il devra alors être corrigé dans toutes les fonctions qui auront été copiées/collées.
\item
\textbf{Conservez de petites interfaces et signatures de fonctions/méthodes}. Quatre paramètres, pas plus.
\textbf{Conservez de petites interfaces et signatures de fonctions/méthodes}.
Quatre paramètres, pas plus.
Au besoin, refactorisez certains paramètres dans une classe ou une structure, qui sera plus facile à tester.
\end{itemize}
\subsection{Au niveau des classes}
\begin{itemize}
\item
\textbf{Privilégiez un couplage faible entre vos classes}.
@ -947,9 +926,8 @@ La classe appellante n'aura alors que les méthodes offertes par l'interface com
Dans la même veine, faites en sorte que les dépendances aillent toutes "dans le même sens", ce qui limitera l'effet spaghetti associé au code, tout en améliorant sa lisibilité et l'intuitivité de sa compréhension.
\subsection{Au niveau des composants}
\begin{itemize}
\item
\textbf{Tout comme pour les classes, il faut conserver un couplage faible au niveau des composants} également.
@ -957,10 +935,8 @@ Une manière d'arriver à ce résultat est de conserver un nombre de points d'en
Pour une architecture n-tiers par exemple, la couche d'abstraction à la base de données ne peut être connue que des services; sans cela, au bout de quelques semaines, n'importe quelle couche de présentation risque de contacter directement la base de données, "\emph{juste parce qu'elle en a la possibilité}".
Vous pourriez également passer par des interfaces, afin de réduire le nombre de points d'entrée connus par un composant externe (qui ne connaîtra par exemple que \texttt{IFileTransfer} avec ses méthodes \texttt{put} et \texttt{get}, et non pas les détailsd'implémentation complet d'une classe \texttt{FtpFileTransfer} ou \texttt{SshFileTransfer}).
\item
\textbf{Conserver un bon balancement au niveau des composants}: évitez qu'un composant \textbf{A} ne soit un énorme mastodonte, alors que le
composant juste à côté ne soit capable que d'une action.
De cette manière, les nouvelles fonctionnalités seront mieux réparties parmi les différents systèmes, et les responsabilités seront plus faciles à
gérer.
\textbf{Conserver un bon balancement au niveau des composants}: évitez qu'un composant \textbf{A} ne soit un énorme mastodonte, alors que le composant juste à côté ne soit capable que d'une action.
De cette manière, les nouvelles fonctionnalités seront mieux réparties parmi les différents systèmes, et les responsabilités seront plus faciles à gérer.
Un conseil est d'avoir un nombre de composants compris entre 6 et 12 (idéalement, 12), et que chacun de ces composants soit approximativement de même taille.
\end{itemize}
@ -968,8 +944,7 @@ Un conseil est d'avoir un nombre de composants compris entre 6 et 12 (idéalemen
\begin{itemize}
\item
\textbf{Conserver une densité de code faible}: il n'est évidemment pas possible d'implémenter n'importe quelle nouvelle fonctionnalité en
moins de 20 lignes de code; l'idée ici est que la réécriture du projet ne prenne pas plus de 20 hommes/mois.
\textbf{Conserver une densité de code faible} : il n'est évidemment pas possible d'implémenter n'importe quelle nouvelle fonctionnalité en moins de 20 lignes de code; l'idée ici est que la réécriture du projet ne prenne pas plus de 20 hommes/mois.
Pour cela, il faut (activement) passer du temps à réduire la taille du code existant: soit en faisantdu refactoring (intensif), soit en utilisant des librairies existantes, soit en explosant un système existant en plusieurs sous-systèmes communiquant entre eux.
Mais surtout, en évitant de copier/coller bêtement du code existant.
\item

View File

@ -8,14 +8,13 @@ Accrochez-vous, le sujet peut être complexe.
\section{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, ...: juste un utilisateur et un mot de passe.
Par défaut, Django offre une gestion simplifiée des utilisateurs (pas de connexion LDAP, pas de double authentification, \ldots~: 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}.
\section{Mécanisme d'authentification}
On peut schématiser le flux d'authentification de la manière suivante :
En gros:
\begin{enumerate}
@ -39,12 +38,9 @@ En résumé (bis):
\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.
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.
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
@ -130,16 +126,15 @@ 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}.
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}
\section{Modélisation}
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:
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}
@ -155,17 +150,25 @@ Dans un premier temps, Django a besoin de manipuler \href{https://docs.djangopro
\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à.
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.
\subsection{Extension du modèle 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.
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.
\subsection{Substitution du modèle}
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.
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{minted}{python}
AUTH_USER_MODEL = 'myapp.MyUser'
@ -175,43 +178,44 @@ Avant de commencer, sachez que cette étape doit être effectuée \textbf{avant
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\ lapplication\textgreater{}.\textless{}nom\ de\ la\ classe\textgreater{}}.
\subsection{Social-auth}
Voir ici : \href{https://github.com/omab/python-social-auth}{pythonsocial auth}
Voir ici : \href{https://github.com/omab/python-social-auth}{python
social auth}
\subsection{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_}.
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.
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
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.
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.
\section{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_}.
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.
\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}.
\texttt{LOGIN\_URL}: l'URL de connexion à utiliser.
Par défaut, l'utilisateur doit se rendre sur la page \texttt{/accounts/login}.
\end{itemize}

View File

@ -1,10 +1,7 @@
\chapter{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.
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{minted}{python}
# core/context_processors.py

View File

@ -1,6 +1,7 @@
\chapter{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.
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 à:
@ -15,7 +16,9 @@ Une fois que ces utilisateurs seront configurés, nous pourrons passer à l'éta
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, ... 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}.
La machine hôte peut être louée chez Digital Ocean, Scaleway, OVH, Vultr, \ldots~
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{verbatim}
apt update
@ -33,8 +36,7 @@ La machine hôte peut être louée chez Digital Ocean, Scaleway, OVH, Vultr, ...
\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}
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
@ -44,10 +46,7 @@ La machine hôte peut être louée chez Digital Ocean, Scaleway, OVH, Vultr, ...
\section{Dépendances systèmes}
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.
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 :
@ -69,45 +68,38 @@ Ou passer par une installation alternative:
\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.
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}
\section{Base de données}
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:
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)
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{]}
Microsoft SQLServer grâce aux drivers {[}\ldots\hspace{0pt}à compléter{]}
\item
Oracle via
\href{https://oracle.github.io/python-cx_Oracle/}{cx\_Oracle}.
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).
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.
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.
\subsection{PostgreSQL}
On commence par installer PostgreSQL.
Dans le cas de debian, on exécute la commande suivante:
\begin{verbatim}
@ -138,8 +130,8 @@ Finalement, on peut créer la DB:
\subsection{MariaDB}
Idem, installation, configuration, backup, tout ça. A copier de grimboite, je suis sûr davoir
des notes là-dessus.
Idem, installation, configuration, backup, tout ça.
A copier de grimboite, je suis sûr davoir des notes là-dessus.
\section{Préparation de l'environment utilisateur}
@ -173,6 +165,7 @@ suivantes:
=config.settings_production
\end{verbatim}
\section{Configuration de l'application}
\begin{verbatim}
@ -191,15 +184,15 @@ suivantes:
connexion à la base de données.
\end{itemize}
\section{Création des répertoires de logs}
\section{Création des répertoires de logs}
\begin{verbatim}
mkdir -p /var/www/gwift/static
\end{verbatim}
\section{Socket}
\section{Socket}
Dans le fichier \texttt{/etc/tmpfiles.d/gwift.conf}:
\begin{verbatim}
@ -207,11 +200,11 @@ Dans le fichier \texttt{/etc/tmpfiles.d/gwift.conf}:
\end{verbatim}
Suivi de la création par systemd :
\begin{verbatim}
systemd-tmpfiles --create
\end{verbatim}
\section{Gunicorn}
\begin{verbatim}
@ -239,10 +232,11 @@ Suivi de la création par systemd :
--log-file=-
\end{verbatim}
\section{Supervsion, keepalive et autoreload}
Pour la supervision, on passe par Supervisor. Il existe d'autres
superviseurs,
Pour la supervision, on passe par Supervisor.
Il existe d'autres superviseurs,
\begin{verbatim}
yum install supervisor -y
@ -311,8 +305,8 @@ l'aide de la commande \texttt{supervisorctl}:
gwift: started
\end{verbatim}
\section{Firewall}
\section{Firewall}
\begin{verbatim}
et 443 (HTTPS).
\end{verbatim}
@ -331,9 +325,8 @@ l'aide de la commande \texttt{supervisorctl}:
Et le port 443 (forcément).
\end{itemize}
\section{Reverse proxy}
\begin{verbatim}
yum install nginx -y
usermod -a -G gunicorn_sockets nginx
@ -383,18 +376,15 @@ l'aide de la commande \texttt{supervisorctl}:
\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.
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
Afin d'éviter que Django ne reçoive uniquement des requêtes provenant de 127.0.0.1
\end{itemize}
\section{Mise à jour}
\section{Mise à jour}
\begin{verbatim}
u - <user>
source ~/.venvs/<app>/bin/activate
@ -425,13 +415,11 @@ l'aide de la commande \texttt{supervisorctl}:
}
\end{verbatim}
Puis on démarre logrotate avec \# logrotate -d /etc/logrotate.d/gwift
pour vérifier que cela fonctionne correctement.
Puis on démarre logrotate avec \# logrotate -d /etc/logrotate.d/gwift pour vérifier que cela fonctionne correctement.
\section{Sauvegardes}
Les sauvegardes ont été configurées avec borg:
\texttt{yum\ install\ borgbackup}.
Les sauvegardes ont été configurées avec borg : \texttt{yum\ install\ borgbackup}.
C'est l'utilisateur gwift qui s'en occupe.
@ -443,10 +431,9 @@ 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}
\section{Ansible}
(under construction)

View File

@ -3,34 +3,26 @@
(c/c Ced' - 2020-01-24)
Ça y est, j'ai fait un test sur mon portable avec docker et cookiecutter
pour django.
Ç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.
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
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}
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.
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.
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}.
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.
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.

View File

@ -1,51 +1,33 @@
\chapter{Filtres}
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.
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:
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}
\item
Soit au travers d'une recherche, qui permet d'effectuer une recherche
textuelle, globale et par ensemble à un ensemble de champs,
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.
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}).
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.
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:
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}
\section{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:
\section{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{minted}{python}
...
@ -88,12 +70,10 @@ Une fois l'installation réalisée, il reste deux choses à faire:
\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{}{]}}.
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:
Vous avez suivi les étapes ci-dessus, il suffit d'adapter le fichier \texttt{settings.py} de la manière suivante :
\begin{minted}{python}
REST_FRAMEWORK = {
@ -127,26 +107,17 @@ 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
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.
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}.
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}).
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}).

View File

@ -2,22 +2,21 @@
\includegraphics{images/xkcd-327.png}
\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.
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.
Quand on parle de \texttt{forms}, on ne parle pas uniquement de formulaires Web.
% TODO: tu as une explication (:) dans une explication (:). Je trouve pas ca très français.
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.
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.
@ -25,21 +24,16 @@ Notre base de données étant accessible ici par l'ORM, la solution consiste à
Le flux à suivre est le suivant:
\begin{enumerate}
\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.
\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}
\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.
\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.
@ -68,10 +62,9 @@ class WishlistCreateForm(forms.ModelForm):
fields = ('name', 'description')
\end{minted}
% TODO: je comprends pas pourquoi il y a autant de \ dans le paragraphe.
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{}dont\ repeat\ yourself\textgreater{}\textasciigrave{}\_,\ et\ évite\ quune\ modification\ ne\ pourrisse\ le\ code:\ en\ testant\ les\ deux\ champs\ présent\ dans\ lattribut\ \textasciigrave{}fields},
nous pourrons nous assurer de faire évoluer le formulaire en fonction du
modèle sur lequel il se base.
Cela suit le principe de \texttt{DRY\ \textless{}dont\ repeat\ yourself\textgreater{}\textasciigrave{}\_,\ et\ évite\ quune\ modification\ ne\ pourrisse\ le\ code :\ en\ testant\ les\ deux\ champs\ présent\ dans\ lattribut\ \textasciigrave{}fields}, nous pourrons nous assurer de faire évoluer le formulaire en fonction du modèle sur lequel il se base.
\section{Rendu et affichage}
@ -86,19 +79,13 @@ On a d'un côté le \{\{ form.as\_p \}\} ou \{\{ form.as\_table \}\}, mais il y
\subsection{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.
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.
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)
@ -108,14 +95,11 @@ Pour chaque champ, crispy-forms va :
\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.
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{}}.
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{}}.
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}

View File

@ -6,236 +6,160 @@
\caption{Gwift}
\end{figure}
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} :)).
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.
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.
Ensuite, nous pourrons traduire ces besoins en fonctionnalités et
finalement effectuer le développement.
\section{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.
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
\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.
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.
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.
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.
Un souhait pourrait aussi être réalisé plusieurs fois. Ceci revient à
dupliquer le souhait en question.
\section{Besoins fonctionnels}
\subsection{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.
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.
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.
\subsection{Gestion des listes}
\subsubsection{Modélisation}
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
\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}
\subsubsection{Fonctionnalités}
\begin{itemize}
\item
Un utilisateur authentifié doit pouvoir créer, modifier, désactiver et
supprimer une liste dont il est le propriétaire
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
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
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
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
L'utilisateur doit pouvoir voir toutes les listes qui lui appartiennent
\end{itemize}
\subsection{Gestion des souhaits}
\subsubsection{Modélisation}
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.
\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}
\subsubsection{Fonctionnalités}
\begin{itemize}
\item
Un utilisateur authentifié doit pouvoir créer, modifier, désactiver et
supprimer un souhait dont il est le propriétaire.
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é.
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.
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.
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é.
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é.
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.
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}
\subsection{Réalisation d'un souhait}
\subsubsection{Modélisation}
Les données suivantes peuvent être associées à une réalisation de
souhait:
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
\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}
\subsubsection{Fonctionnalités}
\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\%.
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.
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é.
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}
\section{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.
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:
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.
\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:
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:
% TODO: il y a beaucoup de verbatim dans l'enumerate ci-dessous.
\begin{enumerate}
\def\labelenumi{\alph{enumi}.}
\item
@ -265,27 +189,21 @@ class Part(models.Model):
\end{verbatim}
\end{enumerate}
Les classes sont créées, mais vides. Entrons dans les détails.
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:
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
\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:
@ -313,79 +231,46 @@ external_id = models.UUIDField(unique=True, default=uuid.uuid4, editable=False)
\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}.
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.
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é.
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.
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.
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.
\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
@ -408,71 +293,49 @@ Après implémentation, cela ressemble à ceci:
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)
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
Les clés étrangères sont gérées directement dans la déclaration dumodè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}}.
permet de déclarer une relation 1-N entre deux classes.
% TODO : il y a des fautes de syntaxe dans
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.
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}.
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).
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}.
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}
\subsection{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
\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:
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}
\item
code-block:: python
@ -494,49 +357,35 @@ 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{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.
La classe \texttt{UnknownUser} permet de représenter un utilisateur non
enregistré sur le site et est définie au point suivant.
\subsection{Utilisateurs inconnus}
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.
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:
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.
\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

View File

@ -1,26 +1,24 @@
\chapter{PaaS - 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, ..., 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
\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, 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.}
\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.
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.
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{verbatim}
$ heroku create
Creating app... done, -> young-temple-86098
@ -49,18 +47,14 @@ Au travers de la commande \texttt{heroku\ create}, vous associez donc une nouvel
* 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}.
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
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}.
Prêt à vous lancer ? Commencez par créer un compte : \url{https://signup.heroku.com/python}.
\section{Configuration du compte}
@ -77,29 +71,21 @@ Vous aurez peut-être besoin d'un coup de pouce pour démarrer votre première a
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}
\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)
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}.
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{verbatim}
$ heroku login
\end{verbatim}
Et créer votre application :
\begin{verbatim}
$ heroku create
Creating app... done, -> young-temple-86098
@ -113,12 +99,8 @@ Et créer votre application:
\caption{Notre application est à présent configurée!}
\end{figure}
Ajoutons lui une base de données, que nous sauvegarderons à intervalle
régulier :
\begin{verbatim}
$ heroku addons:create heroku-postgresql:hobby-dev
Creating heroku-postgresql:hobby-dev on -> still-thicket-66406... free
@ -170,18 +152,16 @@ régulier:
\section{Type d'application}
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:
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}
\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.
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
@ -198,19 +178,14 @@ django-storages
\section{Hébergement 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.
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:
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}.}
@ -228,20 +203,11 @@ dans l'ordre:
{[}\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.
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}:
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{minted}{python}
AWS_ACCESS_KEY_ID = os.getenv('ACCESS_KEY_ID')
@ -284,9 +250,7 @@ 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/})
{[}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/})
{[}Using Django and Boto3{]}(\url{https://www.simplecto.com/using-django-and-boto3-with-scaleway-object-storage/})
\end{itemize}

View File

@ -5,14 +5,10 @@ sont deux concepts proches, mais différents:
\begin{itemize}
\item
Internationalisation: \emph{Preparing the software for localization.
Usually done by developers.}
Internationalisation: \emph{Preparing the software for localization. Usually done by developers.}
\item
Localisation: \emph{Writing the translations and local formats.
Usually done by translators.}
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.
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.

View File

@ -1,49 +1,32 @@
\chapter{Infrastructure et 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
\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}
\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
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, ...
vérifie si la requête peut être prise en compte
Le firewall du serveur (Debian GNU/Linux, CentOS, \ldots~ 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)
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.
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).
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}
@ -52,45 +35,44 @@ Si nous schématisons l'infrastructure et le chemin parcouru par une requête, n
\section{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}}
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}}
\section{Load balancer}
% TODO: à écrire
\section{Workers}
% TODO: à écrire
\section{Supervision des processus}
% TODO: à écrire
\section{Bases de données}
% TODO: à écrire
\section{Tâches asynchrones}
% TODO: à écrire
\section{Mise en cache}
% TODO: à écrire
\section{Niveau application}
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}
\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.
\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, ...
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
\section{Supervision globale}
\subsection{Journaux}
La structure des niveaux de journaux est essentielle.
\begin{quote}
@ -102,40 +84,34 @@ Low printer toner is not an ERROR.
\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.
\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, ...
\textbf{INFO} : Enregistre les actions pilotées par un utilisateur - Démarrage de la transaction de paiement, \ldots
\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, ...
\item
\textbf{FATAL} (ou \textbf{EXCEPTION}): ... généralement suivie d'une terminaison du programme - Bind raté
d'un socket, etc.
\textbf{FATAL} (ou \textbf{EXCEPTION}) : \ldots 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:
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}
\item
Formattage: à définir - mais la variante suivante est complète, lisible et pratique:
\texttt{\{levelname\}\ \{asctime\}\ \{module\}\ \{process:d\}\ \{thread:d\}\ \{message\}}
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}.
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}
Pour utiliser nos loggers, il suffit de copier le petit bout de code suivant :
\begin{minted}{python}
import logging
@ -143,10 +119,7 @@ Pour utiliser nos loggers, il suffit de copier le petit bout de code suivant:
logger.debug('helloworld')
\end{minted}
\href{https://docs.djangoproject.com/en/stable/topics/logging/\#examples}{Par
exemples}.
\href{https://docs.djangoproject.com/en/stable/topics/logging/\#examples}{Par exemples}.
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
@ -160,14 +133,15 @@ exemples}.
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}.
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}.
\subsection{Zabbix, Nagios, ...}
% TODO: a écrire
\subsection{Sentry}
% TODO: a écrire
\subsection{Greylog}
% TODO: a écrire

View File

@ -6,15 +6,10 @@ The only way to go fast, is to go well
--- Robert C. Martin
\end{quote}
Nous n'allons pas vous mentir: il existe énormément de tutoriaux très
bien réalisés sur "\emph{Comment réaliser une application Django}" et
autres "\emph{Déployer votre code en 2 minutes}". Nous nous disions
juste que ces tutoriaux restaient relativement haut-niveaux et se
limitaient à un contexte donné, sans réellement préparer à la
maintenance et au suivi de l'application nouvellement développée.
Nous n'allons pas vous mentir: il existe énormément de tutoriaux très bien réalisés sur "\emph{Comment réaliser une application Django}" et autres "\emph{Déployer votre code en 2 minutes}".
Nous nous disions juste que ces tutoriaux restaient relativement haut-niveaux et se limitaient à un contexte donné, sans réellement préparer à la maintenance et au suivi de l'application nouvellement développée.
Les quelques idées ci-dessous de jeter les bases d'un bon développement, en
\begin{itemize}
\item Survolant l'ensemble des lignes directrices reconnues
\item Maintenant une bonne qualité de code
@ -26,136 +21,85 @@ Les quelques idées ci-dessous de jeter les bases d'un bon développement, en
Ces idées ne s'appliquent pas uniquement à Django et à son cadre de travail, ni même au langage Python en particulier.
Ces deux sujets sont cependant de bons candidats et leur utilisation et cadre de travail sont bien définis, documentés et flexibles.
Django se présente comme un \emph{Framework Web pour perfectionnistes
ayant des deadlines} \cite{django} et suit ces quelques principes
\cite{django_design_philosophies}:
Django se présente comme un \emph{Framework Web pour perfectionnistes ayant des deadlines} \cite{django} et suit ces quelques principes \cite{django_design_philosophies}:
\begin{itemize}
\item
\textbf{Faible couplage et forte cohésion}, pour que chaque composant dispose de son indépendance, en n'ayant aucune connaissance des autres couches applicatives. Ainsi, le moteur de rendu ne connait absolument rien à l'existence du moteur de base de données, tout comme le système de vues ne sait pas quel moteur de rendu est utilisé.
\textbf{Faible couplage et forte cohésion}, pour que chaque composant dispose de son indépendance, en n'ayant aucune connaissance des autres couches applicatives.
Ainsi, le moteur de rendu ne connait absolument rien à l'existence du moteur de base de données, tout comme le système de vues ne sait pas quel moteur de rendu est utilisé.
\item
\textbf{Plus de fonctionnalités avec moins de code}: chaque application Django
doit utiliser le moins de code possible
\textbf{Plus de fonctionnalités avec moins de code}: chaque application Django doit utiliser le moins de code possible
\item
\textbf{\emph{Don't repeat yourself}}, chaque concept ou morceau de code ne
doit être présent qu'à un et un seul endroit de vos dépôts.
\textbf{\emph{Don't repeat yourself}}, chaque concept ou morceau de code ne doit être présent qu'à un et un seul endroit de vos dépôts.
\item
\textbf{Rapidité du développement}, en masquant les aspects fastidieux du
développement web actuel
\textbf{Rapidité du développement}, en masquant les aspects fastidieux du développement web actuel
\end{itemize}
Mis côte à côte, le suivi de ces principes permet une bonne stabilité du
projet à moyen et long terme.
Mis côte à côte, le suivi de ces principes permet une bonne stabilité du projet à moyen et long terme.
Sans être parfait, Django offre une énorme flexibilité qui permet de conserver un maximum d'options ouvertes et de facilement expérimenter différentes pistes, jusqu'au moment de prendre une vraie décision.
Pour la (grande) majorité des problèmes rencontrés lors du développement d'une application Web, Django proposera une solution pragmatique, compréhensible et facile à mettre en place: pour tout problème communément connu, vous disposerez d'une solution logique.
Tout pour plaire à n'importe quel directeur IT.
\textbf{Dans la première partie}, nous verrons comment partir d'un
environnement sain, comment le configurer correctement, comment
installer Django de manière isolée et comment démarrer un nouveau
projet. Nous verrons rapidement comment gérer les dépendances, les
versions et comment appliquer et suivre un score de qualité de notre
code. Ces quelques points pourront être appliqués pour n'importe quel
langage ou cadre de travail. Nous verrons aussi que la configuration
proposée par défaut par le framework n'est pas idéale dans la majorité
des cas.
\textbf{Dans la première partie}, nous verrons comment partir d'un environnement sain, comment le configurer correctement, comment installer Django de manière isolée et comment démarrer un nouveau projet.
Nous verrons rapidement comment gérer les dépendances, les versions et comment appliquer et suivre un score de qualité de notre code.
Ces quelques points pourront être appliqués pour n'importe quel langage ou cadre de travail.
Nous verrons aussi que la configuration proposée par défaut par le framework n'est pas idéale dans la majorité des cas.
Pour cela, nous présenterons différents outils, la rédaction de tests
unitaires et d'intégration pour limiter les régressions, les règles de
nomenclature et de contrôle du contenu, comment partir d'un squelette
plus complet, ainsi que les bonnes étapes à suivre pour arriver à un
déploiement rapide et fonctionnel avec peu d'efforts.
Pour cela, nous présenterons différents outils, la rédaction de tests unitaires et d'intégration pour limiter les régressions, les règles de nomenclature et de contrôle du contenu, comment partir d'un squelette plus complet, ainsi que les bonnes étapes à suivre pour arriver à un déploiement rapide et fonctionnel avec peu d'efforts.
A la fin de cette partie, vous disposerez d'un code propre et d'un
projet fonctionnel, bien qu'encore un peu inutile.
A la fin de cette partie, vous disposerez d'un code propre et d'un projet fonctionnel, bien qu'encore un peu inutile.
textbf{Dans la deuxième partie}, nous aborderons les grands principes
de modélisation, en suivant les lignes de conduites du cadre de travail.
Nous aborderons les concepts clés qui permettent à une application de
rester maintenable, les formulaires, leurs validations, comment gérer
les données en entrée, les migrations de données et l'administration.
textbf{Dans la deuxième partie}, nous aborderons les grands principes de modélisation, en suivant les lignes de conduites du cadre de travail.
Nous aborderons les concepts clés qui permettent à une application de rester maintenable, les formulaires, leurs validations, comment gérer les données en entrée, les migrations de données et l'administration.
\textbf{Dans la troisième partie}, nous détaillerons précisément les
étapes de déploiement, avec la description et la configuration de
l'infrastructure, des exemples concrets de mise à disposition sur deux
distributions principales (Debian et CentOS), sur une \emph{*Plateform
as a Service*}, ainsi que l'utilisation de Docker et Docker-Compose.
\textbf{Dans la troisième partie}, nous détaillerons précisément les étapes de déploiement, avec la description et la configuration de l'infrastructure, des exemples concrets de mise à disposition sur deux distributions principales (Debian et CentOS), sur une \emph{*Plateform as a Service*}, ainsi que l'utilisation de Docker et Docker-Compose.
Nous aborderons également la supervision et la mise à jour d'une
application existante, en respectant les bonnes pratiques
d'administration système.
Nous aborderons également la supervision et la mise à jour d'une application existante, en respectant les bonnes pratiques d'administration système.
\textbf{Dans la quatrième partie}, nous aborderons les architectures
typées \emph{entreprise}, les services et les différentes manières de
structurer notre application pour faciliter sa gestion et sa
maintenance, tout en décrivant différents types de scénarii, en fonction
des consommateurs de données.
\textbf{Dans la quatrième partie}, nous aborderons les architectures typées \emph{entreprise}, les services et les différentes manières de structurer notre application pour faciliter sa gestion et sa maintenance, tout en décrivant différents types de scénarii, en fonction des consommateurs de données.
\textbf{Dans la cinquième partie}, nous mettrons ces concepts en
pratique en présentant le développement en pratique de deux
applications, avec la description de problèmes rencontrés et la solution
qui a été choisie: définition des tables, gestion des utilisateurs,
\ldots\hspace{0pt} et mise à disposition.
\textbf{Dans la cinquième partie}, nous mettrons ces concepts en pratique en présentant le développement en pratique de deux applications, avec la description de problèmes rencontrés et la solution qui a été choisie: définition des tables, gestion des utilisateurs, \ldots\hspace{0pt} et mise à disposition.
\subsection{Pour qui ?}
Avant tout, pour moi. Comme le disait le Pr Richard Feynman: "\emph{Si
vous ne savez pas expliquer quelque chose simplement, c'est que vous ne
l'avez pas compris}". \footnote{Et comme l'ajoutait Aurélie Jean dans de
L'autre côté de la machine: \emph{"Si personne ne vous pose de
questions suite à votre explication, c'est que vous n'avez pas été
suffisamment clair !"} \cite{other_side}}
Avant tout, pour moi.
Comme le disait le Pr Richard Feynman: "\emph{Si vous ne savez pas expliquer quelque chose simplement, c'est que vous ne l'avez pas compris}".
\footnote{Et comme l'ajoutait Aurélie Jean dans de L'autre côté de la machine: \emph{"Si personne ne vous pose de questions suite à votre explication, c'est que vous n'avez pas été suffisamment clair !"} \cite{other_side}}
Ce livre s'adresse autant au néophyte qui souhaite se lancer dans le
développement Web qu'à l'artisan qui a besoin d'un aide-mémoire et qui
ne se rappelle plus toujours du bon ordre des paramètres, ou à l'expert
qui souhaiterait avoir un aperçu d'une autre technologie que son domaine
privilégié de compétences.
Ce livre s'adresse autant au néophyte qui souhaite se lancer dans le développement Web qu'à l'artisan qui a besoin d'un aide-mémoire et qui ne se rappelle plus toujours du bon ordre des paramètres, ou à l'expert qui souhaiterait avoir un aperçu d'une autre technologie que son domaine privilégié de compétences.
Beaucoup de concepts présentés peuvent être oubliés ou restés inconnus
jusqu'au moment où ils seront réellement nécessaires. A ce moment-là,
pour peu que votre mémoire ait déjà entraperçu le terme, il vous sera
plus facile d'y revenir et de l'appliquer.
Beaucoup de concepts présentés peuvent être oubliés ou restés inconnus jusqu'au moment où ils seront réellement nécessaires.
A ce moment-là, pour peu que votre mémoire ait déjà entraperçu le terme, il vous sera plus facile d'y revenir et de l'appliquer.
\subsection{Pour aller plus loin}
Il existe énormément de ressources, autant spécifiques à Django que plus
généralistes. Il ne sera pas possible de toutes les détailler; faites un
tour sur
Il existe énormément de ressources, autant spécifiques à Django que plus généralistes.
Il ne sera pas possible de toutes les détailler; faites un tour sur
\begin{itemize}
\item
\url{https://duckduckgo.com},
\item
\url{https://stackoverflow.com},
\item
\url{https://ycombinator.com},
\item
\url{https://lobste.rs/},
\item
\url{https://lecourrierduhacker.com/}
\item
ou \url{https://www.djangoproject.com/}.
\item \url{https://duckduckgo.com},
\item \url{https://stackoverflow.com},
\item \url{https://ycombinator.com},
\item \url{https://lobste.rs/},
\item \url{https://lecourrierduhacker.com/}
\item ou \url{https://www.djangoproject.com/}.
\end{itemize}
Restez curieux, ne vous enclavez pas dans une technologie en particulier
et gardez une bonne ouverture d'esprit.
Restez curieux, ne vous enclavez pas dans une technologie en particulie et gardez une bonne ouverture d'esprit.
\subsection{Conventions}
Les notes indiquent des anecdotes.
Les conseils indiquent des éléments utiles, mais pas spécialement
indispensables.
Les conseils indiquent des éléments utiles, mais pas spécialemen indispensables.
Les notes importantes indiquent des éléments à retenir.
Ces éléments indiquent des points d'attention. Les retenir vous fera
gagner du temps en débuggage.
Ces éléments indiquent des points d'attention.
Les retenir vous fer gagner du temps en débuggage.
Les avertissements indiquent un (potentiel) danger ou des éléments
pouvant amener des conséquences pas spécialement sympathiques.
Les avertissements indiquent un (potentiel) danger ou des élément pouvant amener des conséquences pas spécialement sympathiques.
Les morceaux de code source seront présentés de la manière suivante:
@ -171,7 +115,6 @@ Les morceaux de code source seront présentés de la manière suivante:
\end{listing}
Chaque extrait de code reprendra
\begin{itemize}
\item l'emplacement du fichier, présenté sous forme de commentaire (ligne 1),
\item Des commentaires au niveau des fonctions et méthodes, si cela s'avère nécessaire

View File

@ -1,14 +1,11 @@
\chapter{Licence}
Ce travail est licencié sous Attribution-NonCommercial 4.0 International
Attribution-NonCommercial 4.0 International
Ce travail est licencié sous Attribution-NonCommercial 4.0 International Attribution-NonCommercial 4.0 International
This license requires that reusers give credit to the creator. It allows
reusers to distribute, remix, adapt, and build upon the material in any
medium or format, for noncommercial purposes only.
This license requires that reusers give credit to the creator.
It allows reusers to distribute, remix, adapt, and build upon the material in any medium or format, for noncommercial purposes only.
You are free to :
\begin{itemize}
\item \textbf{Share} — copy and redistribute the material in any medium or format
\item \textbf{Adapt} — remix, transform, and build upon the material
@ -18,14 +15,13 @@ Under the following terms:
\begin{itemize}
\item
\textbf{Attribution}: You must give appropriate credit, provide a link to the license, and indicate if changes were made. You may do so in any reasonable manner, but not in any way that suggests the licensor endorses you or your use.
\textbf{Attribution}: You must give appropriate credit, provide a link to the license, and indicate if changes were made.
You may do so in any reasonable manner, but not in any way that suggests the licensor endorses you or your use.
\item
\textbf{NC}: Only noncommercial use of your work is permitted.
Noncommercial means not primarily intended for or directed towards
commercial advantage or monetary compensation.
Noncommercial means not primarily intended for or directed towards commercial advantage or monetary compensation.
\end{itemize}
\url{https://creativecommons.org/licenses/by-nc/4.0/}
La seule exception concerne les morceaux de code (non attribués),
disponibles sous licence \href{https://mit-license.org/}{MIT}.
La seule exception concerne les morceaux de code (non attribués), disponibles sous licence \href{https://mit-license.org/}{MIT}.

View File

@ -1,8 +1,7 @@
\chapter{Fiabilité, évolutivité et maintenabilité}
\begin{quote}
The primary cost of maintenance is in spelunking and risk
\cite[139]{clean_architecture}
The primary cost of maintenance is in spelunking and risk\cite[139]{clean_architecture}
--- Robert C. Martin
\end{quote}
@ -11,35 +10,20 @@ Pour la méthode de travail et de développement, nous allons nous baser
sur les \href{https://12factor.net/fr/}{The Twelve-factor App} - ou plus
simplement les \textbf{12 facteurs}.
Suivre ces concepts permet de :
\begin{enumerate}
\item
\textbf{Faciliter la mise en place de phases d'automatisation}; plus
concrètement, de faciliter les mises à jour applicatives, simplifier
la gestion de l'hôte qui héberge l'application ou les services,
diminuer la divergence entre les différents environnements d'exécution et offrir la possibilité d'intégrer le
projet dans un processus
d'\href{https://en.wikipedia.org/wiki/Continuous_integration}{intégration
continue} ou
\href{https://en.wikipedia.org/wiki/Continuous_deployment}{déploiement
continu}
\textbf{Faciliter la mise en place de phases d'automatisation}; plus concrètement, de faciliter les mises à jour applicatives, simplifier la gestion de l'hôte qui héberge l'application ou les services, diminuer la divergence entre les différents environnements d'exécution et offrir la possibilité d'intégrer le projet dans un processus d'\href{https://en.wikipedia.org/wiki/Continuous_integration}{intégration continue} ou \href{https://en.wikipedia.org/wiki/Continuous_deployment}{déploiement continu}
\item
\textbf{Faciliter l'intégration de nouveaux développeurs dans l'équipe ou de
personnes souhaitant rejoindre le projet}, dans la mesure où la construction d'un nouvel environnement sera grandement facilitée.
\textbf{Faciliter l'intégration de nouveaux développeurs dans l'équipe ou de personnes souhaitant rejoindre le projet}, dans la mesure où la construction d'un nouvel environnement sera grandement facilitée.
\item
\textbf{Minimiser les divergences entre les différents environnemens}
sur lesquels un projet pourrait être déployé, pour éviter de découvrir un bogue sur l'environnement de production qui serait impossible à reproduire ailleurs, simplement parce qu'un des composants varierait
\textbf{Minimiser les divergences entre les différents environnemens} sur lesquels un projet pourrait être déployé, pour éviter de découvrir un bogue sur l'environnement de production qui serait impossible à reproduire ailleurs, simplement parce qu'un des composants varierait
\item
\textbf{Augmenter l'agilité générale du projet}, en permettant une
meilleure évolutivité architecturale et une meilleure mise à l'échelle.
\textbf{Augmenter l'agilité générale du projet}, en permettant une meilleure évolutivité architecturale et une meilleure mise à l'échelle.
\end{enumerate}
En pratique, les points ci-dessus permettront de gagner un temps précieux à la construction d'un nouvel environnement - qu'il soit sur la machine du petit nouveau dans l'équipe, sur un serveur Azure/Heroku/Digital Ocean ou votre nouveau
Raspberry Pi Zéro planqué à la cave - et vous feront gagner un temps
précieux.
En pratique, les points ci-dessus permettront de gagner un temps précieux à la construction d'un nouvel environnement - qu'il soit sur la machine du petit nouveau dans l'équipe, sur un serveur Azure/Heroku/Digital Ocean ou votre nouveau Raspberry Pi Zéro planqué à la cave - et vous feront gagner un temps précieux.
Pour reprendre plus spécifiquement les différentes idées derrière
cette méthode, nous avons:
Pour reprendre plus spécifiquement les différentes idées derrière cette méthode, nous avons:
\section{Une base de code unique, suivie par un contrôle de versions}
@ -49,26 +33,15 @@ Comme dépôt, nous pourrons par exemple utiliser GitHub, Gitea ou Gitlab, suiva
\includegraphics{images/diagrams/12-factors-1.png}
Comme l'explique Eran Messeri, ingénieur dans le groupe Google Developer
Infrastructure: "Un des avantages d'utiliser un dépôt unique de sources,
est qu'il permet un accès facile et rapide à la forme la plus à jour du
code, sans aucun besoin de coordination. \cite[pp. 288-298]{devops_handbook}.
Ce cépôt n'est pas uniquement destiné à hébergé le code source, mais
également à d'autres artefacts et autres formes de connaissance:
Comme l'explique Eran Messeri, ingénieur dans le groupe Google Developer Infrastructure: "Un des avantages d'utiliser un dépôt unique de sources, est qu'il permet un accès facile et rapide à la forme la plus à jour du code, sans aucun besoin de coordination. \cite[pp. 288-298]{devops_handbook}.
Ce cépôt n'est pas uniquement destiné à hébergé le code source, mais également à d'autres artefacts et autres formes de connaissance :
\begin{itemize}
\item
Standards de configuration (Chef recipes, Puppet manifests, ...
\item
Outils de déploiement
\item
Standards de tests, y compris tout ce qui touche à la sécurité
\item
Outils de déploiement de pipeline
\item
Outils d'analyse et de monitoring
\item
Tutoriaux
\item Standards de configuration (Chef recipes, Puppet manifests, \ldots
\item Outils de déploiement
\item Standards de tests, y compris tout ce qui touche à la sécurité
\item Outils de déploiement de pipeline
\item Outils d'analyse et de monitoring
\item Tutoriaux
\end{itemize}
\section{Déclaration explicite et isolation des dépendances}
@ -104,14 +77,10 @@ Il faut éviter d'avoir à recompiler/redéployer l'application simplement parce
que:
\begin{enumerate}
\item
l'adresse du serveur de messagerie a été modifiée,
\item
un protocole a changé en cours de route
\item
la base de données a été déplacée
\item
...
\item l'adresse du serveur de messagerie a été modifiée,
\item un protocole a changé en cours de route
\item la base de données a été déplacée
\item \ldots
\end{enumerate}
En pratique, toute information susceptible d'évoluer ou de changer (un seuil, une ressource externe, un couple utilisateur/mot de passe, ...) doit se trouver dans un fichier ou dans une variable d'environnement, et doit être facilement modifiable.

View File

@ -24,18 +24,15 @@ Dans le cas de Django, et après avoir activé l'environnement, nous pouvons à
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}:
L'installation de Django a ajouté un nouvel exécutable: \texttt{django-admin}, que l'on peut utiliser pour créer notre nouvel espace de travail.
Par la suite, nous utiliserons \texttt{manage.py}, qui constitue un \textbf{wrapper} autour de \texttt{django-admin}.
Pour démarrer notre projet, nous lançons \texttt{django-admin\ startproject\ gwift}:
\begin{verbatim}
$ django-admin startproject gwift
\end{verbatim}
Cette action a pour effet de créer un nouveau dossier \texttt{gwift},
dans lequel nous trouvons la structure suivante:
Cette action a pour effet de créer un nouveau dossier \texttt{gwift}, dans lequel nous trouvons la structure suivante :
\begin{verbatim}
$ tree gwift
gwift
@ -48,36 +45,25 @@ dans lequel nous trouvons la structure suivante:
-- manage.py
\end{verbatim}
C'est dans ce répertoire que vont vivre tous les fichiers liés au
projet. Le but est de faire en sorte que toutes les opérations
(maintenance, déploiement, écriture, tests, \ldots\hspace{0pt}) puissent
se faire à partir d'un seul point d'entrée.
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.
\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.
\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.
\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.
\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.
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.
La seule condition est que les chemins référencés soient cohérents par rapport à la structure sous-jacente.
Tant que nous y sommes, nous pouvons ajouter un répertoire dans lequel nous stockerons les dépendances et un fichier README:
@ -139,7 +125,6 @@ Nous faisons ici une distinction entre un \textbf{projet} et une \textbf{applica
\end{itemize}
Pour \texttt{gwift}, nous aurons :
\begin{figure}
\centering
\includegraphics{images/django/django-project-vs-apps-gwift.png}
@ -148,13 +133,11 @@ Pour \texttt{gwift}, nous aurons:
\begin{enumerate}
\item
Une première application pour la gestion des listes de souhaits et des
éléments,
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.
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
@ -179,8 +162,7 @@ Le projet s'occupe principalement d'appliquer une couche de glue entre différen
\subsection{manage.py}
Le fichier \texttt{manage.py} que vous trouvez à la racine de votre projet est un \textbf{wrapper} sur les commandes \texttt{django-admin}.
A partir de maintenant, nous n'utiliserons plus que celui-là pour tout
ce qui touchera à la gestion de notre projet:
A partir de maintenant, nous n'utiliserons plus que celui-là pour tout ce qui touchera à la gestion de notre projet :
\begin{itemize}
\item

View File

@ -29,28 +29,19 @@ ressources pourraient vous aider:
\begin{itemize}
\item
\textbf{Pour les débutants},
\href{https://automatetheboringstuff.com/}{Automate the Boring Stuff
with Python} \cite{boring_stuff}, aka. \emph{Practical
Programming for Total Beginners}
\textbf{Pour les débutants}, \href{https://automatetheboringstuff.com/}{Automate the Boring Stuff with Python} \cite{boring_stuff}, aka. \emph{Practical Programming for Total Beginners}
\item
\textbf{Pour un (gros) niveau au dessus} et pour un état de l'art du langage, nous ne pouvons que vous recommander le livre Expert Python Programming \cite{expert_python}, qui aborde énormément d'aspects du langage en détails (mais pas toujours en profondeur): les différents types d'interpréteurs, les éléments de langage avancés, différents outils de productivité, métaprogrammation, optimisation de code, programmation orientée évènements, multithreading et concurrence, tests, ...
\textbf{Pour un (gros) niveau au dessus} et pour un état de l'art du langage, nous ne pouvons que vous recommander le livre Expert Python Programming \cite{expert_python}, qui aborde énormément d'aspects du langage en détails (mais pas toujours en profondeur): les différents types d'interpréteurs, les éléments de langage avancés, différents outils de productivité, métaprogrammation, optimisation de code, programmation orientée évènements, multithreading et concurrence, tests, \ldots
A ce jour, c'est le concentré de sujets liés au langage le plus intéressant qui ait pu arriver entre nos mains.
\end{itemize}
En parallèle, si vous avez besoin d'un aide-mémoire ou d'une liste
exhaustive des types et structures de données du langage, référez-vous
au lien suivant:
\href{https://gto76.github.io/python-cheatsheet/}{Python Cheat Sheet}.
En parallèle, si vous avez besoin d'un aide-mémoire ou d'une liste exhaustive des types et structures de données du langage, référez-vous au lien suivant: \href{https://gto76.github.io/python-cheatsheet/}{Python Cheat Sheet}.
\section{Protocoles de langage}
Le modèle de données du langage spécifie un ensemble de méthodes qui
peuvent être surchargées. Ces méthodes suivent une convention de nommage
et leur nom est toujours encadré par un double tiret souligné; d'où leur
nom de "\emph{dunder methods}\index{dunder}" ou "\emph{double-underscore methods}". La
méthode la plus couramment utilisée est la méthode \texttt{init()}, qui
permet de surcharger l'initialisation d'une instance de classe.
Le modèle de données du langage spécifie un ensemble de méthodes qui peuvent être surchargées.
Ces méthodes suivent une convention de nommage et leur nom est toujours encadré par un double tiret souligné; d'où leur nom de "\emph{dunder methods}\index{dunder}" ou "\emph{double-underscore methods}".
La méthode la plus couramment utilisée est la méthode \texttt{init()}, qui permet de surcharger l'initialisation d'une instance de classe.
\begin{listing}[!ht]
\begin{minted}{python}
@ -83,7 +74,7 @@ Les points principaux à présenter ci-dessus:
\item item()
\item getitem() - pour utiliser les []
\item len() - pour connaître la longueur d'un objet
\item ...
\item \ldots
\end{enumerate}
Le langage autorise nativement plus d'une cinquantaine d'opérateurs différents:
@ -94,7 +85,7 @@ Le langage autorise nativement plus d'une cinquantaine d'opérateurs différents
\item Opérateurs de comparaisons
\item Opérateurs d'identité
\item Opérateurs de comparaison bit à bit
\item ...
\item \ldots
\end{enumerate}
Par exemple, la méthode \texttt{add()} est responsable de l'implémentation de l'opérateur \texttt{+}, la méthode \texttt{sub()} s'occupe de la soustraction ()\texttt{}), tandis que \texttt{mul()} gère l'opérateur \texttt{*}.
@ -156,11 +147,11 @@ Nous pouvons donc utiliser ces mêmes \textbf{dunder methods} (\textbf{double-un
\section{Guide de style}
La première PEP qui va nous intéresser est la PEP8.
Elle spécifie comment du code Python doit être organisé ou formaté, quelles sont les conventions pour l'indentation, le nommage des variables et des classes, ...
Elle spécifie comment du code Python doit être organisé ou formaté, quelles sont les conventions pour l'indentation, le nommage des variables et des classes, \ldots
En bref, elle décrit comment écrire du code proprement, afin que d'autres développeurs puissent le reprendre facilement, ou simplement que votre base de code ne dérive lentement vers un seuil de non-maintenabilité.
Dans cet objectif, un outil existe et listera l'ensemble des conventions qui ne sont pas correctement suivies dans votre projet: flake8. Pour l'installer, passez par pip.
Lancez ensuite la commande \texttt{flake8} suivie du chemin à analyser (\texttt{.}, le nom d'un répertoire, le nom d'un fichier \texttt{.py}, ...).
Lancez ensuite la commande \texttt{flake8} suivie du chemin à analyser (\texttt{.}, le nom d'un répertoire, le nom d'un fichier \texttt{.py}, \ldots).
Si vous souhaitez uniquement avoir le nombre d'erreur de chaque type, saisissez les options \texttt{-\/-statistics\ -qq} - l'attribut \texttt{-qq} permettant simplement d'ignorer toute sortie console autre que les statistiques demandées).
\begin{listing}[!ht]
@ -183,7 +174,7 @@ Si vous ne voulez pas être dérangé sur votre manière de coder, et que vous v
\begin{listing}[!ht]
\begin{verbatim}
$ pyflakes .
...
\ldots
\end{verbatim}
\caption{Une utilisation de pyflakes}
\end{listing}
@ -196,12 +187,12 @@ Toute fonction dans la complexité est supérieure à cette valeur sera considé
Python étant un langage interprété fortement typé, il est plus que conseillé, au même titre que les tests unitaires que nous verrons plus bas, de documenter son code.
Ceci impose une certaine rigueur, tout en améliorant énormément la qualité, la compréhension et la reprise du code par une tierce personne.
Ceci implique aussi de \textbf{tout} documenter: les modules, les paquets, les classes, les fonctions, méthodes, ... ce qui peut aller à contrecourant d'autres pratiques \cite{clean_code}{53-74} ; il y a donc une juste mesure à prendre entre "tout documenter" et "tout bien documenter":
Ceci implique aussi de \textbf{tout} documenter: les modules, les paquets, les classes, les fonctions, méthodes, \ldots ce qui peut aller à contrecourant d'autres pratiques \cite{clean_code}{53-74} ; il y a donc une juste mesure à prendre entre "tout documenter" et "tout bien documenter":
\begin{itemize}
\item
Inutile d'ajouter des watermarks, auteurs, ...
Inutile d'ajouter des watermarks, auteurs, \ldots
Git ou tout VCS s'en sortira très bien et sera beaucoup plus efficace que n'importe quelle chaîne de caractères que vous pourriez indiquer et qui sera fausse dans six mois,
\item
Inutile de décrire quelque chose qui est évident; documenter la méthode \mintinline{python}{get_age()} d'une personne n'aura pas beaucoup d'intérêt
@ -219,7 +210,7 @@ Il existe plusieurs types de balisages reconnus/approuvés:
\item Google Style (parfois connue sous l'intitulé \texttt{Napoleon})
\end{enumerate}
... mais tout système de balisage peut être reconnu, sous réseve de respecter la structure de la PEP257.
\ldots mais tout système de balisage peut être reconnu, sous réseve de respecter la structure de la PEP257.
\subsection{PEP 257}
@ -234,7 +225,7 @@ Elle contient des conventions, pas des règles ou
-- Tim Peters on comp.lang.python, 2001-06-16
\end{quote}
Ainsi, les conventions sont décrites; chaque format propose ensuite son propre balisage (ReStructuredText, Numpy, Napoleon, ...).
Ainsi, les conventions sont décrites; chaque format propose ensuite son propre balisage (ReStructuredText, Numpy, Napoleon, \ldots).
A priori, vous pourriez tout à fait utiliser le vôtre, sous réserve que les conventions de la PEP-257 soient respectées.
\subsection{RestructuredText}
@ -483,7 +474,7 @@ Cet élément peut être:
\begin{enumerate}
\item \textbf{Une ligne de code}
\item \textbf{Un bloc de code} - une fonction, une méthode, une classe, un module, ...
\item \textbf{Un bloc de code} - une fonction, une méthode, une classe, un module, \ldots
\item \textbf{Un projet entier}, en spécifiant la non-prise en compte au niveau du fichier \texttt{.pylintrc}, qui contient
\end{enumerate}
@ -496,7 +487,7 @@ Cet élément peut être:
\section{Formatage de code}
Nous avons parlé ci-dessous de style de codage pour Python (PEP8), de style de rédaction pour la documentation (PEP257), d'un vérificateur pour nous indiquer quels morceaux de code doivent absolument être revus, ...
Nous avons parlé ci-dessous de style de codage pour Python (PEP8), de style de rédaction pour la documentation (PEP257), d'un vérificateur pour nous indiquer quels morceaux de code doivent absolument être revus, \ldots
Reste que ces tâches sont parfois (très) souvent fastidieuses: écrire un code propre et systématiquement cohérent est une tâche ardue.
Heureusement, il existe plusieurs outils pour nous aider au niveau du formatage automatique.
Même si elle n'est pas parfaite, la librairie \href{https://black.readthedocs.io/en/stable/}{Black} arrive à un très bon compromis entre

View File

@ -9,7 +9,9 @@ Nothing within in the system depends on the tests, and the tests always depend i
-- Robert C. Martin, Clean Architecture
\end{quote}
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).
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:
@ -21,13 +23,15 @@ Unit tests combine many features that make them your secret weapon to applicatio
\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.
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.
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.
\begin{enumerate}
\item
@ -38,6 +42,7 @@ of a well-written test suite with good coverage.
Traduit grossièrement depuis un article sur \url{https://medium.com/javascript-scene/what-every-unit-test-needs-f6cd34d9836d\#.kfyvxyb21\%3E\%60_}:
% TODO : Finir le verbatim ci-dessous : "ils sont.... ??????"
\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}
@ -48,8 +53,7 @@ Les tests unitaires combinent de nombreuses fonctionnalités, qui en fait une ar
\begin{enumerate}
\item
Aide au design: écrire des tests avant d'écrire le code vous donnera
une meilleure perspective sur le design à appliquer aux API.
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
@ -98,10 +102,13 @@ Si nous rencontrons une condition, elle passera à 2, etc.
Cette complexité est liée à deux concepts:
\begin{itemize}
\item \textbf{La lisibilité du code}: au plus la complexité cyclomatique sera élevée, au plus le code sera compliqué à comprendre en première instance. Il sera composé de plusieurs conditions, éventuellement imbriquées, il débordera probablement de la hauteur que votre écran sera capable d'afficher
\item \textbf{Les tests unitaires}: pour nous assurer d'une couverture de code correcte, il sera nécessaire de couvrir tous les embranchements présentés. Connaître la complexité permet de savoir combien de tests devront être écrits pour assurer une couverture complète de tous les cas pouvant se présenter.
\item
\textbf{La lisibilité du code}: au plus la complexité cyclomatique sera élevée, au plus le code sera compliqué à comprendre en première instance. Il sera composé de plusieurs conditions, éventuellement imbriquées, il débordera probablement de la hauteur que votre écran sera capable d'afficher
\item
\textbf{Les tests unitaires}: pour nous assurer d'une couverture de code correcte, il sera nécessaire de couvrir tous les embranchements présentés. Connaître la complexité permet de savoir combien de tests devront être écrits pour assurer une couverture complète de tous les cas pouvant se présenter.
\end{itemize}
\section{Lisibilité du code}
Il est important de noter que refactoriser un bloc, par exemple en extrayant une méthode, n'améliore pas la complexité cyclomatique globale de l'application.
@ -195,14 +202,12 @@ Idéalement, chaque fonction ou méthode doit être testée afin de bien en vali
Cela permet d'isoler chaque bloc de manière unitaire, et permet de ne pas rencontrer de régression lors de l'ajout d'une nouvelle fonctionnalité ou de la modification d'une existante.
Il existe plusieurs types de tests (intégration, comportement, ...)
Avoir des tests, c'est bien. S'assurer que tout est testé, c'est mieux.
Avoir des tests, c'est bien.
S'assurer que tout est testé, c'est mieux.
C'est ici qu'il est utile d'avoir le pourcentage de code couvert par les différents tests, pour savoir ce qui peut être amélioré, le but du jeu consistant simplement à augmenter ou égaler le pourcentage de couverture de code existant avant chaque modification.
Gitlab permet de visualiser cette information de manière très propre, en l'affichant au niveau de chaque proposition d'intégration.
La couverture de code est une analyse qui donne un pourcentage lié à la
quantité de code couvert par les tests. Attention qu'il ne s'agit pas de
vérifier que le code est \textbf{bien} testé, mais juste de vérifier
\textbf{quelle partie} du code est testée. Le paquet \texttt{coverage}
se charge d'évaluer le pourcentage de code couvert par les tests.
La couverture de code est une analyse qui donne un pourcentage lié à la quantité de code couvert par les tests. Attention qu'il ne s'agit pas de vérifier que le code est \textbf{bien} testé, mais juste de vérifier \textbf{quelle partie} du code est testée.
Le paquet \texttt{coverage} se charge d'évaluer le pourcentage de code couvert par les tests.
\subsection{Tests d'acceptance}
@ -211,9 +216,7 @@ se charge d'évaluer le pourcentage de code couvert par les tests.
what the customer meant it to.
\end{quote}
Les tests dacceptance vérifient que lapplication fonctionne comme convenu, mais à un
plus haut niveau (fonctionnement correct dune API, validation dune chaîne dactions
effectuées par un humain, ...).
Les tests dacceptance vérifient que lapplication fonctionne comme convenu, mais à un plus haut niveau (fonctionnement correct dune API, validation dune chaîne dactions effectuées par un humain, ...).
\subsection{Tests d'intégration}

View File

@ -20,11 +20,7 @@ Si vous manquez d'idées ou si vous ne savez pas par où commencer:
\href{https://github.com/preservim/nerdtree}{nerdtree}
\end{itemize}
Si vous hésitez, et même si Codium n'est pas le plus léger (la faute à
\href{https://www.electronjs.org/}{Electron}\ldots\hspace{0pt}), il fera
correctement son travail (à savoir: faciliter le vôtre), en intégrant
suffisament de fonctionnalités qui gâteront les papilles émoustillées du
développeur impatient.
Si vous hésitez, et même si Codium n'est pas le plus léger (la faute à \href{https://www.electronjs.org/}{Electron}\ldots\hspace{0pt}), il fera correctement son travail (à savoir : faciliter le vôtre), en intégrant suffisament de fonctionnalités qui gâteront les papilles émoustillées du développeur impatient.
\begin{figure}[H]
\centering
@ -39,7 +35,6 @@ développeur impatient.
\subsection{Mode debug - launch.json}
\section{Terminal}
\emph{A priori}, les IDE proposés ci-dessus fournissent par défaut ou \emph{via} des greffons un terminal intégré.
@ -47,13 +42,11 @@ Ceci dit, disposer d'un terminal séparé facilite parfois certaines tâches.
A nouveau, si vous manquez d'idées :
\begin{enumerate}
\item Soit vous utilisez celui qui intégré à VSCodium et qui fera suffisament bien son travail
\item
Si vous êtes sous Windows, téléchargez une copie de
\href{https://cmder.net/}{Cmder}. Il n'est pas le plus rapide, mais
propose une intégration des outils Unix communs (\texttt{ls},
\texttt{pwd}, \texttt{grep}, \texttt{ssh}, \texttt{git},
\ldots\hspace{0pt}) sans trop se fouler.
Soit vous utilisez celui qui intégré à VSCodium et qui fera suffisament bien son travail
\item
Si vous êtes sous Windows, téléchargez une copie de \href{https://cmder.net/}{Cmder}.
Il n'est pas le plus rapide, mais propose une intégration des outils Unix communs (\texttt{ls}, \texttt{pwd}, \texttt{grep}, \texttt{ssh}, \texttt{git}, \ldots\hspace{0pt}) sans trop se fouler.
\item
Pour tout autre système, vous devriez disposer en natif de ce qu'il faut.
\end{enumerate}
@ -98,8 +91,7 @@ De cette manière, il est beaucoup plus facile pour le développeur de se concen
\caption{Git en action}
\end{figure}
Cas pratique: vous développez cette nouvelle fonctionnalité qui va révolutionner le monde de demain et d'après-demain, quand, tout à coup (!), vous vous rendez compte que vous avez perdu votre conformité aux
normes PCI parce les données des titulaires de cartes ne sont pas isolées correctement.
Cas pratique: vous développez cette nouvelle fonctionnalité qui va révolutionner le monde de demain et d'après-demain, quand, tout à coup (!), vous vous rendez compte que vous avez perdu votre conformité aux normes PCI parce les données des titulaires de cartes ne sont pas isolées correctement.
Il suffit alors de:
\begin{enumerate}
@ -112,11 +104,9 @@ Il suffit alors de:
\item
Solutionner le problème (sans doute un \texttt{;} en trop ?)
\item
Sauver le correctif sur cette branche
(\texttt{git\ add\ .\ \&\&\ git\ commit\ -m\ "Did\ it!"})
Sauver le correctif sur cette branche (\texttt{git\ add\ .\ \&\&\ git\ commit\ -m\ "Did\ it!"})
\item
Récupérer ce correctif sur la branche principal
(\texttt{git\ checkout\ main\ \&\&\ git\ merge\ hotfix/pci-compliance})
Récupérer ce correctif sur la branche principal (\texttt{git\ checkout\ main\ \&\&\ git\ merge\ hotfix/pci-compliance})
\item
Et revenir tranquillou sur votre branche de développement pour fignoler ce générateur de noms de dinosaures rigolos que l'univers vous réclame à cor et à a cri (\texttt{git\ checkout\ features/dinolol})
\end{enumerate}
@ -179,22 +169,15 @@ Most websites don't need that kind of throughput. \cite{consider_sqlite}
\subsection{Gestionnaires}
Il n'est pas obligatoire de disposer d'une application de gestion pour ces moteurs: pour les cas d'utilisation simples, le shell Django pourra largement suffire (nous y reviendrons).
Mais pour faciliter la gestion des bases de données elles-même, et si vous n'êtes pas à l'aise avec la
ligne de commande, choisissez l'une des applications d'administration
ci-dessous en fonction du moteur de base de données que vous souhaitez
utiliser.
Mais pour faciliter la gestion des bases de données elles-même, et si vous n'êtes pas à l'aise avec la ligne de commande, choisissez l'une des applications d'administration ci-dessous en fonction du moteur de base de données que vous souhaitez utiliser.
\begin{itemize}
\item
Pour \textbf{PostgreSQL}, il existe
\href{https://www.pgadmin.org/}{pgAdmin}
Pour \textbf{PostgreSQL}, il existe \href{https://www.pgadmin.org/}{pgAdmin}
\item
Pour \textbf{MariaDB} ou \textbf{MySQL}, partez sur
\href{https://www.phpmyadmin.net/}{PHPMyAdmin}
Pour \textbf{MariaDB} ou \textbf{MySQL}, partez sur \href{https://www.phpmyadmin.net/}{PHPMyAdmin}
\item
Pour \textbf{SQLite}, il existe
\href{https://sqlitebrowser.org/}{SQLiteBrowser} PHPMyAdmin ou
PgAdmin.
Pour \textbf{SQLite}, il existe \href{https://sqlitebrowser.org/}{SQLiteBrowser} PHPMyAdmin ou PgAdmin.
\end{itemize}
\section{Intégration continue}
@ -202,4 +185,3 @@ utiliser.
\subsection{Qualité du code}
Code climate, SonarQube.

View File

@ -1,10 +1,9 @@
\chapter{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:
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{minted}{python}
# gwift/urls.py
@ -18,24 +17,12 @@ urlpatterns = [
\end{minted}
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, pour reprendre l'exemple où nous étions restés:
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, pour reprendre l'exemple où nous étions restés:
\begin{minted}{python}
# gwift/urls.py
@ -51,18 +38,12 @@ urlpatterns = [
]
\end{minted}
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.
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}.
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:
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
@ -70,32 +51,21 @@ de la manière suivante:
| <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.
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.
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.
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.
\section{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}
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):
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{minted}{python}
from wish.views import WishListList
@ -106,17 +76,13 @@ urlpatterns = [
]
\end{minted}
De cette manière, dans nos templates, on peut à présent construire un
lien vers la racine avec le tags suivant:
De cette manière, dans nos templates, on peut à présent construire un lien vers la racine avec le tags suivant:
\begin{minted}{html}
<a href="{% url 'wishlists' %}">{{ yearvar }} Archive</a>
\end{minted}
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:
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{minted}{python}
from django.core.urlresolvers import reverse_lazy

View File

@ -1,16 +1,10 @@
\chapter{Travailler en isolation}
Nous allons aborder la gestion et l'isolation des dépendances. Cette
section est aussi utile pour une personne travaillant seule, que pour
transmettre les connaissances à un nouveau membre de l'équipe ou pour
déployer l'application elle-même.
Nous allons aborder la gestion et l'isolation des dépendances. Cette section est aussi utile pour une personne travaillant seule, que pour transmettre les connaissances à un nouveau membre de l'équipe ou pour déployer l'application elle-même.
Il en était déjà question au deuxième point des 12 facteurs: même dans
le cas de petits projets, il est déconseillé de s'en passer. Cela évite
les déploiements effectués à l'arrache à grand renfort de \texttt{sudo}
et d'installation globale de dépendances, pouvant potentiellement
occasioner des conflits entre les applications déployées:
Il en était déjà question au deuxième point des 12 facteurs: même dans le cas de petits projets, il est déconseillé de s'en passer.
Cela évite les déploiements effectués à l'arrache à grand renfort de \texttt{sudo} et d'installation globale de dépendances, pouvant potentiellement occasioner des conflits entre les applications déployées:
\begin{enumerate}
\item
@ -33,9 +27,9 @@ occasioner des conflits entre les applications déployées:
\caption{\url{https://xkcd.com/1987}}
\end{figure}
Un des reproches que l'on peut faire au langage concerne sa versatilité: il est possible de réaliser beaucoup de choses, mais celles-ci ne sont
pas toujours simples ou directes.
Pour quelqu'un qui débarquererait, la quantité d'options différentes peut paraître rebutante. Nous pensons notamment aux environnements virtuels: ils sont géniaux à utiliser, mais on est passé par virtualenv (l'ancêtre), virtualenvwrapper (sa version améliorée et plus ergonomique), \texttt{venv} (la version intégrée depuis la version 3.3 de l'interpréteur, et \href{https://docs.python.org/3/library/venv.html}{la manière recommandée} de créer un environnement depuis la 3.5).
Un des reproches que l'on peut faire au langage concerne sa versatilité: il est possible de réaliser beaucoup de choses, mais celles-ci ne sont pas toujours simples ou directes.
Pour quelqu'un qui débarquererait, la quantité d'options différentes peut paraître rebutante.
Nous pensons notamment aux environnements virtuels : ils sont géniaux à utiliser, mais on est passé par virtualenv (l'ancêtre), virtualenvwrapper (sa version améliorée et plus ergonomique), \texttt{venv} (la version intégrée depuis la version 3.3 de l'interpréteur, et \href{https://docs.python.org/3/library/venv.html}{la manière recommandée} de créer un environnement depuis la 3.5).
Pour créer un nouvel environnement, vous aurez donc besoin:
@ -48,8 +42,7 @@ Pour créer un nouvel environnement, vous aurez donc besoin:
Il existe plusieurs autres modules permettant d'arriver au même résultat, avec quelques avantages et inconvénients pour chacun d'entre eux.
Le plus prometteur d'entre eux est \href{https://python-poetry.org/}{Poetry}, qui dispose d'une interface
en ligne de commande plus propre et plus moderne que ce que PIP propose.
Le plus prometteur d'entre eux est \href{https://python-poetry.org/}{Poetry}, qui dispose d'une interface en ligne de commande plus propre et plus moderne que ce que PIP propose.
\subsection{Poetry}
@ -77,8 +70,7 @@ 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
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
@ -105,37 +97,26 @@ 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.
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/}.
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.
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.
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:
Pur créer notre répertoire de travail et notre environnement virtuel, exécutez les commandes suivantes:
\begin{verbatim}
mkdir ~/.venvs/
python -m venv ~/.venvs/gwift-venv
\end{verbatim}
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:
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{verbatim}
# GNU/Linux, macOS
@ -147,46 +128,31 @@ souhaitons l'utiliser, grâce à l'une des commandes suivantes:
\end{verbatim}
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
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 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 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.
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.
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é.
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=.
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:
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
@ -297,8 +263,7 @@ Ce qui est quand même 'achement plus simple que d'appréhender tout un
\section{Un système de virtualisation}
Par "\emph{système de virtualisation}", nous entendons n'importe quel application, système d'exploitation, système de containeurisation, ... qui permette de créer ou recréer un environnement de
développement aussi proche que celui en production.
Par "\emph{système de virtualisation}", nous entendons n'importe quel application, système d'exploitation, système de containeurisation, ... qui permette de créer ou recréer un environnement de développement aussi proche que celui en production.
Les solutions sont nombreuses:
\begin{itemize}
@ -331,32 +296,19 @@ Vagrant consiste en un outil de création et de gestion d'environnements virtual
the past. \footnote{\url{https://www.vagrantup.com/intro}}
\end{quote}
La partie la plus importante de la configuration de Vagrant pour votre
projet consiste à placer un fichier \texttt{Vagrantfile} - \emph{a
priori} à la racine de votre projet - et qui contiendra les information
suivantes:
La partie la plus importante de la configuration de Vagrant pour votre projet consiste à placer un fichier \texttt{Vagrantfile} - \emph{a priori} à la racine de votre projet - et qui contiendra les information suivantes:
\begin{itemize}
\item
Le choix du \emph{fournisseur} (\textbf{provider}) de virtualisation
(Virtualbox, Hyper-V et Docker sont natifs; il est également possible
de passer par VMWare, AWS, etc.)
Le choix du \emph{fournisseur} (\textbf{provider}) de virtualisation (Virtualbox, Hyper-V et Docker sont natifs; il est également possible de passer par VMWare, AWS, etc.)
\item
Une \emph{box}, qui consiste à lui indiquer le type et la version
attendue du système virtualisé (Debian 10, Ubuntu 20.04, etc. - et
\href{https://app.vagrantup.com/boxes/search}{il y a du choix}).
Une \emph{box}, qui consiste à lui indiquer le type et la version attendue du système virtualisé (Debian 10, Ubuntu 20.04, etc. - et \href{https://app.vagrantup.com/boxes/search}{il y a du choix}).
\item
La manière dont la fourniture (\textbf{provisioning}) de
l'environnement doit être réalisée: scripts Shell, fichiers, Ansible,
Puppet, Chef, \ldots\hspace{0pt} Choisissez votre favori :-) même s'il
est toujours possible de passer par une installation et une
maintenance manuelle, après s'être connecté sur la machine.
La manière dont la fourniture (\textbf{provisioning}) de l'environnement doit être réalisée : scripts Shell, fichiers, Ansible, Puppet, Chef, \ldots\hspace{0pt} Choisissez votre favori :-) même s'il est toujours possible de passer par une installation et une maintenance manuelle, après s'être connecté sur la machine.
\item
Si un espace de stockage doit être partagé entre la machine virtuelle
et l'hôte
Si un espace de stockage doit être partagé entre la machine virtuelle et l'hôte
\item
Les ports qui doivent être transmis de la machine virtuelle vers
l'hôte.
Les ports qui doivent être transmis de la machine virtuelle vers l'hôte.
\end{itemize}
La syntaxe de ce fichier \texttt{Vagrantfile} est en \href{https://www.ruby-lang.org/en/}{Ruby}.

View File

@ -18,13 +18,15 @@ Il y a une raison très simple à aborder le déploiement dès maintenant: à tr
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é.
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
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.
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
@ -34,34 +36,33 @@ disruptions such as service outages, service impairments, or security or complia
\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.
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.
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}}
\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
\def\labelenumi{\arabic{enumi}.} % TODO : a revoir -> définir des theme dans un fifhier STY non ?
\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}
@ -69,33 +70,21 @@ Dans cette partie, nous aborderons les points suivants:
\begin{enumerate}
\item
Définir l'infrastructure et les composants nécessaires à notre
application
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.
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.
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}
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.
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
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.
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}

View File

@ -11,8 +11,7 @@ Le code qui permet de faire tourner cette application peut ne pas être élégan
Les problèmes arriveront lorsqu'une nouvelle demande sera introduite, lorsqu'un bug sera découvert et devra être corrigé ou lorsqu'une dépendance cessera de fonctionner ou d'être disponible.
Or, une application qui n'évolue pas, meurt.
Toute application est donc destinée, soit à être modifiée, corrigée et suivie, soit à déperrir et à être
délaissée par ses utilisateurs.
Toute application est donc destinée, soit à être modifiée, corrigée et suivie, soit à déperrir et à être délaissée par ses utilisateurs.
Et c'est juste cette maintenance qui est difficile.
L'application des principes présentés et agrégés ci-dessous permet surtout de préparer correctement tout ce qui pourra arriver, sans aller jusqu'au « \textbf{You Ain't Gonna Need It} » (ou \textbf{YAGNI\index{YAGNI}}), qui consiste à surcharger tout développement avec des fonctionnalités non demandées, juste « au cas ou ».
@ -30,40 +29,23 @@ Le développement d'un logiciel nécessite une rigueur d'exécution et des conna
Il nécessite également des intentions, des (bonnes) décisions et énormément d'attention.
Indépendamment de l'architecture que vous aurez choisie, des technologies que vous aurez patiemment évaluées et mises en place, une architecture et une solution peuvent être cassées en un instant, en même temps que tout ce que vous aurez construit, dès que vous en aurez détourné le regard.
Un des objectifs ici est de placer les barrières et les gardes-fous (ou
plutôt, les "\textbf{garde-vous}"), afin de péréniser au maximum les
acquis, stabiliser les bases de tous les environnements (du
développement à la production) qui accueilliront notre application et
fiabiliser ainsi chaque étape de communication.
Un des objectifs ici est de placer les barrières et les gardes-fous (ou plutôt, les "\textbf{garde-vous}"), afin de péréniser au maximum les acquis, stabiliser les bases de tous les environnements (du développement à la production) qui accueilliront notre application et fiabiliser ainsi chaque étape de communication.
Dans cette partie-ci, nous parlerons de \textbf{méthodes de travail},
avec comme objectif d'éviter que l'application ne tourne que sur notre
machine et que chaque déploiement ne soit une plaie à gérer. Chaque mise
à jour doit être réalisable de la manière la plus simple possible, et
chaque étape doit être rendue la plus automatisée/automatisable
possible. Dans son plus simple élément, une application pourrait être
mise à jour simplement en envoyant son code sur un dépôt centralisé: ce
déclencheur doit démarrer une chaîne de vérification
d'utilisabilité/fonctionnalités/débuggabilité/sécurité, pour
immédiatement la mettre à disposition de nouveaux utilisateurs si toute
la chaîne indique que tout est OK. D'autres mécanismes fonctionnent
également, mais au plus les actions nécessitent d'actions humaines,
voire d'intervenants humains, au plus la probabilité qu'un problème
survienne est grande.
Dans cette partie-ci, nous parlerons de \textbf{méthodes de travail}, avec comme objectif d'éviter que l'application ne tourne que sur notre machine et que chaque déploiement ne soit une plaie à gérer.
Chaque mise à jour doit être réalisable de la manière la plus simple possible, et chaque étape doit être rendue la plus automatisée/automatisable possible.
Dans son plus simple élément, une application pourrait être mise à jour simplement en envoyant son code sur un dépôt centralisé: ce déclencheur doit démarrer une chaîne de vérification d'utilisabilité/fonctionnalités/débuggabilité/sécurité, pour immédiatement la mettre à disposition de nouveaux utilisateurs si toute la chaîne indique que tout est OK.
D'autres mécanismes fonctionnent également, mais au plus les actions nécessitent d'actions humaines, voire d'intervenants humains, au plus la probabilité qu'un problème survienne est grande.
Dans une version plus manuelle, cela pourrait se résumer à ces trois
étapes (la dernière étant formellement facultative):
Dans une version plus manuelle, cela pourrait se résumer à ces trois étapes (la dernière étant formellement facultative) :
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
Démarrer un script,
\item
Prévoir un rollback si cela plante (et si cela a planté, préparer un
post-mortem de l'incident pour qu'il ne se produise plus)
Prévoir un rollback si cela plante (et si cela a planté, préparer un post-mortem de l'incident pour qu'il ne se produise plus)
\item
Se préparer une tisane en regardant nos flux RSS (pour peu que cette
technologie existe encore\ldots\hspace{0pt}).
Se préparer une tisane en regardant nos flux RSS (pour peu que cette technologie existe encore\ldots\hspace{0pt}).
\end{enumerate}
\begin{quote}

View File

@ -3,12 +3,10 @@
Dans cette partie, 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.
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.
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}.
@ -26,11 +24,9 @@ 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.
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.
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}

View File

@ -1,21 +1,21 @@
\part{Services Oriented Applications}
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.
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.
\begin{quote}
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}
\end{quote}
On a parcouru les templates et le mode "monolithique de DJango".
Maintenant, on va aborder différentes options:
\begin{enumerate}
\item Le mode "intermédiaire", qui consiste à garder tous les mécanismes internes à Django, mais à ajouter une couche de dynamisme au travers d'une (légère) couche de JavaScript ou via HTMX.
\item Le mode "warrior", qui consiste lui à ajouter une API, d'abord pour vos clients et utilisateurs, mais aussi pour votre propre consommation \footnote{Aussi intitulé "Eat your own dog's food"}
\item
Le mode "intermédiaire", qui consiste à garder tous les mécanismes internes à Django, mais à ajouter une couche de dynamisme au travers d'une (légère) couche de JavaScript ou via HTMX.
\item
Le mode "warrior", qui consiste lui à ajouter une API, d'abord pour vos clients et utilisateurs, mais aussi pour votre propre consommation \footnote{Aussi intitulé "Eat your own dog's food"}
\end{enumerate}