Reformatage de fichier, de code. Je mets de pour des commentaires à l'intérieur des fichiers.
continuous-integration/drone/pr Build is failing Details
continuous-integration/drone/push Build is failing Details

This commit is contained in:
Gregory Trullemans 2022-05-02 22:07:57 +02:00
parent d6d282a1e3
commit d1bcbbe1d4
10 changed files with 609 additions and 971 deletions

View File

@ -3,34 +3,26 @@
(c/c Ced' - 2020-01-24) (c/c Ced' - 2020-01-24)
Ça y est, j'ai fait un test sur mon portable avec docker et cookiecutter Ça y est, j'ai fait un test sur mon portable avec docker et cookiecutter pour django.
pour django.
D'abords, après avoir installer docker-compose et les dépendances sous 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.
debian, tu dois t'ajouter dans le groupe docker, sinon il faut être root Ensuite, j'ai relancé mon pc car juste relancé un shell n'a pas suffit pour que je puisse utiliser docker avec mon compte.
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 Bon après c'est facile, un petit virtualenv pour cookiecutter, suivit d'une installation du template django.
d'une installation du template django. Et puis j'ai suivi sans t Et puis j'ai suivi sans t
\url{https://cookiecutter-django.readthedocs.io/en/latest/developing-locally-docker.html} \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 Alors, il télécharge les images, fait un petit update, installe les dépendances de dev, install les requirement pip \ldots\hspace{0pt}
dépendances de dev, install les requirement pip \ldots\hspace{0pt}
Du coup, ça prend vite de la place: image.png 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 L'image de base python passe de 179 à 740 MB.
1,5 GB d'un coup. 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 Mais par contre, j'ai un python 3.7 direct et postgres 10 sans rien faire ou presque.
faire ou presque.
La partie ci-dessous a été reprise telle quelle de 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}.
\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 le serveur de déploiement ne doit avoir qu'un accès en lecture au dépôt source.
source.
On peut aussi passer par fabric, ansible, chef ou puppet. On peut aussi passer par fabric, ansible, chef ou puppet.

View File

