Chapitre 5 : Interagir avec une base de données : les modèles
Au chapitre 3, nous avons couvert les bases de la construction de sites web avec Django : paramétrage des vues et de l’URLConf. Comme nous l’avons expliqué, une vue prends en charge la logique arbitraire, puis retourne une réponse. Dans notre exemple, la logique consistait à calculer la date et l’heure courante.
Dans les applications web modernes, la logique abitraire implique souvent une interaction avec une base de données. En coulisse, un site web motorisé par une base de données se connecte à un serveur de base de données, y récupère quelque donnée, puis affiche cette donnée, bien formatée, sur une page web. De façon similaire, le site peut aussi fournir une fonctionnalité permettant aux visiteurs du site d’enrichir la base de données comme ils l’entendent.
De nombreux sites web complexes proposent une combinaison des deux. Amazon.com, en l’occurence, est un bel exemple de site reposant sur une base de données. Chaque page de produit est essentiellement une requête sur la base produits d’Amazon formatée en HTML, et lorsque vous postez un avis d’utilisateur, il est ajouté à la base de données des commentaires.
Django est taillé pour fabriquer des sites web reposant sur une base de données, il fournit donc une puissante façon de faire des requêtes dans la base en utilisant Python. Ce chapitre explique cette fonctionnalité : La couche base de données de Django.
(Remarque : Bien qu’il ne soit pas strictement nécessaire de connaître les fondamentaux de la théorie des bases de données et du SQL pour pouvoir utiliser la couche de base de données de Django, cela reste vivement recommandé. Une introduction à ces concepts est en dehors de la portée de ce livre, mais continuez la lecture même si vous êtes un débutant en base de données. Vous serez probablement apte à suivre et à saisir les concepts à partir du contexte).
La mauvaise façon de faire des requêtes de base de données dans les vues.
Tout comme le chapitre 3 détaillait une mauvaise façon de produire un contenu à l’intérieur d’une vue (en codant le texte directement à l’intérieur de la vue), il y a une mauvaise façon de récupérer une donnée depuis une base de données à l’intérieur d’une vue. C’est simple : utilisez simplement n’importe quelle bibliothèque Python existante pour effectuer une requête SQL et faites quelque chose avec les résultats.
Dans cette vue d’exemple, nous utilisons la bibliothèque MySQLdb (disponible sur http://www.djangoproject.com/r/python-mysql/ ) pour nous connecter à une base de données MySQL, récupérer quelques enregistrements, destinés à enrichir un gabarit envoyé à l’affichage sous forme de page web.
from django.shortcuts import render_to_response
import MySQLdb
def book_list(request):
db = MySQLdb.connect(user='me', db='mydb', passwd='secret',
host='localhost')
cursor = db.cursor()
cursor.execute('SELECT name FROM books ORDER BY name')
names = [row[0] for row in cursor.fetchall()]
db.close()
return render_to_response('book_list.html', {'names': names})
Cette approche est fonctionnelle, mais quelques problèmes doivent vous interpeller immédiatement :
- Nous codons en dur les paramètres de connection à la base de données. Idéalement, ces paramètres doivent être stockés dans la configuration de Django.
- Nous avons à écrire pas mal de code fonctionnel : créer une connection, créer un curseur, exécuter une instruction, puis fermer la connection. Idéalement, tout ce que nous devons avoir à faire est de préciser le résultat attendu.
- Nous sommes liés à MySQL. Si, en cours de route, nous passons de MySQL à PostgreSQL, nous aurons à utiliser un adaptateur de base de donnéeq différent (par exemple psycopg plutôt que MySQLdb), modifier les paramètres de connection, et selon la nature de l’instruction SQL — potentiellement réécrire le SQL. Idéalement, le serveur de base de données que nous utilisons doit être dématérialisé, de façon à pouvoir effectuer un changement de serveur de base de données à un seul endroit.
Comme vous pouviez l’attendre, la couche base de données de Django à pour but de résoudre ces problèmes. Voici un avant goût de la méthode pour réécrire la vue précédente en utilisant l’API de base de donnée de Django :
from django.shortcuts import render_to_response
from mysite.books.models import Book
def book_list(request):
books = Book.objects.order_by('name')
return render_to_response('book_list.html', {'books': books})
Nous expliquerons ce code plus tard dans le chapitre. Pour l’instant, contentez vous de voir à quoi il ressemble.
Le patron de développement MTV — Modèle Gabarit Vues
Avant de plonger à nouveau dans le code, prenons le temps de considérer l’architecture globale d’une application web Django reposant sur une base de données.
Comme nous l’avons mentionné aux chapitres précédents, Django est conçu pour encourager le faible couplage et la stricte séparation entres les composants d’une application. Si vous suivez cette philosophie, il est aisé de faire des changements sur une pièce particulière de l’application sans affecter les autres pièces. Dans les fonctions view, en l’occurence, nous avons discuté de l’importance de séparer la logique métier de la logique de présentation, en utilisant un système de gabarit. Avec la couche base de données, nous appliquons cette même philosophie à la logique d’accès aux données.
Ces trois éléments mis ensemble — logique d’accès aux données, logique métier, et logique de présentation — forment un concept parfois appelé en architecture logicielle le patron de conception Modèle-Vue-Contrôleur (MVC). Dans ce patron, le ‘Modèle’ fait référence à la couche d’accès aux données, la ‘Vue’ fait référence à la partie du système qui sélectionne ce qui doit être affiché et comment l’afficher, enfin le ‘Contrôleur’ qui fait référence à la partie du système décidant des vues à utiliser, en fonction des entrées de l’utilisateur, accédant aux modèles selon ses besoins.
Pourquoi cet acronyme ?
Le but de définir explicitement des patrons tel que le MVC est principalement d’optimiser la communication entre développeurs. Plutôt que d’avoir à dire à vos collègues, «faisons une abstraction de l’accès aux données, puis séparons la couche qui gère l’affichage des données, et plaçons une couche au milieu qui régule cela», vous pouvez profiter d’un vocabulaire commun et dire, «utilisons ici le patron MVC».
Django suit le patron MVC d’assez près pour pouvoir être appelé un framework MVC. Voici grossièrement comment le M, le V et le C s’appliquent sous Django :
- M, la partie accès aux données, gérée par la couche base de données de Django, est décrite dans ce chapitre.
- V, la partie qui selectionne les données à afficher et la façon de le faire, est gérée par les vues et les gabarits.
- C, la partie qui délègue à une vue en fonction de l’entrée utilisateur, est gérée par le framework lui même, en suivant votre URLconf et en appelant la fonction Python correspondant à l’URL fournie.
Puisque le ‘C’ est géré par le framework lui même et que le côté le plus excitant de Django apparaît dans les modèles, gabarits et vues, Django à été classé dans les framework MTV. Dans le patron de développement MTV, MGV
- M équivaut au «Modèle», la couche d’accès aux données. Cette couche contient tout ce qui concerne les données : comment y accéder, comment les valider, quels comportements ont-elles, et les relations entre ces données.
- T signifie «Template», G pour «Gabarit», la couche de présentation. Cette couche contient les décisions relatives à la présentation : comment quelquechose doit être affiché sur une page web ou sur un autre type de document.
- V signifie «Vue», la couche de logique métier. Cette couche contient la logique qui accède au modèle et transmet au(x) gabarit(s) approprié(s). Vous pouvez le voir comme un pont entre les modèles et les gabarits.
Si vous êtes familier avec d’autres frameworks de développement web MVC, tel que Ruby on Rails, vous devrez considérer les vues Django comme étant les contrôleurs et les gabarits Django comme étant les vues. Il s’agit d’une malencontreuse confusion liée à la différence d’interprétation du MVC. Avec l’interprétation de Django, la «vue» décrit les données qui seront présentées à l’utilisateur ; ce n’est pas uniquement l’apparence des données, mais aussi la sélection des données présentées. À l’inverse, Ruby on Rails et les frameworks similaires suggèrent que le travail du contrôleur est de décider quelles sont les données présentées à l’utilisateur, tandis que la vue reste strictement centrée sur le comment de l’apparence des données, pas sur le choix des données présentées.
Aucune interprétation n’est «meilleure» que l’autre. L’important est de comprendre les concepts sous-jacents.
Configuration de la base de données
Avec toute cette philosophie à l’esprit, commençons à explorer la couche base de données de Django. Tout d’abord, nous devons prendre en compte un peu de configuration initiale : nous avons besoin de préciser à Django le serveur de base de données à utiliser et la manière de s’y connecter.
Nous supposerons que vous avez configuré un serveur de base de donnée, que vous l’avez activé, et que vous y avez créée une base de données (par exemple, en utilisant une instruction CREATE DATABASE). SQLite est un cas à part ; dans ce cas, il n’y a pas de base de données à créer, puisque SQLite utilise des fichiers autonomes sur le système de fichier pour y stocker ses données.
Comme pour TEMPLATE_DIRS au chapitre précédent, la configuration des bases de données se trouve dans le fichier de configuration de Django, appelé settings.py par défaut. Éditez ce fichier et consultez les paramétrages de base de données :
DATABASE_ENGINE = '' DATABASE_NAME = '' DATABASE_USER = '' DATABASE_PASSWORD = '' DATABASE_HOST = '' DATABASE_PORT = ''
Voici un aperçu de chaque paramètre.
- DATABASE_ENGINE indique à Django le système de gestion de base de données à utiliser. Si vous utilisez une base de données avec Django, DATABASE_ENGINE doit être paramétrée avec l’une des chaînes présentées au tableau 5-1.
| Table 5-1. Database Engine Settings | ||
|---|---|---|
| Settings | Database | Required Adapter |
| postgresql | PostgreSQL | psycopg version 1.x, http://www.djangoproject.com/r/python-pgsql/1/. |
| postgresql_psycopg2 | PostgreSQL | psycopg version 2.x, http://www.djangoproject.com/r/python-pgsql/. |
| mysql | MySQL | MySQLdb, http://www.djangoproject.com/r/python-mysql/. |
| sqlite3 | SQLite | Pas d’adaptateur nécessaire si vous utilisez Python 2.5+. Sinon, pysqlite, http://www.djangoproject.com/r/python-sqlite/. |
| ado_mssql | Microsoft SQL Server | adodbapi version 2.0.1+, http://www.djangoproject.com/r/python-ado/. |
| oracle | Oracle | cx_Oracle, http://www.djangoproject.com/r/python-oracle/. |
Notez que quel que soit le choix de votre base de données, vous devrez récupérer et installer l’adaptateur de base de données approprié. Chacun d’entre eux est disponible librement sur le web ; suivez simplement les liens dans la colonne «Adaptateur requis» du Tableau 5-1.
DATABASE_NAME informe Django du nom de votre base de données. Si vous utilisez SQLite, précisez le chemin absolu de votre fichier de base de données sur votre système de fichier. (par exemple, '/home/django/mydata.db').
DATABASE_USER indique à Django le nom d’utilisateur à utiliser lors d’une connection à votre base de données. Si vous utilisez SQLite, laissez ce champ vide.
DATABASE_PASSWORD précise à Django le mot de passe à utiliser lors de la connection à votre base de données. Si vous utilisez SQLite ou si vous avez un mot de passe vide, laisser ce champ vide.
DATABASE_HOST renseigne Django sur l’hôte à utiliser lors de la connection à votre base de données. Si votre base de données est sur l’ordinateur où est installé Django (c’est à dire, localhost), laissez ce champ vide. Si vous utilisez SQLite, laissez ce champ vide.
MySQL est ici un cas à part. Si cette valeur commence par un slash slash ('/') et que vous utilisez MySQL, MySQL se connectera via un socket Unix au socket spécifié, par exemple :
DATABASE_HOST = '/var/run/mysql'
Si vous utilisez MySQL et que cette valeur ne commence pas par un slash, alors cette valeur est considérée comme étant l’hôte.
DATABASE_PORT dit à Django quel port utiliser lors de la connection à votre base de données. Si vous utilisez SQLite, laissez ce champ vide. Dans le cas contraire, si vous laissez ce champ vide, l’adaptateur de base de données sous-jacent utilisera n’importe quel port configuré par défaut pour votre serveur de base de données. Dans la plupart des cas, le port par défaut est le bon, vous pouvez donc laissez ce champ vide.
Après avoir saisi ces paramètres, testez votre configuration. Premièrement, à l’intérieur du répertoire de projet monsite que vous avez créé au chapitre 2, lancez la commande python manage.py shell.
Vous remarquerez que cela démarre une console interactive Python. Les apparences sont parfois trompeuses, cependant ! Il y a une différence importante entre la commande python manage.py shell à l’intérieur du répertoire de votre projet Django et la commande plus générique python. Cette dernière lance la console Python de base, la précédente indique à Django quels sont les fichiers de configuration à utiliser avant de lancer la console. C’est un point clef pour pouvoir faire des requêtes sur une base de données : Django doit connaître quels fichiers de configuration il doit utiliser de façon à obtenir vos informations de connection à la base de données.
À l’arrière plan, python manage.py shell considère simplement que vos fichiers de configuration se trouvent dans le même répertoire que manage.py. Il y a d’autre façons de dire à Django quels sont les modules de paramètres à utiliser, mais ces subtilités seront couvertes plus tard. Pour l’instant, utilisez python manage.py shell à chaque fois que vous avez besoin de plonger dans la console Python pour des explorations spécifiques à Django.
Une fois dans la console, saisissez ces commandes pour tester la configuration de votre base de données :
>>> from django.db import connection >>> cursor = connection.cursor()
Si rien ne se passe, alors votre base de données est correctement configurée. Sinon, cherchez des indices dans le message d’erreur sur ce qui à pu mal se passer. Le Tableau 5-2 montre quelques erreurs courantes.
| Table 5-2. Database Configuration Error Messages | |
|---|---|
| Error Message | Solution |
| You haven?t set the DATABASE_ENGINE setting yet. | Set the DATABASE_ENGINE setting to something other than an empty string. |
| Environment variable DJANGO_SETTINGS_MODULE is undefined. | Run the command python manage.py shell rather than python. |
| Error loading _____ module : No module named _____. | You haven’t installed the appropriate database-specific adapter (e.g., psycopg or MySQLdb ). |
| _____ isn?t an available database backend. | Set your DATABASE_ENGINE setting to one of the valid engine settings described previously. Perhaps you made a typo? |
| database _____ does not exist. | Change the DATABASE_NAME setting to point to a database that exists, or execute the appropriate CREATE DATABASE statement in order to create it. |
| role _____ does not exist. | Change the DATABASE_USER setting to point to a user that exists, or create the user in your database. |
| could not connect to server. | Make sure DATABASE_HOST and DATABASE_PORT are set correctly, and make sure the server is running. |
Votre première application
Maintenant que vous avez vérifié le bon fonctionnement de votre connexion, il est temps de créer une appli Django — un paquet de code Django, incluant des modèles et des vues, qui résident ensemble dans un unique paquet Python et qui représente une application Django complète.
Il est important d’expliquer ici la terminologie, afin de ne pas perdre en route les débutants. Nous avons déjà créer un projet, au chapitre 2. Quelle est donc la difference entre un projet et une appli ? La différence à un rapport avec configuration versus code :
Un projet est une instance d’un certain jeux d’applications Django, plus la configuration de ces applications.
Techniquement, le seul prérequis d’un projet est qu’il fournisse un fichier de configuration, qui definisse les informations de connection à la base de données, la liste des applications installées, le TEMPLATE_DIRS, et ainsi de suite.
Une appli est un jeu portable de fonctionnalités Django, habituellement incluses dans des modèles et des vues, qui résident ensemble dans un unique paquet Python.
Par exemple, Django est fournit avec de nombreuses applis, comme un système de commentaires ainsi qu’une interface d’administration automatique. Un point clef à noter à propos de ces applications est qu’elles sont portables et réutilisables au travers de multiples projets.
Il y a très peu de règles pré-établies sur la façon dont vous adapterez votre code Django à ce régime ; c’est flexible. Si vous construisez un simple site internet, vous utiliserez probablement une seule application. Si vous développez un site web complexe avec des parties sans relations, telles qu’un système de commerce en ligne et un tableau d’affichage, vous voudrez probablement séparer celles-ci dans des applis séparées, de façon à pouvoir les réutiliser individuellement à l’avenir.
Évidemment, vous n’avez pas nécessairement besoin de créer d’applis, comme cela a été mis en évidence au travers de la fonction vue d’exemple que nous avons crées plus avant dans ce livre. Dans ce cas, nous avions simplement créé un fichier appelé views.py, puis rempli ce fichier avec des fonctions vue, et pointé nos URLconf sur ces fonctions. Nous n’avons pas eu besoin d’«applis».
Cependant, il y a un pré-requis concernant les conventions de l’appli : si vous utilisez la couche de base de données de Django (modèles), vous devez créer une appli Django. Les Modèles doivent vivre à l’intérieur des application. Ainsi, avant de pouvoir commencer à écrire nos modèles, nous aurons besoin de créer une nouvelle appli.
À l’intérieur du répertoire de projet monsite que vous avez créé au chapitre 2, siasissez cette commande pour créer une nouvelle appli nommée livres :
python manage.py startapp books
Cette commande ne produit aucune sortie, mais crée un répertoire books à l’intérieur du répertoire monsite. Voyons le contenu de ce répertoire :
books/
__init__.py
models.py
views.py
Ces fichiers contiendrons les modèles et les vues pour cette appli.
Observez les fichiers models.py et views.py avec votre éditeur de texte favori. Les deux fichiers sont vides, à l’exception d’un import dans models.py. C’est l’ardoise vierge de votre appli Django.
Définir les Modèles en Python
Comme nous en avons déjà discuté plus tôt dans ce chapitre, le «M» de «MTV» symbolise le «Modèle». Un modèle Django est une description des données dans votre base de données, représentée sous la forme de code Python. C’est votre structure de données, — l’équivalent de votre instruction SQL CREATE TABLE— sauf qu’elle est en Python à la place d’être écrite en SQL, et elle inclut plus que les simples définitions de colonnes. Django utilise un modèle pour exécuter du code SQL dans les coulisses et retourne des structures de données en Python représentant les colonnes de vos tables dans votre base de données. Django utilise aussi des modèles pour représenter des concepts de plus haut niveau que SQL ne peut manipuler.
Si vous êtes familier des bases de données, votre pensée immédiate doit être, «N’est-ce pas redondant de définir les modèles de données en Python et en SQL ?». Django fonctionne de cette façon pour plusieurs raisons :
L’introspection implique des ressources et reste imparfaite. De façon à fournir une API d’accès au données qui reste pratique, Django à en quelque sorte besoin de connaître la structure de la base de donnée, et il existe deux manières d’accomplir ceci. La première méthode consiste à décrire explicitement les données en Python, alors que la seconde méthode consiste à analyser la base de donnée au lancement pour déterminer le modèle de données.
La seconde méthode semble plus propre, parce que les métadonnées concernant vos tables résident à un seul endroit, mais elle introduit quelques problèmes. Premièrement, l’analyse d’une base de donnée au lancement nécessite évidemment plus de ressources. Si le framework devait analyser la base de données à chaque fois qu’il effectue une requête, ou lorsque le serveur web est initalisé, ceci aboutirait à un niveau de charge innacceptable. (Même si certains croient que ce niveau de surcharge est acceptable, les développeurs de Django cherche à minimiser la charge du framework autant que possible, et cette approche à contribué à faire de Django le plus rapide des frameworks de haut-niveau lors de comparatifs avec ces compétiteurs). Deuxièmement, certaines bases de données, notablement d’anciennes versions de MySQL, ne stockent pas suffisement de métadonnées pour permettre une analyse complète et précise.
Écrire du Python est amusant, et tout faire en Python limite le nombre de fois ou vous votre cerveau doit faire un «changement de contexte». Ceci aide à la productivité, lorsque vous restez mentalement dans unique environnement de programmation, aussi longtemps que possible. Avoir à écrire du SQL, puis du Python, puis ensuite à nouveau du SQL est perturbant.
Avoir ses modèles de données stockés sous forme de code plutôt que dans votre base de données facilite la gestion de vos modèles sous contrôle de version. De cette façon, vous pouvez facilement conserver la trace des changements apportés à vos structures de données.
SQL autorise uniquement un certain niveau de métadonnées dans une structure de données. La plupart des systèmes de gestion de base de données, par exemple, ne fournissent pas un type spécialisé de données pour représenter les adresses de courrier électronique ou les URLs. Les modèles Django le permettent. Les avantages d’une structure de données de plus haut niveau sont une plus grande productivité et un code plus réutilisable.
SQL varie selon la plate-forme de base de données. Si vous distribuez une application web par exemple, il est beaucoup plus pragmatique de distribuer un module Python décrivant la structure de données plutôt qu’un jeu séparé d’instructions CREATE TABLE pour MySQL, PostgreSQL et SQLite.
Un inconvénient de cette approche, toutefois, est qu’il est possible que le code Python se désynchronise avec ce qui est effectivement dans la base de données. Si vous apportez des changements à un modèle Django, vous devrez faire les mêmes changements à l’intérieur de votre base de données afin de conserver la cohérence entre la base et le modèle. Nous détaillerons quelques stratégies pour gérer ce problème, plus tard dans ce chapitre.
Finalement, vous devez noter que Django embarque un utilitaire qui peut générer des modèles en analysant une base de données existante. C’est utile pour démarrer rapidement à partir de données existantes.
Votre premier Modèle
À l’aide d’un exemple que nous suivrons tout au long de ce chapitre et du suivant, nous nous concentrerons sur un schéma basique livre/auteur/éditeur. Nous utilisons cela à titre d’exemple car les relations conceptuelles entre les livres, les auteurs et les éditeurs sont bien connues, et il s’agit d’un schéma courant utilisé dans les livres d’introduction à SQL. Et puis vous lisez un livre écrit par des auteurs et publié par un éditeur.
Nous supposerons les concepts, champs et relations suivants :
- Un auteur à un titre (par exemple, Mr. ou Mrs.), un prénom, un nom, une adresse électronique et une photo d’identité.
- Un éditeur à un nom, une adresse postale, une ville, un état/province, un pays et un site internet.
- Un livre possède un titre et une date de publication. Il a aussi un ou plusieurs auteurs (une relation plusieurs-à-plusieurs avec les auteurs) et un éditeur unique (une relation un-a-plusieurs — c’est à dire une clef étrangère — avec les éditeurs).
La première étape dans l’utilisation un schéma relationnel avec Django est de l’exprimer sous la forme de code Python. Dans le fichier models.py qui a été créé par la commande startapp, saisissez les lignes suivantes :
from django.db import models
class Publisher(models.Model):
name = models.CharField(max_length=30)
address = models.CharField(max_length=50)
city = models.CharField(max_length=60)
state_province = models.CharField(max_length=30)
country = models.CharField(max_length=50)
website = models.URLField()
class Author(models.Model):
salutation = models.CharField(max_length=10)
first_name = models.CharField(max_length=30)
last_name = models.CharField(max_length=40)
email = models.EmailField()
headshot = models.ImageField(upload_to='/tmp')
class Book(models.Model):
title = models.CharField(max_length=100)
authors = models.ManyToManyField(Author)
publisher = models.ForeignKey(Publisher)
publication_date = models.DateField()
Examinons rapidement ce code pour aborder les bases. La première chose à noter est que chaque modèle est représenté par une classe Python qui est une sous-classe de django.db.models.Model. La classe mère, Model, contient toute la machinerie nécessaire pour rendre ces objets capables d’intéragir avec une base de données — et qui laisse nos modèles responsables seulement de définir leurs champs, dans une syntaxe agréable et compacte. Croyez-le où non, c’est là le seul code nécessaire pour avoir un accès basique aux données avec Django.
Chaque modèle correspond généralement à une seule table dans la base, et chaque attribut du modèle correspond généralement à une colonne dans cette table de la base de données, Le nom de l’attribut correspond au nom de colonne, et le type de champ (par exemple, CharField) correspond au type de la colonne de la base de données (par exemple, varchar). Par exemple, le modèle Éditeur est équivalent à la table suivante (en supposant la syntaxe de PostgreSQL, CREATE TABLE) :
CREATE TABLE "books_publisher" (
"id" serial NOT NULL PRIMARY KEY,
"name" varchar(30) NOT NULL,
"address" varchar(50) NOT NULL,
"city" varchar(60) NOT NULL,
"state_province" varchar(30) NOT NULL,
"country" varchar(50) NOT NULL,
"website" varchar(200) NOT NULL
);
Évidemment, Django peut générer cette instruction CREATE TABLE automatiquement, comme nous le verrons dans un instant.
L’exception à la règle «une classe par table de la base de données» est le cas des relations plusieurs- à-plusieurs. Dans notre modèle d’exemple, Book possède un champ ManyToManyField appelé auteurs. Cela indique que ce livre a un ou plusieurs auteurs, mais la table Book de la base de données ne possède pas de colonne auteurs. À la place, Django crée une table additionnelle — une table jointure «plusieurs à plusieurs» — qui se charge de la correspondance entre livres et auteurs.
Pour une liste complète des types de champs et des options de syntaxe pour les modèles, consultez l’annexe B.
Finalement, notez que nous n’avons pas explicitement défini de clef primaire pour chacun de ces modèles. À moins de lui indiquer le contraire, Django attribue automatiquement à chaque modèle un champ clef primaire, entier appelé id. Chaque modèle nécessite une unique colonne de clef primaire.
Installation du modèle
Nous avons écrit le code ; à présent, créons les tables dans notre base de données. Afin d’y parvenir la première chose à faire est d’activer ces modèles dans notre projet Django. Nous faisons cela en ajoutant l’application books à la liste des applications installées, dans le fichier de configuration.
Éditez à nouveau le fichier settings.py, et cherchez le paramètre INSTALLED_APPS. INSTALLED_APPS indique à Django quelles sont les applications activées pour un projet donné. Par défaut, il recherche quelquechose du genre :
INSTALLED_APPS = (
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.sites',
)
Commentez temporairement toutes ces chaînes de caractères en ajoutant un caractère dièse (#) au début de la ligne. Les plus courantes sont par défaut incluses pour des raisons de commodité, nous les activerons et les détaillerons par la suite). Pendant que vous y êtes, modifiez les paramètres par défaut de MIDDLEWARE_CLASSES et de TEMPLATE_CONTEXT_PROCESSORS. Ils dépendent de certaines applications que nous venons de commenter. Ajoutez ensuite 'mysite.books' à la liste INSTALLED_APPS, afin d’obtenir la configuration suivante :
MIDDLEWARE_CLASSES = (
# 'django.middleware.common.CommonMiddleware',
# 'django.contrib.sessions.middleware.SessionMiddleware',
# 'django.contrib.auth.middleware.AuthenticationMiddleware',
# 'django.middleware.doc.XViewMiddleware',
)
TEMPLATE_CONTEXT_PROCESSORS = ()
#...
INSTALLED_APPS = (
#'django.contrib.auth',
#'django.contrib.contenttypes',
#'django.contrib.sessions',
#'django.contrib.sites',
'mysite.books',
)
(Puisqu’il s’agit ici d’un tuple composé d’un seul élément, n’oubliez pas la virgule finale. Ainsi, les auteurs de ce livre préfèrent mettre une virgule après chaque élément d’un tuple, sans se soucier de savoir si le tuple contient un élément unique. Ceci évite le problème d’oubli de virgules, et puis il n’est pas pénalisant d’utiliser cette virgule supplémentaire).
'mysite.books' se réfère à l’application books sur laquelle nous travaillons. Chaque appli enregistrée dans INSTALLED_APPS est représentée par son chemin Python complet — autrement dit, le chemin des paquets, séparés par des points, menant jusqu’au paquet de l’application.
À présent que l’application Django à été activée dans le fichier de configuration, nous pouvons créer les tables dans notre base de données. Tout d’abord, validons les modèles en lancant cette commande :
python manage.py validate
La commande validate verifie à la fois si la syntaxe et la logique de vos modèles sont corrects. Si c’est le cas, vous verrez le message 0 errors found. Dans le cas contraire, assurez vous que le code de votre modèle soit correcte. Le message d’erreur devrait vous fournir de précieuses informations sur ce qui ne vas pas dans votre code.
À chaque fois que vous pensez avoir des soucis avec vos modèles, lancez python manage.py validate. Elle remonte les problèmes de modèles courants.
Si vos modèles sont valides, lancez la commande suivante afin que Django génère les instructions CREATE TABLE pour vos modèles dans l’application books (avec une colorisation syntaxique disponible en prime si vous utilisez Unix).
python manage.py sqlall books
Dans cette commande, books est le nom de l’appli. C’est celui que vous avez spécifiez lorsque vous avez utilisez la commande manage.py startapp. Lorsque vous lancez la commande, vous devez voir apparaître quelque chose qui ressemble à ceci :
BEGIN;
CREATE TABLE "books_publisher" (
"id" serial NOT NULL PRIMARY KEY,
"name" varchar(30) NOT NULL,
"address" varchar(50) NOT NULL,
"city" varchar(60) NOT NULL,
"state_province" varchar(30) NOT NULL,
"country" varchar(50) NOT NULL,
"website" varchar(200) NOT NULL
);
CREATE TABLE "books_book" (
"id" serial NOT NULL PRIMARY KEY,
"title" varchar(100) NOT NULL,
"publisher_id" integer NOT NULL REFERENCES "books_publisher"
("id"),
"publication_date" date NOT NULL
);
CREATE TABLE "books_author" (
"id" serial NOT NULL PRIMARY KEY,
"salutation" varchar(10) NOT NULL,
"first_name" varchar(30) NOT NULL,
"last_name" varchar(40) NOT NULL,
"email" varchar(75) NOT NULL,
"headshot" varchar(100) NOT NULL
);
CREATE TABLE "books_book_authors" (
"id" serial NOT NULL PRIMARY KEY,
"book_id" integer NOT NULL REFERENCES "books_book" ("id"),
"author_id" integer NOT NULL REFERENCES "books_author"
("id"),
UNIQUE ("book_id", "author_id")
);
CREATE INDEX books_book_publisher_id ON "books_book"
("publisher_id");
COMMIT;
Remarquez les points suivants :
- Les noms de table sont automatiquement générés en combinant le nom de l’application (books) et le nom du modèle, en bas de casse (publisher, book, and author). Vous pouvez outrepasser cette fonctionalité, comme le détaille l’annexe B.
- Comme nous l’avons précisés plus tôt, Django ajoute automatiquement une clef primaire à chaque table. — le champs id. Vous pouvez aussi l’outrepasser.
- Par convention, Django ajoute "_id" au nom des champs de clef étrangère. Comme vous l’avez déjà deviné, vous pouvez aussi outrepasser cette fonctionnalité.
- La relation avec la clef étrangère est explicitement faite par une instruction REFERENCES.
- Ces instructions CREATE TABLE sont taillées pour la base de données que vous utilisez, aussi les champs spécifiques à une base de données, tels que auto_increment (MySQL), serial (PostgreSQL), ou integer primary key (SQLite) sont automatiquement pris en charge pour vous. La même chose s’applique pour les noms de colonne mis entre parenthèses (par exemple, en utilisant des guillemets simples ou doubles). Cet exemple suit la syntaxe de PostgreSQL.
La commande sqlall ne crée pas réellement les tables et n’atteint même pas votre base de données — elle affiche juste sa sortie à l’écran afin que vous puissiez voir le SQL que Django exécutera si vous le lui demandez. Si vous le voulez, vous pouvez copiez puis coller ce SQL dans votre client de base de données, ou bien utiliser les tubes Unix pour lui transmettre directement. Cependant, Django fournit une méthode aisée pour soumettre le SQL à la base de données. Lancez la commande syncdb, comme ceci :
python manage.py syncdb
Vous verrez quelquechose comme ceci :
Creating table books_publisher Creating table books_book Creating table books_author Installing index for books.Book model
La commande syncdb est une simple «synchro» de vos modèles vers votre base de données. Elle consulte tous les modèles dans chaque application présente dans votre paramètre INSTALLED_APPS, vérifie l’existence de la table appropriée dans votre base de données, et crée les tables si elles n’existent pas déjà. Notez que syncdb ne synchronise pas les changements ou les suppressions de modèles. Si vous apportez un changement à un modèle, ou si vous l’effacez, et que vous désirez ensuite mettre à jour votre base de données, syncdb n’y pourra rien. (Des détails sur le sujet viendrons ensuite).
Si vous lancez python manage.py syncdb à nouveau, rien n’arrivera, puisque vous n’avez pas ajouté de modèles à l’appli books ni ajouté une quelconque application à INSTALLED_APPS. Ainsi, vous pouvez lancer python manage.py syncdb sans risques — rien ne sera cassé.
Si vous êtes intéressé, prenez le temps de plonger dans la console cliente de votre serveur de base de données et regardez les tables que Django y a crée. Vous pouvez utiliser manuellement la console de votre client (par exemple, psql pour PostgreSQL) ou bien lancer la commande python manage.py dbshell, qui s’occupera de démarrer pour vous la ligne de commande du client, selon votre paramètre DATABASE_SERVER. La dernière méthode reste toujours la plus pratique.
Accès basique aux données
Une fois le modèle crée, Django fournit automatiquement une API Python de haut niveau pour travailler avec ceux-ci. Testez par vous même en lancant la commande python manage.py shell puis en saisissant le code suivant :
>>> from books.models import Publisher >>> p1 = Publisher(name='Addison-Wesley', address='75 Arlington Street', ... city='Boston', state_province='MA', country='U.S.A.', ... website='http://www.apress.com/') >>> p1.save() >>> p2 = Publisher(name="O'Reilly", address='10 Fawcett St.', ... city='Cambridge', state_province='MA', country='U.S.A.', ... website='http://www.oreilly.com/') >>> p2.save() >>> publisher_list = Publisher.objects.all() >>> publisher_list [<Publisher: Publisher object>, <Publisher: Publisher object>]
Ces quelques lignes accomplissent pas mal de choses. Voici les détails :
- Pour créer un objet, importez simplement la classe du modèle approprié et instanciez la en lui fournissant les valeurs pour chaque champ.
- Pour sauvegarder l’objet dans la base de données, appelez la méthode save() sur l’objet. En coulisses, Django exécute l’instruction SQL INSERT.
- Pour récupérer les objets depuis la base de données, utilisez l’attribut Publisher.objects. Pour obtenir la liste de tous les objets Publisher de la base de données avec l’instruction Publisher.objects.all(). En coulisse, Django execute une instruction SQL SELECT.
Naturellement, vous pouvez faire beaucoup avec l’API de base de données de Django — mais d’abord, occupons nous des quelques petits soucis.
Ajouter la représentation sous forme de chaîne aux modèles
Lorsque nous avons affiché la liste des éditeurs, tout ce que nous avons obtenu à été cet affichage sans grand intérêt qui rendait difficile la différentiation des objets Publisher.
[<Publisher: Publisher object>, <Publisher: Publisher object>]
Nous pouvons arranger cela simplement en ajoutant une méthode appelée __str__() à nos objets Publisher. Une méthode __str__() indique à Python comment afficher la représentation d’un objet sous forme de «chaîne de caractères». Vous pouvez voir ceci en action en ajoutant une méthode __str__() aux trois modèles :
from django.db import models
class Publisher(models.Model):
name = models.CharField(max_length=30)
address = models.CharField(max_length=50)
city = models.CharField(max_length=60)
state_province = models.CharField(max_length=30)
country = models.CharField(max_length=50)
website = models.URLField()
**def __str__(self):**
**return self.name**
class Author(models.Model):
salutation = models.CharField(max_length=10)
first_name = models.CharField(max_length=30)
last_name = models.CharField(max_length=40)
email = models.EmailField()
headshot = models.ImageField(upload_to='/tmp')
**def __str__(self):**
**return '%s %s' % (self.first_name,
self.last_name)**
class Book(models.Model):
title = models.CharField(max_length=100)
authors = models.ManyToManyField(Author)
publisher = models.ForeignKey(Publisher)
publication_date = models.DateField()
**def __str__(self):**
**return self.title**
Comme vous pouvez le voir, une méthode __str__() peut faire tout ce qui est nécessaire pour retourner une représentation textuelle. Ici, les méthodes __str__() des objets Publisher et Book retournent respectivement le nom et le titre de l’objet, alors que __str__() pour Author est un peu plus complexe ? Elle assemble les champs first_name et last_name. Le seul impératif pour __str__() est quelle retourne une chaîne de caractère. Si __str__() ne retourne pas de chaîne de caractères — admettons quelle retourne un entier — alors Python lévera une TypeError avec une message ressemblant à "__str__ returned non-string".
Pour que les modifications soient effectives, sortez de la console Python et relancez là à nouveau grâce à la commande python manage.py shell. (C’est la manière la plus simple de prendre en compte les changements apportés au code). À présent la liste des objets Publisher est plus facile à comprendre.
>>> from books.models import Publisher >>> publisher_list = Publisher.objects.all() >>> publisher_list [<Publisher: Addison-Wesley>, <Publisher: O'Reilly>]
Assurez vous que tous les modèles que vous avez défini possède une méthode __str__() — pas seulement pour votre propre confort lorsque vous utilisez la console intéractive, mais aussi parceque Django utilise la sortie de __str__() à divers endroits lorsque il à besoin d’afficher des objets.
Finallement, notez que __str__() est un bon d’exemple d’ajout de fonctionnalités aux modèles. Un modèle Django décrit plus que le schéma des tables d’un objet dans la base de données ; il décrit aussi toutes les fonctionnalités qu’un objet sait utiliser. __str__() est un exemple d’une telle fonctionnalité — un modèle sait comment s’afficher lui-même.
Insérer et mettre à jour des données
Vous avez déjà vu cela en action : pour insérer une colonne dans une base de données, commencez par créer une instance de votre modèle en utilisant les arguments mot-clef, comme ceci :
>>> p = Publisher(name='Apress', ... address='2855 Telegraph Ave.', ... city='Berkeley', ... state_province='CA', ... country='U.S.A.', ... website='http://www.apress.com/')
Le fait d’instancier une classe de modèle n’affecte pas la base de données.
Pour sauvegarder l’enregistrement dans la base de données (c’est à dire, pour effectuer l’instruction SQL INSERT), appellé la méthode save() des objets :
>>> p.save()
En SQL, ceci peut être facilement traduit par :
INSERT INTO book_publisher
(name, address, city, state_province, country, website)
VALUES
('Apress', '2855 Telegraph Ave.', 'Berkeley', 'CA',
'U.S.A.', 'http://www.apress.com/');
Puisque le modèle Publisher utilise une clef primaire autoincrémentée, l’appel initial à save() effectue une chose supplémentaire : il calcul les valeurs de clef primaire pour l’enregistrement donné et le fixe à la valeur de l’attribut id sur l’instance :
>>> p.id 52 # this will differ based on your own data
Des appels successifs à save() sauvegarderons instantanément l’enregistrement, sans créer de nouvel enregistrement (c’est à dire, faire une instruction SQL UPDATE à la place d’un INSERT) :
>>> p.name = 'Apress Publishing' >>> p.save()
L’instruction précédente save() revient à faire le SQL suivant :
UPDATE book_publisher SET
name = 'Apress Publishing',
address = '2855 Telegraph Ave.',
city = 'Berkeley',
state_province = 'CA',
country = 'U.S.A.',
website = 'http://www.apress.com'
WHERE id = 52;
Selectionner les objets
Créer et mettre à jour les données est certainement amusant, mais inutile sans un moyen de parcourir ces données. Nous avons déjà vu une façon de chercher toutes les données relatives à un modèle donné :
>>> Publisher.objects.all() [<Publisher: Addison-Wesley>, <Publisher: O'Reilly>, <Publisher: Apress Publishing>]
Ce qui se traduit en SQL par :
SELECT
id, name, address, city, state_province, country, website
FROM book_publisher;
Note
Remarquez que Django n’utilise pas SELECT * lorsqu’il recherche des données, mais qu’il liste explicitement tous les champs. C’est un choix d’architecture : dans certaines circonstances SELECT * peut être moins rapide, et surtout (plus important) le fait de lister les champs est plus proche d’une des philosophie du Zen de Python : «Explicite vaut mieux qu’implicite».
Pour en savoir plus au sujet du Zen de Python, tapez import this à l’invite d’une console Python.
Regardons d’un peu plus près chacun des composants de cette ligne Publisher.objects.all() :
Pour commencer, nous avons le modèle que nous avons défini, Publisher. Pas de surprise ici : lorsque vous recherchez des données, vous utilisez le modèle correspondant à ces données.
Ensuite, nous avons ces histoires d’objects. Techniquement, il s’agit d’un manager. Les managers sont détaillés à l’annexe B. Pour le moment, tout ce que vous devez savoir est que les managers s’occupent, au niveau des tables, des toutes les opérations sur les données y compris, et c’est le plus important, la recherche.
Tous les modèles obtiennent automatiquement un manager d’objects ; vous l’utiliserez à chaque fois que vous rechercherez des instances de modèles.
Enfin, nous avons all(). C’est une méthode appliquée au manager d’objects qui retourne toutes les lignes de la base de données. Bien que cet objet ressemble à une liste, c’est en fait un QuerySet — un objet qui représente un jeu de lignes de la base de données. L’annexe C traite les QuerySets en détail. Pour le reste de ce chapitre, nous les utiliserons comme les listes qu’elles émulent.
Toute requête dans la base de donnée suivra ce patron généraliste — nous appellerons des méthodes sur le manager attaché au modèle sur lequel nous voulons effectuer la requête.
Filtrer les données
Bien que récupérer tous les objets soit certainement utile, la plupart du temps nous allons vouloir avoir affaire à une sélection de données. Nous faisons cela grâce à la méthode filter() :
>>> Publisher.objects.filter(name="Apress Publishing") [<Publisher: Apress Publishing>]
filter() prend des mots-clefs pour arguments qui seront traduits en clauses SQL WHERE appropriées. L’exemple précédent sera traduit en quelque chose du genre :
SELECT
id, name, address, city, state_province, country, website
FROM book_publisher
WHERE name = 'Apress Publishing';
Vous pouvez passer de multiples arguments à``filter()`` pour préciser les choses :
>>> Publisher.objects.filter(country="U.S.A.", state_province="CA") [<Publisher: Apress Publishing>]
Ces arguments multiples seront traduits en clauses SQL AND. Ce bout de code d’exemple sera traduit en :
SELECT
id, name, address, city, state_province, country, website
FROM book_publisher
WHERE country = 'U.S.A.' AND state_province = 'CA';
Notez que par défaut la requête utilise l’opérateur SQL = pour faire des requêtes à la correspondance exacte. D’autres types de requête sont disponibles :
>>> Publisher.objects.filter(name__contains="press") [<Publisher: Apress Publishing>]
Il s’agit d’un double tiret bas entre name et contains. Tout comme Python, Django utilise le double tiret bas pour signaler que quelque chose de «magique» se produit à cet endroit, la partie __contains étant traduite par Django en instruction SQL LIKE :
SELECT
id, name, address, city, state_province, country, website
FROM book_publisher
WHERE name LIKE '%press%';
Beaucoup d’autres types de requêtes sont disponibles, y compris icontains (LIKE insensible à la casse), startswith, endswith et range (requête SQL BETWEEN). L’annexe C décrit tous ces types en détail.
Récupérer des objets seuls
Parfois vous désirez récupérer un unique objet. C’est ce pour quoi est faite la méthode get() :
>>> Publisher.objects.get(name="Apress Publishing") <Publisher: Apress Publishing>
À la place d’une liste (où plutôt, un QuerySet), un unique objet est retourné. À cause de cela , une requête retournant plusieurs objets lévera une exception :
>>> Publisher.objects.get(country="U.S.A.")
Traceback (most recent call last):
...
AssertionError: get() returned more than one Publisher -- it returned
2!
Une requête qui ne retourne pas d’objet lève aussi une exception :
>>> Publisher.objects.get(name="Penguin")
Traceback (most recent call last):
...
DoesNotExist: Publisher matching query does not exist.
Ordonner les données
En jouant avec les exemples précédents, vous avez peut être découvert que les objets sont retournés dans un ordre aléatoire. Vous n’hallucinez pas ; jusqu’ici nous n’avons pas indiqué à la base de données comment ordonner ses résultats, nous avons donc obtenu les données dans un ordre arbitrairement choisi par la base de données.
C’est évidemment un peu idiot ; nous ne voudrions pas d’une page web listant les éditeurs dans un ordre aléatoire. Donc, en pratique, nous allons probablement vouloir utiliser order_by() pour arranger nos données en une liste utile :
>>> Publisher.objects.order_by("name")
[<Publisher: Apress Publishing>, <Publisher: Addison-Wesley>,
<Publisher: O'Reilly>]
Ceci ne semble pas très différent de l’exemple all() précédent, mais le SQL inclut à présent un ordonnancement spécifique :
SELECT
id, name, address, city, state_province, country, website
FROM book_publisher
ORDER BY name;
Nous pouvons organiser les choses à partir du champ que nous voulons :
>>> Publisher.objects.order_by("address")
[<Publisher: O'Reilly>, <Publisher: Apress Publishing>, <Publisher:
Addison-Wesley>]
>>> Publisher.objects.order_by("state_province")
[<Publisher: Apress Publishing>, <Publisher: Addison-Wesley>,
<Publisher: O'Reilly>]
et même par des champs multiples :
>>> Publisher.objects.order_by("state_provice", "address")
[<Publisher: Apress Publishing>, <Publisher: O'Reilly>, <Publisher:
Addison-Wesley>]
Nous pouvons aussi préciser l’inversion de l’ordre en préfixant le nom du champ avec unMyModel.objects.all()[:5]``-`` (il s’agit du signe moins) :
>>> Publisher.objects.order_by("-name")
[<Publisher: O'Reilly>, <Publisher: Apress Publishing>, <Publisher:
Addison-Wesley>]
Bien que cette flexibilité soit utile, l’utilisation systématique de order_by() peut s’avérer répétitive. La plupart du temps vous aurez un champ particulier à ordonner. Dans ces cas, Django vous permet d’associer un classement par défaut à votre modèle :
class Publisher(models.Model):
name = models.CharField(max_length=30)
address = models.CharField(max_length=50)
city = models.CharField(max_length=60)
state_province = models.CharField(max_length=30)
country = models.CharField(max_length=50)
website = models.URLField()
def __str__(self):
return self.name
**class Meta:**
**ordering = ["name"]**
La partie ordering = ["name"] indique à Django qu’à moins de préciser explicitement un order_by(), tous les éditeurs devront être classés par nom.
C’est quoi ce «Meta truc» ?
Django utilise cette class Meta comme un endroit où préciser les métadonnées additionnelles d’un modèle. C’est totalement optionnel, mais cela permet de faire des choses très utiles. Consultez l’annexe B pour les options que vous pouvez placer sous Meta.
Chaîner les requêtes
Vous avez vu comment vous pouviez filtrer les données, et comment vous pouviez les classer. Parfois, évidemment, vous allez vouloir faire les deux. Dans ce cas, vous «chaîner» simplement les requêtes :
>>> Publisher.objects.filter(country="U.S.A.").order_by("-name")
[<Publisher: O'Reilly>, <Publisher: Apress Publishing>, <Publisher:
Addison-Wesley>]
Comme vous pouviez vous y attendre, ceci se traduit par une requête SQL avec WHERE et ORDER BY :
SELECT
id, name, address, city, state_province, country, website
FROM book_publisher
WHERE country = 'U.S.A'
ORDER BY name DESC;
Vous pouvez chaîner autant de requêtes que vous le désirez, il n’y a pas de limite.
Découper les données
Un autre besoin courant est de faire une recherche uniquement sur un nombre déterminé de lignes. Imaginez que vous ayez des milliers d’éditeurs dans votre base de données, mais que vous ne vouliez afficher que le premier. Vous pouvez faire cela en utilisant la syntaxe standard Python de découpe de listes (slicing) :
>>> Publisher.objects.all()[0] <Publisher: Addison-Wesley>
Ceci se traduit en :
SELECT
id, name, address, city, state_province, country, website
FROM book_publisher
ORDER BY name
LIMIT 1;
Et après ?
Nous n’avons fait qu’effleurer la surface de l’utilisation des modèles, mais vous en savez à présent suffisament pour comprendre tous les exemples du reste du livre. Lorsque vous serez prêt à apprendre tous les détails de la recherche des objets, consultez l’annexe C.
Supprimer des objets
Pour supprimer des objets, appeler simplement la méthode delete() sur ceux-ci :
>>> p = Publisher.objects.get(name="Addison-Wesley") >>> p.delete() >>> Publisher.objects.all() [<Publisher: Apress Publishing>, <Publisher: O'Reilly>]
Vous pouvez aussi supprimer des objets en masse en appelant delete() sur le résultat d’une recherche :
>>> publishers = Publisher.objects.all() >>> publishers.delete() >>> Publisher.objects.all() []
Note
Les suppressions sont permanentes, soyez donc attentifs ! En fait, c’est généralement une bonne idée d’éviter la suppression des objets, à moins que ce ne soit réellement nécessaire — les bases de données relationnelles ne gère pas si bien l’«annulation de la dernière action», et la restauration à partir des sauvegardes reste pénible.
C’est souvent une bonne idée d’ajouter des drapeaux «actifs» à vos modèles de données. Vous pouvez ainsi faire des recherches uniquement sur les objets «actifs», et simplement fixer le champ actif à False au lieu d’effacer l’objet. Ensuite, si vous constatez que vous faites des erreurs, vous pouvez simplement inverser le drapeau.
Apporter des modifications au schéma de la base de données
Lorsque nous avons introduit la commande syncdb plus avant dans ce chapitre, nous avons noté que syncdb créait principalement les tables qui n’existaient pas encore dans votre base de données — elle ne synchronise pas les modifications de modèles ou n’effectue pas de suppression des modèles, vous devrez effectuer manuellement les changements dans votre base de données. Cette partie explique comment faire cela.
Lorsque vous devez faire des modifications de schéma, il est important de garder à l’esprit la façon dont la couche base de données de Django fonctionne :
- Django se plaindra bruyamment si un modèle contient un champ qui n’a pas encore été créé dans les tables de la base de données. Ceci causera une erreur lors de la première requête sur cette table avec l’API de base de données de Django (autrement dit, ceci se produira lors de l’execution, et pas lors de la compilation).
- Django ne se soucie pas de savoir si la table contient des colonnes qui ne sont pas définies dans le modèle.
- Django ne se soucie pas de savoir si la base de données contient une table qui ne soit pas représentée par un modèle.
Apporter des modifications au schéma est une affaire de changement de diverses pièces — le code Python et la base de données elle même — dans l’ordre précis.
Ajouter des champs
Lorsque vous ajoutez un champ à une table, un modèle, dans un environnement de production, l’astuce est de tirer partie du fait que Django ne se préoccupe pas de savoir si une table contient des colonnes qui ne soient pas définies dans le modèle. La stratégie est d’ajouter la colonne dans la base de données, puis de mettre à jour le modèle Django afin d’y inclure le nouveau champ.
Cependant, apparaît ici un problème de type «qui de la poule ou de l’oeuf ?», puisqu’afin de connaitre la façon dont la nouvelle colonne de la base de données doit être exprimé en SQL, vous devez regarder la sortie de la commande manage.py sqlall, qui à besoin quant à elle de l’existence du champ dans le modèle. (Notez que vous n’avez pas nécessairement besoin de créer une colonne avec exactement le même SQL que celui qu’utiliserait Django, mais c’est une bonne idée de faire ainsi, simplement afin de s’assurer que tout soit synchro).
La solution à ce problème de poule et d’oeuf est d’utiliser un environnement de développement à la place de faire les changements sur un serveur de production. (Vous êtes en train d’utiliser un environnement de test/développement, n’est-ce pas ?). Voici les différentes étapes à suivre :
Pour commencer, assurez vous de suivre ces étapes dans un environnement de développement (c’est à dire, pas sur le serveur de production) :
- Ajouter le champ à votre modèle.
- Lancez manage.py sqlall [votreappli] pour voir la nouvelle instruction SQL CREATE TABLE pour le modèle. Notez la définition de colonne pour le nouveau champ.
- Démarrez la console interactive de votre base de données (par exemple, psql ou mysql, ou encore en utilisant manage.py dbshell). Éxécutez une instruction ALTER TABLE qui ajoutera la nouvelle colonne.
- (Optionel.) Lancez la console interactive de Python avec manage.py shell et vérifiez que le nouveau champ à été correctement ajouté en important le modèle et en sélectionnant dans la table (par exemple, MyModel.objects.all()[:5] ).
Ensuite, sur le serveur de production, appliquez ces étapes :
- Démarrez la console de votre base de données.
- Exécutez l’instruction ALTER TABLE que vous avez utilisé lors de l’étape 3 sur votre environnement de développement.
- Ajoutez le champ à votre modèle. Si vous utilisez un gestionnaire de version et que vous avez soumis vos changements dans l’environnement de développement lors de la première étape, il est temps à présent de mettre à jour le code (par exemple, svn update, avec Subversion) sur votre serveur de production.
- Redémarrez le serveur web pour prendre en compte les modifications apportées au code.
Par exemple, examinons ce que nous aurions fait si nous avions ajoutés un champ num_pages au modèle Book décrit plut tôt dans ce chapitre. Tout d’abord, nous aurions altéré le modèle dans notre environnement de développement pour qu’il ressemble à ceci :
class Book(models.Model):
title = models.CharField(max_length=100)
authors = models.ManyToManyField(Author)
publisher = models.ForeignKey(Publisher)
publication_date = models.DateField()
**num_pages = models.IntegerField(blank=True, null=True)**
def __str__(self):
return self.title
(Note : Lisez l’encart «Ajouter des colonnes NOT NULL» pour des détails importants sur la raison d’inclure blank=True et null=True).
Ensuite nous aurions lancé la commande manage.py sqlall books pour voir l’instruciton CREATE TABLE. Nous aurions obtenu quelquechose du genre :
CREATE TABLE "books_book" (
"id" serial NOT NULL PRIMARY KEY,
"title" varchar(100) NOT NULL,
"publisher_id" integer NOT NULL REFERENCES "books_publisher"
("id"),
"publication_date" date NOT NULL,
"num_pages" integer NULL
);
La nouvelle colonne se présentant ainsi :
"num_pages" integer NULL
Ensuite, nous aurions démarré la console interactive de notre base de données de développement en tapant psql (pour PostgreSQL), puis nous aurions executé les instructions suivantes :
ALTER TABLE books_book ADD COLUMN num_pages integer;
Ajouter des colonnes NOT NULL
Il y a là une subtilité qui mérite attention. Lorsque nous avons ajouté le champ num_pages à notre modèle, nous avons inclus les options blank=True et null=True. Nous avons fait ainsi parce qu’une colonne de données contiendra des valeurs NULL lors de son initialisation.
Cependant, il est aussi possible d’ajouter des colonnes qui n’admettent pas les valeurs NULL. Pour faire cela, vous devez créer les colonnes comme étant NULL, puis remplir les valeurs de colonnes en utilisant un/des jeu(x) par défaut, pour ensuite altérer la colonne pour fixer le modificateur Par exemple :
BEGIN; ALTER TABLE books_book ADD COLUMN num_pages integer; UPDATE books_book SET num_pages=0; ALTER TABLE books_book ALTER COLUMN num_pages SET NOT NULL; COMMIT;
Si vous suivez cette voie, souvenez-vous que vous devez laisser blank=True et null=True dans vos modèles.
Après l’instruction ALTER TABLE, nous devons vérifier que la modification a correctement fonctionné en démarrant la console Python pour y saisir ce code :
>>> from mysite.books.models import Book >>> Book.objects.all()[:5]
Si ce code s’avérait sans erreur, nous aurions à basculer vers notre serveur de production et à exécuter l’instruction ALTER TABLE sur la base de données de production. Ensuite, nous aurions eu à mettre à jour le modèle dans l’environnement de production et à redémarrer le serveur web.
Supprimer des champs
Supprimer un champ d’un modèle est beaucoup plus aisé que d’en ajouter un. Pour supprimer un champ, suivez simplement ces étapes :
Supprimez le champ de votre modèle et redémarrez le serveur web.
Supprimez la colonne de votre base de données, en utilisant la commande :
ALTER TABLE books_book DROP COLUMN num_pages;
Supprimer les champs Plusieurs-à-plusieurs
Puisque les champs Plusieurs-à-plusieurs diffèrent des champs normaux, la procédure de suppression est différente :
Supprimez le champ Plusieurs-à-plusieurs (ManyToManyField) de votre modèle et redémarrez le serveur web.
Supprimez la table Plusieurs-à-plusieurs (many-to-many) de votre base de données, en utilisant une commande semblable à :
DROP TABLE books_books_publishers;
Supprimer des modèles
Supprimer un modèle est aussi facile que de supprimer un champ. Pour supprimer un modèle, suivez simplement ces étapes :
Supprimer le modèle de votre fichier models.py et redémarrer le serveur web.
Supprimer la table de votre base de données, en utilisant une commande telle que :
DROP TABLE books_book;
En ensuite ?
Une fois que vous avez défini vos modèles, la prochaine étape est de garnir votre base de données avec des données. Vous pouvez aussi ajouter des données déjà existantes, auquel cas le chapitre 16 vous donnera des conseils sur l’intégration de base de données existantes. Vous pouvez aussi compter sur les utilisateurs pour fournir les données, auquel cas le chapitre 7 vous enseignera comment gérer les formulaires de données soumis par des utilisateurs.
Dans certains cas, vous ou votre équipe peuvent avoir besoin d’entrer des données manuellement, auquel cas il sera utile d’avoir une interface web pour saisir et gérer les données. Le chapitre suivant couvre l’interface d’administration de Django, qui existe précisément pour cette raison.
Dernière modification: 2010-03-08 19:26:28.438120