Blog ENI : Toute la veille numérique !
🎁 Jusqu'au 31/12, recevez notre
offre d'abonnement à la Bibliothèque Numérique. Cliquez ici
🎁 Jusqu'au 31/12, recevez notre
offre d'abonnement à la Bibliothèque Numérique. Cliquez ici
  1. Livres et vidéos
  2. Django
  3. Modèles
Extrait - Django Développez vos applications web en Python (fonctionnalités essentielles et bonnes pratiques)
Extraits du livre
Django Développez vos applications web en Python (fonctionnalités essentielles et bonnes pratiques)
3 avis
Revenir à la page d'achat du livre

Modèles

Introduction

Ce chapitre est consacré à la modélisation et au stockage des objets des applications. En dehors des objets intrinsèques à l’infrastructure logicielle, il va s’agir en l’occurrence de messages échangés entre des utilisateurs.

Chaîne de caractères : simple ou double guillemets ?

En Python, les chaînes de caractères littérales peuvent indifféremment être encadrées par des couples de guillemets simples ou doubles.

Le guide de style (cf. conventions établies par le document Python Enhancement Proposal PEP 8) ne donne pas de recommandation particulière d’emploi de l’une ou l’autre manière. Il indique juste que si la chaîne contient un guillemet d’un genre, utiliser l’autre genre pour l’encadrement épargne l’emploi du caractère d’échappement, ce qui est bénéfique à la lisibilité.

Les normes de style qu’il est demandé de respecter aux contributeurs du code de Django ne sont pas non plus directives, à part une préférence pour les simples guillemets, sauf si la chaîne en contient.

Dans ce chapitre et par la suite, il est tenté de suivre les conventions préférentielles suivantes, qui semblent correspondre aux exemples vus dans la documentation de Django et qui sont...

Instanciation de la base de données

Jusqu’à présent, il n’a pas été nécessaire de disposer d’une base de données. Au plus, un fichier dj\db.sqlite3 existe, mais de taille nulle, généré en conséquence de la configuration par défaut donnée par l’assistant de création de projet. Il a été vu qu’on pouvait même désactiver le paramètre de configuration DATABASES et éliminer ce fichier.

Pour autant, il a été possible de préparer des vues et donc de servir des pages. Ceci démontre qu’il est tout à fait possible d’employer Django sans nécessairement devoir mettre en œuvre une base de données, notamment pour de simples sites délivrant de l’information statique, sans interaction avec les utilisateurs. 

Pour l’application à réaliser, il va falloir un espace de persistance des messages et une base de données est le support privilégié pour satisfaire ce besoin.

1. Création de la base de données

 Ouvrez l’outil pgAdmin4.

Pour rappel, il s’agit d’un outil d’administration en mode graphique, empaqueté avec l’installation de la base de données PostgreSQL, dont nous avions recommandé de déposer un raccourci sur le bureau. Au lancement, son interface est présentée dans un onglet du navigateur par défaut.

À condition que, dans le menu hiérarchique de gauche, le focus soit détenu par un nœud compatible, à savoir un Server Group (comme PostgreSQL 11 sur la copie d’écran plus loin) ou son descendant Databases, alors la section Object de la barre de menus, ainsi que le menu contextuel par clic droit, proposent une entrée Create / Database… pour amorcer la création d’une base.

Le nom de la base est quelconque, il est fait le choix de djBook.

Pour faire simple, on peut laisser ici le propriétaire de la base à l’utilisateur postgres, qui est le super-utilisateur établi par l’installateur. Mais concernant le site de production, pour des raisons de sécurité, il faut prévoir un compte utilisateur dédié à cet usage.

images/mdl1.png

Dans l’onglet...

Champs

La modélisation des objets d’une application est un fondement crucial du processus de développement, car c’est une phase structurante, c’est-à-dire qu’une grande part des travaux de codage est ensuite gouvernée par la façon dont les instances d’objets se présentent et sont liées les unes aux autres. La pertinence du modèle conceptuel de données a une incidence sur de nombreuses qualités espérées du code et de l’application : fluidité, lisibilité, maintenance, rapidité, montée en charge, etc. Des méthodes et des outils existent pour assister la démarche de conception, comme Merise ou UML (Unified Modeling Language).

Quelle que soit la manière, il faudra s’interroger sur les questions typiques : Quelles sont les natures d’objets à considérer ? Classes concrètes ou abstraites ? Quelle hiérarchie d’héritage ? Quelles relations entre les instances et leurs orientations : faut-il dire « A possède B » ou « B appartient à A » et où matérialiser une cardinalité dans le cas de multipropriété ?

Pour rester simple, l’application se contente d’une seule nature d’objets : des messages. De même, le nombre de propriétés d’un message est volontairement réduit.