@ -1,51 +1,33 @@
\chapter{Filtres} \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.
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
A ce stade, nous pouvons juste récupérer des informations présentes dans lui-même.
notre base de données, mais à part les parcourir, il est difficile d'en Une solution élégante consiste à autoriser le consommateur à filtrer les données, directement au niveau de l'API.
faire quelque chose. 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} \begin{enumerate}
\item \item
Soit au travers d'une recherche, qui permet d'effectuer une recherche Soit au travers d'une recherche, qui permet d'effectuer une recherche textuelle, globale et par ensemble à un ensemble de champs,
textuelle, globale et par ensemble à un ensemble de champs, \item
\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} \end{enumerate}
Dans notre exemple, la première possibilité sera utile pour rechercher Dans notre exemple, la première possibilité sera utile pour rechercher une personne répondant à un ensemble de critères.
une personne répondant à un ensemble de critères. Typiquement, 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}).
\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 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.
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 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 :
proposer immédiatement les champs, et donc d'informer le consommateur
des possibilités:
\includegraphics{images/rest/drf-filters-and-searches.png} \includegraphics{images/rest/drf-filters-and-searches.png}
\section{Recherches}
La fonction de recherche est déjà implémentée au niveau de \section{Recherches}
Django-Rest-Framework, et aucune dépendance supplémentaire n'est 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.
nécessaire. Au niveau du \texttt{viewset}, il suffit d'ajouter deux Au niveau du \texttt{viewset}, il suffit d'ajouter deux informations:
informations:
\begin{minted}{python} \begin{minted}{python}
... ...
@ -81,20 +63,18 @@ et nous l'ajoutons parmi les applications installées:
Successfully installed django-filter-2.4.0 Successfully installed django-filter-2.4.0
\end{verbatim} \end{verbatim}
Une fois l'installation réalisée, il reste deux choses à faire: Une fois l'installation réalisée, il reste deux choses à faire :
\begin{enumerate} \begin{enumerate}
\def\labelenumi{\arabic{enumi}.} \def\labelenumi{\arabic{enumi}.}
\item \item
Ajouter \texttt{django\_filters} parmi les applications installées: Ajouter \texttt{django\_filters} parmi les applications installées :
\item \item
Configurer la clé \texttt{DEFAULT\_FILTER\_BACKENDS} à la valeur Configurer la clé \texttt{DEFAULT\_FILTER\_BACKENDS} à la valeur \texttt{{[}\textquotesingle{}django\_filters.rest\_framework.DjangoFilterBackend\textquotesingle{}{]}}.
\texttt{{[}\textquotesingle{}django\_filters.rest\_framework.DjangoFilterBackend\textquotesingle{}{]}}. \end{enumerate}
\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} \begin{minted}{python}
REST_FRAMEWORK = { REST_FRAMEWORK = {
'DEFAULT_PAGINATION_CLASS': 'DEFAULT_PAGINATION_CLASS':
@ -125,28 +105,19 @@ class PeopleViewSet(viewsets.ModelViewSet):
A ce stade, nous avons deux problèmes: A ce stade, nous avons deux problèmes:
\begin{enumerate} \begin{enumerate}
\def\labelenumi{\arabic{enumi}.} \def\labelenumi{\arabic{enumi}.}
\item \item
Le champ que nous avons défini au niveau de la propriété Le champ que nous avons défini au niveau de la propriété \texttt{filterset\_fields} exige une correspondance exacte.
\texttt{filterset\_fields} exige une correspondance exacte. Ainsi, Ainsi, \texttt{/api/v1/people/?last\_name=Bon} ne retourne rien, alors que \texttt{/api/v1/people/?last\_name=Bond} nous donnera notre agent
\texttt{/api/v1/people/?last\_name=Bon} ne retourne rien, alors que secret préféré.
\texttt{/api/v1/people/?last\_name=Bond} nous donnera notre agent \item
secret préféré. Il n'est pas possible d'aller appliquer un critère de sélection sur la propriété d'une relation.
\item 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} \end{enumerate}
Pour ces deux points, nous allons définir un nouveau filtre, en 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}.
surchargeant une nouvelle classe dont la classe mère serait de type
\texttt{django\_filters.FilterSet}.
TO BE CONTINUED. TO BE CONTINUED.
A noter qu'il existe un paquet 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.
{[}Django-Rest-Framework-filters{]}(\url{https://github.com/philipn/django-rest-framework-filters}), Il faut donc le faire à la main (ou patcher le paquet\ldots\hspace{0pt}).
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,44 +2,38 @@
\includegraphics{images/xkcd-327.png} \includegraphics{images/xkcd-327.png}
\begin{quote} \begin{quote}
Le form, il s'assure que l'utilisateur n'a pas encodé de conneries et Le form, il s'assure que l'utilisateur n'a pas encodé de conneries et que l'ensemble reste cohérent.
que l'ensemble reste cohérent. Il (le form) n'a pas à savoir que tu as Il (le form) n'a pas à savoir que tu as implémenté des closure tables dans un graph dirigé acyclique.
implémenté des closure tables dans un graph dirigé acyclique.
\end{quote} \end{quote}
Ou comment valider proprement des données entrantes. Ou comment valider proprement des données entrantes.
Quand on parle de \texttt{forms}, on ne parle pas uniquement de formulaires Web. Quand on parle de \texttt{forms}, on ne parle pas uniquement de formulaires Web.
On pourrait considérer qu'il s'agit de leur objectif principal, mais on peut également voir un peu plus loin: on peut en fait voir les \texttt{forms} comme le point d'entrée pour chaque donnée arrivant dans notre application: il s'agit en quelque sorte d'un ensemble de règles complémentaires à celles déjà présentes au niveau du modèle. % 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. 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 Mauvaise idée.
de \href{https://docs.python.org/3/library/csv.html\#csv.DictReader}{DictReader}), et la version + à base de form. 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. 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. Notre base de données étant accessible ici par l'ORM, la solution consiste à introduire une couche supplémentaire de validation.
Le flux à suivre est le suivant: Le flux à suivre est le suivant:
\begin{enumerate} \begin{enumerate}
\item \item Création d'une instance grâce à un dictionnaire
Création d'une instance grâce à un dictionnaire \item Validation des données et des informations reçues
\item \item Traitement, si la validation a réussi.
Validation des données et des informations reçues
\item
Traitement, si la validation a réussi.
\end{enumerate} \end{enumerate}
Ils jouent également deux rôles importants: Ils jouent également deux rôles importants:
\begin{enumerate} \begin{enumerate}
\item \item Valider des données, en plus de celles déjà définies au niveau du modèle
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
Contrôler le rendu à appliquer aux champs.
\end{enumerate} \end{enumerate}
Ils agissent come une glue entre l'utilisateur et la modélisation de vos structures de données. Ils agissent come une glue entre l'utilisateur et la modélisation de vos structures de données.
@ -53,7 +47,7 @@ A compléter ;-)
\section{Dépendance avec le modèle} \section{Dépendance avec le modèle}
Un \textbf{form} peut dépendre d'une autre classe Django. Un \textbf{form} peut dépendre d'une autre classe Django.
Pour cela, il suffit de fixer l'attribut \texttt{model} au niveau de la \texttt{class\ Meta} dans la définition. Pour cela, il suffit de fixer l'attribut \texttt{model} au niveau de la \texttt{class\ Meta} dans la définition.
\begin{minted}{python} \begin{minted}{python}
@ -68,16 +62,15 @@ class WishlistCreateForm(forms.ModelForm):
fields = ('name', 'description') fields = ('name', 'description')
\end{minted} \end{minted}
De cette manière, notre form dépendra automatiquement des champs déjà déclarés dans la classe \texttt{Wishlist}. % TODO: je comprends pas pourquoi il y a autant de \ dans le paragraphe.
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}, De cette manière, notre form dépendra automatiquement des champs déjà déclarés dans la classe \texttt{Wishlist}.
nous pourrons nous assurer de faire évoluer le formulaire en fonction du 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.
modèle sur lequel il se base.
\section{Rendu et affichage} \section{Rendu et affichage}
Le formulaire permet également de contrôler le rendu qui sera appliqué lors de la génération de la page. Le formulaire permet également de contrôler le rendu qui sera appliqué lors de la génération de la page.
Si les champs dépendent du modèle sur lequel se base le formulaire, ces widgets doivent être initialisés dans Si les champs dépendent du modèle sur lequel se base le formulaire, ces widgets doivent être initialisés dans
l'attribut \texttt{Meta}. l'attribut \texttt{Meta}.
Sinon, ils peuvent l'être directement au niveau du champ. Sinon, ils peuvent l'être directement au niveau du champ.
\subsection{Squelette par défaut} \subsection{Squelette par défaut}
@ -86,36 +79,27 @@ On a d'un côté le \{\{ form.as\_p \}\} ou \{\{ form.as\_table \}\}, mais il y
\subsection{Crispy-forms} \subsection{Crispy-forms}
Comme on l'a vu à l'instant, les forms, en Django, c'est le bien. Cela Comme on l'a vu à l'instant, les forms, en Django, c'est le bien.
permet de valider des données reçues en entrée et d'afficher (très) 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.
facilement des formulaires à compléter par l'utilisateur.
Par contre, c'est lourd. Dès qu'on souhaite peaufiner un peu Par contre, c'est lourd.
l'affichage, contrôler parfaitement ce que l'utilisateur doit remplir, 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.
modifier les types de contrôleurs, les placer au pixel près, Et c'est là qu'intervient \href{http://django-crispy-forms.readthedocs.io/en/latest/}{Django-Crispy-Forms}.
\ldots\hspace{0pt} Tout ça demande énormément de temps. Et c'est là 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.
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) (c/c depuis le lien ci-dessous)
Pour chaque champ, crispy-forms va : Pour chaque champ, crispy-forms va :
\begin{itemize} \begin{itemize}
\item \item
utiliser le \texttt{verbose\_name} comme label. utiliser le \texttt{verbose\_name} comme label.
\item \item
vérifier les paramètres \texttt{blank} et \texttt{null} pour savoir si vérifier les paramètres \texttt{blank} et \texttt{null} pour savoir si le champ est obligatoire.
le champ est obligatoire. \item
\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 \item
\texttt{\textless{}input\textgreater{}}. récupérer les valeurs du paramètre \texttt{choices} (si présent) pour la balise \texttt{\textless{}select\textgreater{}}.
\item
récupérer les valeurs du paramètre \texttt{choices} (si présent) pour
la balise \texttt{\textless{}select\textgreater{}}.
\end{itemize} \end{itemize}
\url{http://dotmobo.github.io/django-crispy-forms.html} \url{http://dotmobo.github.io/django-crispy-forms.html}

View File

@ -6,548 +6,397 @@
\caption{Gwift} \caption{Gwift}
\end{figure} \end{figure}
Pour prendre un exemple concret, nous allons créer un site permettant de 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} :)).
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 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.
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} \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 : Il sera nécessaire de s'authentifier pour :
\begin{itemize} \begin{itemize}
\item \item Créer une liste associée à l'utilisateur en cours
Créer une liste associée à l'utilisateur en cours \item Ajouter un nouvel élément à une liste
\item
Ajouter un nouvel élément à une liste
\end{itemize} \end{itemize}
Il ne sera pas nécessaire de s'authentifier pour : Il ne sera pas nécessaire de s'authentifier pour :
\begin{itemize} \begin{itemize}
\item \item
Faire une promesse d'offre pour un élément appartenant à une liste, Faire une promesse d'offre pour un élément appartenant à une liste, associée à un utilisateur.
associée à un utilisateur.
\end{itemize} \end{itemize}
L'utilisateur ayant créé une liste pourra envoyer un email directement 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.
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. A chaque souhait, on pourrait de manière facultative ajouter un prix.
Dans ce cas, le souhait pourrait aussi être subdivisé en plusieurs Dans ce cas, le souhait pourrait aussi être subdivisé en plusieurs parties, de manière à ce que plusieurs personnes puissent participer à sa réalisation.
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} \section{Besoins fonctionnels}
\subsection{Gestion des utilisateurs} \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 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.
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.
\subsection{Gestion des listes} \subsection{Gestion des listes}
\subsubsection{Modélisation} \subsubsection{Modélisation}
Les données suivantes doivent être associées à une liste :
Les données suivantes doivent être associées à une liste:
\begin{itemize} \begin{itemize}
\item \item un identifiant
un identifiant \item un identifiant externe (un GUID, par exemple)
\item \item un nom
un identifiant externe (un GUID, par exemple) \item une description
\item \item le propriétaire, associé à l'utilisateur qui l'aura créée
un nom \item une date de création
\item \item une date de modification
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} \end{itemize}
\subsubsection{Fonctionnalités} \subsubsection{Fonctionnalités}
\begin{itemize} \begin{itemize}
\item \item
Un utilisateur authentifié doit pouvoir créer, modifier, désactiver et Un utilisateur authentifié doit pouvoir créer, modifier, désactiver et supprimer une liste dont il est le propriétaire
supprimer une liste dont il est le propriétaire
\item \item
Un utilisateur doit pouvoir associer ou retirer des souhaits à une Un utilisateur doit pouvoir associer ou retirer des souhaits à une liste dont il est le propriétaire
liste dont il est le propriétaire
\item \item
Il faut pouvoir accéder à une liste, avec un utilisateur authentifier Il faut pouvoir accéder à une liste, avec un utilisateur authentifier ou non, \textbf{via} son identifiant externe
ou non, \textbf{via} son identifiant externe
\item \item
Il faut pouvoir envoyer un email avec le lien vers la liste, contenant Il faut pouvoir envoyer un email avec le lien vers la liste, contenant son identifiant externe
son identifiant externe
\item \item
L'utilisateur doit pouvoir voir toutes les listes qui lui L'utilisateur doit pouvoir voir toutes les listes qui lui appartiennent
appartiennent
\end{itemize} \end{itemize}
\subsection{Gestion des souhaits} \subsection{Gestion des souhaits}
\subsubsection{Modélisation} \subsubsection{Modélisation}
Les données suivantes peuvent être associées à un souhait :
Les données suivantes peuvent être associées à un souhait:
\begin{itemize} \begin{itemize}
\item \item un identifiant
un identifiant \item identifiant de la liste
\item \item un nom
identifiant de la liste \item une description
\item \item le propriétaire
un nom \item une date de création
\item \item une date de modification
une description \item une image, afin de représenter l'objet ou l'idée
\item \item un nombre (1 par défaut)
le propriétaire \item un prix facultatif
\item \item un nombre de part, facultatif également, si un prix est fourni.
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} \end{itemize}
\subsubsection{Fonctionnalités} \subsubsection{Fonctionnalités}
\begin{itemize} \begin{itemize}
\item \item
Un utilisateur authentifié doit pouvoir créer, modifier, désactiver et Un utilisateur authentifié doit pouvoir créer, modifier, désactiver et supprimer un souhait dont il est le propriétaire.
supprimer un souhait dont il est le propriétaire.
\item \item
On ne peut créer un souhait sans liste associée On ne peut créer un souhait sans liste associée
\item \item
Il faut pouvoir fractionner un souhait uniquement si un prix est Il faut pouvoir fractionner un souhait uniquement si un prix est donné.
donné.
\item \item
Il faut pouvoir accéder à un souhait, avec un utilisateur authentifié Il faut pouvoir accéder à un souhait, avec un utilisateur authentifié ou non.
ou non.
\item \item
Il faut pouvoir réaliser un souhait ou une partie seulement, avec un Il faut pouvoir réaliser un souhait ou une partie seulement, avec un utilisateur authentifié ou non.
utilisateur authentifié ou non.
\item \item
Un souhait en cours de réalisation et composé de différentes parts ne Un souhait en cours de réalisation et composé de différentes parts ne peut plus être modifié.
peut plus être modifié.
\item \item
Un souhait en cours de réalisation ou réalisé ne peut plus être Un souhait en cours de réalisation ou réalisé ne peut plus être supprimé.
supprimé.
\item \item
On peut modifier le nombre de fois qu'un souhait doit être réalisé On peut modifier le nombre de fois qu'un souhait doit être réalisé dans la limite des réalisations déjà effectuées.
dans la limite des réalisations déjà effectuées.
\end{itemize} \end{itemize}
\subsection{Réalisation d'un souhait} \subsection{Réalisation d'un souhait}
\subsubsection{Modélisation} \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} \begin{itemize}
\item \item identifiant du souhait
identifiant du souhait \item identifiant de l'utilisateur si connu
\item \item identifiant de la personne si utilisateur non connu
identifiant de l'utilisateur si connu \item un commentaire
\item \item une date de réalisation
identifiant de la personne si utilisateur non connu
\item
un commentaire
\item
une date de réalisation
\end{itemize} \end{itemize}
\subsubsection{Fonctionnalités} \subsubsection{Fonctionnalités}
\begin{itemize} \begin{itemize}
\item \item
L'utilisateur doit pouvoir voir si un souhait est réalisé, en partie L'utilisateur doit pouvoir voir si un souhait est réalisé, en partie ou non.
ou non. Il doit également avoir un pourcentage de complétion sur la Il doit également avoir un pourcentage de complétion sur la possibilité de réalisation de son souhait, entre 0\% et 100\%.
possibilité de réalisation de son souhait, entre 0\% et 100\%.
\item \item
L'utilisateur doit pouvoir voir la ou les personnes ayant réalisé un L'utilisateur doit pouvoir voir la ou les personnes ayant réalisé un souhait.
souhait.
\item \item
Il y a autant de réalisation que de parts de souhait réalisées ou de 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é.
nombre de fois que le souhait est réalisé.
\end{itemize} \end{itemize}
\section{Modélisation} \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.
Comme on l'a vu dans la description des fonctionnalités, on va \textbf{grosso modo} avoir besoin des éléments suivants :
L'ORM de Django permet de travailler uniquement avec une définition de
classes, et de faire en sorte que le lien avec la base de données soit
géré uniquement de manière indirecte, par Django lui-même. On peut
schématiser ce comportement par une classe = une table.
Comme on l'a vu dans la description des fonctionnalités, on va
\textbf{grosso modo} avoir besoin des éléments suivants:
\begin{itemize} \begin{itemize}
\item \item Des listes de souhaits
Des listes de souhaits \item Des éléments qui composent ces listes
\item \item Des parts pouvant composer chacun de ces éléments
Des éléments qui composent ces listes \item Des utilisateurs pour gérer tout ceci.
\item
Des parts pouvant composer chacun de ces éléments
\item
Des utilisateurs pour gérer tout ceci.
\end{itemize} \end{itemize}
Nous proposons dans un premier temps d'éluder la gestion des Nous proposons dans un premier temps d'éluder la gestion des utilisateurs, et de simplement se concentrer sur les fonctionnalités principales.
utilisateurs, et de simplement se concentrer sur les fonctionnalités Cela nous donne ceci:
principales. Cela nous donne ceci:
% TODO: il y a beaucoup de verbatim dans l'enumerate ci-dessous.
\begin{enumerate} \begin{enumerate}
\def\labelenumi{\alph{enumi}.} \def\labelenumi{\alph{enumi}.}
\item \item
code-block:: python code-block:: python
\begin{verbatim} \begin{verbatim}
# wish/models.py # wish/models.py
\end{verbatim} \end{verbatim}
\begin{verbatim} \begin{verbatim}
from django.db import models from django.db import models
\end{verbatim} \end{verbatim}
\begin{verbatim} \begin{verbatim}
class Wishlist(models.Model): class Wishlist(models.Model):
pass pass
\end{verbatim} \end{verbatim}
\begin{verbatim} \begin{verbatim}
class Item(models.Model): class Item(models.Model):
pass pass
\end{verbatim} \end{verbatim}
\begin{verbatim} \begin{verbatim}
class Part(models.Model): class Part(models.Model):
pass pass
\end{verbatim} \end{verbatim}
\end{enumerate} \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 Listes de souhaits
Comme déjà décrit précédemment, les listes de souhaits peuvent Comme déjà décrit précédemment, les listes de souhaits peuvent s'apparenter simplement à un objet ayant un nom et une description.
s'apparenter simplement à un objet ayant un nom et une description. Pour Pour rappel, voici ce qui avait été défini dans les spécifications:
rappel, voici ce qui avait été défini dans les spécifications:
\begin{itemize} \begin{itemize}
\item \item un identifiant
un identifiant \item un identifiant externe
\item \item un nom
un identifiant externe \item une description
\item \item une date de création
un nom \item une date de modification
\item
une description
\item
une date de création
\item
une date de modification
\end{itemize} \end{itemize}
Notre classe \texttt{Wishlist} peut être définie de la manière suivante: Notre classe \texttt{Wishlist} peut être définie de la manière suivante:
\begin{enumerate} \begin{enumerate}
\def\labelenumi{\alph{enumi}.} \def\labelenumi{\alph{enumi}.}
\item \item
code-block:: python code-block:: python
\begin{verbatim} \begin{verbatim}
# wish/models.py # wish/models.py
\end{verbatim} \end{verbatim}
\begin{verbatim} \begin{verbatim}
class Wishlist(models.Model): class Wishlist(models.Model):
\end{verbatim} \end{verbatim}
\begin{verbatim} \begin{verbatim}
name = models.CharField(max_length=255) name = models.CharField(max_length=255)
description = models.TextField() description = models.TextField()
created_at = models.DateTimeField(auto_now_add=True) created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True) updated_at = models.DateTimeField(auto_now=True)
external_id = models.UUIDField(unique=True, default=uuid.uuid4, editable=False) external_id = models.UUIDField(unique=True, default=uuid.uuid4, editable=False)
\end{verbatim} \end{verbatim}
\end{enumerate} \end{enumerate}
Que peut-on constater? Que peut-on constater ?
\begin{itemize} \begin{itemize}
\item \item
Que s'il n'est pas spécifié, un identifiant \texttt{id} sera Que s'il n'est pas spécifié, un identifiant \texttt{id} sera automatiquement généré et accessible dans le modèle.
automatiquement généré et accessible dans le modèle. Si vous souhaitez 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}.
malgré tout spécifier que ce soit un champ en particulier qui devienne \item
la clé primaire, il suffit de l'indiquer grâce à l'attribut Que chaque type de champs (\texttt{DateTimeField}, \texttt{CharField}, \texttt{UUIDField}, etc.) a ses propres paramètres d'initialisation.
\texttt{primary\_key=True}. Il est intéressant de les apprendre ou de se référer à la documentation en cas de doute.
\item
Que chaque type de champs (\texttt{DateTimeField}, \texttt{CharField},
\texttt{UUIDField}, etc.) a ses propres paramètres d'initialisation.
Il est intéressant de les apprendre ou de se référer à la
documentation en cas de doute.
\end{itemize} \end{itemize}
Au niveau de notre modélisation: Au niveau de notre modélisation :
\begin{itemize} \begin{itemize}
\item \item
La propriété \texttt{created\_at} est gérée automatiquement par Django 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é.
grâce à l'attribut \texttt{auto\_now\_add}: de cette manière, lors \item
d'un \textbf{ajout}, une valeur par défaut ("\textbf{maintenant}") 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.
sera attribuée à cette propriété. 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 \item
La propriété \texttt{updated\_at} est également gérée automatique, La propriété \texttt{external\_id} est de type \texttt{UUIDField}.
cette fois grâce à l'attribut \texttt{auto\_now} initialisé à Lorsqu'une nouvelle instance sera instanciée, cette propriété prendra la valeur générée par la fonction \texttt{uuid.uuid4()}.
\texttt{True}: lors d'une \textbf{mise à jour}, la propriété se verra \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.
automatiquement assigner la valeur du moment présent. Cela ne permet
évidemment pas de gérer un historique complet et ne nous dira pas
\textbf{quels champs} ont été modifiés, mais cela nous conviendra dans
un premier temps.
\item
La propriété \texttt{external\_id} est de type \texttt{UUIDField}.
Lorsqu'une nouvelle instance sera instanciée, cette propriété prendra
la valeur générée par la fonction \texttt{uuid.uuid4()}. \textbf{A
priori}, chacun des types de champs possède une propriété
\texttt{default}, qui permet d'initialiser une valeur sur une nouvelle
instance.
\end{itemize} \end{itemize}
Souhaits Souhaits
Nos souhaits ont besoin des propriétés suivantes: Nos souhaits ont besoin des propriétés suivantes :
\begin{itemize} \begin{itemize}
\item \item un identifiant
un identifiant \item l'identifiant de la liste auquel le souhait est lié
\item \item un nom
l'identifiant de la liste auquel le souhait est lié \item une description
\item \item le propriétaire
un nom \item une date de création
\item \item une date de modification
une description \item une image permettant de le représenter.
\item \item un nombre (1 par défaut)
le propriétaire \item un prix facultatif
\item \item un nombre de part facultatif, si un prix est fourni.
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} \end{itemize}
Après implémentation, cela ressemble à ceci: Après implémentation, cela ressemble à ceci :
\begin{enumerate} \begin{enumerate}
\def\labelenumi{\alph{enumi}.} \def\labelenumi{\alph{enumi}.}
\item \item
code-block:: python code-block:: python
\begin{verbatim} \begin{verbatim}
# wish/models.py # wish/models.py
\end{verbatim} \end{verbatim}
\begin{verbatim} \begin{verbatim}
class Wish(models.Model): class Wish(models.Model):
\end{verbatim} \end{verbatim}
\begin{verbatim} \begin{verbatim}
wishlist = models.ForeignKey(Wishlist) wishlist = models.ForeignKey(Wishlist)
name = models.CharField(max_length=255) name = models.CharField(max_length=255)
description = models.TextField() description = models.TextField()
created_at = models.DateTimeField(auto_now_add=True) created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True) updated_at = models.DateTimeField(auto_now=True)
picture = models.ImageField() picture = models.ImageField()
numbers_available = models.IntegerField(default=1) numbers_available = models.IntegerField(default=1)
number_of_parts = models.IntegerField(null=True) number_of_parts = models.IntegerField(null=True)
estimated_price = models.DecimalField(max_digits=19, decimal_places=2, estimated_price = models.DecimalField(max_digits=19, decimal_places=2, null=True)
null=True)
\end{verbatim} \end{verbatim}
\end{enumerate}
A nouveau, que peut-on constater ?
\begin{itemize}
\item
Les clés étrangères sont gérées directement dans la déclaration du
modèle. Un champ de type `ForeignKey
\textless{}\url{https://docs.djangoproject.com/en/1.8/ref/models/fields/\#django.db.models.ForeignKey\%3E\%60_}
permet de déclarer une relation 1-N entre deux classes. Dans la même
veine, une relation 1-1 sera représentée par un champ de type
`OneToOneField
\textless{}\url{https://docs.djangoproject.com/en/1.8/topics/db/examples/one_to_one/\%3E\%60}\emph{,
alors qu'une relation N-N utilisera un `ManyToManyField
\textless{}\url{https://docs.djangoproject.com/en/1.8/topics/db/examples/many_to_many/\%3E\%60}}.
\item
L'attribut \texttt{default} permet de spécifier une valeur initiale,
utilisée lors de la construction de l'instance. Cet attribut peut
également être une fonction.
\item
Pour rendre un champ optionnel, il suffit de lui ajouter l'attribut
\texttt{null=True}.
\item
Comme cité ci-dessus, chaque champ possède des attributs spécifiques.
Le champ \texttt{DecimalField} possède par exemple les attributs
\texttt{max\_digits} et \texttt{decimal\_places}, qui nous permettra
de représenter une valeur comprise entre 0 et plus d'un milliard (avec
deux chiffres décimaux).
\item
L'ajout d'un champ de type \texttt{ImageField} nécessite
l'installation de \texttt{pillow} pour la gestion des images. Nous
l'ajoutons donc à nos pré-requis, dans le fichier
\texttt{requirements/base.txt}.
\end{itemize}
\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
\end{itemize}
Elles constituent la dernière étape de notre modélisation et représente
la réalisation d'un souhait. Il y aura autant de part d'un souhait que
le nombre de souhait à réaliser fois le nombre de part.
Elles permettent à un utilisateur de participer au souhait émis par un
autre utilisateur. Pour les modéliser, une part est liée d'un côté à un
souhait, et d'autre part à un utilisateur. Cela nous donne ceci:
\begin{enumerate}
\item
code-block:: python
\begin{verbatim}
from django.contrib.auth.models import User
\end{verbatim}
\begin{verbatim}
class WishPart(models.Model):
\end{verbatim}
\begin{verbatim}
wish = models.ForeignKey(Wish)
user = models.ForeignKey(User, null=True)
unknown_user = models.ForeignKey(UnknownUser, null=True)
comment = models.TextField(null=True, blank=True)
done_at = models.DateTimeField(auto_now_add=True)
\end{verbatim}
\end{enumerate} \end{enumerate}
La classe \texttt{User} référencée au début du snippet correspond à A nouveau, que peut-on constater ?
l'utilisateur qui sera connecté. Ceci est géré par Django. Lorsqu'une \begin{itemize}
requête est effectuée et est transmise au serveur, cette information \item
sera disponible grâce à l'objet \texttt{request.user}, transmis à chaque Les clés étrangères sont gérées directement dans la déclaration dumodèle.
fonction ou \textbf{Class-based-view}. C'est un des avantages d'un Un champ de type `ForeignKey
framework tout intégré: il vient \textbf{batteries-included} et beaucoup \textless{}\url{https://docs.djangoproject.com/en/1.8/ref/models/fields/\#django.db.models.ForeignKey\%3E\%60_}
de détails ne doivent pas être pris en compte. Pour le moment, nous nous permet de déclarer une relation 1-N entre deux classes.
limiterons à ceci. Par la suite, nous verrons comment améliorer la % TODO : il y a des fautes de syntaxe dans
gestion des profils utilisateurs, comment y ajouter des informations et 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}}.
comment gérer les cas particuliers. \item
L'attribut \texttt{default} permet de spécifier une valeur initiale, utilisée lors de la construction de l'instance.
Cet attribut peut également être une fonction.
\item
Pour rendre un champ optionnel, il suffit de lui ajouter l'attribut \texttt{null=True}.
\item
Comme cité ci-dessus, chaque champ possède des attributs spécifiques.
Le champ \texttt{DecimalField} possède par exemple les attributs \texttt{max\_digits} et \texttt{decimal\_places}, qui nous permettra de représenter une valeur comprise entre 0 et plus d'un milliard (avec deux chiffres décimaux).
\item
L'ajout d'un champ de type \texttt{ImageField} nécessite l'installation de \texttt{pillow} pour la gestion des images.
Nous l'ajoutons donc à nos pré-requis, dans le fichier \texttt{requirements/base.txt}.
\end{itemize}
\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
\end{itemize}
Elles constituent la dernière étape de notre modélisation et représente la réalisation d'un souhait.
Il y aura autant de part d'un souhait que le nombre de souhait à réaliser fois le nombre de part.
Elles permettent à un utilisateur de participer au souhait émis par un autre utilisateur.
Pour les modéliser, une part est liée d'un côté à un souhait, et d'autre part à un utilisateur.
Cela nous donne ceci :
\begin{enumerate}
\item
code-block:: python
\begin{verbatim}
from django.contrib.auth.models import User
\end{verbatim}
\begin{verbatim}
class WishPart(models.Model):
\end{verbatim}
\begin{verbatim}
wish = models.ForeignKey(Wish)
user = models.ForeignKey(User, null=True)
unknown_user = models.ForeignKey(UnknownUser, null=True)
comment = models.TextField(null=True, blank=True)
done_at = models.DateTimeField(auto_now_add=True)
\end{verbatim}
\end{enumerate}
La classe \texttt{User} référencée au début du snippet correspond à l'utilisateur qui sera connecté.
Ceci est géré par Django.
Lorsqu'une requête est effectuée et est transmise au serveur, cette information sera disponible grâce à l'objet \texttt{request.user}, transmis à chaque fonction ou \textbf{Class-based-view}.
C'est un des avantages d'un
framework tout intégré : il vient \textbf{batteries-included} et beaucoup de détails ne doivent pas être pris en compte.
Pour le moment, nous nous limiterons à ceci.
Par la suite, nous verrons comment améliorer la gestion des profils utilisateurs, comment y ajouter des informations et comment gérer les cas particuliers.
La classe \texttt{UnknownUser} permet de représenter un utilisateur non enregistré sur le site et est définie au point suivant.
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} \subsection{Utilisateurs inconnus}
Utilisateurs inconnus Utilisateurs inconnus
\begin{enumerate} \begin{enumerate}
\def\labelenumi{\alph{enumi}.} \def\labelenumi{\alph{enumi}.}
\item \item
todo:: je supprimerais pour que tous les utilisateurs soient gérés au todo:: je supprimerais pour que tous les utilisateurs soient gérés au même endroit.
même endroit.
\end{enumerate} \end{enumerate}
Pour chaque réalisation d'un souhait par quelqu'un, il est nécessaire de 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:
sauver les données suivantes, même si l'utilisateur n'est pas enregistré
sur le site:
\begin{itemize} \begin{itemize}
\item \item un identifiant
un identifiant \item un nom
\item \item une adresse email.
un nom 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
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} \end{itemize}
Ceci nous donne après implémentation: Ceci nous donne après implémentation :
\begin{enumerate} \begin{enumerate}
\def\labelenumi{\alph{enumi}.} \def\labelenumi{\alph{enumi}.}
\item \item
code-block:: python code-block:: python
\begin{verbatim} \begin{verbatim}
class UnkownUser(models.Model): class UnkownUser(models.Model):
\end{verbatim} \end{verbatim}
\begin{verbatim} \begin{verbatim}
name = models.CharField(max_length=255) name = models.CharField(max_length=255)
email = models.CharField(email = models.CharField(max_length=255, unique=True) email = models.CharField(email = models.CharField(max_length=255, unique=True)
\end{verbatim} \end{verbatim}
\end{enumerate} \end{enumerate}

View File

@ -1,26 +1,24 @@
\chapter{PaaS - Heroku} \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, \ldots, vous lui envoyer les paramètres nécessaires et le tout démarre gentiment sans que vous ne deviez superviser l'hôte.
\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 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
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. début.
\begin{figure} \begin{figure}
\centering \centering
\includegraphics{images/deployment/heroku.png} \includegraphics{images/deployment/heroku.png}
\caption{Invest in apps, not ops. Heroku handles the hard stuff --- \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.}
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} \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 Le fonctionnement est relativement simple: pour chaque application, Heroku crée un dépôt Git qui lui est associé.
unitaires et d'intégration auront été réalisés. 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.
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: 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} \begin{verbatim}
$ heroku create $ heroku create
Creating app... done, -> young-temple-86098 Creating app... done, -> young-temple-86098
@ -43,24 +41,20 @@ Au travers de la commande \texttt{heroku\ create}, vous associez donc une nouvel
\begin{verbatim} \begin{verbatim}
Pour définir de quel type d'application il s'agit, Heroku nécessite un minimum de configuration. Pour définir de quel type d'application il s'agit, Heroku nécessite un minimum de configuration.
Celle-ci se limite aux deux fichiers suivants: Celle-ci se limite aux deux fichiers suivants :
* Déclarer un fichier `Procfile` qui va simplement décrire le fichier à passer au protocole WSGI * Déclarer un fichier `Procfile` qui va simplement décrire le fichier à passer au protocole WSGI
* Déclarer un fichier `requirements.txt` (qui va éventuellement chercher ses propres dépendances dans un sous-répertoire, avec l'option `-r`) * 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} \end{verbatim}
Après ce paramétrage, il suffit de pousser les changements vers ce 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}.
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 Heroku propose des espaces de déploiements, mais pas d'espace de stockage.
stockage. Il est possible d'y envoyer des fichiers utilisateurs 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.
(typiquement, des media personnalisés), mais ceux-ci seront perdus lors Il est donc primordial de configurer correctement l'hébergement des fichiers média, de préférences sur un stockage compatible S3.
du redémarrage du container. Il est donc primordial de configurer s3
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: Prêt à vous lancer ? Commencez par créer un compte : \url{https://signup.heroku.com/python}.
\url{https://signup.heroku.com/python}.
\section{Configuration du compte} \section{Configuration du compte}
@ -69,37 +63,29 @@ Prêt à vous lancer ? Commencez par créer un compte:
Vous aurez peut-être besoin d'un coup de pouce pour démarrer votre première application; heureusement, la documentation est super bien faite: Vous aurez peut-être besoin d'un coup de pouce pour démarrer votre première application; heureusement, la documentation est super bien faite:
\begin{figure} \begin{figure}
\centering \centering
\includegraphics{images/deployment/heroku-new-app.png} \includegraphics{images/deployment/heroku-new-app.png}
\caption{Heroku: Commencer à travailler avec un langage} \caption{Heroku: Commencer à travailler avec un langage}
\end{figure} \end{figure}
Installez ensuite la CLI (\emph{Command Line Interface}) en suivant \href{https://devcenter.heroku.com/articles/heroku-cli}{la documentation suivante}. 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: Au besoin, cette CLI existe pour :
\begin{enumerate} \begin{enumerate}
\item \item
macOS, \emph{via} `brew ` macOS, \emph{via} `brew `
\item \item
Windows, grâce à un 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)
\href{https://cli-assets.heroku.com/heroku-x64.exe}{binaire x64} (la \item
version 32 bits existe aussi, mais il est peu probable que vous en 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}.
ayez besoin)
\item
GNU/Linux, via un script Shell
\texttt{curl\ https://cli-assets.heroku.com/install.sh\ \textbar{}\ sh}
ou sur \href{https://snapcraft.io/heroku}{SnapCraft}.
\end{enumerate} \end{enumerate}
Une fois installée, connectez-vous: Une fois installée, connectez-vous :
\begin{verbatim} \begin{verbatim}
$ heroku login $ heroku login
\end{verbatim} \end{verbatim}
Et créer votre application: Et créer votre application :
\begin{verbatim} \begin{verbatim}
$ heroku create $ heroku create
Creating app... done, -> young-temple-86098 Creating app... done, -> young-temple-86098
@ -113,12 +99,8 @@ Et créer votre application:
\caption{Notre application est à présent configurée!} \caption{Notre application est à présent configurée!}
\end{figure} \end{figure}
Ajoutons lui une base de données, que nous sauvegarderons à intervalle Ajoutons lui une base de données, que nous sauvegarderons à intervalle
régulier: régulier :
\begin{verbatim} \begin{verbatim}
$ heroku addons:create heroku-postgresql:hobby-dev $ heroku addons:create heroku-postgresql:hobby-dev
Creating heroku-postgresql:hobby-dev on -> still-thicket-66406... free Creating heroku-postgresql:hobby-dev on -> still-thicket-66406... free
@ -127,7 +109,7 @@ régulier:
! data from another database with pg:copy ! data from another database with pg:copy
Created postgresql-clear-39693 as DATABASE_URL Created postgresql-clear-39693 as DATABASE_URL
Use heroku addons:docs heroku-postgresql to view documentation Use heroku addons:docs heroku-postgresql to view documentation
$ heroku pg:backups schedule --at '14:00 Europe/Brussels' DATABASE_URL $ heroku pg:backups schedule --at '14:00 Europe/Brussels' DATABASE_URL
Scheduling automatic daily backups of postgresql-clear-39693 at 14:00 Scheduling automatic daily backups of postgresql-clear-39693 at 14:00
Europe/Brussels... done Europe/Brussels... done
@ -145,21 +127,21 @@ régulier:
heroku config:set DJANGO_DEBUG=False heroku config:set DJANGO_DEBUG=False
heroku config:set DJANGO_SETTINGS_MODULE=config.settings.production heroku config:set DJANGO_SETTINGS_MODULE=config.settings.production
heroku config:set DJANGO_SECRET_KEY="$(openssl rand -base64 64)" heroku config:set DJANGO_SECRET_KEY="$(openssl rand -base64 64)"
# Generating a 32 character-long random string without any of the visually # Generating a 32 character-long random string without any of the visually
similar characters "IOl01": similar characters "IOl01":
heroku config:set DJANGO_ADMIN_URL="$(openssl rand -base64 4096 | tr -dc 'A- heroku config:set DJANGO_ADMIN_URL="$(openssl rand -base64 4096 | tr -dc 'A-
HJ-NP-Za-km-z2-9' | head -c 32)/" HJ-NP-Za-km-z2-9' | head -c 32)/"
# Set this to your Heroku app url, e.g. 'bionic-beaver-28392.herokuapp.com' # Set this to your Heroku app url, e.g. 'bionic-beaver-28392.herokuapp.com'
heroku config:set DJANGO_ALLOWED_HOSTS= heroku config:set DJANGO_ALLOWED_HOSTS=
# Assign with AWS_ACCESS_KEY_ID # Assign with AWS_ACCESS_KEY_ID
heroku config:set DJANGO_AWS_ACCESS_KEY_ID= heroku config:set DJANGO_AWS_ACCESS_KEY_ID=
# Assign with AWS_SECRET_ACCESS_KEY # Assign with AWS_SECRET_ACCESS_KEY
heroku config:set DJANGO_AWS_SECRET_ACCESS_KEY= heroku config:set DJANGO_AWS_SECRET_ACCESS_KEY=
# Assign with AWS_STORAGE_BUCKET_NAME # Assign with AWS_STORAGE_BUCKET_NAME
heroku config:set DJANGO_AWS_STORAGE_BUCKET_NAME= heroku config:set DJANGO_AWS_STORAGE_BUCKET_NAME=
git push heroku master git push heroku master
@ -170,24 +152,22 @@ régulier:
\section{Type d'application} \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} \begin{enumerate}
\item \item
Un fichier \texttt{requirements.txt} (qui peut éventuellement faire appel à un autre fichier, \textbf{via} l'argument \texttt{-r}) Un fichier \texttt{requirements.txt} (qui peut éventuellement faire appel à un autre fichier, \textbf{via} l'argument \texttt{-r})
\item \item
Un fichier \texttt{Procfile} ({[}sans extension{]}(\url{https://devcenter.heroku.com/articles/procfile)}!), Un fichier \texttt{Procfile} ({[}sans extension{]}(\url{https://devcenter.heroku.com/articles/procfile)}!), qui expliquera la commande pour le protocole WSGI.
qui expliquera la commande pour le protocole WSGI.
\end{enumerate} \end{enumerate}
Dans notre exemple: Dans notre exemple :
\begin{verbatim} \begin{verbatim}
# requirements.txt # requirements.txt
django==3.2.8 django==3.2.8
gunicorn gunicorn
boto3 boto3
django-storages django-storages
\end{verbatim} \end{verbatim}
\begin{verbatim} \begin{verbatim}
@ -198,50 +178,36 @@ django-storages
\section{Hébergement S3} \section{Hébergement S3}
Pour cette partie, nous allons nous baser sur Pour cette partie, nous allons nous baser sur l'\href{https://www.scaleway.com/en/object-storage/}{Object Storage de Scaleway}.
l'\href{https://www.scaleway.com/en/object-storage/}{Object Storage de Ils offrent 75GB de stockage et de transfert par mois, ce qui va nous laisser suffisament d'espace pour jouer un peu.
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} \includegraphics{images/deployment/scaleway-object-storage-bucket.png}
L'idée est qu'au moment de la construction des fichiers statiques, 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.
Django aille simplement les héberger sur un espace de stockage La complexité va être de configurer correctement les différents points de terminaison.
compatible S3. La complexité va être de configurer correctement les Pour héberger nos fichiers sur notre \textbf{bucket} S3, il va falloir suivre et appliquer quelques étapes dans l'ordre :
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} \begin{enumerate}
\def\labelenumi{\arabic{enumi}.} \def\labelenumi{\arabic{enumi}.}
\item \item
Configurer un bucket compatible S3 - je parlais de Scaleway, mais il y Configurer un bucket compatible S3 - je parlais de Scaleway, mais il y
en a - \textbf{littéralement} - des dizaines. en a - \textbf{littéralement} - des dizaines.
\item \item
Ajouter la librairie \texttt{boto3}, qui s'occupera de "parler" avec Ajouter la librairie \texttt{boto3}, qui s'occupera de "parler" avec
ce type de protocole ce type de protocole
\item \item
Ajouter la librairie \texttt{django-storage}, qui va elle s'occuper de Ajouter la librairie \texttt{django-storage}, qui va elle s'occuper de
faire le câblage entre le fournisseur (\textbf{via} \texttt{boto3}) et faire le câblage entre le fournisseur (\textbf{via} \texttt{boto3}) et
Django, qui s'attend à ce qu'on lui donne un moteur de gestion Django, qui s'attend à ce qu'on lui donne un moteur de gestion
\textbf{via} la clé \textbf{via} la clé
{[}\texttt{DJANGO\_STATICFILES\_STORAGE}{]}(\url{https://docs.djangoproject.com/en/3.2/ref/settings/\#std:setting-STATICFILES_STORAGE}). {[}\texttt{DJANGO\_STATICFILES\_STORAGE}{]}(\url{https://docs.djangoproject.com/en/3.2/ref/settings/\#std:setting-STATICFILES_STORAGE}).
\end{enumerate} \end{enumerate}
La première étape consiste à se rendre dans {[}la console 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.
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} \includegraphics{images/deployment/scaleway-api-key.png}
Selon la documentation de 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} :
\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} \begin{minted}{python}
AWS_ACCESS_KEY_ID = os.getenv('ACCESS_KEY_ID') AWS_ACCESS_KEY_ID = os.getenv('ACCESS_KEY_ID')
@ -283,10 +249,8 @@ qui sera confirmée par le \textbf{bucket}:
Sources complémentaires: Sources complémentaires:
\begin{itemize} \begin{itemize}
\item \item
{[}How to store Django static and media files on S3 in {[}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/})
production{]}(\url{https://coderbook.com/@marcus/how-to-store-django-static-and-media-files-on-s3-in-production/}) \item
\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} \end{itemize}

View File

@ -4,15 +4,11 @@ La localisation (\emph{l10n}) et l'internationalization (\emph{i18n})
sont deux concepts proches, mais différents: sont deux concepts proches, mais différents:
\begin{itemize} \begin{itemize}
\item \item
Internationalisation: \emph{Preparing the software for localization. Internationalisation: \emph{Preparing the software for localization. Usually done by developers.}
Usually done by developers.} \item
\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} \end{itemize}
L'internationalisation est donc le processus permettant à une L'internationalisation est donc le processus permettant à une application d'accepter une forme de localisation.
application d'accepter une forme de localisation. La seconde ne va donc La seconde ne va donc pas sans la première, tandis que la première ne fait qu'autoriser la seconde.
pas sans la première, tandis que la première ne fait qu'autoriser la
seconde.

View File

@ -1,141 +1,117 @@
\chapter{Infrastructure et composants} \chapter{Infrastructure et composants}
Pour une mise ne production, le standard \emph{de facto} est le suivant: Pour une mise ne production, le standard \emph{de facto} est le suivant :
\begin{itemize} \begin{itemize}
\item \item Nginx comme reverse proxy
Nginx comme reverse proxy \item HAProxy pour la distribution de charge
\item \item Gunicorn ou Uvicorn comme serveur d'application
HAProxy pour la distribution de charge \item Supervisor pour le monitoring
\item \item PostgreSQL ou MySQL/MariaDB comme bases de données.
Gunicorn ou Uvicorn comme serveur d'application \item Celery et RabbitMQ pour l'exécution de tâches asynchrones
\item \item Redis / Memcache pour la mise à en cache (et pour les sessions ? A vérifier).
Supervisor pour le monitoring \item Sentry, pour le suivi des bugs
\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} \end{itemize}
Si nous schématisons l'infrastructure et le chemin parcouru par une requête, nous pourrions arriver à la synthèse suivante: Si nous schématisons l'infrastructure et le chemin parcouru par une requête, nous pourrions arriver à la synthèse suivante :
\begin{enumerate} \begin{enumerate}
\item \item
L'utilisateur fait une requête via son navigateur (Firefox ou Chrome) L'utilisateur fait une requête via son navigateur (Firefox ou Chrome)
\item \item
Le navigateur envoie une requête http, sa version, un verbe (GET, Le navigateur envoie une requête http, sa version, un verbe (GET, POST, \ldots\hspace{0pt}), un port et éventuellement du contenu
POST, \ldots\hspace{0pt}), un port et éventuellement du contenu \item
\item Le firewall du serveur (Debian GNU/Linux, CentOS, \ldots~ vérifie si la requête peut être prise en compte
Le firewall du serveur (Debian GNU/Linux, CentOS, ... \item
vérifie si la requête peut être prise en compte La requête est transmise à l'application qui écoute sur le port (probablement 80 ou 443; et \emph{a priori} Nginx)
\item \item
La requête est transmise à l'application qui écoute sur le port Elle est ensuite transmise par socket et est prise en compte par un des \emph{workers} (= un processus Python) instancié par Gunicorn.
(probablement 80 ou 443; et \emph{a priori} Nginx) Si l'un de ces travailleurs venait à planter, il serait automatiquement réinstancié par Supervisord.
\item \item
Elle est ensuite transmise par socket et est prise en compte par un Qui la transmet ensuite à l'un de ses \emph{workers} (= un processus Python).
des \emph{workers} (= un processus Python) instancié par Gunicorn. Si \item
l'un de ces travailleurs venait à planter, il serait automatiquement Après exécution, une réponse est renvoyée à l'utilisateur.
réinstancié par Supervisord.
\item
Qui la transmet ensuite à l'un de ses \emph{workers} (= un processus
Python).
\item
Après exécution, une réponse est renvoyée à l'utilisateur.
\end{enumerate} \end{enumerate}
\includegraphics{images/diagrams/architecture.png} \includegraphics{images/diagrams/architecture.png}
\section{Reverse proxy} \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.
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}} 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} \section{Load balancer}
% TODO: à écrire
\section{Workers} \section{Workers}
% TODO: à écrire
\section{Supervision des processus} \section{Supervision des processus}
% TODO: à écrire
\section{Bases de données} \section{Bases de données}
% TODO: à écrire
\section{Tâches asynchrones} \section{Tâches asynchrones}
% TODO: à écrire
\section{Mise en cache} \section{Mise en cache}
% TODO: à écrire
\section{Niveau application} \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 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} \begin{enumerate}
\item \item effectuer le routage des données,
effectuer le routage des données, \item trouver la bonne fonction à exécuter,
\item \item récupérer les données depuis la base de données,
trouver la bonne fonction à exécuter, \item effectuer le rendu ou la conversion des données,
\item \item et renvoyer une réponse à l'utilisateur.
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} \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} \section{Supervision globale}
\subsection{Journaux} \subsection{Journaux}
La structure des niveaux de journaux est essentielle. La structure des niveaux de journaux est essentielle.
\begin{quote} \begin{quote}
When deciding whether a message should be ERROR or WARN, imagine being woken up at 4 a.m. When deciding whether a message should be ERROR or WARN, imagine being woken up at 4 a.m.
Low printer toner is not an ERROR. Low printer toner is not an ERROR.
--- Dan North former ToughtWorks consultant --- Dan North former ToughtWorks consultant
\end{quote} \end{quote}
\begin{itemize} \begin{itemize}
\item \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, \textbf{DEBUG}: Il s'agit des informations qui concernent tout ce qui peut se passer durant l'exécution de l'application.
sauf s'il est nécessaire d'isoler un comportement en particulier, auquel cas il suffit de le réactiver temporairement. 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 \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 \item
\textbf{WARN}: Regroupe les informations qui pourraient potentiellement devenir des erreurs. \textbf{WARN} : Regroupe les informations qui pourraient potentiellement devenir des erreurs.
\item \item
\textbf{ERROR}: Indique les informations internes - Erreur lors de l'appel d'une API, erreur interne, ... \textbf{ERROR} : Indique les informations internes - Erreur lors de l'appel d'une API, erreur interne, ...
\item \item
\textbf{FATAL} (ou \textbf{EXCEPTION}): ... généralement suivie d'une terminaison du programme - Bind raté \textbf{FATAL} (ou \textbf{EXCEPTION}) : \ldots généralement suivie d'une terminaison du programme - Bind raté d'un socket, etc.
d'un socket, etc.
\end{itemize} \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, 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.
gestionnaires (\emph{handlers}) et loggers distincts, en fonction de nos applications. 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:
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} \begin{enumerate}
\item \item
Formattage: à définir - mais la variante suivante est complète, lisible et pratique: Formattage : à définir - mais la variante suivante est complète, lisible et pratique: \texttt{\{levelname\}\ \{asctime\}\ \{module\}\ \{process:d\}\ \{thread:d\}\ \{message\}}
\texttt{\{levelname\}\ \{asctime\}\ \{module\}\ \{process:d\}\ \{thread:d\}\ \{message\}} \item
\item Handler : juste un, qui définit un \texttt{StreamHandler}
Handler: juste un, qui définit un \texttt{StreamHandler} \item
\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} \end{enumerate}
Pour utiliser nos loggers, il suffit de copier le petit bout de code suivant: Pour utiliser nos loggers, il suffit de copier le petit bout de code suivant :
\begin{minted}{python} \begin{minted}{python}
import logging import logging
@ -143,31 +119,29 @@ Pour utiliser nos loggers, il suffit de copier le petit bout de code suivant:
logger.debug('helloworld') logger.debug('helloworld')
\end{minted} \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} \begin{enumerate}
\def\labelenumi{\arabic{enumi}.} \def\labelenumi{\arabic{enumi}.}
\item \item
Sentry via sentry\_sdk Sentry via sentry\_sdk
\item \item
Nagios Nagios
\item \item
LibreNMS LibreNMS
\item \item
Zabbix Zabbix
\end{enumerate} \end{enumerate}
Il existe également \href{https://munin-monitoring.org}{Munin}, 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}.
\href{https://www.elastic.co}{Logstash, ElasticSearch et Kibana
(ELK-Stack)} ou \href{https://www.fluentd.org}{Fluentd}.
\subsection{Zabbix, Nagios, ...} \subsection{Zabbix, Nagios, ...}
% TODO: a écrire
\subsection{Sentry} \subsection{Sentry}
% TODO: a écrire
\subsection{Greylog} \subsection{Greylog}
% TODO: a écrire

View File

@ -1,20 +1,15 @@
\chapter{Introduction} \chapter{Introduction}
\begin{quote} \begin{quote}
The only way to go fast, is to go well The only way to go fast, is to go well
--- Robert C. Martin --- Robert C. Martin
\end{quote} \end{quote}
Nous n'allons pas vous mentir: il existe énormément de tutoriaux très 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}".
bien réalisés sur "\emph{Comment réaliser une application Django}" et 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.
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
Les quelques idées ci-dessous de jeter les bases d'un bon développement, en
\begin{itemize} \begin{itemize}
\item Survolant l'ensemble des lignes directrices reconnues \item Survolant l'ensemble des lignes directrices reconnues
\item Maintenant une bonne qualité de code \item Maintenant une bonne qualité de code
@ -23,163 +18,111 @@ Les quelques idées ci-dessous de jeter les bases d'un bon développement, en
\item Permettant à quiconque de reprendre ce qui aura déjà été écrit. \item Permettant à quiconque de reprendre ce qui aura déjà été écrit.
\end{itemize} \end{itemize}
Ces idées ne s'appliquent pas uniquement à Django et à son cadre de travail, ni même au langage Python en particulier. 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. 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 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}:
ayant des deadlines} \cite{django} et suit ces quelques principes
\cite{django_design_philosophies}:
\begin{itemize} \begin{itemize}
\item \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.
\item 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{Plus de fonctionnalités avec moins de code}: chaque application Django \item
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 \item
\textbf{\emph{Don't repeat yourself}}, chaque concept ou morceau de code ne \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.
doit être présent qu'à un et un seul endroit de vos dépôts. \item
\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} \end{itemize}
Mis côte à côte, le suivi de ces principes permet une bonne stabilité du Mis côte à côte, le suivi de ces principes permet une bonne stabilité du projet à moyen et long terme.
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. 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. 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. Tout pour plaire à n'importe quel directeur IT.
\textbf{Dans la première partie}, nous verrons comment partir d'un \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.
environnement sain, comment le configurer correctement, comment Nous verrons rapidement comment gérer les dépendances, les versions et comment appliquer et suivre un score de qualité de notre code.
installer Django de manière isolée et comment démarrer un nouveau Ces quelques points pourront être appliqués pour n'importe quel langage ou cadre de travail.
projet. Nous verrons rapidement comment gérer les dépendances, les Nous verrons aussi que la configuration proposée par défaut par le framework n'est pas idéale dans la majorité des cas.
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 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.
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 A la fin de cette partie, vous disposerez d'un code propre et d'un projet fonctionnel, bien qu'encore un peu inutile.
projet fonctionnel, bien qu'encore un peu inutile.
textbf{Dans la deuxième partie}, nous aborderons les grands principes textbf{Dans la deuxième partie}, nous aborderons les grands principes de modélisation, en suivant les lignes de conduites du cadre de travail.
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.
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 \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.
é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 Nous aborderons également la supervision et la mise à jour d'une application existante, en respectant les bonnes pratiques d'administration système.
application existante, en respectant les bonnes pratiques
d'administration système.
\textbf{Dans la quatrième partie}, nous aborderons les architectures \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.
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 \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.
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 ?} \subsection{Pour qui ?}
Avant tout, pour moi. Comme le disait le Pr Richard Feynman: "\emph{Si Avant tout, pour moi.
vous ne savez pas expliquer quelque chose simplement, c'est que vous ne 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}".
l'avez pas compris}". \footnote{Et comme l'ajoutait Aurélie Jean dans de \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}}
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 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.
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 Beaucoup de concepts présentés peuvent être oubliés ou restés inconnus jusqu'au moment où ils seront réellement nécessaires.
jusqu'au moment où ils seront réellement nécessaires. A ce moment-là, 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.
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} \subsection{Pour aller plus loin}
Il existe énormément de ressources, autant spécifiques à Django que plus Il existe énormément de ressources, autant spécifiques à Django que plus généralistes.
généralistes. Il ne sera pas possible de toutes les détailler; faites un Il ne sera pas possible de toutes les détailler; faites un tour sur
tour sur
\begin{itemize} \begin{itemize}
\item \item \url{https://duckduckgo.com},
\url{https://duckduckgo.com}, \item \url{https://stackoverflow.com},
\item \item \url{https://ycombinator.com},
\url{https://stackoverflow.com}, \item \url{https://lobste.rs/},
\item \item \url{https://lecourrierduhacker.com/}
\url{https://ycombinator.com}, \item ou \url{https://www.djangoproject.com/}.
\item
\url{https://lobste.rs/},
\item
\url{https://lecourrierduhacker.com/}
\item
ou \url{https://www.djangoproject.com/}.
\end{itemize} \end{itemize}
Restez curieux, ne vous enclavez pas dans une technologie en particulier Restez curieux, ne vous enclavez pas dans une technologie en particulie et gardez une bonne ouverture d'esprit.
et gardez une bonne ouverture d'esprit.
\subsection{Conventions} \subsection{Conventions}
Les notes indiquent des anecdotes. Les notes indiquent des anecdotes.
Les conseils indiquent des éléments utiles, mais pas spécialement Les conseils indiquent des éléments utiles, mais pas spécialemen indispensables.
indispensables.
Les notes importantes indiquent des éléments à retenir. Les notes importantes indiquent des éléments à retenir.
Ces éléments indiquent des points d'attention. Les retenir vous fera Ces éléments indiquent des points d'attention.
gagner du temps en débuggage. Les retenir vous fer gagner du temps en débuggage.
Les avertissements indiquent un (potentiel) danger ou des éléments Les avertissements indiquent un (potentiel) danger ou des élément pouvant amener des conséquences pas spécialement sympathiques.
pouvant amener des conséquences pas spécialement sympathiques.
Les morceaux de code source seront présentés de la manière suivante: Les morceaux de code source seront présentés de la manière suivante:
\begin{listing}[htbp] \begin{listing}[htbp]
\begin{minted}{Python} \begin{minted}{Python}
# <folder>/<fichier>.<extension> # <folder>/<fichier>.<extension>
def function(param): def function(param):
""" """
""" """
callback() callback()
\end{minted} \end{minted}
\end{listing} \end{listing}
Chaque extrait de code reprendra Chaque extrait de code reprendra
\begin{itemize} \begin{itemize}
\item l'emplacement du fichier, présenté sous forme de commentaire (ligne 1), \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 \item Des commentaires au niveau des fonctions et méthodes, si cela s'avère nécessaire
\item Un surlignage sur les parties importantes ou récemment modifiées \item Un surlignage sur les parties importantes ou récemment modifiées
\end{itemize} \end{itemize}
La plupart des commandes qui seront présentées dans ce livre le seront depuis un shell sous GNU/Linux. La plupart des commandes qui seront présentées dans ce livre le seront depuis un shell sous GNU/Linux.
Certaines d'entre elles pourraient devoir être adaptées si vous utilisez un autre système d'exploitation (macOS) ou n'importe quelle autre grosse bouse commerciale. Certaines d'entre elles pourraient devoir être adaptées si vous utilisez un autre système d'exploitation (macOS) ou n'importe quelle autre grosse bouse commerciale.
Les morceaux de code que vous trouverez ci-dessous seront développés pour Python3.9+ et Django 3.2+. Les morceaux de code que vous trouverez ci-dessous seront développés pour Python3.9+ et Django 3.2+.
Ils nécessiteront peut-être quelques adaptations pour fonctionner sur une version antérieure. Ils nécessiteront peut-être quelques adaptations pour fonctionner sur une version antérieure.

View File

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

View File

@ -1,74 +1,47 @@
\chapter{Fiabilité, évolutivité et maintenabilité} \chapter{Fiabilité, évolutivité et maintenabilité}
\begin{quote} \begin{quote}
The primary cost of maintenance is in spelunking and risk The primary cost of maintenance is in spelunking and risk\cite[139]{clean_architecture}
\cite[139]{clean_architecture}
--- Robert C. Martin --- Robert C. Martin
\end{quote} \end{quote}
Pour la méthode de travail et de développement, nous allons nous baser 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 sur les \href{https://12factor.net/fr/}{The Twelve-factor App} - ou plus
simplement les \textbf{12 facteurs}. simplement les \textbf{12 facteurs}.
Suivre ces concepts permet de: Suivre ces concepts permet de :
\begin{enumerate} \begin{enumerate}
\item \item
\textbf{Faciliter la mise en place de phases d'automatisation}; plus \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}
concrètement, de faciliter les mises à jour applicatives, simplifier \item
la gestion de l'hôte qui héberge l'application ou les services, \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.
diminuer la divergence entre les différents environnements d'exécution et offrir la possibilité d'intégrer le \item
projet dans un processus \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
d'\href{https://en.wikipedia.org/wiki/Continuous_integration}{intégration \item
continue} ou \textbf{Augmenter l'agilité générale du projet}, en permettant une meilleure évolutivité architecturale et une meilleure mise à l'échelle.
\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.
\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
\item
\textbf{Augmenter l'agilité générale du projet}, en permettant une
meilleure évolutivité architecturale et une meilleure mise à l'échelle.
\end{enumerate} \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 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.
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 Pour reprendre plus spécifiquement les différentes idées derrière cette méthode, nous avons:
cette méthode, nous avons:
\section{Une base de code unique, suivie par un contrôle de versions} \section{Une base de code unique, suivie par un contrôle de versions}
Chaque déploiement de l'application, et quel que soit l'environnement ciblé, se basera sur une source unique, afin de minimiser les différences que l'on pourrait trouver entre deux environnements d'un même projet. Chaque déploiement de l'application, et quel que soit l'environnement ciblé, se basera sur une source unique, afin de minimiser les différences que l'on pourrait trouver entre deux environnements d'un même projet.
Git est reconnu dans l'industrie comme standard des systèmes de contrôles de versions, malgré une courbe d'apprentissage assez ardue. Git est reconnu dans l'industrie comme standard des systèmes de contrôles de versions, malgré une courbe d'apprentissage assez ardue.
Comme dépôt, nous pourrons par exemple utiliser GitHub, Gitea ou Gitlab, suivant que vous ayez besoin d'une plateforme centralisée, propriétaire, payante ou auto-hébergée. \index{Git} \index{Github} \index{Gitlab} \index{Gitea} Comme dépôt, nous pourrons par exemple utiliser GitHub, Gitea ou Gitlab, suivant que vous ayez besoin d'une plateforme centralisée, propriétaire, payante ou auto-hébergée. \index{Git} \index{Github} \index{Gitlab} \index{Gitea}
\includegraphics{images/diagrams/12-factors-1.png} \includegraphics{images/diagrams/12-factors-1.png}
Comme l'explique Eran Messeri, ingénieur dans le groupe Google Developer 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}.
Infrastructure: "Un des avantages d'utiliser un dépôt unique de sources, Ce cépôt n'est pas uniquement destiné à hébergé le code source, mais également à d'autres artefacts et autres formes de connaissance :
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} \begin{itemize}
\item \item Standards de configuration (Chef recipes, Puppet manifests, \ldots
Standards de configuration (Chef recipes, Puppet manifests, ... \item Outils de déploiement
\item \item Standards de tests, y compris tout ce qui touche à la sécurité
Outils de déploiement \item Outils de déploiement de pipeline
\item \item Outils d'analyse et de monitoring
Standards de tests, y compris tout ce qui touche à la sécurité \item Tutoriaux
\item
Outils de déploiement de pipeline
\item
Outils d'analyse et de monitoring
\item
Tutoriaux
\end{itemize} \end{itemize}
\section{Déclaration explicite et isolation des dépendances} \section{Déclaration explicite et isolation des dépendances}
@ -104,17 +77,13 @@ Il faut éviter d'avoir à recompiler/redéployer l'application simplement parce
que: que:
\begin{enumerate} \begin{enumerate}
\item \item l'adresse du serveur de messagerie a été modifiée,
l'adresse du serveur de messagerie a été modifiée, \item un protocole a changé en cours de route
\item \item la base de données a été déplacée
un protocole a changé en cours de route \item \ldots
\item
la base de données a été déplacée
\item
...
\end{enumerate} \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. 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.
Ceci permet de paramétrer facilement un environnement (par exemple, un container), simplement en modifiant une variable de configuration qui spécifierait la base de données sur laquelle l'application devra se connecter. Ceci permet de paramétrer facilement un environnement (par exemple, un container), simplement en modifiant une variable de configuration qui spécifierait la base de données sur laquelle l'application devra se connecter.
@ -181,8 +150,8 @@ sur les \emph{releases} de Gitea, sur un serveur d'artefacts (\href{https://fr.w
Toute information stockée en mémoire ou sur disque ne doit pas altérer le comportement futur de l'application, par exemple après un redémarrage non souhaité. Toute information stockée en mémoire ou sur disque ne doit pas altérer le comportement futur de l'application, par exemple après un redémarrage non souhaité.
Dit autrement, l'exécution de l'application ne doit pas dépendre de la présence d'une information stockée en mémoire ou sur disque. Dit autrement, l'exécution de l'application ne doit pas dépendre de la présence d'une information stockée en mémoire ou sur disque.
Pratiquement, si l'application devait rencontrer un problème, il est nécessaire qu'elle puisse redémarrer rapidement, éventuellement en étant déployée sur un autre serveur - par exemple suite à un problème matériel. Pratiquement, si l'application devait rencontrer un problème, il est nécessaire qu'elle puisse redémarrer rapidement, éventuellement en étant déployée sur un autre serveur - par exemple suite à un problème matériel.
Toute information stockée physiquement sur le premier hôte durant son exécution sur le premier hôte, puisqu'elle pourra avoir été entretemps perdue. Toute information stockée physiquement sur le premier hôte durant son exécution sur le premier hôte, puisqu'elle pourra avoir été entretemps perdue.
Lors d'une initialisation ou réinitialisation, la solution consiste donc à jouer sur les variables d'environnement (cf. \#3) et sur les informations que l'on pourra trouver au niveau des ressources attachées (cf \#4), et faire en sorte que les informations et données primordiales puissent être récupérées ou reconstruites. Lors d'une initialisation ou réinitialisation, la solution consiste donc à jouer sur les variables d'environnement (cf. \#3) et sur les informations que l'on pourra trouver au niveau des ressources attachées (cf \#4), et faire en sorte que les informations et données primordiales puissent être récupérées ou reconstruites.
Il serait également difficile d'appliquer une mise à l'échelle de l'application, en ajoutant un nouveau serveur d'exécution, si une donnée indispensable à son fonctionnement devait se trouver sur la seule machine où elle est actuellement exécutée. Il serait également difficile d'appliquer une mise à l'échelle de l'application, en ajoutant un nouveau serveur d'exécution, si une donnée indispensable à son fonctionnement devait se trouver sur la seule machine où elle est actuellement exécutée.
@ -202,7 +171,7 @@ Le serveur (= l'hôte) choisit d'appliquer une correspondance entre "son" port 4
\section{Connaissance et confiance des processys systèmes} \section{Connaissance et confiance des processys systèmes}
Comme décrit plus haut (cf. \#6), l'application doit utiliser des processus \emph{stateless} (sans état). Nous pouvons créer et utiliser des processus supplémentaires pour tenir plus facilement une lourde charge, ou dédier des particuliers pour certaines tâches: requêtes HTTP \emph{via} des processus Web; \emph{long-running} jobs pour des processus asynchrones, ... Comme décrit plus haut (cf. \#6), l'application doit utiliser des processus \emph{stateless} (sans état). Nous pouvons créer et utiliser des processus supplémentaires pour tenir plus facilement une lourde charge, ou dédier des particuliers pour certaines tâches: requêtes HTTP \emph{via} des processus Web; \emph{long-running} jobs pour des processus asynchrones, ...
Si cela existe sur l'hôte hébergeant l'application, ne vous fatiguez pas: utilisez le. Si cela existe sur l'hôte hébergeant l'application, ne vous fatiguez pas: utilisez le.
\includegraphics{images/12factors/process-types.png} \includegraphics{images/12factors/process-types.png}
@ -211,12 +180,12 @@ Si cela existe sur l'hôte hébergeant l'application, ne vous fatiguez pas: util
Par "arrêt élégant", nous voulons surtout éviter le fameux Par "arrêt élégant", nous voulons surtout éviter le fameux
\texttt{kill\ -9\ \textless{}pid\textgreater{}} (ou équivalent), ou tout autre arrêt brutal d'un processus qui nécessiterait une intervention urgente du \texttt{kill\ -9\ \textless{}pid\textgreater{}} (ou équivalent), ou tout autre arrêt brutal d'un processus qui nécessiterait une intervention urgente du
superviseur. superviseur.
En prévoyant une manière élégante d'envoyer un signal de terminaison, En prévoyant une manière élégante d'envoyer un signal de terminaison,
\begin{enumerate} \begin{enumerate}
\item Les requêtes en cours peuvent se terminer au mieux, \item Les requêtes en cours peuvent se terminer au mieux,
\item Le démarrage rapide de nouveaux processus améliorera la balance d'un processus en cours d'extinction vers des processus tout frais, en autorisant l'exécution parallèle d'anciens et de nouveaux "types" de processus \item Le démarrage rapide de nouveaux processus améliorera la balance d'un processus en cours d'extinction vers des processus tout frais, en autorisant l'exécution parallèle d'anciens et de nouveaux "types" de processus
\end{enumerate} \end{enumerate}
L'intégration de ces mécanismes dès les premières étapes de L'intégration de ces mécanismes dès les premières étapes de
@ -232,25 +201,25 @@ Conserver les différents environnements aussi similaires
que possible, et limiter les divergences entre un environnement de que possible, et limiter les divergences entre un environnement de
développement et de production développement et de production
L'exemple donné est un développeur qui utilise macOS, NGinx et SQLite, tandis que l'environnement de production tourne sur une CentOS avec Apache2 et PostgreSQL. L'exemple donné est un développeur qui utilise macOS, NGinx et SQLite, tandis que l'environnement de production tourne sur une CentOS avec Apache2 et PostgreSQL.
Faire en sorte que tous les environnements soient les plus similaires possibles limite les divergences entre environnements, facilite les déploiements et limite la casse et la découverte de modules non compatibles, au plus proche de la phase de développement, selon le principe de la corde d'Andon \cite[p. 140]{devops_handbook} \index{Andon} \footnote{Pour donner un exemple tout bête, SQLite utilise un Faire en sorte que tous les environnements soient les plus similaires possibles limite les divergences entre environnements, facilite les déploiements et limite la casse et la découverte de modules non compatibles, au plus proche de la phase de développement, selon le principe de la corde d'Andon \cite[p. 140]{devops_handbook} \index{Andon} \footnote{Pour donner un exemple tout bête, SQLite utilise un
\href{https://www.sqlite.org/datatype3.html}{mécanisme de stockage dynamique}, associée à la valeur plutôt qu'au schéma, \emph{via} un système d'affinités. Un autre moteur de base de données définira un schéma statique et rigide, où la valeur sera déterminée par son contenant. \href{https://www.sqlite.org/datatype3.html}{mécanisme de stockage dynamique}, associée à la valeur plutôt qu'au schéma, \emph{via} un système d'affinités. Un autre moteur de base de données définira un schéma statique et rigide, où la valeur sera déterminée par son contenant.
Un champ \texttt{URLField} proposé par Django a une longeur maximale par défaut de \href{https://docs.djangoproject.com/en/3.1/ref/forms/fields/\#django.forms.URLField}{200 caractères}. Un champ \texttt{URLField} proposé par Django a une longeur maximale par défaut de \href{https://docs.djangoproject.com/en/3.1/ref/forms/fields/\#django.forms.URLField}{200 caractères}.
Si vous faites vos développements sous SQLite et que vous rencontrez une URL de plus de 200 caractères, votre développement sera passera parfaitement bien, mais plantera en production (ou en \emph{staging}, si vous faites les choses peu mieux) parce que les données seront tronquées, et que cela ne plaira pas à la base de données. Si vous faites vos développements sous SQLite et que vous rencontrez une URL de plus de 200 caractères, votre développement sera passera parfaitement bien, mais plantera en production (ou en \emph{staging}, si vous faites les choses peu mieux) parce que les données seront tronquées, et que cela ne plaira pas à la base de données.
Conserver des environements similaires limite ce genre de désagréments.} Conserver des environements similaires limite ce genre de désagréments.}
\section{Journaux de flux évènementiels} \section{Journaux de flux évènementiels}
Une application ne doit jamais se soucier de l'endroit où les évènements qui la concerne seront écrits, mais se doit simplement de les envoyer sur la sortie \texttt{stdout}. Une application ne doit jamais se soucier de l'endroit où les évènements qui la concerne seront écrits, mais se doit simplement de les envoyer sur la sortie \texttt{stdout}.
De cette manière, que nous soyons en développement sur le poste d'un développeur avec une sortie console ou sur une machine de production avec un envoi vers une instance \href{https://www.graylog.org/}{Greylog} ou \href{https://sentry.io/welcome/}{Sentry}, le routage des journaux sera réalisé en fonction de sa nécessité et de sa criticité, et non pas parce que le développeur l'a spécifié en dur dans son code. De cette manière, que nous soyons en développement sur le poste d'un développeur avec une sortie console ou sur une machine de production avec un envoi vers une instance \href{https://www.graylog.org/}{Greylog} ou \href{https://sentry.io/welcome/}{Sentry}, le routage des journaux sera réalisé en fonction de sa nécessité et de sa criticité, et non pas parce que le développeur l'a spécifié en dur dans son code.
Cette phase est critique, dans la mesure où les journaux d'exécution sont la seule manière pour une application de communiquer son état vers l'extérieur: recevoir une erreur interne de serveur est une chose; pouvoir obtenir un minimum d'informations, voire un contexte de plantage complet en est une autre. Cette phase est critique, dans la mesure où les journaux d'exécution sont la seule manière pour une application de communiquer son état vers l'extérieur: recevoir une erreur interne de serveur est une chose; pouvoir obtenir un minimum d'informations, voire un contexte de plantage complet en est une autre.
La différence entre ces deux points vous fera, au mieux, gagner plusieurs heures sur l'identification ou la résolution d'un problème. La différence entre ces deux points vous fera, au mieux, gagner plusieurs heures sur l'identification ou la résolution d'un problème.
\section{Isolation des tâches administratives} \section{Isolation des tâches administratives}
Evitez qu'une migration ne puisse être démarrée depuis une URL de l'application, ou qu'un envoi massif de notifications ne soit accessible pour n'importe quel utilisateur: les tâches administratives ne doivent être accessibles qu'à un administrateur. Evitez qu'une migration ne puisse être démarrée depuis une URL de l'application, ou qu'un envoi massif de notifications ne soit accessible pour n'importe quel utilisateur: les tâches administratives ne doivent être accessibles qu'à un administrateur.
Les applications 12facteurs favorisent les langages qui mettent un environnement REPL (pour \emph{Read}, \emph{Eval}, \emph{Print} et \emph{Loop}) \index{REPL} à disposition (au hasard: \href{https://pythonprogramminglanguage.com/repl/}{Python} ou \href{https://kotlinlang.org/}{Kotlin}), ce qui facilite les étapes de maintenance. Les applications 12facteurs favorisent les langages qui mettent un environnement REPL (pour \emph{Read}, \emph{Eval}, \emph{Print} et \emph{Loop}) \index{REPL} à disposition (au hasard: \href{https://pythonprogramminglanguage.com/repl/}{Python} ou \href{https://kotlinlang.org/}{Kotlin}), ce qui facilite les étapes de maintenance.
\section{Conclusions} \section{Conclusions}