Modèles
Introduction aux modèles
1. Django et modèle de données
Django est un framework de développement d’applications conçues au-dessus d’une base de données relationnelle. Chacun de ces mots revêt une importance capitale. Django est fait pour gérer la partie technique et vous offrir des facilités pour créer rapidement une application. Il s’appuie sur une base de données relationnelle. Ce type de base de données est le plus répandu, elles existent depuis très longtemps, sont très stables et très performantes. On peut citer PostgreSQL, appréciée pour sa performance et sa robustesse, ou MariaDB, appréciée pour sa simplicité d’utilisation et sa popularité. Toutes deux sont extrêmement fiables.
Bien que Django soit polyvalent et qu’il puisse fonctionner au-dessus de MongoDB, par exemple, une base de données orientée document, il est vraiment taillé et efficace pour travailler avec une base de données relationnelle.
Développer une application consiste alors simplement à gérer son modèle de données et à l’utiliser pour gérer des fonctionnalités ou des affichages.
Le fait de concevoir le modèle de données est la toute première des préoccupations du développeur Django, loin devant le fait de savoir utiliser ces données et de devoir les rendre dans divers formats.
2. Qu’est-ce qu’un modèle ?
En modélisation de base de données, on parle d’entité et de relation. Une entité peut être par exemple, un produit, une commande, une livraison ou encore une personne, un rôle ou une mission. La relation permet de lier des entités. On peut décider qu’une personne a un et un seul rôle, qu’une personne a 0 à plusieurs missions, mais qu’une mission est remplie par une et une seule personne, qu’une commande a un à plusieurs produits, mais qu’un produit peut se trouver dans 0 à n commandes.
C’est en définissant précisément ces règles que l’on pourra modéliser comment les données pourront vivre, comment elles seront validées et comment elles seront...
Champs courants
1. Introduction
Pour visualiser le code relatif à ce chapitre : git diff v1.1.0..v1.1.1 pour les modèles du fil rouge.
Différents types de champs non utilisés dans le fil rouge sont dans l’app example : git diff v1.1.1..v1.1.2.
Comme nous le disions à la fin de la section précédente, la partie que l’on découvre ici est l’ORM de Django, ORM signifiant Object-Relational Mapping.
L’idée est de créer des classes Python, décrivant des entités SQL. Les attributs de la classe d’un modèle sont en fait la description des champs de ce modèle, qui correspondront à des colonnes de la table associée au modèle.
Nous venons de voir à la section précédente comment créer une telle classe. Nous allons voir maintenant comment créer les attributs de nos modèles et donc les colonnes de notre base de données.
Avec une bonne connaissance de SQL, on raisonne spontanément en type de colonnes. Et il est vrai que pour la plupart des bases de données, comme PostgreSQL, MariaDB ou MySQL, voire même MSSQL ou OracleDB, il existe une typologie similaire et des contraintes de même ordre.
Attention tout de même à SQLite qui est une base de données à typage dynamique (https://fr.wikipedia.org/wiki/SQLite#Types_de_données). Le type de la donnée n’est pas forcément le même que le type de la colonne (vous pouvez par exemple insérer un texte dans une colonne définie comme contenant des entiers) et c’est une fonctionnalité qui a du sens dans le contexte d’utilisation de cette base de données.
En revanche, l’utilisation de SQLite ne fait pas sens avec Django, à moins de créer une application qui ne sera utilisée que par un seul utilisateur, ou par un nombre très restreint d’utilisateurs. Elle ne doit pas non plus avoir de demande spécifique au niveau des performances. SQLite n’étant pas client-serveur, elle est plutôt faite pour remplacer le stockage par fichier structuré comme CSV, par exemple, en offrant plus de complexité dans l’architecture de données.
Mais au-delà de cela, on peut rajouter plus de contraintes et se concentrer sur des propriétés...
Relations un à plusieurs
1. Travailler avec des énumérations
Pour visualiser le code relatif à ce chapitre : git diff v1.1.2..v1.1.3.
Lorsque l’on travaille sur des modèles, il n’est pas rare de devoir utiliser des champs qui ne peuvent avoir qu’une sélection précise de valeurs. Il faut donc contrôler que la valeur effectivement choisie par l’utilisateur fasse bien partie de cette sélection. De plus, lorsque l’on construit le formulaire, il est plus pratique d’avoir une liste de sélection avec les valeurs autorisées, plutôt que de laisser l’utilisateur saisir cette valeur, au risque de se tromper.
On peut aussi voir une énumération comme une petite table ne contenant qu’un identifiant et un libellé et dont le contenu est statique.
L’identifiant peut être, au choix, une chaîne de caractères ou un entier.
On définira cette énumération dans un fichier à part, pour pouvoir l’importer plus facilement et aussi pour améliorer l’organisation de son code. Nous créons donc un fichier enums.py dans lequel nous allons saisir ceci :
from django.db import models
from django.utils.translation import gettext_lazy as gettext
__all__ = (
"GameStatus",
"GameLevel",
)
class GameStatus(models.TextChoices):
"""Statuses of a game"""
DRAFT = "draft", gettext("draft")
READY = "ready", gettext("ready")
ONGOING = "ongoing", gettext("ongoing")
DONE = "done", gettext("done")
class GameLevel(models.IntegerChoices):
"""Level of difficulty of a game"""
EASY = 1, gettext("easy")
MEDIUM = 2, gettext("medium")
HARD = 3, gettext("hard")
EXTREME = 4, gettext("extreme")
NIGHTMARE = 5, gettext("nightmare") ...Relations un à un et héritage
1. Relations un à un explicites
Pour visualiser le code relatif à ce chapitre : git diff v1.1.6..v1.1.7.
Dans le cadre d’une relation un à plusieurs, on utilise une clé étrangère. Le modèle qui possède la clé étrangère va donc pointer vers un et un seul enregistrement de la table pointée. Par contre, absolument rien ne contraint l’objet pointé, qui peut être pointé par 0, 1 ou plusieurs enregistrements.
Par exemple, une question doit forcément pointer vers un jeu, car elle fait partie de ce jeu. Par contre, un jeu peut parfaitement n’avoir aucune question (ce qui est au minimum le cas juste après sa création), mais il peut en avoir une seule, deux, trois ou autant que l’on souhaite.
Dans le cas d’une relation un à un, un objet pointe vers une clé étrangère, l’objet lié ne peut être pointé qu’une seule fois. Techniquement, cela ressemble à une clé étrangère avec contrainte d’unicité, mais dans les faits, il ne faut pas utiliser cette technique-là.
Cela peut être utilisé pour créer une table contenant des données liées à un objet, mais avec une sémantique particulière, spécifique à un domaine fonctionnel particulier.
Un des exemples que l’on va mettre en œuvre concerne la classe Player. En fait, un joueur est quelqu’un qui possède...
Relations plusieurs à plusieurs
1. Relations implicites
Pour visualiser le code relatif à ce chapitre : git diff v1.1.8..v1.1.9.
Une relation un à plusieurs est une relation où un objet pointe vers un autre objet. Par défaut, il n’y a pas de contrainte sur la relation inverse. L’autre objet peut donc être pointé 0, 1 ou plusieurs fois.
Cela se résout par l’utilisation d’une clé étrangère.
Dans notre exemple, une question pointe vers un jeu. Elle fait partie de ce jeu. On aurait pu décider de gérer les questions de manière indépendante et de voir un jeu comme une sélection de questions. Une question pouvant se retrouver dans 0, un ou plusieurs jeux. Fonctionnellement, un tel choix peut se justifier, mais il change profondément la logique de fonctionnement ainsi que la logique de saisie des données.

Représentation des deux types de relations
Comme nous pouvons le voir sur le premier diagramme, la relation entre la question et le jeu est directe et asymétrique.
Le second diagramme ne présente aucune clé étrangère dans les deux tables Jeu et Question, mais ajoute une table d’association qui ne contient rien d’autre qu’une clé primaire et surtout deux clés étrangères, une vers chaque table associée.
Associer le jeu 42 avec la question 666 revient à créer un enregistrement de Jeu_Question contenant une clé étrangère jeu_id ayant pour valeur 42 et une clé étrangère question_id ayant pour valeur 666. Il y a également une contrainte d’unicité sur ces deux clés étrangères, une association identique ne pouvant pas se produire plusieurs fois.
Pour des besoins basiques, c’est-à-dire si l’on n’a pas besoin de champs supplémentaires pour caractériser l’association, on peut rajouter au modèle un champ qui crée la relation de manière implicite.
Nous allons, pour suivre notre fil rouge, créer une classe pour gérer une partie d’un joueur. Cette classe va donc pointer vers ce joueur, ainsi que le jeu auquel le joueur a joué. On décide, pour des raisons fonctionnelles, qu’un joueur ne peut jouer qu’une seule fois à...