move old pdf to the repo github wiki

This commit is contained in:
Antoine Bartuccio 2024-07-16 17:27:47 +02:00 committed by thomas girod
parent c1acadbf3d
commit 223aa37161
24 changed files with 153 additions and 2513 deletions

View File

@ -1 +0,0 @@
_minted-Rapport/

View File

@ -1,13 +0,0 @@
CC=pdflatex
all: rapport clean
rapport: Rapport.tex
@echo "Compiling "$<
$(CC) -shell-escape $<
$(CC) -shell-escape $<
clean:
@echo "Cleaning folder"
rm *.aux; rm *.log; rm *.out; rm *.toc; rm *.snm; rm *.nav; rm *.lof

View File

@ -1,490 +0,0 @@
%%
%
% Skia
% skia@libskia.so
%
%%
\documentclass[a4paper]{report}
%packages
\usepackage[utf8]{inputenc}
\usepackage[francais]{babel}
\usepackage{graphicx}\graphicspath{{pix/}}
\usepackage{float}
\usepackage{scrextend}
\usepackage[T1]{fontenc}
\usepackage{color}
\usepackage{fancyhdr}
%Options: Sonny, Lenny, Glenn, Conny, Rejne, Bjarne, Bjornstrup
\usepackage[Bjornstrup]{fncychap}
\usepackage{minted}
\usepackage[colorlinks=true,linkcolor=black]{hyperref}
\usepackage{pdfpages}
%\usepackage{titlesec, blindtext, color}
%pdf metadata
\hypersetup{
unicode=true,
colorlinks=true,
citecolor=black,
filecolor=black,
linkcolor=black,
urlcolor=black,
pdfauthor={Skia <skia@libskia.so>},
pdftitle={},
pdfcreator={pdftex},
pdfsubject={},
pdfkeywords={},
}
\definecolor{keywords}{RGB}{200,0,90}
\definecolor{comments}{RGB}{50,50,253}
\definecolor{red}{RGB}{160,0,0}
\definecolor{brown}{RGB}{160,100,100}
\definecolor{green}{RGB}{0,200,0}
\definecolor{darkgreen}{RGB}{0,130,0}
\definecolor{gray}{RGB}{100,100,100}
%inner meta
\title{Sith: Développement de nouvelles applications}
\author{Florent \textsc{Jacquet}\\
Guillaume \textsc{Renaud}}
\date{Dernière version: \today}
\begin{document}
% \maketitle
\includepdf[pages={1}]{Couvertures.pdf}
\tableofcontents
\chapter*{Remerciements}
\section*{Guillaume \textsc{Renaud}}
\par Je remercie tout d'abord Monsieur Frédéric \textsc{Lassabe} qui nous a permis d'effectuer cette TO52 lors de notre cursus à
l'UTBM, nous permettant ainsi de mêler nôtre travail scolaire à nôtre envie de participer à l'amélioration de la vie
associative de l'UTBM.
\par Je tiens aussi à remercier Florent \textsc{Jacquet} qui m'a aidé tout au long de ce travail et à qui j'ai pu poser mes
différentes questions pour apprendre et comprendre plus rapidement que si j'avais été seul.
\section*{Florent \textsc{Jacquet}}
\par Je remercie également Frédéric \textsc{Lassabe} , non seulement pour la TO, mais également pour la précédente TW. Sans ces
deux UV hors emploi du temps, jamais un projet comme ce site de l'AE n'aurait pu voir le jour. Cela a demandé beaucoup
d'investissement, et il est plus qu'appréciable de pouvoir obtenir quelques crédits en retour.
\par Je tiens également à remercier l'ensemble de l'équipe info de l'AE qui s'est motivée ce semestre à organiser des
réunions hebdomadaires afin de reprendre le projet du mieux possible. C'est maintenant à eux que va être confié le
projet, et il est agréable de constater qu'ils n'ont pas attendu le dernier moment pour se pencher sur la question.
\chapter*{Introduction}
\addcontentsline{toc}{chapter}{Introduction}
\par Après le développement de la base du nouveau site de l'AE, le projet \emph{Sith}, au Printemps 2016, la mise en
production a pu avoir lieu avec succès fin Août 2016.
\par Mais le site était encore très incomplet, et il était nécessaire d'y ajouter un grand nombre de fonctionnalités
moins critiques, celles-ci n'ayant pas de rapport avec l'argent, mais tout de même très utiles pour le fonctionnement
de l'AE.
\par Parmis elles, se trouvait notamment une application de gestion des stocks, qui a été confiée à Guillaume, puisqu'elle
concernait en premier lieu le \emph{Bureau des Festivités} et qu'il en était le président. Il était donc parmis les
mieux placé pour évaluer le besoin et développer l'outil, d'autant qu'il fallait le concevoir depuis le début, ces
fonctions n'étant pas du tout présentes dans l'ancien site.
\par Du reste, Florent a eu la responsabilité de développer les autres applications, ou bien de gérer leur développement
lors qu'il était fait par quelqu'un d'autre.
\chapter{Eboutic}
\label{sec:eboutic}
\par Développeur principal: Florent
\section{But}
\label{sub:but}
\par Fournir une boutique en ligne, avec paiement sécurisé, compatible avec l'API de paiement du Crédit Agricole.
\begin{itemize}
\item Gérer les cotisations
\item Gérer les rechargements de compte AE
\item Gérer différents groupes de vente
\end{itemize}
\section{Principaux problèmes}
\label{sec:principaux_problemes}
\subsection{Interaction avec l'API}
\label{sub:interaction_avec_l_api}
\par C'est la principale contrainte de cette application. On doit interagir avec les serveurs du Crédit Agricole, et
pour cela, ces derniers n'aident pas beaucoup.
\par Ils fournissent un PDF peu clair\footnote{disponible dans le dossier \url{doc/Etransaction/} des sources du site}
expliquant l'implémentation d'un site marchand, en plus des nombreux autres PDF de documentation disponibles à l'adresse
\url{https://e-transactions.avem-groupe.com/pages/global.php?page=telechargement}.
\par Une implémentation de référence uniquement en PHP, et contenant que peut de fonctionnalités par rapport à ce
que dit le PDF peut aussi être obtenue, mais n'est guère utile excepté pour la vérification cryptographique de la
signature de la réponse. Mais encore, il faut arriver à traduire les fonctions propres à PHP, et ce n'est pas toujours
une mince affaire, mais fort heureusement, les algorithmes sont encore assez standards et l'on trouve vite de l'aide
quant à ces fonctions.
\par De plus, certaines informations concernants les numéros d'identification de marchand son incohérents
d'une documentation à l'autre, et le plus simple à ce niveau est encore de contacter le support.
\subsection{Accès concurrentiels}
\label{sub:acces_concurrentiels}
\par En production, le projet Sith tourne à l'aide d'\textbf{uWSGI}, qui s'occupe lui de gérer les différents processus du
logiciel. Cela se traduit par des accès concurrentiels à la base de donnée lors de l'appel de deux pages simultanément
qui ont besoin d'accèder aux mêmes ressources.
\par Le problème n'en est la plupart du temps pas un, mais il devient très critique lorsque la page appelée permet par
exemple de recharger un compte AE. Il ne faut alors surtout faire l'opération en double.
\par Pour protéger ces accès en double, on peut alors utiliser des transactions, et \textbf{Django} fournit une
abstraction très pratique: \verb-with transaction.atomic():-.
\par L'Eboutic, avec sa réponse de la banque, est très sujette à ces accès concurrents, et cela a posé quelques
problèmes dans les débuts. La plupart ont été résolu, mais il arrive encore dans les comptoirs d'avoir une vente en
double, sans pour autant avoir le débit du compte qui soit doublé. Cela ne pose pas foncièrement de problèmes, puisque
le solde du compte est tout de même valide, et c'est un problème très compliqué à debugger, puisqu'il survient très
rarement, mais il faudrait tout de même arriver à le résoudre un jour.
\chapter{Le SAS}
\label{sec:le_sas}
\par Développeur principal: Florent
\section{But}
\label{sub:but}
\par Fournir un système de galerie de photo:
\begin{itemize}
\item Upload en ligne via un formulaire pour tous les cotisants.
\item Modération pour l'équipe du SAS.
\item Système d'identification des membres pour retrouver rapidement ses photos.
\item Affichage des photos dans les différents album et sur la page "photo" du profil d'un utilisateur.
\end{itemize}
\section{Principaux problèmes}
\label{sec:principaux_problemes}
\subsection{Gestion des fichiers}
\label{sub:gestion_des_fichiers}
\par L'envoie en grande quantité de photos nécéssite une gestion des fichiers solide, en même temps qu'un formulaire
d'envoie efficace, capable d'envoyer plusieurs dizaines de photos en une seule action de l'utilisateur.
\par L'envoie est donc fait à l'aide de requêtes AJAX pour envoyer les photos une par une et éviter alors le timeout.
\par Concernant les fichiers une fois envoyé, ils sont en réalité traités par la classe \verb#SithFile# qui gère tous
les fichiers du site. Cela ne fait qu'une seule classe à développer, de même qu'un seul système de fichier avec une
seule arborescence, ce qui est beaucoup plus robuste.
\par La difficulté a aussi été de permettre le déplacement des fichiers, par couper-coller, tout en faisant de même dans
le système de fichier réel, afin d'avoir une arborescence cohérente même en cas de perte de la base de données.
\subsection{Optimisation des pages}
\label{sub:optimisation_des_pages}
\par La génération d'un grand nombre de requêtes SQL est un des principaux problèmes de ralentissement d'un site. Le
SAS, avec ses très nombreuses photos, qui requierent une validation des droits, a posé un gros problème à ce niveau.
\par Certaines pages ont pu mettre jusqu'à plus de 10 secondes à générer, ce qui est inconcevable pour une galerie de
photos, mais ce temps à pu être réduit à moins de 3 secondes.
\par L'astuce à été d'utiliser des actions utilisateurs, comme l'upload de nouvelles photos, pour faire plus de
traitement que nécessaire, afin de mettre en "cache" une grande partie des actions, comme par exemple la génération des
miniatures des albums.
\par Une autre technique pour gagner du temps est de mettre en cache certaines requêtes en forcant les \emph{QuerySet}
à s'évaluer dans une \emph{list} \textbf{Python} que l'on stocke afin d'obtenir sa longueur, au lieu de lancer d'abord
un \emph{count}, puis une itération des résultats, qui en utilisant directement l'ORM, conduit à réaliser deux
requêtes SQL.
\par Enfin, le passage à \textbf{HTTP/2} permettrait d'améliorer encore les performances côté utilisateur puisqu'il n'y
aurait plus qu'un seul \emph{socket} d'ouvert pour transférer toutes les photos d'une page par exemple, sans avoir
pour autant à toucher au code.
\chapter{Les élections}
\label{sec:les_elections}
\par Développeur principal: Antoine
\section{But}
\label{sub:but}
\par Fournir un système d'élections:
\begin{itemize}
\item Gestion des différentes élections comprenants à chaque fois une liste de postes pour lesquels les gens
candidatent, ainsi qu'une gestion des listes, pour pouvoir classifier et répartir les candidatures.
\item Gestion d'une page de vote, permettant aux gens autorisés de pouvoir voter.
\item Affichage des résultats une fois le vote terminé.
\item Pas compatible avec la législation française: trop contraignant et pas utile, puisque validation officiel en
AG.
\end{itemize}
\section{Principaux problèmes}
\label{sec:principaux_problemes}
\subsection{Automatisation d'un widget particulier pour les formulaires}
\label{sub:automatisation_d_un_widget_particulier_pour_les_formulaires}
\par La demande est venue du \textbf{BdF} qui a voulu autoriser pour certains poste un nombre de vote supérieur à 1.
Cela signifie que l'on passe d'un choix simple, type \verb#radio# à un choix multiple, type \verb#checkbox#, tout cela
étant paramètrable dans l'élection.
\par Ce genre de choix n'étant pas disponible dans \textbf{Django} de base, il a fallut développer le \emph{widget} à
utiliser dans le formulaire qui permette cette configuration tout en validant bien les données reçues par rapport au
modèle, et éviter ainsi de pouvoir "tricher" en envoyant des requêtes erronées.
\subsection{Revue du code d'un autre développeur}
\label{sub:revue_du_code_d_un_autre_developpeur}
\par \emph{Antoine} étant le principal développeur de cette application, un gros travail de revue de code a dû être
effectuer afin de garantir une certaine cohérence avec le reste du projet.
\par C'est un travail très long et fastidieux, car il faut bien revérifier chaque ligne, sur chaque fichier, tout en
faisant des commentaire lorsque quelque chose ne va pas. \textbf{Gitlab} a, à ce niveau, grandement facilité la tâche, à
l'aide de ses outils de \emph{merge request} assez avancés.
\par En plus du code en lui-même, il a fallut porter une attention particulière aux migrations. Ces fichiers générés
automatiquement par \textbf{Django} sont responsables du maintient d'une base de donnée cohérente malgré les évolutions
des modèles. Même si le code parait donc valide, il est impératif de surveiller que la chaîne de dépendance des dites
migrations ne soit pas cassée, au risque de problèmes potentiels au moment de la mise en production, ce qui entraînent à
coup sûr un \emph{downtime}.
\chapter{Les stocks}
\label{sub:les_stocks}
\par Développeur principal: Guillaume
\vskip 2em
\par Cette application s'occupe de la gestion des stocks des comptoirs de type « BAR ». Elle permet de suivre les
quantités restantes afin de pouvoir déterminer de manière automatisée quels sont les produits qu'il faut acheter et en
quelle quantité.
\section{Liste des modèles}
\label{sec:liste_des_modeles}
\subsection{Stock}
\par Un Stock possède un nom et est lié à la classe Counter. Ainsi, chaque Comptoir peut avoir son propre Stock.
\subsection{StockItem}
\par Un StockItem possède un nom, une quantité unitaire, une quantité effective, une quantité minimale et est lié à la
classe Stock ainsi qu'à la classe ProductType. De cette manière, chaque élément appartient à un Stock et il est
catégorisé de la même manière que les Products (qui sont les objets utilisés pour la vente dans les comptoirs).
\subsection{ShoppingList}
\par Une ShoppingList possède un nom, une date, un booléen (fait ou à faire), un commentaire et est liée à un Stock.
Chaque ShoppingList est donc liée à un Stock ce qui permet d'avoir des listes de courses spécifique à chaque comptoir.
\subsection{ShoppingListItem}
\par Un ShoppingListItem possède un nom, une quantité demandée, une quantité achetée et est lié à la classe StockItem, à
la classe ShoppingList et à la classe ProductType. Cela permet de pouvoir faire plusieurs listes de courses différentes
en même temps et d'en garder un historique.
\section{Fonctionnement}
\label{sec:fonctionnement}
\par Au départ, si le comptoir de type « BAR » n'a pas de stock, la seule chose qu'il est possible de faire est d'en
créer un. Ensuite, il va falloir créer les objets StockItem en indiquant pour chacun les quantités qu'il y a dans le
stock.
\par De plus, la personne (généralement le Responsable du lieu de vie) qui aura la responsabilité d'informatiser les
stocks devra aussi définir la quantité unitaire, effective et minimale.
\par Par exemple, les Cheeseburger vendus aux différents comptoirs sont achetés par boite de 6, la quantité unitaire
sera donc 6, la quantité effective correspondra au nombre de boites restantes dans le stock (c'est à dire dans la
réserve du lieu de vie, une boite sortie du stock est considérée comme consommée) et enfin, la quantité minimale servira
de valeur seuil.
\par Une fois l'état de la réserve retranscrit dans le site, il reste encore gérer les stocks de manière quotidienne.
Pour ce faire, l'application se décompose en 3 parties :
\begin{itemize}
\item Création automatique des listes de courses
\item Approvisionnement du stock
\item Prise d'éléments dans le stock
\end{itemize}
\par Lorsque l'on accède à la partie qui s'occupe de la gestion des listes de courses, il y a un bouton permettant de
créer une liste de courses en fonction de l'état des stocks à cet instant, puis un premier tableau contenant les listes
de courses qu'il faut faire et enfin un second tableau servant d'historique des listes de courses déjà effectuées.
\par Pour chaque liste de course ainsi créée, qu'elle soit « faite » ou « à faire », il est possible de cliquer sur son
nom pour voir le détail de ce qu'elle comprend.
\subsection{Création automatique des listes de courses}
\par En cliquant sur le bouton permettant de créer une nouvelle liste de courses, il faut remplir un formulaire. Les
informations à donner dans ce formulaire sont le nom de la liste de course (par exemple, une liste spéciale pour
Leclerc), ensuite, apparaissent tous les StockItem ayant une quantité effective inférieure au seuil fixé par leur
quantité minimale. Il faut donc donner pour chacun de ces éléments une quantité à acheter. Enfin, un dernier champ de
commentaire peut être compléter, il sert à demander l'achat d'éléments qui n'apparaissent pas dans le Stock, par
exemple, des couteaux, fourchettes ou encore tasses...
\par Lors de la validation de ce formulaire, la liste de courses est créée et est ajoutée au tableau contenant les
listes de courses à faire.
\subsection{Approvisionnement du stock}
\par Au retour des courses, il faut ranger les produits achetés dans la réserve. À ce moment-là, il faut aussi mettre à
jour le stock. Une opération « Mettre à jour le stock » est disponible pour chaque liste de courses du tableau « À
faire ».
\par En effectuant cette action, il va falloir indiquer les quantités effectivement achetées. En effet, les quantités
demandées ne sont pas forcément celles achetées, c'est donc les quantités effectives qu'il faut ajouter au stock. Une
fois ce formulaire validé, la liste de couses passera de l'état « à faire » à l'état « faite ».
\subsection{Prise d'éléments dans le stock}
\par La réserve étant accessible aux barmen afin qu'ils puissent réapprovisionner les réfrigérateurs à tout moment,
l'interface permettant de prendre des éléments dans le stock a été ajoutée dans les onglets de l'interface des ventes
(là où le barman inscrit le code du compte du client qui souhaite commander quelque chose). Ainsi, en revenant de la
réserve, le barman doit indiquer le nombre de chaque produit qu'il a rapporté.
\par Dans un souci de simplicité pour le gérant du lieu de vie, ce formulaire de prise des éléments dans le stock est
aussi accessible depuis son interface de gestion.
\section{Améliorations à apporter}
\label{sec:amelioration_a_apporter}
\begin{itemize}
\item Il n'est pas encore possible de modifier les quantités demandées pour un ou plusieurs des produits d'une liste
de course. Il faudrait rendre cela possible car actuellement, il faut supprimer la liste de courses et la
refaire en changeant les quantités souhaitées.
\item Il faudrait améliorer la manière dont on ajoute les éléments non définis en tant que StockItem dans la liste
de course. Un objet ShoppingListItem avec une référence « Null » vers la classe StockItem pourrait être créé à
la place de compléter le champ de commentaires.
\item Dans les améliorations sur le long terme, il faudrait que la décrémentation des quantités de chaque élément
dans le stock soit automatique. En repensant une partie de l'architecture de l'application, on pourrait faire en
sorte que chaque vente faite au comptoir face diminuer les quantités restantes (cela remplacerait le formulaire
de « Prise d'éléments dans le stock », mais cela ne serait pas applicable à tous les produits mis en vente. Par
exemple, pour les cacahuètes que nous vendons au bol et non par paquet)
\item Avec le système de notifications qui a été mis en place sur le site, on pourrait faire en sorte que le ou les
responsables des lieux de vie reçoivent une notification lorsque la liste de courses contient plus de 5 éléments
\end{itemize}
\chapter{La laverie}
\label{sec:la_laverie}
\par Développeur principal: Florent
\section{But}
\label{sub:but}
\par Cette application doit fournir un système de gestion de laverie. Cela comprend:
\begin{itemize}
\item Un système de planning et de réservation de créneaux
\item Un système de vente de jetons de laverie, lié aux comptoirs et au compte AE, permettant aux permanenciers de
cliquer les jetons en même temps qu'ils vérifient l'état de la cotisation.
\item Un système d'inventaire, pour gérer les différentes machines dans les différents lieux, et gérer également le
retour des jetons après utilisation.
\end{itemize}
\section{Principaux problèmes}
\label{sec:principaux_problemes}
\subsection{Génération de plannings}
\label{sub:generation_de_plannings}
\par Il y a là beaucoup de cas à prendre en compte. Lorsque que quelqu'un veut réserver directement un "Lavage +
Séchage", un simple "Lavage", ou un simple "Séchage", il faut toujours vérifier la disponibilité des créneaux en
fonction du nombre de machine de chaque type présent dans la laverie en question \footnote{Belfort ou Sevenans, en
l'occurrence}, et cela représente vite un grand nombre de combinaisons à vérifier.
\par De plus, la réservation doit rester ergonomique, et s'afficher dans un format le plus lisible possible pour un
humain. Là dessus, un tableau est le plus approprié, avec chaque jour représenté par une colonne, et chaque créneau par
une ligne.\\
Mais cela ne représente malheureusement pas la temporalité, et la génération du tableau devient alors plutôt compliquée,
et d'autant plus si l'on veut qu'il soit sémantiquement correct en HTML.
\subsection{Gestion des timezones}
\label{sub:gestion_des_timezones}
\par La gestion et le stockage des crénaux implique l'utilisation de champs de type \verb#DateTime#. \textbf{Django} les
gère très bien, particulièrement au niveau des \emph{timezones}, où ce dernier n'hésite pas à lancer un warning lorsque
l'objet \emph{date} passé ne contient pas d'information de fuseau horaire.
\par Mais avec notre décalage d'une heure par rapport au temps UTC, tous les horaires se retrouvent décalés, et gérer
cela convenablement sans sortir d'avertissement a été plutôt compliqué. La solution a été de forcer un peu partout la
\emph{timezone} à UTC, afin de ne pas créer de décalage, mais en conservant tout de même l'information de fuseau
horaire, et sans tout casser lors du passage à l'heure d'hiver.
\chapter{La communication}
\label{sec:la_communication}
\par Développeur principal: Florent
\section{But}
\label{sub:but}
\par Cette application a plusieurs but:
\begin{itemize}
\item Donner la possibilité au responsable communication d'éditer les différents textes, messages, et pages
statiques du site.
\item Fournir un système de news.
\item Fournir un système de newsletter: le Weekmail.
\end{itemize}
\section{Principaux problèmes}
\label{sec:principaux_problemes}
\subsection{Envoie de mails}
\label{sub:envoie_de_mails}
\par Un outil de \emph{newsletter} nécessite l'envoie de mail. C'est là quelque chose de relativement compliqué à
tester, d'autant plus lorsqu'il s'agit de mailing-list contenant l'intégralité des étudiants de l'UTBM.
\par \textbf{Django} fournit toutefois un outil très pratique: il contient plusieurs \emph{backend} d'emails, dont entre
autre un \emph{SMTP}, et un \emph{console}. Le \emph{SMTP} est bien évidemment utilisé en production pour envoyer
effectivement les mails, mais il est compliqué à utiliser en développement, car il suppose que le développeur a à sa
disposition un serveur de ce type. On utilise alors le backend \emph{console}, qui affiche simplement dans le thread
d'execution de \textbf{Django} une version texte de l'email envoyé, avec d'une part les entêtes, d'autre part le corps
de message.
\par Mais autant pour tester l'envoie d'un mail unique à une adresse unique, cela fonctionne parfaitement bien, ce n'est
toutefois pas suffisant pour tester un envoie massif à plusieurs mailings, avec en plus encore d'autres adresses en
\emph{Bcc} pour les gens ne faisant pas partie des mailings "classiques", mais souhaitant quand même recevoir le
\textbf{Weekmail}.
\subsection{Amélioration de l'outil de recherche}
\label{sub:amelioration_de_l_outil_de_recherche}
\par Pour la gestion de l'AE, il est nécessaire de pouvoir rechercher et trouver efficacement n'importe quel membre, en
tapant au choix son nom, prénom, ou surnom, voire une combinaison de ces trois champs.
\par Mais une fonction de recherche aussi complexe est très difficile a mettre en place efficacement sans un traitement
préalable, d'où la nécessité d'indexer les entrées à chercher. Un indexeur étant très complexe, mais également très
courant, il n'a pas été difficile de trouver une application déjà existante fournissant ces fonctionnalités.
\par Le choix s'est porté sur \textbf{Haystack}, en l'utilisant avec l'indexeur \textbf{Whoosh}, plutôt efficace pour
des bases raisonnables, et surtout écrit en pure \textbf{Python}, donc ne nécessitant pas d'installation compliquée en
parallèle du site.
\par Le résultat est plutôt satisfaisant, mais il faudrait encore améliorer les résultats en utilisant les fonctions de
\emph{boost} pour certains champs. De plus, une certaine lenteur se fait encore sentir avec certaines recherches trop
communes ou générales.
\chapter{Conclusions personnelles}
\section{Florent}
\label{sec:skia}
\par Développer de nouvelles application m'a permis d'apréhender d'autres problématiques, comme la gestion des fichiers
dans le SAS, ou bien des contraintes de concurrence et d'atomicité sur l'Eboutic.
\par Mais la plus grosse partie de mon travail ce semestre a surtout été de superviser une équipe de développement
naissante, de relire les "Merge request", et de m'assurer de la cohérence du code des contributeurs avec le reste du
projet.
\par J'ai églament pu approfondir mon utilisation de Gitlab à travers ses outils de gestion de projet, de revue de code,
et de gestion des permissions sur les différentes branches.
\section{Guillaume}
\label{sec:lo_j}
\par Je suis très heureux d'avoir pu participer à ce projet de TO52 sur le développement de modules sur le site de
l'Association des Étudiants. J'ai pu apprendre à travailler avec un nouvel environnement informatique tout en
contribuant au développement d'outils pour l'association dont je suis Président, le Bureau des Festivités.
\subsection{Django}
\par Ayant déjà travaillé avec le framework Spring et Java durant mon stage ST40, j'ai pu m'appuyer sur des notions
générales afin d'apprendre et de comprendre le fonctionnement du Python et de Django que je ne connaissais pas du tout.
\par Mon apprentissage a été assez long au départ, car il y avait beaucoup d'informations à intégrer. Django est un
framework très pratique qui permet d'effectuer de nombreuses tâches assez rébarbatives de manière automatique certes,
mais encore faut-il comprendre ce qu'il se passe en arrière-plan. C'est cet apprentissage qui m'a pris le plus de temps.
\par Mon deuxième point de difficulté a été les formulaires. Là encore, Django est très pratique dès lors qu'il s'agit
de faire un formulaire avec tous les champs d'un même Model. Cependant, il m'a fallu de l'aide et du temps pour
comprendre comment faire pour ajouter d'autres champs en plus de ceux du Model au formulaire et pour comprendre comment
récupérer les valeurs associées à chacun d'eux.
\subsection{Git}
\par J'ai eu aussi à apprendre le fonctionnement de Git que j'avais déjà pu manipuler quelque peu mais il me manquait
quand même beaucoup d'éléments.
\par Aujourd'hui, je pense pouvoir dire que j'ai progressé dans ce domaine mais il me reste encore bien des choses à
apprendre pour être capable de l'utiliser de manière efficace.
\includepdf[pages={2}]{Couvertures.pdf}
\end{document}

View File

@ -1,18 +0,0 @@
LATEX := pdflatex
TARGET := slide.pdf
.PHONY: all clean distclean
all: $(TARGET) clean
%.pdf: %.tex
echo "Building pdf"
$(LATEX) --shell-escape $<
rm -f $@
$(LATEX) --shell-escape $<
clean:
rm -f *.log *.nav *.snm *.aux *.out *.toc *.pyg
distclean: clean
rm -f $(TARGET)

View File

@ -1,133 +0,0 @@
%%
%% This is file `beamercolorthememetropolis.sty',
%% generated with the docstrip utility.
%%
%% The original source files were:
%%
%% beamercolorthememetropolis.dtx (with options: `package')
%% ---------------------------------------------------------------------------
%% Copyright 2015 Matthias Vogelgesang and the LaTeX community. A full list of
%% contributors can be found at
%%
%% https://github.com/matze/mtheme/graphs/contributors
%%
%% and the original template was based on the HSRM theme by Benjamin Weiss.
%%
%% This work is licensed under a Creative Commons Attribution-ShareAlike 4.0
%% International License (https://creativecommons.org/licenses/by-sa/4.0/).
%% ---------------------------------------------------------------------------
\NeedsTeXFormat{LaTeX2e}
\ProvidesPackage{beamercolorthememetropolis}[2016/02/21 Metropolis color theme]
\RequirePackage{pgfopts}
\pgfkeys{
/metropolis/color/block/.cd,
.is choice,
transparent/.code=\metropolis@block@transparent,
fill/.code=\metropolis@block@fill,
}
\pgfkeys{
/metropolis/color/background/.cd,
.is choice,
dark/.code=\metropolis@colors@dark,
light/.code=\metropolis@colors@light,
}
\newcommand{\metropolis@color@setdefaults}{
\pgfkeys{/metropolis/color/.cd,
background=light,
block=transparent,
}
}
\definecolor{mDarkBrown}{HTML}{604c38}
\definecolor{mDarkTeal}{HTML}{23373b}
\definecolor{mLightBrown}{HTML}{EB811B}
\definecolor{mLightGreen}{HTML}{14B03D}
\newcommand{\metropolis@colors@dark}{
\setbeamercolor{normal text}{%
fg=black!2,
bg=mDarkTeal
}
}
\newcommand{\metropolis@colors@light}{
\setbeamercolor{normal text}{%
fg=mDarkTeal,
bg=black!2
}
}
\setbeamercolor{alerted text}{%
fg=mLightBrown
}
\setbeamercolor{example text}{%
fg=mLightGreen
}
\setbeamercolor{titlelike}{use=normal text, parent=normal text}
\setbeamercolor{author}{use=normal text, parent=normal text}
\setbeamercolor{date}{use=normal text, parent=normal text}
\setbeamercolor{institute}{use=normal text, parent=normal text}
\setbeamercolor{structure}{use=normal text, fg=normal text.fg}
\setbeamercolor{palette primary}{%
use=normal text,
fg=normal text.bg,
bg=normal text.fg
}
\setbeamercolor{frametitle}{%
use=palette primary,
parent=palette primary
}
\setbeamercolor{progress bar}{%
use=alerted text,
fg=alerted text.fg,
bg=alerted text.fg!50!black!30
}
\setbeamercolor{title separator}{
use=progress bar,
parent=progress bar
}
\setbeamercolor{progress bar in head/foot}{%
use=progress bar,
parent=progress bar
}
\setbeamercolor{progress bar in section page}{
use=progress bar,
parent=progress bar
}
\newcommand{\metropolis@block@transparent}{
\setbeamercolor{block title}{%
use=normal text,
fg=normal text.fg,
bg=
}
\setbeamercolor{block body}{
bg=
}
}
\newcommand{\metropolis@block@fill}{
\setbeamercolor{block title}{%
use=normal text,
fg=normal text.fg,
bg=normal text.bg!80!fg
}
\setbeamercolor{block body}{
use={block title, normal text},
bg=block title.bg!50!normal text.bg
}
}
\setbeamercolor{block title alerted}{%
use={block title, alerted text},
bg=block title.bg,
fg=alerted text.fg
}
\setbeamercolor{block title example}{%
use={block title, example text},
bg=block title.bg,
fg=example text.fg
}
\setbeamercolor{block body alerted}{use=block body, parent=block body}
\setbeamercolor{block body example}{use=block body, parent=block body}
\setbeamercolor{footnote}{fg=normal text.fg!90}
\setbeamercolor{footnote mark}{fg=.}
\metropolis@color@setdefaults
\ProcessPgfPackageOptions{/metropolis/color}
\mode<all>
\endinput
%%
%% End of file `beamercolorthememetropolis.sty'.

View File

@ -1,283 +0,0 @@
%%
%% This is file `beamerfontthememetropolis.sty',
%% generated with the docstrip utility.
%%
%% The original source files were:
%%
%% beamerfontthememetropolis.dtx (with options: `package')
%% ---------------------------------------------------------------------------
%% Copyright 2015 Matthias Vogelgesang and the LaTeX community. A full list of
%% contributors can be found at
%%
%% https://github.com/matze/mtheme/graphs/contributors
%%
%% and the original template was based on the HSRM theme by Benjamin Weiss.
%%
%% This work is licensed under a Creative Commons Attribution-ShareAlike 4.0
%% International License (https://creativecommons.org/licenses/by-sa/4.0/).
%% ---------------------------------------------------------------------------
\NeedsTeXFormat{LaTeX2e}
\ProvidesPackage{beamerfontthememetropolis}[2016/02/21 Metropolis font theme]
\RequirePackage{etoolbox}
\RequirePackage{ifxetex}
\RequirePackage{ifluatex}
\RequirePackage{pgfopts}
\ifboolexpr{bool {xetex} or bool {luatex}}{
\RequirePackage[no-math]{fontspec}
\newcounter{fontsnotfound}
\newcommand{\checkfont}[1]{%
\suppressfontnotfounderror=1%
\font\x = "#1" at 10pt
\selectfont
\ifx\x\nullfont%
\stepcounter{fontsnotfound}%
\fi%
\suppressfontnotfounderror=0%
}
\newcommand{\iffontsavailable}[3]{%
\setcounter{fontsnotfound}{0}%
\expandafter\forcsvlist\expandafter%
\checkfont\expandafter{#1}%
\ifnum\value{fontsnotfound}=0%
#2%
\else%
#3%
\fi%
}
\iffontsavailable{Fira Sans Light,%
Fira Sans Light Italic,%
Fira Sans,%
Fira Sans Italic}%
{%
\setsansfont[ItalicFont={Fira Sans Light Italic},%
BoldFont={Fira Sans},%
BoldItalicFont={Fira Sans Italic}]%
{Fira Sans Light}%
}{%
\iffontsavailable{Fira Sans Light OT,%
Fira Sans Light Italic OT,%
Fira Sans OT,%
Fira Sans Italic OT}%
{%
\setsansfont[ItalicFont={Fira Sans Light Italic OT},%
BoldFont={Fira Sans OT},%
BoldItalicFont={Fira Sans Italic OT}]%
{Fira Sans Light OT}%
}{%
\PackageWarning{beamerthememetropolis}{%
Could not find Fira Sans fonts%
}
}
}
\iffontsavailable{Fira Mono, Fira Mono Bold}{%
\setmonofont[BoldFont={Fira Mono Medium}]{Fira Mono}%
}{%
\iffontsavailable{Fira Mono OT, Fira Mono Bold OT}{%
\setmonofont[BoldFont={Fira Mono Medium OT}]{Fira Mono OT}%
}{%
\PackageWarning{beamerthememetropolis}{%
Could not find Fira Mono fonts%
}
}
}
\AtBeginEnvironment{tabular}{%
\addfontfeature{Numbers={Monospaced}}%
}
}{%
\PackageWarning{beamerthememetropolis}{%
You need to compile with XeLaTeX or LuaLaTeX to use the Fira fonts%
}
}
\setbeamerfont{title}{size=\Large,%
series=\bfseries}
\setbeamerfont{author}{size=\small}
\setbeamerfont{date}{size=\small}
\setbeamerfont{section title}{size=\Large,%
series=\bfseries}
\setbeamerfont{block title}{size=\normalsize,%
series=\bfseries}
\setbeamerfont{block title alerted}{size=\normalsize,%
series=\bfseries}
\setbeamerfont*{subtitle}{size=\large}
\setbeamerfont{frametitle}{size=\large,%
series=\bfseries}
\setbeamerfont{caption}{size=\small}
\setbeamerfont{caption name}{series=\bfseries}
\setbeamerfont{description item}{series=\bfseries}
\setbeamerfont{page number in head/foot}{size=\scriptsize}
\setbeamerfont{bibliography entry author}{size=\normalsize,%
series=\normalfont}
\setbeamerfont{bibliography entry title}{size=\normalsize,%
series=\bfseries}
\setbeamerfont{bibliography entry location}{size=\normalsize,%
series=\normalfont}
\setbeamerfont{bibliography entry note}{size=\small,%
series=\normalfont}
\setbeamerfont{standout}{size=\Large,%
series=\bfseries}
\pgfkeys{
/metropolis/font/titleformat title/.cd,
.is choice,
regular/.code={%
\let\metropolis@titleformat\@empty%
\setbeamerfont{title}{shape=\normalfont}%
},
smallcaps/.code={%
\let\metropolis@titleformat\@empty%
\setbeamerfont{title}{shape=\scshape}%
},
allsmallcaps/.code={%
\let\metropolis@titleformat\lowercase%
\setbeamerfont{title}{shape=\scshape}%
\PackageWarning{beamerthememetropolis}{%
Be aware that titleformat title=allsmallcaps can lead to problems%
}
},
allcaps/.code={%
\let\metropolis@titleformat\uppercase%
\setbeamerfont{title}{shape=\normalfont}
\PackageWarning{beamerthememetropolis}{%
Be aware that titleformat title=allcaps can lead to problems%
}
},
}
\pgfkeys{
/metropolis/font/titleformat subtitle/.cd,
.is choice,
regular/.code={%
\let\metropolis@subtitleformat\@empty%
\setbeamerfont{subtitle}{shape=\normalfont}%
},
smallcaps/.code={%
\let\metropolis@subtitleformat\@empty%
\setbeamerfont{subtitle}{shape=\scshape}%
},
allsmallcaps/.code={%
\let\metropolis@subtitleformat\lowercase%
\setbeamerfont{subtitle}{shape=\scshape}%
\PackageWarning{beamerthememetropolis}{%
Be aware that titleformat subtitle=allsmallcaps can lead to problems%
}
},
allcaps/.code={%
\let\metropolis@subtitleformat\uppercase%
\setbeamerfont{subtitle}{shape=\normalfont}%
\PackageWarning{beamerthememetropolis}{%
Be aware that titleformat subtitle=allcaps can lead to problems%
}
},
}
\pgfkeys{
/metropolis/font/titleformat section/.cd,
.is choice,
regular/.code={%
\let\metropolis@sectiontitleformat\@empty%
\setbeamerfont{section title}{shape=\normalfont}%
},
smallcaps/.code={%
\let\metropolis@sectiontitleformat\@empty%
\setbeamerfont{section title}{shape=\scshape}%
},
allsmallcaps/.code={%
\let\metropolis@sectiontitleformat\MakeLowercase%
\setbeamerfont{section title}{shape=\scshape}%
\PackageWarning{beamerthememetropolis}{%
Be aware that titleformat section=allsmallcaps can lead to problems%
}
},
allcaps/.code={%
\let\metropolis@sectiontitleformat\MakeUppercase%
\setbeamerfont{section title}{shape=\normalfont}%
\PackageWarning{beamerthememetropolis}{%
Be aware that titleformat section=allcaps can lead to problems%
}
},
}
\pgfkeys{
/metropolis/font/titleformat frame/.cd,
.is choice,
regular/.code={%
\let\metropolis@frametitleformat\@empty%
\setbeamerfont{frametitle}{shape=\normalfont}%
},
smallcaps/.code={%
\let\metropolis@frametitleformat\@empty%
\setbeamerfont{frametitle}{shape=\scshape}%
},
allsmallcaps/.code={%
\let\metropolis@frametitleformat\MakeLowercase%
\setbeamerfont{frametitle}{shape=\scshape}%
\PackageWarning{beamerthememetropolis}{%
Be aware that titleformat frame=allsmallcaps can lead to problems%
}
},
allcaps/.code={%
\let\metropolis@frametitleformat\MakeUppercase%
\setbeamerfont{frametitle}{shape=\normalfont}
\PackageWarning{beamerthememetropolis}{%
Be aware that titleformat frame=allcaps can lead to problems%
}
},
}
\pgfkeys{
/metropolis/font/.cd,
titleformattitle/.code=\pgfkeysalso{titleformat title=#1},
titleformatsubtitle/.code=\pgfkeysalso{titleformat subtitle=#1},
titleformatsection/.code=\pgfkeysalso{titleformat section=#1},
titleformatframe/.code=\pgfkeysalso{titleformat frame=#1},
}
\newcommand{\metropolis@font@setdefaults}{
\pgfkeys{/metropolis/font/.cd,
titleformat title=regular,
titleformat subtitle=regular,
titleformat section=regular,
titleformat frame=regular,
}
}
\def\metropolis@titleformat#1{#1}
\def\metropolis@subtitleformat#1{#1}
\def\metropolis@sectiontitleformat#1{#1}
\def\metropolis@frametitleformat#1{#1}
\patchcmd{\beamer@title}%
{\def\inserttitle{#2}}%
{\def\inserttitle{\metropolis@titleformat{#2}}}%
{}%
{\PackageError{beamerfontthememetropolis}{Patching title failed}}
\patchcmd{\beamer@subtitle}%
{\def\insertsubtitle{#2}}%
{\def\insertsubtitle{\metropolis@subtitleformat{#2}}}%
{}%
{\PackageError{beamerfontthememetropolis}{Patching subtitle failed}}
\patchcmd{\sectionentry}
{\def\insertsectionhead{#2}}
{\def\insertsectionhead{\metropolis@sectiontitleformat{#2}}}
{}
{\PackageError{beamerfontthememetropolis}{Patching section title failed}}
\patchcmd{\beamer@section}
{\def\insertsectionhead{\hyperlink{Navigation\the\c@page}{#1}}}
{\def\insertsectionhead{\hyperlink{Navigation\the\c@page}{%
\metropolis@sectiontitleformat{#1}}}}
{}
{\PackageError{beamerfontthememetropolis}{Patching section title failed}}
\patchcmd{\beamer@@frametitle}
{\beamer@ifempty{#2}{}{%
\gdef\insertframetitle{{#2\ifnum\beamer@autobreakcount>0\relax{}\space%
\usebeamertemplate*{frametitle continuation}\fi}}%
\gdef\beamer@frametitle{#2}%
\gdef\beamer@shortframetitle{#1}%
}}
{\beamer@ifempty{#2}{}{%
\gdef\insertframetitle{{\metropolis@frametitleformat{#2}\ifnum%
\beamer@autobreakcount>0\relax{}\space%
\usebeamertemplate*{frametitle continuation}\fi}}%
\gdef\beamer@frametitle{#2}%
\gdef\beamer@shortframetitle{#1}%
}}
{}
{\PackageError{beamerfontthememetropolis}{Patching frame title failed}}
\metropolis@font@setdefaults
\ProcessPgfPackageOptions{/metropolis/font}
\endinput
%%
%% End of file `beamerfontthememetropolis.sty'.

View File

@ -1,281 +0,0 @@
%%
%% This is file `beamerinnerthememetropolis.sty',
%% generated with the docstrip utility.
%%
%% The original source files were:
%%
%% beamerinnerthememetropolis.dtx (with options: `package')
%% ---------------------------------------------------------------------------
%% Copyright 2015 Matthias Vogelgesang and the LaTeX community. A full list of
%% contributors can be found at
%%
%% https://github.com/matze/mtheme/graphs/contributors
%%
%% and the original template was based on the HSRM theme by Benjamin Weiss.
%%
%% This work is licensed under a Creative Commons Attribution-ShareAlike 4.0
%% International License (https://creativecommons.org/licenses/by-sa/4.0/).
%% ---------------------------------------------------------------------------
\NeedsTeXFormat{LaTeX2e}
\ProvidesPackage{beamerinnerthememetropolis}[2016/02/21 Metropolis inner theme]
\RequirePackage{etoolbox}
\RequirePackage{keyval}
\RequirePackage{calc}
\RequirePackage{pgfopts}
\RequirePackage{tikz}
\pgfkeys{
/metropolis/inner/sectionpage/.cd,
.is choice,
none/.code=\metropolis@disablesectionpage,
simple/.code={\metropolis@enablesectionpage
\setbeamertemplate{section page}[simple]},
progressbar/.code={\metropolis@enablesectionpage
\setbeamertemplate{section page}[progressbar]},
}
\pgfkeys{
/metropolis/inner/subsectionpage/.cd,
.is choice,
none/.code=\metropolis@disablesubsectionpage,
simple/.code={\metropolis@enablesubsectionpage
\setbeamertemplate{section page}[simple]},
progressbar/.code={\metropolis@enablesubsectionpage
\setbeamertemplate{section page}[progressbar]},
}
\newcommand{\metropolis@inner@setdefaults}{
\pgfkeys{/metropolis/inner/.cd,
sectionpage=progressbar,
subsectionpage=none
}
}
\setbeamertemplate{title page}{
\begin{minipage}[b][\paperheight]{\textwidth}
\ifx\inserttitlegraphic\@empty\else\usebeamertemplate*{title graphic}\fi
\vfill%
\ifx\inserttitle\@empty\else\usebeamertemplate*{title}\fi
\ifx\insertsubtitle\@empty\else\usebeamertemplate*{subtitle}\fi
\usebeamertemplate*{title separator}
\ifx\beamer@shortauthor\@empty\else\usebeamertemplate*{author}\fi
\ifx\insertdate\@empty\else\usebeamertemplate*{date}\fi
\ifx\insertinstitute\@empty\else\usebeamertemplate*{institute}\fi
\vfill
\vspace*{1mm}
\end{minipage}
}
\def\maketitle{%
\ifbeamer@inframe
\titlepage
\else
\frame[plain,noframenumbering]{\titlepage}
\fi
}
\def\titlepage{%
\usebeamertemplate{title page}
}
\setbeamertemplate{title graphic}{
\vbox to 0pt {
\vspace*{2em}
\inserttitlegraphic%
}%
\nointerlineskip%
}
\setbeamertemplate{title}{
\raggedright%
\linespread{1.0}%
\inserttitle%
\par%
\vspace*{0.5em}
}
\setbeamertemplate{subtitle}{
\insertsubtitle%
\par%
\vspace*{0.5em}
}
\setbeamertemplate{title separator}{
\begin{tikzpicture}
\draw[fg, fill=fg] (0,0) rectangle (\textwidth, 0.4pt);
\end{tikzpicture}%
\par%
}
\setbeamertemplate{author}{
\vspace*{2em}
\insertauthor%
\par%
\vspace*{0.25em}
}
\setbeamertemplate{date}{
\insertdate%
\par%
}
\setbeamertemplate{institute}{
\vspace*{3mm}
\insertinstitute%
\par%
}
\defbeamertemplate{section page}{simple}{
\begin{center}
\usebeamercolor[fg]{section title}
\usebeamerfont{section title}
\insertsectionhead\par
\ifx\insertsubsection\@empty\else
\usebeamercolor[fg]{subsection title}
\usebeamerfont{subsection title}
\insertsubsection
\fi
\end{center}
}
\defbeamertemplate{section page}{progressbar}{
\centering
\begin{minipage}{22em}
\raggedright
\usebeamercolor[fg]{section title}
\usebeamerfont{section title}
\insertsectionhead\\[-1ex]
\usebeamertemplate*{progress bar in section page}
\par
\ifx\insertsubsection\@empty\else%
\usebeamercolor[fg]{subsection title}%
\usebeamerfont{subsection title}%
\insertsubsection
\fi
\end{minipage}
\par
\vspace{\baselineskip}
}
\newcommand{\metropolis@disablesectionpage}{
\AtBeginSection{
% intentionally empty
}
}
\newcommand{\metropolis@enablesectionpage}{
\AtBeginSection{
\ifbeamer@inframe
\sectionpage
\else
\frame[plain,c,noframenumbering]{\sectionpage}
\fi
}
}
\setbeamertemplate{subsection page}{%
\usebeamertemplate*{section page}
}
\newcommand{\metropolis@disablesubsectionpage}{
\AtBeginSubsection{
% intentionally empty
}
}
\newcommand{\metropolis@enablesubsectionpage}{
\AtBeginSubsection{
\ifbeamer@inframe
\subsectionpage
\else
\frame[plain,c,noframenumbering]{\subsectionpage}
\fi
}
}
\newlength{\metropolis@progressonsectionpage}
\setbeamertemplate{progress bar in section page}{
\setlength{\metropolis@progressonsectionpage}{%
\textwidth * \ratio{\insertframenumber pt}{\inserttotalframenumber pt}%
}%
\begin{tikzpicture}
\draw[bg, fill=bg] (0,0) rectangle (\textwidth, 0.4pt);
\draw[fg, fill=fg] (0,0) rectangle (\metropolis@progressonsectionpage, 0.4pt);
\end{tikzpicture}%
}
\def\inserttotalframenumber{100}
\newlength{\metropolis@blocksep}
\newlength{\metropolis@blockadjust}
\setlength{\metropolis@blocksep}{0.75ex}
\setlength{\metropolis@blockadjust}{0.25ex}
\providecommand{\metropolis@strut}{%
\vphantom{ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz()}%
}
\newcommand{\metropolis@block}[1]{
\par\vskip\medskipamount%
\setlength{\parskip}{0pt}
\ifbeamercolorempty[bg]{block title#1}{%
\begin{beamercolorbox}[rightskip=0pt plus 4em]{block title#1}}{%
\ifbeamercolorempty[bg]{block title}{%
\begin{beamercolorbox}[rightskip=0pt plus 4em]{block title#1}%
}%
{%
\begin{beamercolorbox}[
sep=\dimexpr\metropolis@blocksep-\metropolis@blockadjust\relax,
leftskip=\metropolis@blockadjust,
rightskip=\dimexpr\metropolis@blockadjust plus 4em\relax
]{block title#1}%
}}%
\usebeamerfont*{block title#1}%
\metropolis@strut%
\insertblocktitle%
\metropolis@strut%
\end{beamercolorbox}%
\nointerlineskip%
\ifbeamercolorempty[bg]{block body#1}{%
\begin{beamercolorbox}[vmode]{block body#1}}{
\ifbeamercolorempty[bg]{block body}{%
\begin{beamercolorbox}[vmode]{block body#1}%
}{%
\begin{beamercolorbox}[sep=\metropolis@blocksep, vmode]{block body#1}%
\vspace{-\metropolis@parskip}
}}%
\usebeamerfont{block body#1}%
\setlength{\parskip}{\metropolis@parskip}%
}
\setbeamertemplate{block begin}{\metropolis@block{}}
\setbeamertemplate{block alerted begin}{\metropolis@block{ alerted}}
\setbeamertemplate{block example begin}{\metropolis@block{ example}}
\setbeamertemplate{block end}{\end{beamercolorbox}\vspace*{0.2ex}}
\setbeamertemplate{block alerted end}{\end{beamercolorbox}\vspace*{0.2ex}}
\setbeamertemplate{block example end}{\end{beamercolorbox}\vspace*{0.2ex}}
\setbeamertemplate{itemize items}{\textbullet}
\setbeamertemplate{caption label separator}{: }
\setbeamertemplate{caption}[numbered]
\setbeamertemplate{footnote}{%
\parindent 0em\noindent%
\raggedright
\usebeamercolor{footnote}\hbox to 0.8em{\hfil\insertfootnotemark}\insertfootnotetext\par%
}
\newlength{\metropolis@parskip}
\setlength{\metropolis@parskip}{0.5em}
\setlength{\parskip}{\metropolis@parskip}
\linespread{1.15}
\define@key{beamerframe}{c}[true]{% centered
\beamer@frametopskip=0pt plus 1fill\relax%
\beamer@framebottomskip=0pt plus 1fill\relax%
\beamer@frametopskipautobreak=0pt plus .4\paperheight\relax%
\beamer@framebottomskipautobreak=0pt plus .6\paperheight\relax%
\def\beamer@initfirstlineunskip{}%
}
\providebool{metropolis@standout}
\define@key{beamerframe}{standout}[true]{%
\booltrue{metropolis@standout}
\begingroup
\setkeys{beamerframe}{c}
\setkeys{beamerframe}{noframenumbering}
\ifbeamercolorempty[bg]{palette primary}{
\setbeamercolor{background canvas}{
use=palette primary,
bg=-palette primary.fg
}
}{
\setbeamercolor{background canvas}{
use=palette primary,
bg=palette primary.bg
}
}
\centering
\usebeamercolor[fg]{palette primary}
\usebeamerfont{standout}
}
\apptocmd{\beamer@reseteecodes}{%
\ifbool{metropolis@standout}{
\endgroup
\boolfalse{metropolis@standout}
}{}
}{}{}
\metropolis@inner@setdefaults
\ProcessPgfPackageOptions{/metropolis/inner}
\endinput
%%
%% End of file `beamerinnerthememetropolis.sty'.

View File

@ -1,126 +0,0 @@
%%
%% This is file `beamerouterthememetropolis.sty',
%% generated with the docstrip utility.
%%
%% The original source files were:
%%
%% beamerouterthememetropolis.dtx (with options: `package')
%% ---------------------------------------------------------------------------
%% Copyright 2015 Matthias Vogelgesang and the LaTeX community. A full list of
%% contributors can be found at
%%
%% https://github.com/matze/mtheme/graphs/contributors
%%
%% and the original template was based on the HSRM theme by Benjamin Weiss.
%%
%% This work is licensed under a Creative Commons Attribution-ShareAlike 4.0
%% International License (https://creativecommons.org/licenses/by-sa/4.0/).
%% ---------------------------------------------------------------------------
\NeedsTeXFormat{LaTeX2e}
\ProvidesPackage{beamerouterthememetropolis}[2016/02/21 Metropolis outer theme]
\RequirePackage{etoolbox}
\RequirePackage{calc}
\RequirePackage{pgfopts}
\pgfkeys{
/metropolis/outer/numbering/.cd,
.is choice,
none/.code=\setbeamertemplate{frame numbering}[none],
counter/.code=\setbeamertemplate{frame numbering}[counter],
fraction/.code=\setbeamertemplate{frame numbering}[fraction],
}
\pgfkeys{
/metropolis/outer/progressbar/.cd,
.is choice,
none/.code={%
\setbeamertemplate{headline}[plain]
\setbeamertemplate{frametitle}[plain]
\setbeamertemplate{footline}[plain]
},
head/.code={\pgfkeys{/metropolis/outer/progressbar=none}
\addtobeamertemplate{headline}{}{%
\usebeamertemplate*{progress bar in head/foot}
}
},
frametitle/.code={\pgfkeys{/metropolis/outer/progressbar=none}
\addtobeamertemplate{frametitle}{}{%
\usebeamertemplate*{progress bar in head/foot}
}
},
foot/.code={\pgfkeys{/metropolis/outer/progressbar=none}
\addtobeamertemplate{footline}{}{%
\usebeamertemplate*{progress bar in head/foot}%
}
},
}
\newcommand{\metropolis@outer@setdefaults}{
\pgfkeys{/metropolis/outer/.cd,
numbering=counter,
progressbar=none,
}
}
\setbeamertemplate{navigation symbols}{}
\defbeamertemplate{frame footer}{none}{}
\defbeamertemplate{frame footer}{custom}[1]{ #1 }
\defbeamertemplate{frame numbering}{none}{}
\defbeamertemplate{frame numbering}{counter}{\insertframenumber}
\defbeamertemplate{frame numbering}{fraction}{
\insertframenumber/\inserttotalframenumber
}
\defbeamertemplate{headline}{plain}{}
\defbeamertemplate{footline}{plain}{%
\begin{beamercolorbox}[wd=\textwidth, sep=3ex]{footline}%
\usebeamerfont{page number in head/foot}%
\usebeamertemplate*{frame footer}
\hfill%
\usebeamertemplate*{frame numbering}
\end{beamercolorbox}%
}
\newlength{\metropolis@frametitle@padding}
\setlength{\metropolis@frametitle@padding}{2.2ex}
\newcommand{\metropolis@frametitlestrut@start}{
\rule{0pt}{\metropolis@frametitle@padding +%
\totalheightof{%
\ifcsdef{metropolis@frametitleformat}{\metropolis@frametitleformat X}{X}%
}%
}%
}
\newcommand{\metropolis@frametitlestrut@end}{
\rule[-\metropolis@frametitle@padding]{0pt}{\metropolis@frametitle@padding}
}
\defbeamertemplate{frametitle}{plain}{%
\nointerlineskip%
\begin{beamercolorbox}[%
wd=\paperwidth,%
sep=0pt,%
leftskip=\metropolis@frametitle@padding,%
rightskip=\metropolis@frametitle@padding,%
]{frametitle}%
\metropolis@frametitlestrut@start\insertframetitle\metropolis@frametitlestrut@end%
\end{beamercolorbox}%
}
\newlength{\metropolis@progressinheadfoot}
\setbeamertemplate{progress bar in head/foot}{
\nointerlineskip
\setlength{\metropolis@progressinheadfoot}{%
\paperwidth * \ratio{\insertframenumber pt}{\inserttotalframenumber pt}%
}%
\begin{beamercolorbox}[wd=\paperwidth]{progress bar in head/foot}
\begin{tikzpicture}
\draw[bg, fill=bg] (0,0) rectangle (\paperwidth, 0.4pt);
\draw[fg, fill=fg] (0,0) rectangle (\metropolis@progressinheadfoot, 0.4pt);
\end{tikzpicture}%
\end{beamercolorbox}
}
\AtBeginDocument{%
\apptocmd{\appendix}{%
\pgfkeys{%
/metropolis/outer/.cd,
numbering=none,
progressbar=none}
}{}{}
}
\metropolis@outer@setdefaults
\ProcessPgfPackageOptions{/metropolis/outer}
\endinput
%%
%% End of file `beamerouterthememetropolis.sty'.

View File

@ -1,105 +0,0 @@
%%
%% This is file `beamerthememetropolis.sty',
%% generated with the docstrip utility.
%%
%% The original source files were:
%%
%% beamerthememetropolis.dtx (with options: `package')
%% ---------------------------------------------------------------------------
%% Copyright 2015 Matthias Vogelgesang and the LaTeX community. A full list of
%% contributors can be found at
%%
%% https://github.com/matze/mtheme/graphs/contributors
%%
%% and the original template was based on the HSRM theme by Benjamin Weiss.
%%
%% This work is licensed under a Creative Commons Attribution-ShareAlike 4.0
%% International License (https://creativecommons.org/licenses/by-sa/4.0/).
%% ---------------------------------------------------------------------------
\NeedsTeXFormat{LaTeX2e}
\ProvidesPackage{beamerthememetropolis}
[2016/02/21 v1.1 Metropolis Beamer theme]
\RequirePackage{etoolbox}
\RequirePackage{pgfopts}
\pgfkeys{/metropolis/.cd,
.search also={
/metropolis/inner,
/metropolis/outer,
/metropolis/color,
/metropolis/font,
}
}
\pgfkeys{
/metropolis/titleformat plain/.cd,
.is choice,
regular/.code={%
\let\metropolis@plaintitleformat\@empty%
\setbeamerfont{standout}{shape=\normalfont}%
},
smallcaps/.code={%
\let\metropolis@plaintitleformat\@empty%
\setbeamerfont{standout}{shape=\scshape}%
},
allsmallcaps/.code={%
\let\metropolis@plaintitleformat\MakeLowercase%
\setbeamerfont{standout}{shape=\scshape}%
\PackageWarning{beamerthememetropolis}{%
Be aware that titleformat plain=allsmallcaps can lead to problems%
}
},
allcaps/.code={%
\let\metropolis@plaintitleformat\MakeUppercase%
\setbeamerfont{standout}{shape=\normalfont}%
\PackageWarning{beamerthememetropolis}{%
Be aware that titleformat plain=allcaps can lead to problems%
}
},
}
\pgfkeys{
/metropolis/titleformat/.code=\pgfkeysalso{
font/titleformat title=#1,
font/titleformat subtitle=#1,
font/titleformat section=#1,
font/titleformat frame=#1,
titleformat plain=#1,
}
}
\pgfkeys{/metropolis/.cd,
usetitleprogressbar/.code=\pgfkeysalso{outer/progressbar=frametitle},
noslidenumbers/.code=\pgfkeysalso{outer/numbering=none},
usetotalslideindicator/.code=\pgfkeysalso{outer/numbering=fraction},
nosectionslide/.code=\pgfkeysalso{inner/sectionpage=none},
darkcolors/.code=\pgfkeysalso{color/background=dark},
blockbg/.code=\pgfkeysalso{color/block=fill, inner/block=fill},
}
\newcommand{\metropolis@setdefaults}{
\pgfkeys{/metropolis/.cd,
titleformat plain=regular,
}
}
\useinnertheme{metropolis}
\useoutertheme{metropolis}
\usecolortheme{metropolis}
\usefonttheme{metropolis}
\AtEndPreamble{%
\@ifpackageloaded{pgfplots}{%
\RequirePackage{pgfplotsthemetol}
}{}
}
\newcommand{\metroset}[1]{\pgfkeys{/metropolis/.cd,#1}}
\def\metropolis@plaintitleformat#1{#1}
\newcommand{\plain}[2][]{%
\PackageWarning{beamerthememetropolis}{%
The syntax `\plain' may be deprecated in a future version of Metropolis.
Please use a frame with [standout] instead.
}
\begin{frame}[standout]{#1}
\metropolis@plaintitleformat{#2}
\end{frame}
}
\newcommand{\mreducelistspacing}{\vspace{-\topsep}}
\metropolis@setdefaults
\ProcessPgfOptions{/metropolis}
\endinput
%%
%% End of file `beamerthememetropolis.sty'.

View File

@ -1,123 +0,0 @@
%%
%% This is file `pgfplotsthemetol.sty',
%% generated with the docstrip utility.
%%
%% The original source files were:
%%
%% pgfplotsthemetol.dtx (with options: `package')
%% ---------------------------------------------------------------------------
%% Copyright 2015 Matthias Vogelgesang and the LaTeX community. A full list of
%% contributors can be found at
%%
%% https://github.com/matze/mtheme/graphs/contributors
%%
%% and the original template was based on the HSRM theme by Benjamin Weiss.
%%
%% This work is licensed under a Creative Commons Attribution-ShareAlike 4.0
%% International License (https://creativecommons.org/licenses/by-sa/4.0/).
%% ---------------------------------------------------------------------------
\NeedsTeXFormat{LaTeX2e}
\ProvidesPackage{pgfplotsthemetol}
[2015/06/16 PGFplots colors based on Paul Tol's SRON technical note]
\definecolor{TolDarkPurple}{HTML}{332288}
\definecolor{TolDarkBlue}{HTML}{6699CC}
\definecolor{TolLightBlue}{HTML}{88CCEE}
\definecolor{TolLightGreen}{HTML}{44AA99}
\definecolor{TolDarkGreen}{HTML}{117733}
\definecolor{TolDarkBrown}{HTML}{999933}
\definecolor{TolLightBrown}{HTML}{DDCC77}
\definecolor{TolDarkRed}{HTML}{661100}
\definecolor{TolLightRed}{HTML}{CC6677}
\definecolor{TolLightPink}{HTML}{AA4466}
\definecolor{TolDarkPink}{HTML}{882255}
\definecolor{TolLightPurple}{HTML}{AA4499}
\pgfplotscreateplotcyclelist{mbarplot cycle}{%
{draw=TolDarkBlue, fill=TolDarkBlue!70},
{draw=TolLightBrown, fill=TolLightBrown!70},
{draw=TolLightGreen, fill=TolLightGreen!70},
{draw=TolDarkPink, fill=TolDarkPink!70},
{draw=TolDarkPurple, fill=TolDarkPurple!70},
{draw=TolDarkRed, fill=TolDarkRed!70},
{draw=TolDarkBrown, fill=TolDarkBrown!70},
{draw=TolLightRed, fill=TolLightRed!70},
{draw=TolLightPink, fill=TolLightPink!70},
{draw=TolLightPurple, fill=TolLightPurple!70},
{draw=TolLightBlue, fill=TolLightBlue!70},
{draw=TolDarkGreen, fill=TolDarkGreen!70},
}
\pgfplotscreateplotcyclelist{mlineplot cycle}{%
{TolDarkBlue, mark=*, mark size=1.5pt},
{TolLightBrown, mark=square*, mark size=1.3pt},
{TolLightGreen, mark=triangle*, mark size=1.5pt},
{TolDarkBrown, mark=diamond*, mark size=1.5pt},
}
\pgfplotsset{
compat=1.9,
mlineplot/.style={
mbaseplot,
xmajorgrids=true,
ymajorgrids=true,
major grid style={dotted},
axis x line=bottom,
axis y line=left,
legend style={
cells={anchor=west},
draw=none
},
cycle list name=mlineplot cycle,
},
mbarplot base/.style={
mbaseplot,
bar width=6pt,
axis y line*=none,
},
mbarplot/.style={
mbarplot base,
ybar,
xmajorgrids=false,
ymajorgrids=true,
area legend,
legend image code/.code={%
\draw[#1] (0cm,-0.1cm) rectangle (0.15cm,0.1cm);
},
cycle list name=mbarplot cycle,
},
horizontal mbarplot/.style={
mbarplot base,
xmajorgrids=true,
ymajorgrids=false,
xbar stacked,
area legend,
legend image code/.code={%
\draw[#1] (0cm,-0.1cm) rectangle (0.15cm,0.1cm);
},
cycle list name=mbarplot cycle,
},
mbaseplot/.style={
legend style={
draw=none,
fill=none,
cells={anchor=west},
},
x tick label style={
font=\footnotesize
},
y tick label style={
font=\footnotesize
},
legend style={
font=\footnotesize
},
major grid style={
dotted,
},
axis x line*=bottom,
},
disable thousands separator/.style={
/pgf/number format/.cd,
1000 sep={}
},
}
\endinput
%%
%% End of file `pgfplotsthemetol.sty'.

View File

@ -1,158 +0,0 @@
\documentclass[10pt]{beamer}
\beamertemplatenavigationsymbolsempty
\usepackage[utf8]{inputenc}
\usepackage{default}
\usepackage{graphicx}
\graphicspath{{pictures/}}
\usepackage[french]{babel}
\usepackage[T1]{fontenc}
\usetheme{metropolis}
%\usecolortheme{dove}
\begin{document}
\begin{frame}
\frametitle{Université de Technologie de Belfort-Montbéliard\\
Département informatique}
\vskip 4em
\begin{center}
{\LARGE Développement de nouveaux modules sur le projet Sith}\\
\end{center}
\vskip 4em
Florent \textsc{Jacquet}\\
Guillaume \textsc{Renaud}\\
{\scriptsize TO52 - A16}
\end{frame}
\begin{frame}
\frametitle{Sommaire}
\tableofcontents
\end{frame}
\section{Les nouvelles applications}
\subsection{Eboutic}
\begin{frame}[fragile]\frametitle{Eboutic}
\begin{itemize}
\item Fournir une boutique
\item Paiement en ligne en lien avec l'API du Credit Agricole
\item Gestion des cotisations et rechargements
\item Attention aux accès concurrentiels: pas visibles pendant le développement, car mono-thread, mais problèmes
à la mise en production
\end{itemize}
\end{frame}
\subsection{Le SAS}
\begin{frame}[fragile]\frametitle{Le SAS - Stock à Souvenirs}
\begin{itemize}
\item Galerie de photos
\item Upload simple pour tout le monde, même pour plusieurs dizaines de photos
\item Modération et gestion des droits basée sur la gestion des fichiers, ce qui a permis d'améliorer ces
derniers
\item Problèmes d'optimisation de certaines pages qui mettaient plus de 9 secondes à générer (plus que 2s
maintenant)
\end{itemize}
\end{frame}
\subsection{Les élections}
\begin{frame}[fragile]\frametitle{Les élections}
\begin{itemize}
\item Grosse partie "gestion": c'est Sli qui a principalement développé l'application
\item Revue des \textsc{merges request} et choix de design
\item Problèmatique de législation vite ignorées puisque validation officielle en AG
\end{itemize}
\end{frame}
\subsection{La laverie}
\begin{frame}[fragile]\frametitle{La laverie}
\begin{itemize}
\item Gestion d'un planning de reservation en prenant bien en compte les différents états (hors-service, ...) de
chaque machine
\item Génération de formulaires dynamiques en fonction des réservations (factory design pattern)
\end{itemize}
\end{frame}
\subsection{La communication}
\begin{frame}[fragile]\frametitle{La communication}
\begin{itemize}
\item Dynamise le site avec tous les textes paramètrables
\item Fourni un système de news
\item Fourni une newsletter
\end{itemize}
\begin{itemize}
\item Envoie de mails en masse
\item Beaucoup de templates
\end{itemize}
\end{frame}
\section{La gestion des stocks}
\subsection{Fonctionnement}
\begin{frame}[fragile]{Fonctionnement}
\begin{itemize}
\item Création automatique des listes de courses
\item Approvisionnement des stocks
\item Prise d'éléments dans le stock
\end{itemize}
\end{frame}
\subsection{Améliorations et difficultés}
\begin{frame}[fragile]\frametitle{Améliorations et difficultés}
\begin{itemize}
\item Mise à jour quantité liste de courses
\item Mise à jour automatique du stock selon les ventes
\item Ajout au système de notifications
\end{itemize}
\textbf{Difficultés}
\begin{itemize}
\item Découverte du design pattern "factory" pour les formulaires dynamiques
\item Apprentissage de Python, en plus du framework
\end{itemize}
\end{frame}
\section{Le rôle de mainteneur}
\subsection{Réviser les merge requests}
\begin{frame}[fragile]\frametitle{Réviser les merge requests}
\begin{itemize}
\item Long et fastidieux
\item Nécessaire pour maintenir une base de code cohérente
\item Permet de retrouver les bugs des nouveaux contributeurs
\item Oriente les contributeurs sur la bonne voie et la marche à suivre avec Django/Jinja2/etc...
\end{itemize}
\end{frame}
\subsection{Gestion des bugs, des tickets, de la mise en production...}
\begin{frame}[fragile]\frametitle{Gestion des bugs, des tickets, de la mise en production...}
\begin{itemize}
\item Ouverture/fermeture des tickets
\item Mailing list/IRC
\item Mise en production, gestion des migrations
\item Restauration de la base de tests régulièrement
\end{itemize}
\par Organisation de la passation
\end{frame}
\section{Conclusion}
\begin{frame}[fragile]\frametitle{Conclusion}
\begin{itemize}
\item Apprentissage Django/Git
\item Nouvelle mise en pratique des concepts de base de données relationnelles
\item Utilisation poussée de Gitlab
\item Formation de nouveaux contributeurs
\end{itemize}
\end{frame}
\begin{frame}[fragile]
\begin{center}
\textbf{Merci de votre attention}\\
Questions?\\
Remarques?\\
\end{center}
\end{frame}
\end{document}

View File

@ -1 +0,0 @@
_minted-Rapport

View File

@ -1,13 +0,0 @@
CC=pdflatex
all: rapport clean
rapport: Rapport.tex
@echo "Compiling "$<
$(CC) -shell-escape $<
$(CC) -shell-escape $<
clean:
@echo "Cleaning folder"
rm *.aux; rm *.log; rm *.out; rm *.toc; rm *.snm; rm *.nav; rm *.lof

Binary file not shown.

View File

@ -1,683 +0,0 @@
%%
%
% Skia
% skia@libskia.so
%
%%
\documentclass[a4paper]{report}
%packages
\usepackage[utf8]{inputenc}
\usepackage[francais]{babel}
\usepackage{graphicx}\graphicspath{{pix/}}
\usepackage{float}
\usepackage{scrextend}
\usepackage[T1]{fontenc}
\usepackage{color}
\usepackage{fancyhdr}
%Options: Sonny, Lenny, Glenn, Conny, Rejne, Bjarne, Bjornstrup
\usepackage[Bjornstrup]{fncychap}
\usepackage{minted}
\usepackage[colorlinks=true,linkcolor=black]{hyperref}
\usepackage{pdfpages}
\usepackage{titlesec, blindtext, color}
%pdf metadata
\hypersetup{
unicode=true,
colorlinks=true,
citecolor=black,
filecolor=black,
linkcolor=black,
urlcolor=black,
pdfauthor={Skia <skia@libskia.so>},
pdftitle={},
pdfcreator={pdftex},
pdfsubject={},
pdfkeywords={},
}
\definecolor{keywords}{RGB}{200,0,90}
\definecolor{comments}{RGB}{50,50,253}
\definecolor{red}{RGB}{160,0,0}
\definecolor{brown}{RGB}{160,100,100}
\definecolor{green}{RGB}{0,200,0}
\definecolor{darkgreen}{RGB}{0,130,0}
\definecolor{gray}{RGB}{100,100,100}
%inner meta
\title{Architecture de Sith: le nouveau site AE}
\author{Skia (Florent JACQUET)}
\date{Dernière version: \today}
\begin{document}
\maketitle
\tableofcontents
\chapter{Introduction}
\par Il y a longtemps, au début des années 2000, l'Association des Étudiants a mis en place un site internet qui n'a eu de
cesse d'évoluer au fil des ans. Grâce aux différents contributeurs qui s'y sont plongés, et qui ont pu y ajouter leurs
bouts de code plus ou moins utiles, le site possède désormais un ensemble de fonctionnalités impressionnant.
\par De la comptabilité à la gestion de la laverie, en passant par le forum ou le Matmatronch', le site de l'AE prend
actuellement en charge la quasi totalité de la gestion de l'argent, et c'est là un de ses rôles les plus importants.
\par Mais les vieilles technologies qu'il emploie, et l'entretien plus ou moins aléatoire qu'il a subit, en font un
outil très difficile à maintenir à l'heure actuelle, et le besoin d'une refonte s'imposait de plus en plus.
\par Le choix de technologies récentes, maintenues, et éprouvées a donc été fait, et le développement a pu commencer dès
Novembre 2015, avec l'objectif d'une mise en production dans l'été 2016, au moins dans une version incluant
l'intégralité des fonctions liées à l'argent, qui sont les plus critiques.
\par Soutenant les projets libres, j'ai décidé de placer le projet sous licence MIT, assurant ainsi une pérénité aux
source. Si quelqu'un dans le futur souhaite le relicencier sous GPL (ou autre licence plus restrictive que la MIT, voire
contagieuse), cela reste possible, mais je n'impose au départ que très peu de restrictions \footnote{La seule condition
en réalité, est de toujours garder à sa place une copie de la licence originale, à savoir le fichier LICENSE à la racine
du site.} .
\chapter{Les technologies}
\label{cha:les_technologies}
\par C'est là un des choix les plus important lors d'un tel projet, puisqu'il se fait au début, et qu'il n'est ensuite plus
possible de revenir en arrière. Le PHP vieillissant, et
piègeux\footnote{\url{https://eev.ee/blog/2012/04/09/php-a-fractal-of-bad-design/}} a donc été mis de côté au profit
d'un language plus stable, le \emph{Python} dans sa version 3.
\section{Python3}
\label{sec:python3}
\par Le site étant développé en \emph{Python}, il est impératif d'avoir un environnement de développement approprié à ce
language. L'outil \verb#virtualenv# permet d'installer un environnement \emph{Python} de manière locale, sans avoir besoin des
droits root pour installer des packages. De plus cela permet d'avoir sur sa machine plusieurs environnements différents,
adaptés à chaque projet, avec chacun des versions différentes des même paquets.
\par La procédure pour installer son \verb#virtualenv# est décrite dans le fichier \verb#README# situé à la racine du
projet.
\section{Django}
\label{sec:django}
\par \emph{Django} est un framework web pour \emph{Python}, apparu en 2005, et fournissant un grand nombre de
fonctionnalités pour développer un site rapidement et simplement. Cela inclut entre autre un serveur Web, pour les
échanges HTTP, un parseur d'URL, pour le routage des différentes URI du site, un ORM\footnote{Object-Relational Mapper}
pour la gestion de la base de donnée, ou encore un moteur de templates, pour les rendus HTML.
\par La version 1.8 de \emph{Django} a été choisie pour le développement de ce projet, car c'est une version LTS (Long Term
Support), c'est à dire qu'elle restera stable et maintenue plus longtemps que les autres (au moins jusqu'en Avril 2018).
\par La documentation est disponible à cette addresse: \url{https://docs.djangoproject.com/en/1.8/}. Bien que ce rapport
présente dans les grandes lignes le fonctionnement de \emph{Django}, il n'est pas et ne se veut pas exhaustif, et la
documentation restera donc toujours la référence à ce sujet.
\subsection{Le fichier de management et l'organisation d'un projet}
\label{sub:Le fichier de management et l'organisation d'un projet}
\par Lors de la création d'un projet \emph{Django}, plusieurs fichiers sont créés. Ces fichiers sont essentiels pour le projet,
mais ne contiennent en général pas de code à proprement parler. Ce n'est pas là qu'on y développe quoi que ce soit.
\subsubsection{manage.py}
\label{ssub:manage.py}
\par Le fichier \verb-manage.py-, situé à la racine, permet de lancer toutes les tâches d'administration du site. Parmis
elles:
\begin{itemize}
\item \textbf{startapp} \\
Créer une application.
\item \textbf{makemigrations} \\
Parser les modèles pour créer les fichiers de migration de la base de donnée.
\item \textbf{migrate} \\
Appliquer les migrations sur la base de données.
\item \textbf{runserver} \\
Pour lancer le serveur Web, et donc le site en lui même, dans une version de développement.
\item \textbf{makemessages} \\
Pour générer les fichiers de traduction, dans le dossier \verb#locale#.
\item \textbf{compilemessages} \\
Pour compiler les fichiers de traduction, dans le dossier \verb#locale#, et passer de \verb#django.po# à
\verb#django.mo#, sa version binaire, optimisée pour l'utilisation.
\end{itemize}
\subsubsection{Un premier dossier}
\label{ssub:Un premier dossier}
\par Un premier dossier est toujours créé, du nom du projet, et contenant plusieurs fichiers: \verb#settings.py#,
\verb#urls.py#, et \verb#wsgi.py#.
\par \verb#settings.py# est un fichier \emph{Python} servant à définir un grand nombre de constantes paramètrant le
fonctionnement du site. L'avantage par rapport à un fichier de configuration classique est que ce dernier est
executable, et on peut donc y mettre de la logique, afin d'avoir des paramètres dynamiques.
\par \verb#urls.py# est le fichier principale contenant les routes du site, c'est à dire les URLs existantes. Il se
charge en général d'inclure les fichiers \verb#urls.py# de chaque application afin de garder une architecture modulaire
et simple.
\par \verb#wsgi.py# contient quant à lui les paramètres pour la mise en production du site en tant qu'application WSGI
(Web Server Gateway Interface) pour tourner derrière un serveur Web.
\subsection{Organisation d'une application}
\label{sub:organisation_d_une_application}
\par Lorsque l'on créer une application avec \verb#./manage.py startapp#, on obtient une fois de plus un dossier type.
On trouve dans celui-ci un certain nombre de fichiers:
\begin{itemize}
\item \textbf{\_\_init\_\_.py} \\
Permet de définir le dossier comme un package \emph{Python}. Ce fichier est généralement vide.
\item \textbf{models.py} \\
C'est là que l'on définit tous les modèles, c'est à dire toutes les classes qui définissent des tables dans la base
de donnée.
\item \textbf{views.py} \\
Les vues y sont définies.
\item \textbf{admin.py} \\
C'est là que l'on déclare quels modèles doivent apparaîtrent dans l'interface fournie par le module
d'administration.
\item \textbf{tests.py} \\
Ce dernier fichier sert à écrire les tests fonctionnels, unitaires, ou d'intégation à l'aide de la librairie de
test de \emph{Django}.
\item \textbf{migrations} \\
Ce dossier sert à stocker les fichiers de migration de la base de donnée générés par \verb#./manage.py makemigrations#.
\end{itemize}
\vskip 1em
\par On rajoute par la suite généralement plusieurs fichiers:
\begin{itemize}
\item \textbf{urls.py} \\
Pour y définir toutes les URLs de l'application, et ensuite inclure ce fichier dans le fichier \verb#urls.py#
global au projet.
\item \textbf{templates} \\
Celui-ci est un dossier, et on y remet en général un sous dossier du nom de l'application afin de s'en servir de
namespace pour les templates.
\end{itemize}
\vskip 1em
\par Dans le cas où un fichier \emph{Python} deviendrait trop gros ou trop complexe, il est toujours possible de le diviser en
plusieurs fichiers que l'on met dans un dossier du même nom que ce fichier de départ, et contenant en plus un fichier
\verb#__init__.py#. De plus, pour faciliter les imports depuis ce dossier, on peut mettre dans \verb#__init__.py# la
ligne\footnote{Un exemple est disponible dans l'application core}:
\mint{python}|from .[nom_de_fichier_sans_le_.py] import *|
\subsection{Les modèles avec l'ORM}
\label{sub:les_modèles_avec_l_orm}
\subsubsection{Le modèle en lui même}
\label{ssub:Le modèle en lui même}
\par Rien ne vaudra un bon exemple pour comprendre comment sont construits les modèles avec \emph{Django}:
\begin{addmargin}[-7em]{0em}
\begin{minted}{python}
class Club(models.Model): # (1)
"""
The Club class, made as a tree to allow nice tidy organization
""" # (2)
name = models.CharField(_('name'), max_length=30) # (3)
parent = models.ForeignKey('Club', related_name='children', null=True, blank=True) # (4)
unix_name = models.CharField(_('unix name'), max_length=30, unique=True,
validators=[ # (5)
validators.RegexValidator(
r'^[a-z0-9][a-z0-9._-]*[a-z0-9]$',
_('Enter a valid unix name. This value may contain only '
'letters, numbers ./-/_ characters.')
),
],
error_messages={ # (6)
'unique': _("A club with that unix name already exists."),
},
)
address = models.CharField(_('address'), max_length=254)
email = models.EmailField(_('email address'), unique=True)
owner_group = models.ForeignKey(Group, related_name="owned_club",
default=settings.SITH_GROUP_ROOT_ID) # (7)
edit_groups = models.ManyToManyField(Group, related_name="editable_club", blank=True) # (8)
view_groups = models.ManyToManyField(Group, related_name="viewable_club", blank=True)
home = models.OneToOneField(SithFile, related_name='home_of_club', verbose_name=_("home"), null=True, blank=True,
on_delete=models.SET_NULL) # (9)
\end{minted}
\end{addmargin}
\par Explications:
\begin{description}
\item[(1)] Un modèle hérite toujours de \verb#models.Model#. Il peut y avoir des intermédiaires, mais \verb#Model#
sera toujours en haut.
\item[(2)] Toujours penser à commenter le modèle.
\item[(3)] Un premier attribut: \verb#name#, de type \verb#CharField#. Il constitue une colonne dans la base de
donnée une fois que \verb#./manage.py migrate# a été appliqué.
\item[(4)] Une \verb#ForeignKey#, l'une des relations les plus utilisées. \verb#related_name# précise le nom qui sert
de retour vers cette classe depuis la classe pointée. Ici, elle est même récursive, puisque l'on pointe vers la
classe que l'on est en train de définir, ce qui donne au final une structure d'arbre.
\item[(5)] On peut toujours préciser des \verb#validators#, afin que le modèle soit contraint, et que \emph{Django}
maintienne toujours des informations cohérentes dans la base.
\item[(6)] Un message d'erreur peut être précisé pour expliciter à l'utilisateur les problèmes rencontrés.
\item[(7)] On utilise ici le champ \verb#default# pour préciser une valeur par défaut au modèle, et celui-ci est
affecté à une valeur contenue dans les \verb#settings# de \emph{Django}.
\item[(8)] Les \verb#ManyToManyField# permettent de générer automatiquement une table intermédiaire de manière
transparente afin d'avoir des relations doubles dans les deux classes mises en jeu.
\item[(9)] Le \verb#OneToOneField# est très utilisé pour étendre une table avec des informations supplémentaires
sans toucher à la table originale.
\item[PRIMARY KEY] Les plus observateurs d'entre vous auront remarqué qu'il n'y a pas ici de \verb#PRIMARY KEY# de précisé. En
effet, \emph{Django} s'en occupe automatiquement en rajoutant un champ \verb#id# jouant ce rôle. On peut alors y
accèder en l'appelant par son nom, \verb#id# la plupart du temps, sauf s'il a été personnalisé, ou bien par
l'attribut générique \verb#pk#, toujours présent pour désigner la \verb#PRIMARY KEY# d'un modèle, quelle qu'elle
soit.
\end{description}
\subsubsection{Les migrations}
\label{ssub:Les migrations}
\par Les migrations sont à lancer à chaque fois que l'on modifie un modèle. Elles permettent de conserver la base de
donnée tout en la faisant évoluer dans sa structure, pour ajouter ou supprimer une colonne dans une table par exemple.
\par Lancer la commande \verb#./manage.py makemigrations [nom de l'appli]# va permettre de générer un fichier \emph{Python}
automatiquement, qui sera mis à la suite des précédents, et qui sera appliqué sur la base au moment du lancement de
\verb#./manage.py migrate#.
\subsection{Les vues}
\label{sub:les_vues}
\par Les vues sont les parties de code s'occupant de l'interface avec l'utilisateur. Elles sont appelées par les URLs,
et renvoient des réponses HTTP en fonction du traitement effectué.
\subsubsection{Les URL}
\label{ssub:Les URL}
\par Les URLs sont définies par application, et centralisées dans le dossier du projet. Il s'agit à chaque fois d'une
liste d'appel à la fonction \verb#url()#, qui comprends toujours une expression rationnelle décrivant l'URL, une
fonction passée en tant que callback qui sera appelé au moment où l'URL est résolue, et enfin un nom, permettant de s'y
référer dans les fonctions de résolution inverse, comme dans les templates par exemple. Nous détaillerons cette
utilisation plus tard.
\par Pour garder une organisation claire, les URLs sont classées par espaces de noms (namespace) afin d'avoir à éviter
de préfixer tous les noms pour s'y retrouver. Le namespace d'une URL est généralement le même nom que celui de
l'application dans laquelle elle se trouve.
\subsubsection{Les fonctions de vue}
\label{ssub:Les fonctions de vue}
\par Une fonction de vue prend toujours en paramètre une variable \verb#request# et renvoie toujours un objet
\verb#HTTPResponse#, contenant un code de retour HTTP, ainsi qu'une chaîne de caractères contenant la réponse en elle
même.
\par Entre temps, le traitement des informations permet de mettre à jour, de créer, ou de supprimer les objets définis
dans les modèles, par le biais des paramètres passé dans la requête. Ainsi, on peut accèder aux informations des
variables \verb#GET# et \verb#POST# très facilement en appelant respectivement \verb#request.GET['ma_clef']# et
\verb#request.POST['ma_clef']#, ces deux variables fonctionnant comme des dictionnaires.
\subsubsection{Des vues basées sur des classes}
\label{ssub:Des vues basées sur des classes}
\par Les vues avec \emph{Django} peuvent aussi être définies comme des classes. Elles héritent alors à ce moment là
toutes de la classe \verb#View#, mais ont toutefois souvent beaucoup d'intermédiaires et n'héritent donc pas directement
de cette dernière.
\par L'avantage de ces vues sous forme de classe est de pouvoir séparer toute la chaîne de traitement entre les
différentes méthodes, et ainsi permettre, en jouant avec l'héritage, de fournir alors très peu d'informations à la
classe, tout en lui permettant d'effectuer un travail correct.
\par Ainsi, on retrouve de base, dans les filles de \verb#View#, un grand nombre de classes prédéfinies pour la plupart
des comportement. \verb#DetailView#, \verb#CreateView#, \verb#ListView#, sont quelques exemples de classes renvoyant
respectivement un objet en détails, un formulaire pour créer un nouvel objet, et enfin une liste d'objets. Il existe
cependant un bon nombre de ces vues fournissant d'autres fonctionnalités, et si malgré tout, aucune ne peut convenir, il
reste possible de se baser sur l'une d'elle et surcharger l'une de ses fonctions pour l'adapter à ses besoins.
\par L'écosystème des \verb#class-based views# étant toutefois assez complexe et riche, un site web a été créé afin
d'offrir un bon résumé de la situation: \emph{Classy class-based views}, accessible à l'adresse
\url{http://ccbv.co.uk/}.
\section{Jinja2}
\label{sec:jinja2}
\par \emph{Jinja2} est un moteur de template écrit en \emph{Python} qui s'inspire fortement de la syntaxe des templates de
\emph{Django}, mais qui apporte toutefois son lot d'améliorations non négligeables. En effet, l'ajout des macros, par
exemple, permet de factoriser une grande partie du code.
\par Un moteur de template permet de générer du contenu textuel de manière procédural en fonction des données à
afficher. Cela permet en pratique de pouvoir inclure du code proche de \emph{Python} dans la syntaxe au milieu d'un
document contenant principalement du HTML. Ainsi, si on a une liste d'objets, on peut facilement executer une boucle
\verb#for# afin de faire afficher simplement tous les objets selon le même format.
\noindent De même, il est facile d'inclure un \verb#if# pour décider à l'execution d'afficher ou non un lien en fonction
des droits que l'utilisateur possède sur le site, par exemple.
\par En plus des structures conditionnelles classiques, un moteur de templates permet de formater des données plus
simplement, comme par exemple des dates, en fonction de la langue actuellement utilisée par l'utilisateur.
\par Enfin, bien utilisés, les templates permettent d'utiliser des fonctions d'inclusion, ce qui permet de hiérarchiser
les fichiers, et de s'assurer de l'unité de certaines parties du site. Ainsi, les \emph{headers}, les \emph{footers}, et
autres menus que l'on retrouve sur toutes les pages du site sont définis chacun dans un seul fichier et inclus dans
tous les autres.
\subsection{Exemple de template Jinja2}
\label{sub:exemple_de_template_jinja2}
\begin{addmargin}[-4em]{0em}
\begin{minted}{jinja}
{% extends "core/base.jinja" %} {# (1) #}
{% block title %} {# (2) #}
{% trans user_name=user.get_display_name() %}{{ user_name }}'s tools{% endtrans %} {# (3) #}
{% endblock %}
{% block content %}
<h3>{% trans %}User Tools{% endtrans %}</h3> {# (4) #}
<p><a href="{{ url('core:user_profile', user_id=request.user.id) }}{# (5) #}">
{% trans %}Back to profile{% endtrans %}</a>
</p>
<h4>{% trans %}Sith management{% endtrans %}</h4>
<ul>
{% if user.is_root %} {# (6) #}
<li><a href="{{ url('core:group_list') }}">{% trans %}Groups{% endtrans %}</a></li>
{% endif %}
{% if user.is_in_group(settings.SITH_GROUP_ACCOUNTING_ADMIN_ID) %}
<li><a href="{{ url('accounting:bank_list') }}">Accounting</a></li>
{% endif %}
{% if user.is_board_member or user.is_root %}
<li><a href="{{ url('subscription:subscription') }}">Subscriptions</a></li>
<li><a href="{{ url('counter:admin_list') }}">Counters management</a></li>
{% endif %}
</ul>
<h4>{% trans %}Club tools{% endtrans %}</h4>
<ul>
{% for m in user.memberships.filter(end_date=None).all() %} {# (7) #}
<li><a href="{{ url('club:tools', club_id=m.club.id) }}">{{ m.club }}</a></li>
{% endfor %}
</ul>
{% endblock %}
\end{minted}
\end{addmargin}
\begin{description}
\item[(1)] Nous faisons ici une extension d'un template existant afin de bénéficier des blocs déjà défini, et afin
d'intégrer le contenu de ce template dans celui déjà défini.
\item[(2)] \verb#title# est un bloc défini dans le template \verb#base.jinja#. Le redéfinir joue alors le même rôle
qu'une surcharge de méthode dans de l'héritage, et permet de remplacer le contenu du bloc, tout en conservant sa
place dans le template parent.
\item[(3)] La variable \verb#user# faisant ici partie du contexte, nous pouvons donc appeler une de ses méthodes
pour obtenir un contenu dynamiquement.
\item[(4)] Les blocs \verb#{% trans %}# et \verb#{% endtrans %}# permettent de définir les chaînes qui vont ensuite
être traduites.
\item[(5)] L'appel à la fonction \verb#url()# permet de résoudre la route afin d'obtenir l'adresse appropriée en
fonction des arguments passé. Cette fonction fait généralement partie du contexte global, et est donc accessible
dans tous les templates.
\item[(6)] Les structures conditionnelles permettent d'afficher ou pas un élément en fonction de la valeur d'une
variable ou du retour d'une fonction.
\item[(7)] Le \verb#for# permet, comme en Python, d'itérer sur les éléments d'une liste. Ici, on fait même une
requête via l'ORM de \emph{Django} en utilisant un filtre pour obtenir directement des valeurs depuis la base de
donnée de manière transparente.
\end{description}
\subsection{Le contexte}
\label{sub:le_contexte}
\par Le contexte dans lequel le template s'execute influe beaucoup sur la capacité de \emph{Jinja} à s'adapter
dynamiquement au contenu. Plus on a de variables disponibles, plus on va pouvoir générer un contenu s'y adaptant.
\par Il est possible de définir le contexte global, et donc ce qui est accessible dans tous les templates, comme il est
possible d'ajouter manuellement et spécifiquement des variables au contexte pour un template particulier, dans une vue
particulière.
\chapter{Organisation du projet}
\label{cha:organisation_du_projet}
\par Après cette présentations des différentes technologies employées dans le projet, passons maintenant à une partie plus
spécifique à Sith en lui même.
\section{Les options spécifiques}
\label{sec:les_options_sp_cifiques}
\subsection{Django-jinja}
\label{sub:django_jinja}
\par \emph{Jinja} n'étant pas inclus de base dans \emph{Django}, le paquet \emph{Django-jinja} a été mis en place afin
de bénéficier au mieux des performances de chacun, comme les filtres personnalisés de \emph{Django} dans la puissance des
macros de \emph{Jinja}. Tout cela se trouve dans la variable \verb#TEMPLATES#.
\par \emph{Jinja} a été ajouté afin de s'occuper uniquement des fichiers ayant l'extension \verb#.jinja# dans les
dossiers \verb#templates# de chaque application.
\par Un certain nombre de variables et fonctions ont été ajoutés au contexte global. Parmis elles, l'ensemble des
filtres que \emph{Django} fournit, mais aussi un filtre pour passer de \emph{Markdown} à \emph{HTML}, l'ensemble du
contenu de \verb#settings#, et enfin des fonction utiles dont voici la liste:
\begin{itemize}
\item \textbf{can\_edit\_prop} permet, en fonction d'une variable \verb#user# et d'un objet, de savoir si
l'utilisateur donnée peut modifier les propriétés de cet objet.
\item \textbf{can\_edit} permet, en fonction d'une variable \verb#user# et d'un objet, de savoir si
l'utilisateur donnée peut éditer l'objet.
\item \textbf{can\_view} permet, en fonction d'une variable \verb#user# et d'un objet, de savoir si
l'utilisateur donnée peut voir l'objet.
\end{itemize}
\subsection{Le fichier \textbf{settings\_custom.py}}
\label{sub:le_fichier_settings_custom.py}
\par Afin de faciliter la configuration des différentes instances du projet, un fichier \verb#settings_custom.py# a été
créé à côté de \verb#settings.py#. Celui-ci est automatiquement inclu à la fin de \verb#settings.py#, pour que tout ce
qui est défini dans le \verb#_custom# vienne remplacer les valeurs par défaut. Seul \verb#settings.py# est versionné
dans \emph{Git}, et est exhaustif concernant la configuration et les variables requises. \verb#settings_custom.py# quant
à lui n'est même pas obligatoire, et s'il existe, ne peut contenir que quelques variables qui diffèrent de la
configuration par défaut\footnote{Typiquement, ajouter \textbf{DEBUG=True} }.
\section{Les commandes ajoutées}
\label{sec:les_commandes_ajoutees}
\par Si cela ne suffit pas, il est possible d'enrichir de nouvelles commandes le script \verb#manage.py#. Cela a été fait
pour \emph{Sith}, afin de pouvoir très rapidement déployer un environnement en ayant déjà les quelques données
nécéssaires au fonctionnement du projet, comme le groupe \verb#root# par exemple.
\subsection{setup}
\label{sub:setup}
\par La fonction \verb#setup# s'occupe simplement de supprimer le fichier \verb#db.sqlite3#, qui est le fichier de base
de donnée utilisé pour le développement, et relance une procédure de migration pour reconstruire une base de donnée
propre avant de la peupler.
\par Cette commande permet donc de s'assurer que la base de donnée utilisée est neuve et non corrompue.
\subsection{populate}
\label{sub:populate}
\par \verb#populate# permet de remplir la base de donnée avec dans un premier temps les données \textbf{nécessaires} au
bon fonctionnement du site. Cela comprend notamment un superutilisateur, les groupes définis dans
\verb#settings.SITH_GROUP_*_ID#, dont le groupe \verb#root# fait partie, une première page de Wiki, ainsi qu'un club
racine, l'AE dans notre cas.
\par Cette fonction prend un éventuel argument, \verb#--prod#, qui lui permet de mettre en place le strict minimum
énoncé précédemment. Sinon, elle continue en ajoutant un certain nombre de données pratiques pour le développement,
comme un certain nombre d'utilisateurs avec différents droits, de nouvelles pages dans le Wiki, de nouveaux clubs, des
comptoirs et des produits, ou encore des données de comptabilité.
\par L'argument \verb#--prod# peut, en outre, être passé directement depuis la fonction \verb#setup#.
\chapter{Les applications}
\label{cha:les_applications}
\par Chaque application va être détaillée ici. Cela permet de mettre en valeur le rôle de chacune, et de signaler les
éventuelles particularités qui peuvent s'y trouver.
\section{Core}
\label{sec:core}
\subsection{Résumé}
\label{sub:resume}
\par L'application \emph{Core} est de loin la plus importante de toutes. C'est elle qui gère les utilisateurs ainsi que
leurs droits. Le CMS y est aussi définit pour tout ce qui est pages de Wiki, pages statiques, ou l'ajout du filtre
\verb#markdown# pour les templates.
\subsection{Liste des modèles}
\label{sub:liste_des_modeles}
\begin{itemize}
\item \textbf{Group} \\
Ce modèle se subdivise en deux: RealGroup et MetaGroup, décrivants respectivement un vrai groupe géré à la main
dans la liste des groupes, et un meta-groupe, géré automatiquement, en général par les clubs, ou bien par les
cotisations.
\item \textbf{User} \\
Le modèle des utilisateurs, qui est ensuite décliné ou référencé dans beaucoup d'applications pour les
utilisations spécifiques. C'est toutefois ici que sont déclarés les fonctions de gestion des droits des
utilisateurs, afin de pouvoir les utiliser partout ailleurs.
\item \textbf{AnonymousUser} \\
Cette classe n'est pas un modèle stocké en base, puisqu'elle sert à instancier la variable \verb#user#
lorsqu'aucun utilisateur n'est connecté au site.
\item \textbf{Page} \\
Décrit une entité page, servant dans le Wiki ou pour les pages statiques du site. Cette classe s'occupe des
méta-données de la page, comme ses droits, mais son contenu est en réalité stocké dans un objet \verb#PageRev#.
\item \textbf{PageRev} \\
Décrit une révision de page. Utiliser une autre classe avec une \verb#ForeignKey# permet de gérer facilement un
historique des révisions.
\end{itemize}
\subsection{La gestion des droits}
\label{sub:la_gestion_des_droits}
\par La gestion des droits est implémentée de manière globale dans l'application Core.
\par On trouve en effet dans \verb#views/__init__.py# un certain nombre de mixins \footnote{Un mixin est, dans
\emph{Django}, un terme désignant une classe abstraite qui ne peut pas servir de parente seule. Elle permet de
surcharger certaines méthodes d'une autre classe abstraite afin de l'adapter à un comportement plus spécifique, mais
reste totalement inutile quand elle est seule. La gestion des droits est un bon exemple puisqu'elle ne s'occupe pas
vraiment de traitement des données comme les autres vues le feraient, elle permet simplement d'ajouter une condition à une
autre classe où cette dernière renverrait un \emph{403 Forbidden} } s'occupant de cela, en se basant sur
un modèle général permettant de rendre compatible rapidement n'importe quel modèle que l'on voudrait protéger. Il suffit
alors de déclarer dans la classe une certain nombre de méthodes et/ou d'attributs, le reste étant simplement déjà pris
en charge par les mixins suivants:
\begin{itemize}
\item \textbf{CanCreateMixin} \\
Cette classe est à mettre en parente d'une classe héritant de \verb#CreateView#, afin d'empêcher quelqu'un
n'ayant pas les droits de créer un objet.\\
Méthode correspondante à créer dans les modèles: \\
\verb#def can_be_created_by(user):# \\
(Attention, ce n'est pas une méthode prenant \verb#self# en premier paramètre!)
\item \textbf{CanEditPropMixin} \\
Cette classe protège l'objet pour l'édition avancée. Par exemple: éditer les droits sur une page, ou éditer les
droits accordé à un utilisateur. \\
Attribut correspondant à créer dans les modèles: \\
\verb#owner_group = models.ForeignKey(Group, # \\
\verb# related_name="owned_user", default=settings.SITH_GROUP_ROOT_ID)# \\
Méthode correspondante à créer dans les modèles: \\
\verb#def is_owned_by(self, user):# \\
\item \textbf{CanEditMixin} \\
Cette classe protège l'objet pour l'édition non avancée. Par exemple: éditer une page, ou éditer le profil d'un
utilisateur. \\
Attribut correspondant à créer dans les modèles: \\
\verb#edit_groups = models.ManyToManyField(Group, # \\
\verb# related_name="editable_user", blank=True)# \\
Méthode correspondante à créer dans les modèles: \\
\verb#def can_be_edited_by(self, user):# \\
\item \textbf{CanViewMixin} \\
Cette classe protège l'objet pour la vue. Par exemple: consulter une page, ou voir le profil d'un utilisateur. \\
Attribut correspondant à créer dans les modèles: \\
\verb#view_groups = models.ManyToManyField(Group, # \\
\verb# related_name="viewable_user", blank=True)# \\
Méthode correspondante à créer dans les modèles: \\
\verb#def can_be_viewed_by(self, user):# \\
\end{itemize}
\par Pour savoir si l'on doit implémenter les méthodes, les attributs, ou les deux, il faut simplement se poser la
question de savoir si l'objet en question requiert une gestion des droits à l'échelle de la classe ou à l'échelle de
l'objet, et si cette gestion peut être calculé par de la logique.
\par Si on a besoin d'une gestion pour la classe, ou si du code peut être implémenter pour déterminer qui peut avoir tel
droit, alors la méthode suffira. Mais si on a besoin d'une gestion au niveau de l'objet, alors il faudra certainement
recourir aux attributs.
\par Exemples:
\begin{itemize}
\item Les comptes en banque sont gérés uniquement par les personnes faisant partie du groupe \verb#admin-compta#.
Ils ont donc tous les mêmes droits, c'est une gestion au niveau de la classe, donc les méthodes suffisent.
\item Les classeurs de comptabilité sont gérés par les trésoriers des clubs, ils n'ont pas tous les mêmes droits,
mais cela peut tout de même se calculer en fonction des postes dans les clubs correspondants. On a donc besoin
des méthodes uniquement.
\item Les pages n'appartiennent pas forcément à un club, ni à une quelconque entité, mais ont tout de même besoin de
gestion des droits au niveau de l'objet. L'ajout des attributs est donc nécessaire pour pouvoir gérer cela au
cas par cas.
\end{itemize}
\section{Subscription}
\label{sec:subscription}
\subsection{Résumé}
\label{sub:resume}
\par Cette application ajoute le support des cotisations. Elle fournit également les interfaces de cotisation.
\subsection{Liste des modèles}
\label{sub:liste_des_modeles}
\begin{itemize}
\item \textbf{Subscription} \\
Un modèle cotisation, pour stocker ces dernières. Cette classe fait automatiquement les calculs de début et de
fin de cotisation en fonction de la date du jour, du type de cotisation, et de la durée en semestre de
cotisation.
\end{itemize}
\section{Accounting}
\label{sec:accounting}
\subsection{Résumé}
\label{sub:resume}
\par Cette application sert à gérer la comptabilité. Elle est architecturée de façon hiérarchique, avec en haut, les
comptes bancaires réels, qui contiennent eux des comptes de clubs, permettant de les diviser en plusieurs petits comptes
en interne, et enfin les classeurs de trésorerie, propres à chaque compte club, permettant de faire les comptes en
triant par semestre.
\par De plus, cette application définit un nouveau type de champ dans la base de donnée: le champ \verb#CurrencyField#,
permettant de stocker de valeurs monétaires.
\subsection{Liste des modèles}
\label{sub:liste_des_modeles}
\begin{itemize}
\item \textbf{BankAccount} \\
Le modèle des comptes bancaires.
\item \textbf{ClubAccount} \\
Le modèle des comptes clubs.
\item \textbf{GeneralJournal} \\
Le modèle des classeurs de comptabilité, généralement semestriels, mais ils peuvent toutefois fonctionner en
année pour les activités plus longues comme le Gala.
\item \textbf{AccountingType} \\
Le modèle pour stocker les types comptables, servant à remplir les opérations.
\item \textbf{SimpleAccountingType} \\
Le modèle pour stocker les types comptables simplifiés, servant à remplir les opérations.
\item \textbf{Label} \\
Le modèle pour stocker les étiquettes, servant à classifer les opérations.
\item \textbf{Operation} \\
Le modèle des opérations, servant à remplir les classeurs comptables. Une opération peut être un débit ou un
crédit, et permet ensuite d'éditer des factures, par exemple.
\end{itemize}
\section{Counter}
\label{sec:counter}
\subsection{Résumé}
\label{sub:resume}
\par Cette application s'occupe de la gestion des comptoirs. Elle définit ainsi des produits, et ajoute également le
support du compte AE pour les utilisateurs.
\subsection{Liste des modèles}
\label{sub:liste_des_modeles}
\begin{itemize}
\item \textbf{Customer} \\
Ce modèle étend l'utilisateur pour lui rajouter un compte AE. Il est lié à la classe \verb#User# par un
\verb#OneToOneField#.
\item \textbf{ProductType} \\
Ce modèle ajoute des types de produits afin de catégoriser ces derniers.
\item \textbf{Product} \\
Ce modèle décrit les produits pouvant être vendus dans les différents comptoirs.
\item \textbf{Counter} \\
Ce modèle décrit les comptoirs, qui permettent de générer des recharges de compte et des ventes de produits.
\item \textbf{Refilling} \\
Ce modèle permet de stocker les rechargements de compte.
\item \textbf{Selling} \\
Ce modèle permet d'enregistrer toutes les ventes de produits.
\end{itemize}
\section{Club}
\label{sec:club}
\subsection{Résumé}
\label{sub:resume}
\par Cette application permet de générer les clubs et les adhésions des utilisateurs à ceux-ci.
\subsection{Liste des modèles}
\label{sub:liste_des_modeles}
\begin{itemize}
\item \textbf{Club} \\
Le modèle des clubs.
\item \textbf{Membership} \\
Le modèle des adhésions. Stocker cela dans un modèle à part permet de conserver un historique des personnes
ayant eu un rôle quelconque dans un club quelconque.
\end{itemize}
\chapter{Conclusion}
\par Encore une fois, ce rapport ne se veut absolument pas exhaustif sur quoi que ce soit.
\par Concernant \emph{Python}, \emph{Django}, ou \emph{Jinja}, les documentations respectives sont toujours très bien
faites, et permettront de répondre à toutes les questions techniques concernant les technologies.
\par Concernant le projet \emph{Sith} en lui-même, ce rapport n'est pas non plus exhaustif. Pour cela, lire le code des
différentes sections sera le meilleur moyen de comprendre le fonctionnement des différentes applications. Pour obtenir plus
rapidement un résumé à jour des sources, le fichier \verb#Doxyfile# présent à la racine du site permet de regénérer de
la documentation exhaustive rapidement à l'aide de \emph{Doxygen} (voir la section correspondante dans le README).
\par J'espère toutefois que même s'il n'est pas complet, ce rapport permettra à tout futur contributeur de rentrer plus
rapidement dans le projet.
\par L'idéal serait également de maintenir à jour ce rapport du mieux possible en même temps que le développement
avance, même si je ne me fais guère d'illusions en pratique.
\section{Pour le futur...}
\label{sec:pour_le_futur}
\par En l'état actuel des choses, un grand nombre d'éléments sont encore manquants au site:
\begin{itemize}
\item Une API REST pour pouvoir facilement intégrer d'autres outils autour du site.
\item Du CSS, pour qu'il soit un peu plus joli à regarder.
\item Du Javascript, et particulièrement de l'AJAX pour améliorer l'ergonomie de certaines pages.
\item Un grand nombre de vues pour aider à gérer les données plus efficacement, ou à les gérer tout court dans
certains cas.
\item Quelques applications utiles à qui existent sur le site actuel, mais que je n'ai pas encore eu le temps de
développer: un forum, une galerie de photos, une gestion basique des fichiers pour uploader des documents dans
les pages ou le forum, un système de news, une newsletter (Weekmail), une gestion des sondages et des élections,
etc...
\end{itemize}
\section{Apports personnels}
\label{sec:apports_personnels}
\par Même s'il est vrai que j'ai beaucoup appris en développant ce site, cela reste avant tout un travail de quantité
plus que de qualité: on définit des modèles, puis les vues correspondantes en terminant par les templates, et on répète
l'opération pour l'application suivante.
\par Mais j'ai tout de même pu mettre en place de l'intégration continue pour ce projet, ce qui a été certes, très
rapide, mais toutefois très enrichissant, étant donnée que ces méthodes sont très en vogue ces derniers temps.
\par J'ai également pu me familiariser d'avantage avec le fonctionnement d'un ORM, et la magie noire que cela permet de
faire \footnote{Je trouve toujours magique de pouvoir faire une requête SQL au milieu d'un template sans que cela
paraisse affreux :)}.
\par Enfin, j'espère que ce projet va, en plus d'être correctement mené au bout, pouvoir être repris par la suite par
les futurs membres de l'équipe info de l'AE, et pourquoi pas par d'autres contributeurs...
\vskip 3em
\emph{Skia <skia@libskia.so> - 2016}
\end{document}

View File

@ -0,0 +1,8 @@
!!!note "Page générée"
Cette page est générée à partir du wiki GitHub du projet puisqu'elle
héberge du contenu binaire lourd qui encombrerait le repo git.
La [page originale ici](https://github.com/ae-utbm/sith3/wiki/Archives) peut être plus à jour puisque le contenu
de cette page date de la dernière génération de la documentation.
{! include-markdown 'https://raw.githubusercontent.com/wiki/ae-utbm/sith3/Archives.md', '') !}

View File

@ -48,6 +48,9 @@ plugins:
show_inherited_members: true
merge_init_into_class: true
show_root_toc_entry: false
- include-markdown:
opening_tag: "{!"
closing_tag: "!}"
- search
nav:
- Accueil: index.md
@ -55,6 +58,7 @@ nav:
- Accueil: explanation/index.md
- Technologies utilisées: explanation/technos.md
- Conventions: explanation/conventions.md
- Archives: explanation/archives.md
- Tutoriels:
- Installer le projet: tutorial/install.md
- Configurer son éditeur: tutorial/devtools.md

225
poetry.lock generated
View File

@ -60,6 +60,28 @@ files = [
[package.extras]
dev = ["freezegun (>=1.0,<2.0)", "pytest (>=6.0)", "pytest-cov"]
[[package]]
name = "backports-strenum"
version = "1.3.1"
description = "Base class for creating enumerated constants that are also subclasses of str"
optional = false
python-versions = ">=3.8.6,<3.11"
files = [
{file = "backports_strenum-1.3.1-py3-none-any.whl", hash = "sha256:cdcfe36dc897e2615dc793b7d3097f54d359918fc448754a517e6f23044ccf83"},
{file = "backports_strenum-1.3.1.tar.gz", hash = "sha256:77c52407342898497714f0596e86188bb7084f89063226f4ba66863482f42414"},
]
[[package]]
name = "bracex"
version = "2.4"
description = "Bash style brace expander."
optional = false
python-versions = ">=3.8"
files = [
{file = "bracex-2.4-py3-none-any.whl", hash = "sha256:efdc71eff95eaff5e0f8cfebe7d01adf2c8637c8c92edaf63ef348c241a82418"},
{file = "bracex-2.4.tar.gz", hash = "sha256:a27eaf1df42cf561fed58b7a8f3fdf129d1ea16a81e1fadd1d17989bc6384beb"},
]
[[package]]
name = "certifi"
version = "2024.7.4"
@ -283,63 +305,63 @@ files = [
[[package]]
name = "coverage"
version = "7.5.4"
version = "7.6.0"
description = "Code coverage measurement for Python"
optional = false
python-versions = ">=3.8"
files = [
{file = "coverage-7.5.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6cfb5a4f556bb51aba274588200a46e4dd6b505fb1a5f8c5ae408222eb416f99"},
{file = "coverage-7.5.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2174e7c23e0a454ffe12267a10732c273243b4f2d50d07544a91198f05c48f47"},
{file = "coverage-7.5.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2214ee920787d85db1b6a0bd9da5f8503ccc8fcd5814d90796c2f2493a2f4d2e"},
{file = "coverage-7.5.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1137f46adb28e3813dec8c01fefadcb8c614f33576f672962e323b5128d9a68d"},
{file = "coverage-7.5.4-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b385d49609f8e9efc885790a5a0e89f2e3ae042cdf12958b6034cc442de428d3"},
{file = "coverage-7.5.4-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:b4a474f799456e0eb46d78ab07303286a84a3140e9700b9e154cfebc8f527016"},
{file = "coverage-7.5.4-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:5cd64adedf3be66f8ccee418473c2916492d53cbafbfcff851cbec5a8454b136"},
{file = "coverage-7.5.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:e564c2cf45d2f44a9da56f4e3a26b2236504a496eb4cb0ca7221cd4cc7a9aca9"},
{file = "coverage-7.5.4-cp310-cp310-win32.whl", hash = "sha256:7076b4b3a5f6d2b5d7f1185fde25b1e54eb66e647a1dfef0e2c2bfaf9b4c88c8"},
{file = "coverage-7.5.4-cp310-cp310-win_amd64.whl", hash = "sha256:018a12985185038a5b2bcafab04ab833a9a0f2c59995b3cec07e10074c78635f"},
{file = "coverage-7.5.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:db14f552ac38f10758ad14dd7b983dbab424e731588d300c7db25b6f89e335b5"},
{file = "coverage-7.5.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3257fdd8e574805f27bb5342b77bc65578e98cbc004a92232106344053f319ba"},
{file = "coverage-7.5.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3a6612c99081d8d6134005b1354191e103ec9705d7ba2754e848211ac8cacc6b"},
{file = "coverage-7.5.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d45d3cbd94159c468b9b8c5a556e3f6b81a8d1af2a92b77320e887c3e7a5d080"},
{file = "coverage-7.5.4-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ed550e7442f278af76d9d65af48069f1fb84c9f745ae249c1a183c1e9d1b025c"},
{file = "coverage-7.5.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:7a892be37ca35eb5019ec85402c3371b0f7cda5ab5056023a7f13da0961e60da"},
{file = "coverage-7.5.4-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:8192794d120167e2a64721d88dbd688584675e86e15d0569599257566dec9bf0"},
{file = "coverage-7.5.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:820bc841faa502e727a48311948e0461132a9c8baa42f6b2b84a29ced24cc078"},
{file = "coverage-7.5.4-cp311-cp311-win32.whl", hash = "sha256:6aae5cce399a0f065da65c7bb1e8abd5c7a3043da9dceb429ebe1b289bc07806"},
{file = "coverage-7.5.4-cp311-cp311-win_amd64.whl", hash = "sha256:d2e344d6adc8ef81c5a233d3a57b3c7d5181f40e79e05e1c143da143ccb6377d"},
{file = "coverage-7.5.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:54317c2b806354cbb2dc7ac27e2b93f97096912cc16b18289c5d4e44fc663233"},
{file = "coverage-7.5.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:042183de01f8b6d531e10c197f7f0315a61e8d805ab29c5f7b51a01d62782747"},
{file = "coverage-7.5.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a6bb74ed465d5fb204b2ec41d79bcd28afccf817de721e8a807d5141c3426638"},
{file = "coverage-7.5.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b3d45ff86efb129c599a3b287ae2e44c1e281ae0f9a9bad0edc202179bcc3a2e"},
{file = "coverage-7.5.4-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5013ed890dc917cef2c9f765c4c6a8ae9df983cd60dbb635df8ed9f4ebc9f555"},
{file = "coverage-7.5.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1014fbf665fef86cdfd6cb5b7371496ce35e4d2a00cda501cf9f5b9e6fced69f"},
{file = "coverage-7.5.4-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:3684bc2ff328f935981847082ba4fdc950d58906a40eafa93510d1b54c08a66c"},
{file = "coverage-7.5.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:581ea96f92bf71a5ec0974001f900db495488434a6928a2ca7f01eee20c23805"},
{file = "coverage-7.5.4-cp312-cp312-win32.whl", hash = "sha256:73ca8fbc5bc622e54627314c1a6f1dfdd8db69788f3443e752c215f29fa87a0b"},
{file = "coverage-7.5.4-cp312-cp312-win_amd64.whl", hash = "sha256:cef4649ec906ea7ea5e9e796e68b987f83fa9a718514fe147f538cfeda76d7a7"},
{file = "coverage-7.5.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:cdd31315fc20868c194130de9ee6bfd99755cc9565edff98ecc12585b90be882"},
{file = "coverage-7.5.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:02ff6e898197cc1e9fa375581382b72498eb2e6d5fc0b53f03e496cfee3fac6d"},
{file = "coverage-7.5.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d05c16cf4b4c2fc880cb12ba4c9b526e9e5d5bb1d81313d4d732a5b9fe2b9d53"},
{file = "coverage-7.5.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c5986ee7ea0795a4095ac4d113cbb3448601efca7f158ec7f7087a6c705304e4"},
{file = "coverage-7.5.4-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5df54843b88901fdc2f598ac06737f03d71168fd1175728054c8f5a2739ac3e4"},
{file = "coverage-7.5.4-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:ab73b35e8d109bffbda9a3e91c64e29fe26e03e49addf5b43d85fc426dde11f9"},
{file = "coverage-7.5.4-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:aea072a941b033813f5e4814541fc265a5c12ed9720daef11ca516aeacd3bd7f"},
{file = "coverage-7.5.4-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:16852febd96acd953b0d55fc842ce2dac1710f26729b31c80b940b9afcd9896f"},
{file = "coverage-7.5.4-cp38-cp38-win32.whl", hash = "sha256:8f894208794b164e6bd4bba61fc98bf6b06be4d390cf2daacfa6eca0a6d2bb4f"},
{file = "coverage-7.5.4-cp38-cp38-win_amd64.whl", hash = "sha256:e2afe743289273209c992075a5a4913e8d007d569a406ffed0bd080ea02b0633"},
{file = "coverage-7.5.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b95c3a8cb0463ba9f77383d0fa8c9194cf91f64445a63fc26fb2327e1e1eb088"},
{file = "coverage-7.5.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3d7564cc09dd91b5a6001754a5b3c6ecc4aba6323baf33a12bd751036c998be4"},
{file = "coverage-7.5.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:44da56a2589b684813f86d07597fdf8a9c6ce77f58976727329272f5a01f99f7"},
{file = "coverage-7.5.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e16f3d6b491c48c5ae726308e6ab1e18ee830b4cdd6913f2d7f77354b33f91c8"},
{file = "coverage-7.5.4-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dbc5958cb471e5a5af41b0ddaea96a37e74ed289535e8deca404811f6cb0bc3d"},
{file = "coverage-7.5.4-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:a04e990a2a41740b02d6182b498ee9796cf60eefe40cf859b016650147908029"},
{file = "coverage-7.5.4-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:ddbd2f9713a79e8e7242d7c51f1929611e991d855f414ca9996c20e44a895f7c"},
{file = "coverage-7.5.4-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:b1ccf5e728ccf83acd313c89f07c22d70d6c375a9c6f339233dcf792094bcbf7"},
{file = "coverage-7.5.4-cp39-cp39-win32.whl", hash = "sha256:56b4eafa21c6c175b3ede004ca12c653a88b6f922494b023aeb1e836df953ace"},
{file = "coverage-7.5.4-cp39-cp39-win_amd64.whl", hash = "sha256:65e528e2e921ba8fd67d9055e6b9f9e34b21ebd6768ae1c1723f4ea6ace1234d"},
{file = "coverage-7.5.4-pp38.pp39.pp310-none-any.whl", hash = "sha256:79b356f3dd5b26f3ad23b35c75dbdaf1f9e2450b6bcefc6d0825ea0aa3f86ca5"},
{file = "coverage-7.5.4.tar.gz", hash = "sha256:a44963520b069e12789d0faea4e9fdb1e410cdc4aab89d94f7f55cbb7fef0353"},
{file = "coverage-7.6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:dff044f661f59dace805eedb4a7404c573b6ff0cdba4a524141bc63d7be5c7fd"},
{file = "coverage-7.6.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a8659fd33ee9e6ca03950cfdcdf271d645cf681609153f218826dd9805ab585c"},
{file = "coverage-7.6.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7792f0ab20df8071d669d929c75c97fecfa6bcab82c10ee4adb91c7a54055463"},
{file = "coverage-7.6.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d4b3cd1ca7cd73d229487fa5caca9e4bc1f0bca96526b922d61053ea751fe791"},
{file = "coverage-7.6.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e7e128f85c0b419907d1f38e616c4f1e9f1d1b37a7949f44df9a73d5da5cd53c"},
{file = "coverage-7.6.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a94925102c89247530ae1dab7dc02c690942566f22e189cbd53579b0693c0783"},
{file = "coverage-7.6.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:dcd070b5b585b50e6617e8972f3fbbee786afca71b1936ac06257f7e178f00f6"},
{file = "coverage-7.6.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:d50a252b23b9b4dfeefc1f663c568a221092cbaded20a05a11665d0dbec9b8fb"},
{file = "coverage-7.6.0-cp310-cp310-win32.whl", hash = "sha256:0e7b27d04131c46e6894f23a4ae186a6a2207209a05df5b6ad4caee6d54a222c"},
{file = "coverage-7.6.0-cp310-cp310-win_amd64.whl", hash = "sha256:54dece71673b3187c86226c3ca793c5f891f9fc3d8aa183f2e3653da18566169"},
{file = "coverage-7.6.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c7b525ab52ce18c57ae232ba6f7010297a87ced82a2383b1afd238849c1ff933"},
{file = "coverage-7.6.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4bea27c4269234e06f621f3fac3925f56ff34bc14521484b8f66a580aacc2e7d"},
{file = "coverage-7.6.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ed8d1d1821ba5fc88d4a4f45387b65de52382fa3ef1f0115a4f7a20cdfab0e94"},
{file = "coverage-7.6.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:01c322ef2bbe15057bc4bf132b525b7e3f7206f071799eb8aa6ad1940bcf5fb1"},
{file = "coverage-7.6.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:03cafe82c1b32b770a29fd6de923625ccac3185a54a5e66606da26d105f37dac"},
{file = "coverage-7.6.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0d1b923fc4a40c5832be4f35a5dab0e5ff89cddf83bb4174499e02ea089daf57"},
{file = "coverage-7.6.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:4b03741e70fb811d1a9a1d75355cf391f274ed85847f4b78e35459899f57af4d"},
{file = "coverage-7.6.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a73d18625f6a8a1cbb11eadc1d03929f9510f4131879288e3f7922097a429f63"},
{file = "coverage-7.6.0-cp311-cp311-win32.whl", hash = "sha256:65fa405b837060db569a61ec368b74688f429b32fa47a8929a7a2f9b47183713"},
{file = "coverage-7.6.0-cp311-cp311-win_amd64.whl", hash = "sha256:6379688fb4cfa921ae349c76eb1a9ab26b65f32b03d46bb0eed841fd4cb6afb1"},
{file = "coverage-7.6.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f7db0b6ae1f96ae41afe626095149ecd1b212b424626175a6633c2999eaad45b"},
{file = "coverage-7.6.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:bbdf9a72403110a3bdae77948b8011f644571311c2fb35ee15f0f10a8fc082e8"},
{file = "coverage-7.6.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9cc44bf0315268e253bf563f3560e6c004efe38f76db03a1558274a6e04bf5d5"},
{file = "coverage-7.6.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:da8549d17489cd52f85a9829d0e1d91059359b3c54a26f28bec2c5d369524807"},
{file = "coverage-7.6.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0086cd4fc71b7d485ac93ca4239c8f75732c2ae3ba83f6be1c9be59d9e2c6382"},
{file = "coverage-7.6.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1fad32ee9b27350687035cb5fdf9145bc9cf0a094a9577d43e909948ebcfa27b"},
{file = "coverage-7.6.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:044a0985a4f25b335882b0966625270a8d9db3d3409ddc49a4eb00b0ef5e8cee"},
{file = "coverage-7.6.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:76d5f82213aa78098b9b964ea89de4617e70e0d43e97900c2778a50856dac605"},
{file = "coverage-7.6.0-cp312-cp312-win32.whl", hash = "sha256:3c59105f8d58ce500f348c5b56163a4113a440dad6daa2294b5052a10db866da"},
{file = "coverage-7.6.0-cp312-cp312-win_amd64.whl", hash = "sha256:ca5d79cfdae420a1d52bf177de4bc2289c321d6c961ae321503b2ca59c17ae67"},
{file = "coverage-7.6.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d39bd10f0ae453554798b125d2f39884290c480f56e8a02ba7a6ed552005243b"},
{file = "coverage-7.6.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:beb08e8508e53a568811016e59f3234d29c2583f6b6e28572f0954a6b4f7e03d"},
{file = "coverage-7.6.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b2e16f4cd2bc4d88ba30ca2d3bbf2f21f00f382cf4e1ce3b1ddc96c634bc48ca"},
{file = "coverage-7.6.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6616d1c9bf1e3faea78711ee42a8b972367d82ceae233ec0ac61cc7fec09fa6b"},
{file = "coverage-7.6.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ad4567d6c334c46046d1c4c20024de2a1c3abc626817ae21ae3da600f5779b44"},
{file = "coverage-7.6.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:d17c6a415d68cfe1091d3296ba5749d3d8696e42c37fca5d4860c5bf7b729f03"},
{file = "coverage-7.6.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:9146579352d7b5f6412735d0f203bbd8d00113a680b66565e205bc605ef81bc6"},
{file = "coverage-7.6.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:cdab02a0a941af190df8782aafc591ef3ad08824f97850b015c8c6a8b3877b0b"},
{file = "coverage-7.6.0-cp38-cp38-win32.whl", hash = "sha256:df423f351b162a702c053d5dddc0fc0ef9a9e27ea3f449781ace5f906b664428"},
{file = "coverage-7.6.0-cp38-cp38-win_amd64.whl", hash = "sha256:f2501d60d7497fd55e391f423f965bbe9e650e9ffc3c627d5f0ac516026000b8"},
{file = "coverage-7.6.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7221f9ac9dad9492cecab6f676b3eaf9185141539d5c9689d13fd6b0d7de840c"},
{file = "coverage-7.6.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ddaaa91bfc4477d2871442bbf30a125e8fe6b05da8a0015507bfbf4718228ab2"},
{file = "coverage-7.6.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c4cbe651f3904e28f3a55d6f371203049034b4ddbce65a54527a3f189ca3b390"},
{file = "coverage-7.6.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:831b476d79408ab6ccfadaaf199906c833f02fdb32c9ab907b1d4aa0713cfa3b"},
{file = "coverage-7.6.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:46c3d091059ad0b9c59d1034de74a7f36dcfa7f6d3bde782c49deb42438f2450"},
{file = "coverage-7.6.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:4d5fae0a22dc86259dee66f2cc6c1d3e490c4a1214d7daa2a93d07491c5c04b6"},
{file = "coverage-7.6.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:07ed352205574aad067482e53dd606926afebcb5590653121063fbf4e2175166"},
{file = "coverage-7.6.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:49c76cdfa13015c4560702574bad67f0e15ca5a2872c6a125f6327ead2b731dd"},
{file = "coverage-7.6.0-cp39-cp39-win32.whl", hash = "sha256:482855914928c8175735a2a59c8dc5806cf7d8f032e4820d52e845d1f731dca2"},
{file = "coverage-7.6.0-cp39-cp39-win_amd64.whl", hash = "sha256:543ef9179bc55edfd895154a51792b01c017c87af0ebaae092720152e19e42ca"},
{file = "coverage-7.6.0-pp38.pp39.pp310-none-any.whl", hash = "sha256:6fe885135c8a479d3e37a7aae61cbd3a0fb2deccb4dda3c25f92a49189f766d6"},
{file = "coverage-7.6.0.tar.gz", hash = "sha256:289cc803fa1dc901f84701ac10c9ee873619320f2f9aff38794db4a4a0268d51"},
]
[package.dependencies]
@ -639,13 +661,13 @@ files = [
[[package]]
name = "exceptiongroup"
version = "1.2.1"
version = "1.2.2"
description = "Backport of PEP 654 (exception groups)"
optional = false
python-versions = ">=3.7"
files = [
{file = "exceptiongroup-1.2.1-py3-none-any.whl", hash = "sha256:5258b9ed329c5bbdd31a309f53cbfb0b155341807f6ff7606a1e801a891b29ad"},
{file = "exceptiongroup-1.2.1.tar.gz", hash = "sha256:a4785e48b045528f5bfe627b6ad554ff32def154f42372786903b7abcfe1aa16"},
{file = "exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b"},
{file = "exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc"},
]
[package.extras]
@ -714,16 +736,17 @@ dev = ["flake8", "markdown", "twine", "wheel"]
[[package]]
name = "griffe"
version = "0.47.0"
version = "0.48.0"
description = "Signatures for entire Python programs. Extract the structure, the frame, the skeleton of your project, to generate API documentation or find breaking changes in your API."
optional = false
python-versions = ">=3.8"
files = [
{file = "griffe-0.47.0-py3-none-any.whl", hash = "sha256:07a2fd6a8c3d21d0bbb0decf701d62042ccc8a576645c7f8799fe1f10de2b2de"},
{file = "griffe-0.47.0.tar.gz", hash = "sha256:95119a440a3c932b13293538bdbc405bee4c36428547553dc6b327e7e7d35e5a"},
{file = "griffe-0.48.0-py3-none-any.whl", hash = "sha256:f944c6ff7bd31cf76f264adcd6ab8f3d00a2f972ae5cc8db2d7b6dcffeff65a2"},
{file = "griffe-0.48.0.tar.gz", hash = "sha256:f099461c02f016b6be4af386d5aa92b01fb4efe6c1c2c360dda9a5d0a863bb7f"},
]
[package.dependencies]
backports-strenum = {version = ">=1.3", markers = "python_version < \"3.11\""}
colorama = ">=0.4"
[[package]]
@ -1043,15 +1066,33 @@ mergedeep = ">=1.3.4"
platformdirs = ">=2.2.0"
pyyaml = ">=5.1"
[[package]]
name = "mkdocs-include-markdown-plugin"
version = "6.2.1"
description = "Mkdocs Markdown includer plugin."
optional = false
python-versions = ">=3.8"
files = [
{file = "mkdocs_include_markdown_plugin-6.2.1-py3-none-any.whl", hash = "sha256:8dfc3aee9435679b094cbdff023239e91d86cf357c40b0e99c28036449661830"},
{file = "mkdocs_include_markdown_plugin-6.2.1.tar.gz", hash = "sha256:46fc372886d48eec541d36138d1fe1db42afd08b976ef7c8d8d4ea6ee4d5d1e8"},
]
[package.dependencies]
mkdocs = ">=1.4"
wcmatch = ">=8,<9"
[package.extras]
cache = ["platformdirs"]
[[package]]
name = "mkdocs-material"
version = "9.5.28"
version = "9.5.29"
description = "Documentation that simply works"
optional = false
python-versions = ">=3.8"
files = [
{file = "mkdocs_material-9.5.28-py3-none-any.whl", hash = "sha256:ff48b11b2a9f705dd210409ec3b418ab443dd36d96915bcba45a41f10ea27bfd"},
{file = "mkdocs_material-9.5.28.tar.gz", hash = "sha256:9cba305283ad1600e3d0a67abe72d7a058b54793b47be39930911a588fe0336b"},
{file = "mkdocs_material-9.5.29-py3-none-any.whl", hash = "sha256:afc1f508e2662ded95f0a35a329e8a5acd73ee88ca07ba73836eb6fcdae5d8b4"},
{file = "mkdocs_material-9.5.29.tar.gz", hash = "sha256:3e977598ec15a4ddad5c4dfc9e08edab6023edb51e88f0729bd27be77e3d322a"},
]
[package.dependencies]
@ -1802,29 +1843,29 @@ use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"]
[[package]]
name = "ruff"
version = "0.5.1"
version = "0.5.2"
description = "An extremely fast Python linter and code formatter, written in Rust."
optional = false
python-versions = ">=3.7"
files = [
{file = "ruff-0.5.1-py3-none-linux_armv6l.whl", hash = "sha256:6ecf968fcf94d942d42b700af18ede94b07521bd188aaf2cd7bc898dd8cb63b6"},
{file = "ruff-0.5.1-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:204fb0a472f00f2e6280a7c8c7c066e11e20e23a37557d63045bf27a616ba61c"},
{file = "ruff-0.5.1-py3-none-macosx_11_0_arm64.whl", hash = "sha256:d235968460e8758d1e1297e1de59a38d94102f60cafb4d5382033c324404ee9d"},
{file = "ruff-0.5.1-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:38beace10b8d5f9b6bdc91619310af6d63dd2019f3fb2d17a2da26360d7962fa"},
{file = "ruff-0.5.1-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5e478d2f09cf06add143cf8c4540ef77b6599191e0c50ed976582f06e588c994"},
{file = "ruff-0.5.1-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f0368d765eec8247b8550251c49ebb20554cc4e812f383ff9f5bf0d5d94190b0"},
{file = "ruff-0.5.1-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:3a9a9a1b582e37669b0138b7c1d9d60b9edac880b80eb2baba6d0e566bdeca4d"},
{file = "ruff-0.5.1-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bdd9f723e16003623423affabcc0a807a66552ee6a29f90eddad87a40c750b78"},
{file = "ruff-0.5.1-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:be9fd62c1e99539da05fcdc1e90d20f74aec1b7a1613463ed77870057cd6bd96"},
{file = "ruff-0.5.1-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e216fc75a80ea1fbd96af94a6233d90190d5b65cc3d5dfacf2bd48c3e067d3e1"},
{file = "ruff-0.5.1-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:c4c2112e9883a40967827d5c24803525145e7dab315497fae149764979ac7929"},
{file = "ruff-0.5.1-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:dfaf11c8a116394da3b65cd4b36de30d8552fa45b8119b9ef5ca6638ab964fa3"},
{file = "ruff-0.5.1-py3-none-musllinux_1_2_i686.whl", hash = "sha256:d7ceb9b2fe700ee09a0c6b192c5ef03c56eb82a0514218d8ff700f6ade004108"},
{file = "ruff-0.5.1-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:bac6288e82f6296f82ed5285f597713acb2a6ae26618ffc6b429c597b392535c"},
{file = "ruff-0.5.1-py3-none-win32.whl", hash = "sha256:5c441d9c24ec09e1cb190a04535c5379b36b73c4bc20aa180c54812c27d1cca4"},
{file = "ruff-0.5.1-py3-none-win_amd64.whl", hash = "sha256:b1789bf2cd3d1b5a7d38397cac1398ddf3ad7f73f4de01b1e913e2abc7dfc51d"},
{file = "ruff-0.5.1-py3-none-win_arm64.whl", hash = "sha256:2875b7596a740cbbd492f32d24be73e545a4ce0a3daf51e4f4e609962bfd3cd2"},
{file = "ruff-0.5.1.tar.gz", hash = "sha256:3164488aebd89b1745b47fd00604fb4358d774465f20d1fcd907f9c0fc1b0655"},
{file = "ruff-0.5.2-py3-none-linux_armv6l.whl", hash = "sha256:7bab8345df60f9368d5f4594bfb8b71157496b44c30ff035d1d01972e764d3be"},
{file = "ruff-0.5.2-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:1aa7acad382ada0189dbe76095cf0a36cd0036779607c397ffdea16517f535b1"},
{file = "ruff-0.5.2-py3-none-macosx_11_0_arm64.whl", hash = "sha256:aec618d5a0cdba5592c60c2dee7d9c865180627f1a4a691257dea14ac1aa264d"},
{file = "ruff-0.5.2-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0b62adc5ce81780ff04077e88bac0986363e4a3260ad3ef11ae9c14aa0e67ef"},
{file = "ruff-0.5.2-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:dc42ebf56ede83cb080a50eba35a06e636775649a1ffd03dc986533f878702a3"},
{file = "ruff-0.5.2-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c15c6e9f88c67ffa442681365d11df38afb11059fc44238e71a9d9f1fd51de70"},
{file = "ruff-0.5.2-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:d3de9a5960f72c335ef00763d861fc5005ef0644cb260ba1b5a115a102157251"},
{file = "ruff-0.5.2-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fe5a968ae933e8f7627a7b2fc8893336ac2be0eb0aace762d3421f6e8f7b7f83"},
{file = "ruff-0.5.2-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a04f54a9018f75615ae52f36ea1c5515e356e5d5e214b22609ddb546baef7132"},
{file = "ruff-0.5.2-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1ed02fb52e3741f0738db5f93e10ae0fb5c71eb33a4f2ba87c9a2fa97462a649"},
{file = "ruff-0.5.2-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:3cf8fe659f6362530435d97d738eb413e9f090e7e993f88711b0377fbdc99f60"},
{file = "ruff-0.5.2-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:237a37e673e9f3cbfff0d2243e797c4862a44c93d2f52a52021c1a1b0899f846"},
{file = "ruff-0.5.2-py3-none-musllinux_1_2_i686.whl", hash = "sha256:2a2949ce7c1cbd8317432ada80fe32156df825b2fd611688814c8557824ef060"},
{file = "ruff-0.5.2-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:481af57c8e99da92ad168924fd82220266043c8255942a1cb87958b108ac9335"},
{file = "ruff-0.5.2-py3-none-win32.whl", hash = "sha256:f1aea290c56d913e363066d83d3fc26848814a1fed3d72144ff9c930e8c7c718"},
{file = "ruff-0.5.2-py3-none-win_amd64.whl", hash = "sha256:8532660b72b5d94d2a0a7a27ae7b9b40053662d00357bb2a6864dd7e38819084"},
{file = "ruff-0.5.2-py3-none-win_arm64.whl", hash = "sha256:73439805c5cb68f364d826a5c5c4b6c798ded6b7ebaa4011f01ce6c94e4d5583"},
{file = "ruff-0.5.2.tar.gz", hash = "sha256:2c0df2d2de685433794a14d8d2e240df619b748fbe3367346baa519d8e6f1ca2"},
]
[[package]]
@ -2026,13 +2067,13 @@ test = ["pytest"]
[[package]]
name = "sqlparse"
version = "0.5.0"
version = "0.5.1"
description = "A non-validating SQL parser."
optional = false
python-versions = ">=3.8"
files = [
{file = "sqlparse-0.5.0-py3-none-any.whl", hash = "sha256:c204494cd97479d0e39f28c93d46c0b2d5959c7b9ab904762ea6c7af211c8663"},
{file = "sqlparse-0.5.0.tar.gz", hash = "sha256:714d0a4932c059d16189f58ef5411ec2287a4360f17cdd0edd2d09d4c5087c93"},
{file = "sqlparse-0.5.1-py3-none-any.whl", hash = "sha256:773dcbf9a5ab44a090f3441e2180efe2560220203dc2f8c0b0fa141e18b505e4"},
{file = "sqlparse-0.5.1.tar.gz", hash = "sha256:bb6b4df465655ef332548e24f08e205afc81b9ab86cb1c45657a7ff173a3a00e"},
]
[package.extras]
@ -2187,6 +2228,20 @@ files = [
[package.extras]
watchmedo = ["PyYAML (>=3.10)"]
[[package]]
name = "wcmatch"
version = "8.5.2"
description = "Wildcard/glob file name matcher."
optional = false
python-versions = ">=3.8"
files = [
{file = "wcmatch-8.5.2-py3-none-any.whl", hash = "sha256:17d3ad3758f9d0b5b4dedc770b65420d4dac62e680229c287bf24c9db856a478"},
{file = "wcmatch-8.5.2.tar.gz", hash = "sha256:a70222b86dea82fb382dd87b73278c10756c138bd6f8f714e2183128887b9eb2"},
]
[package.dependencies]
bracex = ">=2.1.1"
[[package]]
name = "wcwidth"
version = "0.2.13"
@ -2216,4 +2271,4 @@ filelock = ">=3.4"
[metadata]
lock-version = "2.0"
python-versions = "^3.10"
content-hash = "d6d524c293fe2560487d9b9e6287f0d4a369294ba30be0de4a3ff092844b9a4e"
content-hash = "9038b84fac4dc7ce5aea0520d29e4d5705e2e55f3e165d2455ebc61eafe6cfe0"

View File

@ -73,6 +73,7 @@ mkdocs = "^1.6.0"
mkdocs-material = "^9.5.28"
mkdocstrings = "^0.25.1"
mkdocstrings-python = "^1.10.5"
mkdocs-include-markdown-plugin = "^6.2.1"
[tool.poetry.group.docs]
optional = true