\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}