Les objets, formalisés par une classe, se définissent dans le fichier ayant pour nom conventionnel models.py.

 Complétez le fichier, pour contenir les extraits successifs :

mysite\messenger\models.py 

from django.conf import settings 
from django.db import models 
from django.utils.timezone import now 
 
 
class Message(models.Model): 
   subject = models.CharField("sujet", max_length=120) 
   body = models.TextField("corps", blank=True) 
   # ... 

L’essentiel des pièces pour constituer un modèle est fourni par le module django.db.models : la classe de base pour établir le socle et les types de champs couramment employés. En réalité, les types de champs sont hébergés sous le module django.db.models.fields, mais cette possibilité d’écriture...

Métadonnées

Une métadonnée est une donnée qui sert à qualifier une autre donnée. Dans le cas d’un modèle, il va s’agir de spécifier des informations complémentaires, telles que des caractéristiques, des préférences, des options, des libellés, etc. N’étant pas à mélanger avec les champs, ces définitions doivent être placées dans une classe interne portant le nom Meta.

 Complétez le fichier, pour contenir les extraits successifs :

mysite\messenger\models.py 

# ... 
class Message(models.Model): 
   # ... les champs 
 
   class Meta: 
       verbose_name = "message" 
       verbose_name_plural = "messages" 
       ordering = ['-sent_at', '-id'] 
 
   # ... 

Aucune métadonnée ni la classe Meta ne sont obligatoires, car des valeurs par défaut sont prévues. Beaucoup d’options correspondent à des usages avancés, comme modifier des noms ou des conventions, qu’il n’est même pas nécessaire de connaître lorsqu’on reste dans une situation ordinaire. Quelques options ont toutefois une valeur par défaut, fixe ou issue d’une déduction, qui, bien que fonctionnant dans toutes les situations, n’est pas nécessairement la mieux adaptée à l’application. C’est la situation avec les trois cas mentionnés ici.

De la même...

ORM (Object Relation Mapping) et migrations

Un des points forts de Django est de placer les modèles au cœur de la définition d’une application, c’est-à-dire d’y concentrer le maximum d’informations, pour ensuite en déduire des composants dérivés, par exemple les éléments d’un formulaire HTML, leur validité (telle que : valeur requise ou optionnelle, longueur minimale/maximale, jeu de caractères), etc.

Il s’agit d’un des concepts fondamentaux posés à la création du produit par ses développeurs. Cela signifie qu’un modèle doit porter non seulement les données d’une instance, mais aussi les comportements de l’objet qu’il représente : les moyens d’y accéder en base de données et sa logique métier.

C’est ainsi que, ayant défini précédemment le modèle, cela doit suffire à l’infrastructure logicielle pour se débrouiller dans la manipulation de ce modèle et de ses instances d’objet. En conséquence, on peut engager une étape de création en base de données des structures relatives à ce modèle.

Dans le jargon de l’infrastructure logicielle, cette opération qui consiste à transposer les nouveautés des modèles vers les schémas de la base de données porte le nom de « migration ».

Ce terme semble un peu ésotérique puisque sa signification ordinaire exprime le déplacement physique d’éléments d’une zone vers une autre. Ici, il faut l’entendre dans un sens plus large, comme un changement d’état ou le parcours d’un état A vers un état B.

Deux commandes ont été précédemment explorées, relatives à...

Exploration des métadonnées

Une API est à disposition pour explorer les caractéristiques des classes de modèles, ce qu’on peut aussi appeler introspection de modèles. Elle est accessible par l’attribut _meta de la classe du modèle. Cet attribut porte une instance d’objet de classe django.db.models.options.Options. Si on compte l’usage de cet attribut parmi les sources des applications sous django.contrib, on constate qu’il est mentionné par six applications sur quinze et surtout que la moitié des références sont dans le module d’administration intégré, sans surprise puisque justement son rôle est de construire des pages sur base d’un balayage des applications gérées et de leurs modèles.

La diversité des termes relatifs au concept « meta » semble inutilement déconcertante. Elle s’explique par des raisons historiques. L’attribut existe sous ce nom depuis l’origine du produit, mais comme l’indique son caractère de soulignement en préfixe, il était conçu en tant qu’interface privée. En Python, on sait qu’il s’agit d’une convention, car techniquement rien ne restreint l’accès. L’emploi de la qualification « non public » essaye d’apporter la signification qu’il s’agit moins d’une notion de permission qu’une mise en garde : l’implémentation derrière le nom doit être considérée comme instable, non officielle et pouvant changer à tout moment sans préavis ni engagement de continuité de compatibilité.

Très tôt, les développeurs ont trouvé cette interface bien pratique pour obtenir les informations nécessaires à leur code applicatif, malgré le risque encouru de cessation brutale de fonctionnement à l’occasion d’une montée de version.

