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