De plus, en n’étant pas publique, l’implémentation n’était pas couverte par des tests unitaires.

Constatant cet état de fait, les concepteurs de Django ont admis qu’il fallait apporter un support officiel à cette interface et celui-ci a été introduit dans la version 1.8. À l’occasion...

Gestionnaires

Un gestionnaire de modèles est un objet fondamental parmi les fonctionnalités offertes par une infrastructure logicielle. Il en a été fait un survol à l’occasion d’une section précédente au sujet de l’ORM et des migrations. Le gestionnaire se pose en intermédiaire facilitateur entre le code applicatif qui préfère manipuler confortablement des instances d’objet Python et la base de données qui comprend essentiellement des requêtes en langage SQL. Cette couche d’abstraction est également bien utile en prenant à sa charge les subtiles différences de syntaxe ou de fonctionnalités entre les systèmes de base de données supportés officiellement. Ainsi, les opérations usuelles d’interrogation, de création, de modification et de suppression d’enregistrement se font par de simples appels aux méthodes du gestionnaire.

D’office, avec la définition d’un modèle, un gestionnaire natif est mis à disposition sous un attribut de la classe. Cet attribut se nomme objects. Il se pourrait qu’on souhaite utiliser ce nom en tant que champ du modèle, bien que ce soit rare et mal avisé au regard du potentiel de confusion avec les habitudes. On pourrait juste vouloir donner un autre nom à l’attribut du gestionnaire, bien que là encore l’intérêt est probablement très faible, comparé à la rupture des conventions. Le cas d’usage...

Opérations sur objets

À ce stade, les expérimentations des opérations sur les objets vont se faire de façon manuelle, en mode console. C’est assez rustique, mais avec le mérite de mieux appréhender les étapes pas à pas. Savoir ensuite intégrer ces instructions dans un véritable code applicatif en sera d’autant plus facile.

1. Création

Pour pouvoir créer des messages entre utilisateurs, il faut d’abord disposer d’utilisateurs. Il n’est pas question d’utiliser le compte super-utilisateur, ce n’est pas son propos.

Le gestionnaire du modèle Utilisateur dispose de la méthode create_user([...]) dédiée à cette opération. Il suffit de lui passer en paramètres les valeurs essentielles et elle s’occupe du travail, notamment de la codification du mot de passe. Ce mot de passe n’est évidemment pas stocké en clair ni véritablement chiffré puisqu’il s’agit plutôt du résultat d’une fonction à sens unique basé sur un algorithme de hachage.

L’irréversibilité de la fonction assure qu’il n’est pas possible de remonter au mot de passe en clair même si on obtient la connaissance du produit. Si un utilisateur oublie son mot de passe, on ne peut donc pas le lui rappeler, mais seulement lui permettre d’en établir un nouveau.

Au minimum, la méthode requiert seulement le paramètre username. Il est donc possible de créer un compte sans mot de passe, mais alors le compte se voit attribuer malgré tout un mot de passe fictif, selon un motif qui rend reconnaissable par le système la nature à part de ce compte. En conséquence, ce compte n’est pas autorisé à se connecter au site. Il faut le voir comme un compte spécial, à vocation technique et non destiné à une personne physique. Par exemple, on peut imaginer des comptes fictifs d’attente, de façon à ne pas être empêché de créer des objets ayant une clé étrangère obligatoire vers un compte utilisateur, mais dont la cible définitive ne sera connue que plus tard dans le cycle de vie de l’objet. Un autre cas d’usage transitoire vient de la volonté...

Migration de structures et données

Jusqu’à présent, les migrations de structures pratiquées étaient des opérations dites initiales, c’est-à-dire pour créer les tables, notamment celle de l’application. Il est temps maintenant d’expérimenter une évolution ordinaire au cours du développement d’une application, à savoir enrichir ou modifier un modèle. Ceci a donc naturellement des répercussions sur les tables de la base de données, mais l’outil de migration est encore là pour aider en se chargeant de la basse besogne.

Par commencer, le modèle reçoit un complément de champs bien utiles à la gestion de l’état d’un message :

  • Un champ read_at pour mémoriser le fait que le message a été lu. On pourrait aussi se contenter d’un champ booléen, mais cela ne coûte pas grand-chose d’avoir cette information plus précise, même si elle n’est pas exploitée par la suite.

  • Des champs sender_deleted_at et recipient_deleted_at pour mémoriser la manifestation de chacune des parties à vouloir effacer le message, puisqu’il ne correspond qu’à un unique enregistrement. À nouveau, on pourrait être plus rustique avec des champs de nature booléenne, mais disposer de dates ouvre...