Développez votre site web avec le framework Django
Django « Le framework web pour les perfectionnistes sous pression »
En quelques années, les sites web n'ont cessé d'évoluer. Ils requièrent désormais des développements longs et acharnés, sans oublier le fait que ceux-ci peuvent parfois devenir très complexes et se mesurer en milliers de lignes de code. Aujourd'hui, la simple page web ne suffit plus, et que ce soit dans un cadre professionnel ou personnel, les attentes sont de plus en plus lourdes.
C'est de ce constat qu'est né Django : proposer un développement plus efficace et plus rapide d'une application dynamique web, tout en conservant la qualité ! Ce cours vous apprendra à construire des sites web complexes et élégants, et en un temps record.
Ce cours porte sur la version 1.5 de Django, et n'assure nullement que toutes les méthodes présentées fonctionneront forcément sur des versions antérieures ou postérieures.
Si vous lisez ceci, c'est que vous avez décidé de vous lancer dans l'apprentissage de Django. Avant de commencer, des présentations s'imposent : Django est un framework web écrit en Python, qui se veut complet tout en facilitant la création d'applications web riches.
Avant de commencer à écrire du code, nous allons tout d'abord voir dans ce chapitre ce qu'est un framework en général, et plus particulièrement ce qu'est Django. Dans un second temps, nous verrons comment l'installer sur votre machine, pour pouvoir commencer à travailler ! Est-il utile de vous rappeler encore ici qu'il est nécessaire d'avoir les bases en Python pour pouvoir commencer ce cours ?
Un framework est un ensemble d'outils qui simplifie le travail d'un développeur. Traduit littéralement de l'anglais, un framework est un « cadre de travail ». Il apporte les bases communes à la majorité des programmes ou des sites web. Celles-ci étant souvent identiques (le fonctionnement d'un espace membres est commun à une très grande majorité de sites web de nos jours), un développeur peut les réutiliser simplement et se concentrer sur les particularités de son projet.
Il s'agit donc d'un ensemble de bibliothèques coordonnées, qui permettent à un développeur d'éviter de réécrire plusieurs fois une même fonctionnalité, et donc d'éviter de réinventer constamment la roue. Inutile de dire que le gain en énergie et en temps est considérable !
Quels sont les avantages d'un framework ?
Un framework instaure en quelque sorte sa « ligne de conduite ». Tous les développeurs Django codent de façon assez homogène (leurs codes ont le même fonctionnement, les mêmes principes). De ce fait, lorsqu'un développeur rejoint un projet utilisant un framework qu'il connaît déjà, il comprendra très vite ce projet et pourra se mettre rapidement au travail.
Le fait que chaque framework possède une structure commune pour tous ses projets a une conséquence tout aussi intéressante : en utilisant un framework, votre code sera le plus souvent déjà organisé, propre et facilement réutilisable par autrui.
Voici d'ailleurs un grand défi des frameworks : bien que ceux-ci doivent instaurer une structure commune, ils doivent aussi être souples et modulables, afin de pouvoir être utilisés pour une grande variété de projets, du plus banal au plus exotique. Autrement, leur intérêt serait grandement limité !
Quels sont les désavantages d'un framework ?
Honnêtement, il n'existe pas vraiment de désavantages à utiliser un framework. Il faut bien évidemment prendre du temps à apprendre à en manier un, mais ce temps d'apprentissage est largement récupéré par la suite, vu la vitesse de développement qui peut parfois être décuplée. Nous pourrions éventuellement dire que certains frameworks sont parfois un peu trop lourds, mais il incombe à son utilisateur de choisir le bon framework, adapté à ses besoins.
Fatigué(e) de lire sur un écran ? Découvrez ce cours en livre.
Django est donc un framework Python destiné au web. Ce n'est pas le seul dans sa catégorie, nous pouvons compter d'autres frameworks Python du même genre comme web2py, TurboGears, CherryPy ou Zope. Il a cependant le mérite d'être le plus exhaustif, d'automatiser un bon nombre de choses et de disposer d'une très grande communauté.
Le logo de Django
Django est né en 2003 dans une agence de presse qui devait développer des sites web complets dans des laps de temps très courts (d'où l'idée du framework). En 2005, l'agence de presse Lawrence Journal-World décide de publier Django au grand public, le jugeant assez mature pour être réutilisé n'importe où. Trois ans plus tard, la fondation Django Software est créée par les fondateurs du framework afin de pouvoir maintenir celui-ci et la communauté très active qui l'entoure.
Aujourd'hui, Django est devenu très populaire et est utilisé par des sociétés du monde entier, telles qu'Instagram, Pinterest, et même la NASA !
Logos d'Instagram, de la NASA et de Pinterest
Pourquoi ce succès ?
Si Django est devenu très populaire, c'est notamment grâce à sa philosophie, qui a su séduire de nombreux développeurs et chefs de projets. En effet, le framework prône le principe du « Don't repeat yourself », c'est-à-dire en français « Ne vous répétez pas », et permet le développement rapide de meilleures et plus performantes applications web, tout en conservant un code élégant.
Django a pu appliquer sa philosophie de plusieurs manières. Par exemple, l'administration d'un site sera automatiquement générée, et celle-ci est très facilement adaptable. L'interaction avec une base de données se fait via un ensemble d'outils spécialisés et très pratiques. Il est donc inutile de perdre son temps à écrire directement des requêtes destinées à la base de données, car Django le fait automatiquement. De plus, d'autres bibliothèques complètes et bien pensées sont disponibles, comme un espace membres, ou une bibliothèque permettant la traduction de votre application web en plusieurs langues.
Une communauté à votre service
Évidemment, Django dispose des avantages de tous les frameworks en général. Il est soutenu par une communauté active et expérimentée, qui publie régulièrement de nouvelles versions du framework avec de nouvelles fonctionnalités, des corrections de bugs, etc.
Encore un point, et non des moindres, la communauté autour de Django a rédigé au fil des années une documentation très complète sur docs.djangoproject.com. Bien que celle-ci soit en anglais, elle reste très accessible pour des francophones. Nous ne pouvons que vous conseiller de la lire en parallèle de ce cours si vous voulez approfondir un certain sujet ou si certaines zones d'ombre persistent.
Enfin, pour gagner encore plus de temps, les utilisateurs de Django ont généralement l'esprit open source et fournissent une liste de snippets, des portions de code réutilisables par n'importe qui. Un site est dédié à ces snippets. Si vous devez vous attaquer à une grosse application ou à une portion de code particulièrement difficile, n'hésitez pas à aller chercher dans les snippets, vous y trouverez souvent votre bonheur !
Fatigué(e) de lire sur un écran ? Découvrez ce cours en livre.
Maintenant que nous avons vu les avantages qu'apporte Django, il est temps de passer à son installation. Tout d'abord, assurez-vous que vous disposez bien d'une version de Python supérieure ou égale à la 2.6.5 pour la branche 2.6.x ou à la 2.7.3 pour la branche 2.7.x et supérieure. Pour plus d'informations à ce sujet, vous pouvez vous reporter au cours sur le Python du Site du Zéro.
Il est également plus prudent de supprimer toutes les anciennes installations de Django, si vous en avez déjà. Il peut y avoir des conflits entre les versions, notamment lors de la gestion des projets. Il est essentiel de n'avoir que Django 1.5 sur votre machine, à part si vous avez déjà des applications en production sur des versions antérieures. Dans ce cas, il est conseillé soit de porter toutes vos applications pour Django 1.5, soit d'exécuter vos deux projets avec deux versions de Django bien indépendantes.
Linux et Mac OS
Sous Linux et Mac OS, l'installation de Django peut s'effectuer de deux manières différentes, soit en utilisant le gestionnaire de paquets de votre distribution (ou MacPorts pour Mac OS), soit en installant Django manuellement, via une archive officielle. Nous ne couvrirons pas la première solution, celle-ci dépendant beaucoup trop de votre distribution. Si toutefois vous choisissez cette solution, faites attention à la version de Django disponible dans les dépôts. Il se peut que ce ne soit pas toujours la dernière version qui soit disponible, donc pas à jour et incompatible avec ce cours.
Si vous ne passez pas par les dépôts, le plus simple reste de télécharger une archive. Il suffit ensuite de l'extraire et de l'installer, en effectuant les commandes suivantes dans une console :
tar xzvf Django-1.5.tar.gz
cd Django-1.5
sudo python setup.py install
Windows
Contrairement aux environnements UNIX, l'installation de Django sous Windows requiert quelques manipulations supplémentaires. Téléchargez l'archive de Django et extrayez-la. Avant de continuer, nous allons devoir modifier quelques variables d'environnement, afin de permettre l'installation du framework. Pour cela (sous Windows 7) :
Rendez-vous dans les informations générales du système (via le raccourci Windows + Pause) ;
Cliquez sur Paramètres système avancés, dans le menu de gauche ;
Une fois la fenêtre ouverte, cliquez sur Variables d'environnement ;
Cherchez la variable système (deuxième liste) Path et ajoutez ceci en fin de ligne (faites attention à votre version de Python) : ;C:\Python27\;C:\Python27\Lib\site-packages\django\bin\. Respectez bien le point-virgule permettant de séparer le répertoire de ceux déjà présents, comme indiqué à la figure suivante.
Édition du Path sous Windows 7
Validez, puis quittez. Nous pouvons désormais installer Django via la console Windows (Windows + R puis la commande cmd) :
cd C:\Users\<nom_d'utilisateur>\Downloads\django1.5 # A adapter à votre répertoire de téléchargement
python setup.py install
Les fichiers sont ensuite copiés dans votre dossier d'installation Python (ici C:\Python27).
Vérification de l'installation
Dès que vous avez terminé l'installation de Django, lancez une nouvelle console Windows, puis lancez l'interpréteur Python (via la commande python) et tapez les deux lignes suivantes :
Si vous obtenez également 1.5 comme réponse, félicitations, vous avez correctement installé Django !
Dans la suite de ce cours, nous utiliserons SQLite, qui est simple et déjà inclus dans les bibliothèques de base de Python. Si vous souhaitez utiliser un autre système de gestion de base de données, n'oubliez pas d'installer les outils nécessaires (dépendances, packages, etc.).
En résumé
Un framework (cadre de travail en français) est un ensemble d'outils qui simplifie le travail d'un développeur.
Un framework est destiné à des développeurs, et non à des novices. Un framework nécessite un temps d'apprentissage avant de pouvoir être pleinement utilisé.
Django est un framework web pour le langage Python très populaire, très utilisé par les entreprises dans le monde : Mozilla, Instagram ou encore la NASA l'ont adopté !
Ce cours traite de la version 1.5, sortie en février 2013. Nous ne garantissons pas que les exemples donnés soient compatibles avec des versions antérieures et postérieures.
Fatigué(e) de lire sur un écran ? Découvrez ce cours en livre.
Attaquons-nous au vif du sujet ! Dans ce chapitre, théorique mais fondamental, nous allons voir comment sont construits la plupart des frameworks grâce au modèle MVC, nous aborderons ensuite les spécificités du fonctionnement de Django et comment les éléments d'une application classique Django s'articulent autour du modèle MVT, que nous introduirons également. En dernier lieu, nous expliquerons le système de projets et d'applications, propre à Django, qui permet une séparation nette, propre et précise du code.
Au terme de ce chapitre, vous aurez une vue globale sur le fonctionnement de Django, ce qui vous sera grandement utile lorsque vous commencerez à créer vos premières applications.
Lorsque nous parlons de frameworks qui fournissent une interface graphique à l'utilisateur (soit une page web, comme ici avec Django, soit l'interface d'une application graphique classique, comme celle de votre traitement de texte par exemple), nous parlons souvent de l'architecture MVC. Il s'agit d'un modèle distinguant plusieurs rôles précis d'une application, qui doivent être accomplis. Comme son nom l'indique, l'architecture (ou « patron ») Modèle-Vue-Contrôleur est composé de trois entités distinctes, chacune ayant son propre rôle à remplir.
Tout d'abord, le modèle représente une information enregistrée quelque part, le plus souvent dans une base de données. Il permet d'accéder à l'information, de la modifier, d'en ajouter une nouvelle, de vérifier que celle-ci correspond bien aux critères (on parle d'intégrité de l'information), de la mettre à jour, etc. Il s'agit d'une interface supplémentaire entre votre code et la base de données, mais qui simplifie grandement les choses, comme nous le verrons par la suite.
Ensuite la vue qui est, comme son nom l'indique, la visualisation de l'information. C'est la seule chose que l'utilisateur peut voir. Non seulement elle sert à présenter une donnée, mais elle permet aussi de recueillir une éventuelle action de l'utilisateur (un clic sur un lien, ou la soumission d'un formulaire par exemple). Typiquement, un exemple de vue est une page web, ni plus, ni moins.
Finalement, le contrôleur prend en charge tous les événements de l'utilisateur (accès à une page, soumission d'un formulaire, etc.). Il se charge, en fonction de la requête de l'utilisateur, de récupérer les données voulues dans les modèles. Après un éventuel traitement sur ces données, il transmet ces données à la vue, afin qu'elle s'occupe de les afficher. Lors de l'appel d'une page, c'est le contrôleur qui est chargé en premier, afin de savoir ce qu'il est nécessaire d'afficher.
Schéma de l'architecture MVC
Fatigué(e) de lire sur un écran ? Découvrez ce cours en livre.
L'architecture utilisée par Django diffère légèrement de l'architecture MVC classique. En effet, la « magie » de Django réside dans le fait qu'il gère lui-même la partie contrôleur (gestion des requêtes du client, des droits sur les actions…). Ainsi, nous parlons plutôt de framework utilisant l'architecture MVT : Modèle-Vue-Template.
Cette architecture reprend les définitions de modèle et de vue que nous avons vues, et en introduit une nouvelle : le template (voir figure suivante). Un template est un fichier HTML, aussi appelé en français « gabarit ». Il sera récupéré par la vue et envoyé au visiteur ; cependant, avant d'être envoyé, il sera analysé et exécuté par le framework, comme s'il s'agissait d'un fichier avec du code. Django fournit un moteur de templates très utile qui permet, dans le code HTML, d'afficher des variables, d'utiliser des structures conditionnelles (if/else) ou encore des boucles (for), etc.
Schéma d'exécution d'une requête
Concrètement, lorsque l'internaute appelle une page de votre site réalisé avec Django, le framework se charge, via les règles de routage URL définies, d'exécuter la vue correspondante. Cette dernière récupère les données des modèles et génère un rendu HTML à partir du template et de ces données. Une fois la page générée, l'appel fait chemin arrière, et le serveur renvoie le résultat au navigateur de l'internaute.
On distingue les quatre parties qu'un développeur doit gérer :
Le routage des requêtes, en fonction de l'URL ;
La représentation des données dans l'application, avec leur gestion (ajout, édition, suppression…), c'est-à-dire les modèles ;
L'affichage de ces données et de toute autre information au format HTML, c'est-à-dire les templates ;
Enfin le lien entre les deux derniers points : la vue qui récupère les données et génère le template selon celles-ci.
On en revient donc au modèle MVT. Le développeur se doit de fournir le modèle, la vue et le template. Une fois cela fait, il suffit juste d'assigner la vue à une URL précise, et la page est accessible.
Si le template est un fichier HTML classique, un modèle en revanche sera écrit sous la forme d'une classe où chaque attribut de celle-ci correspondra à un champ dans la base de données. Django se chargera ensuite de créer la table correspondante dans la base de données, et de faire la liaison entre la base de données et les objets de votre classe. Non seulement il n'y a plus besoin d'écrire de requêtes pour interagir avec la base de données, mais en plus le framework propose la représentation de chaque entrée de la table sous forme d'une instance de la classe qui a été écrite. Il suffit donc d'accéder aux attributs de la classe pour accéder aux éléments dans la table et pouvoir les modifier, ce qui est très pratique !
Enfin, une vue est une simple fonction, qui prend comme paramètres des informations sur la requête (s'il s'agit d'une requête GET ou POST par exemple), et les paramètres qui ont été donnés dans l'URL. Par exemple, si l'identifiant ou le nom d'un article du blog a été donné dans l'URL crepes-bretonnes.com/blog/faire-de-bonnes-crepes, la vue récupérera faire-de-bonnes-crepes comme titre et cherchera dans la base de données l'article correspondant à afficher. Suite à quoi la vue générera le template avec le bon article et le renverra à l'utilisateur.
Fatigué(e) de lire sur un écran ? Découvrez ce cours en livre.
En plus de l'architecture MVT, Django introduit le développement d'un site sous forme de projet. Chaque site web conçu avec Django est considéré comme un projet, composé de plusieurs applications. Une application consiste en un dossier contenant plusieurs fichiers de code, chacun étant relatif à une tâche du modèle MVT que nous avons vu. En effet, chaque bloc du site web est isolé dans un dossier avec ses vues, ses modèles et ses schémas d'URL.
Lors de la conception de votre site, vous allez devoir penser aux applications que vous souhaitez développer. Voici quelques exemples d'applications :
Un module d'actualités ;
Un forum ;
Un système de contact ;
Une galerie de photos ;
Un système de dons.
Ce principe de séparation du projet en plusieurs applications possède deux avantages principaux :
Le code est beaucoup plus structuré. Les modèles et templates d'une application ne seront que rarement ou jamais utilisés dans une autre, nous gardons donc une séparation nette entre les différentes applications, ce qui évite de s'emmêler les pinceaux !
Une application correctement conçue pourra être réutilisée dans d'autres projets très simplement, par un simple copier/coller, comme le montre la figure suivante.
Organisation d'un projet Django et réutilisation d'une application
Ici, le développement du système d'articles sera fait une fois uniquement. Pour le second site, une légère retouche des templates suffira. Ce système permet de voir le site web comme des boîtes que nous agençons ensemble, accélérant considérablement le développement pour les projets qui suivent.
En résumé
Django respecte l'architecture MVT, directement inspirée du très populaire modèle MVC ;
Django gère de façon autonome la réception des requêtes et l'envoi des réponses au client (partie contrôleur) ;
Un projet est divisé en plusieurs applications, ayant chacune un ensemble de vues, de modèles et de schémas d'URL ;
Si elles sont bien conçues, ces applications sont réutilisables dans d'autres projets, puisque chaque application est indépendante.
Fatigué(e) de lire sur un écran ? Découvrez ce cours en livre.
Django propose un outil en ligne de commandes très utile qui permet énormément de choses :
Création de projets et applications ;
Création des tables dans la base de données selon les modèles de l'application ;
Lancement du serveur web de développement ;
Etc.
Nous verrons dans ce chapitre comment utiliser cet outil, la structure d'un projet Django classique, comment créer ses projets et applications, et leur configuration.
L'outil de gestion fourni avec Django se nomme django-admin.py et il n'est accessible qu'en ligne de commandes. Pour ce faire, munissez-vous d'une console MS-DOS sous Windows, ou d'un terminal sous Linux et Mac OS X.
Sous Windows, allez dans le menu Démarrer > Exécuter et tapez dans l'invite de commande cmd. Une console s'ouvre, déplacez-vous dans le dossier dans lequel vous souhaitez créer votre projet grâce à la commande cd, suivie d'un chemin. Exemple :
cd C:\Mes Documents\Utilisateur\
Sous Mac OS X et Linux, lancez tout simplement l'application Terminal (elle peut parfois également être nommée Console sous Linux), et déplacez-vous dans le dossier dans lequel vous souhaitez créer votre projet, également à l'aide de la commande cd. Exemple :
cd /home/mathx/Projets/
Tout au long du tutoriel, nous utiliserons un blog sur les bonnes crêpes bretonnes comme exemple. Ainsi, appelons notre projet crepes_bretonnes (seuls les caractères alphanumériques et underscores sont autorisés pour le nom du projet) et créons-le grâce à la commande suivante :
django-admin.py startproject crepes_bretonnes
Un nouveau dossier nommé crepes_bretonnes est apparu et possède la structure suivante :
Dans le dossier principal crepes_bretonnes, nous retrouvons deux éléments : un fichier manage.py et un autre sous-dossier nommé également crepes_bretonnes. Créez dans le dossier principal un dossier nommé templates, lequel contiendra vos templates HTML.
Le sous-dossier contient quatre fichiers Python, à savoir settings.py, urls.py, wsgi.py et __init__.py. Ne touchez surtout pas à ces deux derniers fichiers, ils n'ont pas pour but d'être modifiés ! Les deux autres fichiers ont des noms plutôt éloquents : settings.py contiendra la configuration de votre projet, tandis que urls.py rassemblera toutes les URL de votre site web et la liste des fonctions à appeler pour chaque URL. Nous reviendrons sur ces deux fichiers plus tard.
Ensuite, le fichier manage.py est en quelque sorte un raccourci local de la commande django-admin.py qui prend en charge la configuration de votre projet. Vous pouvez désormais oublier la commande django-admin.py, elle ne sert en réalité qu'à créer des projets, tout le reste se fait via manage.py. Bien évidemment, n'éditez pas ce fichier non plus.
Votre projet étant créé, pour vous assurer que tout a été correctement effectué jusqu'à maintenant, vous pouvez lancer le serveur de développement via la commande python manage.py runserver :
$ python manage.py runserver
Validating models...
0 errors found
March 04, 2013 - 20:31:54
Django version 1.5, using settings 'crepes_bretonnes.settings'
Development server is running at http://127.0.0.1:8000/
Quit the server with CTRL-BREAK.
Cette console vous donnera des informations, des logs (quelle page a été accédée et par qui) et les exceptions de Python lancées en cas d'erreur lors du développement. Par défaut, l'accès au site de développement se fait via l'adresse http://localhost:8000. Vous devriez obtenir quelque chose comme la figure suivante dans votre navigateur :
Votre première page Django
Si ce n'est pas le cas, assurez-vous d'avoir bien respecté toutes les étapes précédentes !
Au passage, manage.py propose bien d'autres sous-commandes, autres que runserver. Une petite liste est fournie avec la sous-commande help :
python manage.py help
Toutes ces commandes sont expliquées dans une annexe, donc nous vous invitons à la survoler de temps en temps, au fur et à mesure que vous avancez dans ce cours, et nous reviendrons sur certaines d'entre elles dans certains chapitres. Il s'agit là d'un outil très puissant qu'il ne faut surtout pas sous-estimer. Le développeur Django y a recours quasiment en permanence, d'où l'intérêt de savoir le manier correctement.
Fatigué(e) de lire sur un écran ? Découvrez ce cours en livre.
Avant de commencer à écrire des applications Django, configurons le projet que nous venons de créer. Ouvrez le fichier settings.py dont nous avons parlé tout à l'heure. Il s'agit d'un simple fichier Python avec une liste de variables que vous pouvez modifier à votre guise. Voici les plus importantes :
DEBUG = True
TEMPLATE_DEBUG = DEBUG
Ces deux variables permettent d'indiquer si votre site web est en mode « debug » ou pas. Le mode de débogage affiche des informations pour déboguer vos applications en cas d'erreur. Ces informations affichées peuvent contenir des données sensibles de votre fichier de configuration. Ne mettez donc jamais DEBUG = True en production !
Le tuple ADMINS, qui est par défaut vide, est censé contenir quelques informations à propos des gestionnaires du site (nom et adresse e-mail). L'adresse e-mail servira notamment à envoyer les erreurs rencontrées par les visiteurs de votre site en production. En voici un exemple :
La configuration de la base de données se fait dans le dictionnaire DATABASES. Nous conseillons pour le développement local l'utilisation d'une base de données SQLite. L'avantage de SQLite comme gestionnaire de base de données pour le développement est simple : il ne s'agit que d'un simple fichier. Il n'y a donc pas besoin d'installer un service à part comme MySQL ; Python et Django se chargent de tout. Si vous n'avez aucune idée de ce qu'est réellement une base de données SQLite, n'ayez aucune crainte, le prochain chapitre vous expliquera en détail en quoi elles consistent et comment elles fonctionnent.
Voici la configuration nécessaire pour l'utilisation de SQLite :
TEMPLATE_DIRS est un simple tuple contenant les listes des dossiers vers les templates. Nous avons créé un dossier templates à la racine de notre projet tout à l'heure, incluons-le donc ici :
Finalement, pour des raisons pratiques qui seront explicitées par la suite, ajoutons une option qui permet de compléter automatiquement les URL par un slash (« / ») à la fin de celles-ci, si celui-ci n'est pas déjà présent. Vous en comprendrez l'utilité lorsque nous aborderons le routage d'URL :
APPEND_SLASH = True # Ajoute un slash en fin d'URL
Voilà ! Les variables les plus importantes ont été expliquées. Pour que ce ne soit pas indigeste, nous n'avons pas tout traité, il en reste en effet beaucoup d'autres. Nous reviendrons sur certains paramètres plus tard. En attendant, si une variable vous intrigue, n'hésitez pas à lire le commentaire (bien qu'en anglais) à côté de la déclaration et à vous référer à la documentation en ligne.
Fatigué(e) de lire sur un écran ? Découvrez ce cours en livre.
Comme nous l'avons expliqué précédemment, un projet se compose de plusieurs applications, chacune ayant un but bien précis (système d'actualités, galerie photos…). Pour créer une application dans un projet, le fonctionnement est similaire à la création d'un projet : il suffit d'utiliser la commande manage.py avec startapp, à l'intérieur de votre projet. Pour notre site sur les crêpes bretonnes, créons un blog pour publier nos nouvelles recettes :
python manage.py startapp blog
Comme pour startproject, startapp crée un dossier avec plusieurs fichiers à l'intérieur. La structure de notre projet ressemble à ceci :
Les noms des fichiers sont relativement évidents :
models.py contiendra vos modèles ;
tests.py permet la création de tests unitaires (un chapitre y est consacré dans la quatrième partie de ce cours) ;
views.py contiendra toutes les vues de votre application.
Dernière petite chose, il faut ajouter cette application au projet. Pour que Django considère le sous-dossier blog comme une application, il faut donc l'ajouter dans la configuration.
Retournez dans settings.py, et cherchez la variable INSTALLED_APPS. Tout en conservant les autres applications installées, ajoutez une chaîne de caractères avec le nom de votre application. Au passage, décommentez l'application django.contrib.admin, il s'agit de l'application qui génère automatiquement l'administration et dont nous nous occuperons plus tard.
Votre variable devrait ressembler à quelque chose comme ceci :
L'administration de projet s'effectue via la commande python manage.py. Tout particulièrement, la création d'un projet se fait via la commande django-admin.py startproject mon_projet.
À la création du projet, Django déploie un ensemble de fichiers, facilitant à la fois la structuration du projet et sa configuration.
Pour tester notre projet, il est possible de lancer un serveur de test, via la commande python manage.py runserver, dans le dossier de notre projet. Ce serveur de test ne doit pas être utilisé en production.
Il est nécessaire de modifier le settings.py, afin de configurer le projet selon nos besoins. Ce fichier ne doit pas être partagé avec les autres membres ou la production, puisqu'il contient des données dépendant de votre installation, comme la connexion à la base de données.
Fatigué(e) de lire sur un écran ? Découvrez ce cours en livre.
Pour que vous puissiez enregistrer les données de vos visiteurs, l'utilisation d'une base de données s'impose. Nous allons dans ce chapitre expliquer le fonctionnement d'une base de données, le principe des requêtes SQL et l'interface que Django propose entre les vues et les données enregistrées. À la fin de ce chapitre, vous aurez assez de connaissances théoriques pour comprendre par la suite le fonctionnement des modèles.
Imaginez que vous souhaitiez classer sur papier la liste des films que vous possédez à la maison. Un film a plusieurs caractéristiques : le titre, le résumé, le réalisateur, les acteurs principaux, le genre, l'année de sortie, une appréciation, etc. Il est important que votre méthode de classement permette de différencier très proprement ces caractéristiques. De même, vous devez être sûrs que les caractéristiques que vous écrivez sont correctes et homogènes. Si vous écrivez la date de sortie une fois en utilisant des chiffres, puis une autre fois en utilisant des lettres, vous perdez en lisibilité et risquez de compliquer les choses.
Il existe plusieurs méthodes de classement pour trier nos films, mais la plus simple et la plus efficace (et à laquelle vous avez sûrement dû penser) est tout simplement un tableau ! Pour classer nos films, les colonnes du tableau renseignent les différentes caractéristiques qu'un film peut avoir, tandis que les lignes représentent toutes les caractéristiques d'un même film. Par exemple :
Titre
Réalisateur
Année de sortie
Note (sur 20)
Pulp Fiction
Quentin Tarantino
1994
20
Inglorious Basterds
Quentin Tarantino
2009
18
Holy Grail
Monty Python
1975
19
Fight Club
David Fincher
1999
20
Life of Brian
Monty Python
1979
17
Le classement par tableau est très pratique et simple à comprendre. Les bases de données s'appuient sur cette méthode de tri pour enregistrer et classer les informations que vous spécifierez.
Une base de données peut contenir plusieurs tableaux, chacun servant à enregistrer un certain type d'élément. Par exemple, dans votre base, vous pourriez avoir un tableau qui recensera vos utilisateurs, un autre pour les articles, encore un autre pour les commentaires, etc.
Nous avons évoqué un autre point important de ces bases de données, avec l'exemple de la date de sortie. Il faut en effet que toutes les données dans une colonne soient homogènes. Autrement dit, elles doivent avoir un même type de données : entier, chaîne de caractères, texte, booléen, date… Si vous enregistrez un texte dans la colonne Note, votre code vous renverra une erreur. Dès lors, chaque fois que vous irez chercher des données dans une table, vous serez sûrs du type des variables que vous obtiendrez.
Fatigué(e) de lire sur un écran ? Découvrez ce cours en livre.
Le langage SQL et les gestionnaires de base de données
Il existe plusieurs programmes qui s'occupent de gérer des bases de données. Nous les appelons, tout naturellement, des gestionnaires de bases de données (ou « SGBD » pour « systèmes de gestion de bases de données »). Ces derniers s'occupent de tout : création de nouvelles tables, ajout de nouvelles entrées dans une table, mise à jour des données, renvoi des entrées déjà enregistrées, etc. Il y a énormément de SGBD, chacun avec des caractéristiques particulières. Néanmoins, ils se divisent en deux grandes catégories : les bases de données SQL et les bases de données non-SQL. Nous allons nous intéresser à la première catégorie (celle que Django utilise).
Les gestionnaires de bases de données SQL sont les plus populaires et les plus utilisés pour le moment. Ceux-ci reprennent l'utilisation du classement par tableau tel que nous l'avons vu. L'acronyme « SQL » signifie « Structured Query Language », ou en français « langage de requêtes structurées ». En effet, lorsque vous souhaitez demander au SGBD toutes les entrées d'une table, vous devez communiquer avec le serveur (le programme qui sert les données) dans un langage qu'il comprend. Ainsi, si pour commander un café vous devez parler en français, pour demander les données au gestionnaire vous devez parler en SQL.
Voici un simple exemple de requête SQL qui renvoie toutes les entrées de la table films dont le réalisateur doit être Quentin Tarantino et qui sont triées par date de sortie :
SELECT titre, annee_sortie, note FROM films WHERE realisateur="Quentin Tarantino" ORDER BY annee_sortie
On a déjà vu plus simple, mais voilà comment communiquent un serveur SQL et un client. Il existe bien d'autres commandes (une pour chaque type de requête : sélection, mise à jour, suppression…) et chaque commande possède ses paramètres.
Heureusement, tous les SGBD SQL parlent à peu près le même SQL, c'est-à-dire qu'une requête utilisée avec un gestionnaire fonctionnera également avec un autre. Néanmoins, ce point est assez théorique, car même si les requêtes assez basiques marchent à peu près partout, les requêtes plus pointues et avancées commencent à diverger selon le SGBD, et si un jour vous devez changer de gestionnaire, nul doute que vous devrez réécrire certaines requêtes. Django a une solution pour ce genre de situations, nous verrons cela par la suite.
Voici quelques gestionnaires SQL bien connus (dont vous avez sûrement déjà dû voir le nom quelque part) :
MySQL : gratuit, probablement le plus connu et le plus utilisé à travers le monde ;
PostgreSQL : gratuit, moins connu que MySQL, mais possède quelques fonctionnalités de plus que ce dernier ;
Oracle Database : généralement utilisé dans de grandes entreprises, une version gratuite existe, mais est très limitée ;
Microsoft SQL Server : payant, développé par Microsoft ;
SQLite : très léger, gratuit, et très simple à installer (en réalité, il n'y a rien à installer).
Lors de la configuration de votre projet Django dans le chapitre précédent, nous vous avons conseillé d'utiliser SQLite. Pourquoi ? Car contrairement aux autres SGBD qui ont besoin d'un serveur lancé en permanence pour traiter les données, une base de données SQLite consiste en un simple fichier. C'est la bibliothèque Python (nommée sqlite3) qui se chargera de modifier et renvoyer les données de la base. C'est très utile en développement, car il n'y a rien à installer, mais en production mieux vaut utiliser un SGBD plus performant comme MySQL.
Fatigué(e) de lire sur un écran ? Découvrez ce cours en livre.
Apprendre le langage SQL et écrire ses propres requêtes est quelque chose d'assez difficile et contraignant lorsque nous débutons. Cela prend beaucoup de temps et est assez rébarbatif. Heureusement, Django propose un système pour bénéficier des avantages d'une base de données SQL sans devoir écrire ne serait-ce qu'une seule requête SQL !
Ce type de système s'appelle ORM pour « object-relationnal mapping ». Derrière ce nom un peu barbare se cache un fonctionnement simple et très utile. Lorsque vous créez un modèle dans votre application Django, le framework va automatiquement créer une table adaptée dans la base de données qui permettra d'enregistrer les données relatives au modèle.
Sans entrer dans les détails (nous verrons cela après), voici un modèle simple qui reviendra par la suite :
class Article(models.Model):
titre = models.CharField(max_length=100)
auteur = models.CharField(max_length=42)
contenu = models.TextField(null=True)
date = models.DateTimeField(auto_now_add=True, auto_now=False, verbose_name="Date de parution")
À partir de ce modèle, Django va créer une table blog_article (« blog » étant le nom de l'application dans laquelle le modèle est ajouté) dont les champs seront titre, auteur, contenu et date. Chaque champ a son propre type (tel que défini dans le modèle), et ses propres paramètres. Tout cela se fait, encore une fois, sans écrire la moindre requête SQL.
La manipulation de données est tout aussi simple bien évidemment. Le code suivant…
… créera une nouvelle entrée dans la base de données. Notez la relation qui se crée : chaque instance du modèle Article qui se crée correspond à une entrée dans la table SQL. Toute manipulation des données dans la base se fait depuis des objets Python, ce qui est bien plus intuitif et simple.
De la même façon, il est possible d'obtenir toutes les entrées de la table. Ainsi le code suivant…
Article.objects.all()
… renverra des instances d'Article, une pour chaque entrée dans la table, comme le schématise la figure suivante :
Fonctionnement de l'ORM de Django
Pour conclure, l'ORM est un système très flexible de Django qui s'insère parfaitement bien dans l'architecture MVT que nous avons décrite précédemment.
Fatigué(e) de lire sur un écran ? Découvrez ce cours en livre.
Pour terminer ce chapitre, nous allons aborder une dernière notion théorique relative aux bases de données SQL, il s'agit des clés étrangères (ou Foreign Keys en anglais).
Reprenons notre exemple de tout à l'heure : nous avons une table qui recense plusieurs films. Imaginons maintenant que nous souhaitions ajouter des données supplémentaires, qui ne concernent pas les films mais les réalisateurs. Nous voudrions par exemple ajouter le pays d'origine et la date de naissance des réalisateurs. Étant donné que certains réalisateurs reviennent plusieurs fois, il serait redondant d'ajouter les caractéristiques des réalisateurs dans les caractéristiques des films. La bonne solution ? Créer une nouvelle table qui recensera les réalisateurs et ajouter un lien entre les films et les réalisateurs.
Lorsque Django crée une nouvelle table depuis un modèle, il va ajouter un autre champ qui n'est pas dans les attributs de la classe. Il s'agit d'un champ tout simple nommé ID (pour « identifiant », synonyme ici de « clé »), qui contiendra un certain nombre unique à l'entrée, et qui va croissant au fil des entrées. Ainsi, le premier réalisateur ajouté aura l'identifiant 1, le deuxième l'identifiant 2, etc.
Voici donc à quoi ressemblerait notre table des réalisateurs :
ID
Nom
Pays d'origine
Date de naissance
1
Quentin Tarantino
USA
1963
2
David Fincher
USA
1962
3
Monty Python
Grande Bretagne
1969
Jusqu'ici, rien de spécial à part la nouvelle colonne ID introduite précédemment. En revanche, dans la table recensant les films, une colonne a été modifiée :
Titre
Réalisateur
Année de sortie
Note (sur 20)
Pulp Fiction
1
1994
20
Inglorious Basterds
1
2009
18
Holy Grail
3
1975
19
Fight Club
2
1999
20
Life of Brian
3
1979
17
Désormais, les noms des réalisateurs sont remplacés par des nombres. Ceux-ci correspondent aux identifiants de la table des réalisateurs. Si nous souhaitons obtenir le réalisateur du film Fight Club, il faut aller regarder dans la table réalisateurs et sélectionner l'entrée ayant l'identifiant 2. Nous pouvons dès lors regarder le nom du réalisateur : nous obtenons bien à nouveau David Fincher, et les données supplémentaires (date de naissance et pays d'origine) sont également accessibles.
Cette méthode de clé étrangère (car la clé vient d'une autre table) permet de créer simplement des liens entre des entrées dans différents tableaux. L'ORM de Django gère parfaitement cette méthode. Vous n'aurez probablement jamais besoin de l'identifiant pour gérer des liaisons, Django s'en occupera et renverra directement l'objet de l'entrée associée.
En résumé
Une base de données permet de stocker vos données de façon organisée et de les récupérer en envoyant des requêtes à votre système de gestion de base de données ;
De manière générale, nous communiquons la plupart du temps avec les bases de données via le langage SQL ;
Il existe plusieurs systèmes de gestion de bases de données, ayant chacun ses particularités ;
Pour faire face à ces différences, Django intègre une couche d'abstraction, afin de communiquer de façon uniforme et plus intuitive avec tous les systèmes : il s'agit de l'ORM que nous avons présenté brièvement ;
Une ligne dans une table peut être liée à une autre ligne d'une autre table via le principe de clés étrangères : nous gardons l'identifiant de la ligne de la seconde table dans une colonne de la ligne de la première table.
Fatigué(e) de lire sur un écran ? Découvrez ce cours en livre.
Dans ce chapitre, nous allons créer notre première page web avec Django. Pour ce faire, nous verrons comment créer une vue dans une application et la rendre accessible depuis une URL. Une fois cela fait, nous verrons comment organiser proprement nos URL afin de rendre le code plus propre et structuré. Nous aborderons ensuite deux cas spécifiques des URL, à savoir la gestion de paramètres et de variables dans celles-ci, et les redirections, messages d'erreur, etc.
Cette partie est fondamentale pour aborder la suite et comprendre le fonctionnement du framework en général. Autrement dit, nous ne pouvons que vous conseiller de bien vous accrocher tout du long !
Commençons enfin notre blog sur les bonnes crêpes bretonnes. En effet, au chapitre précédent, nous avons créé une application « blog » dans notre projet, il est désormais temps de se mettre au travail !
Pour rappel, comme vu dans la théorie, chaque vue se doit d'être associée au minimum à une URL. Avec Django, une vue est représentée par une fonction définie dans le fichier views.py. Cette fonction va généralement récupérer des données dans les modèles (ce que nous verrons plus tard) et appeler le bon template pour générer le rendu HTML adéquat. Par exemple, nous pourrions donner la liste des 10 derniers articles de notre blog au moteur de templates, qui se chargera de les insérer dans une page HTML finale, qui sera renvoyée à l'utilisateur.
Pour débuter, nous allons réaliser quelque chose de relativement simple : une page qui affichera « Bienvenue sur mon blog ! ».
La gestion des vues
Chaque application possède son propre fichier views.py, regroupant l'ensemble des fonctions que nous avons introduites précédemment. Comme tout bon blog, le nôtre possèdera plusieurs vues qui rempliront diverses tâches, comme l'affichage d'un article par exemple.
Commençons à travailler dans blog/views.py. Par défaut, Django a généré gentiment ce fichier avec le commentaire suivant :
# Create your views here.
Pour éviter tout problème par la suite, indiquons à l'interpréteur Python que le fichier sera en UTF-8, afin de prendre en charge les accents. En effet, Django gère totalement l'UTF-8 et il serait bien dommage de ne pas l'utiliser. Insérez ceci comme première ligne de code du fichier :
#-*- coding: utf-8 -*-
Désormais, nous pouvons créer une fonction qui remplira le rôle de la vue. Bien que nous n'ayons vu pour le moment ni les modèles, ni les templates, il est tout de même possible d'écrire une vue, mais celle-ci restera basique. En effet, il est possible d'écrire du code HTML directement dans la vue et de le renvoyer au client :
#-*- coding: utf-8 -*-
from django.http import HttpResponse
def home(request):
text = """<h1>Bienvenue sur mon blog !</h1>
<p>Les crêpes bretonnes ça tue des mouettes en plein vol !</p>"""
return HttpResponse(text)
Ce code se divise en trois parties :
Nous importons la classe HttpResponse du module django.http. Cette classe permet de retourner une réponse (texte brut, JSON ou HTML comme ici) depuis une chaîne de caractères. HttpResponse est spécifique à Django et permet d'encapsuler votre réponse dans un objet plus générique, que le framework peut traiter plus aisément.
Une fonction home a été déclarée, avec comme argument une instance de HttpRequest. Nous avons nommé ici (et c'est presque partout le cas) sobrement cet argument request. Celui-ci contient des informations sur la méthode de la requête (GET, POST), les données des formulaires, la session du client, etc. Nous y reviendrons plus tard.
Finalement, la fonction déclare une chaîne de caractères nommée text et crée une nouvelle instance de HttpResponse à partir de cette chaîne, que la fonction renvoie ensuite au framework.
Par la suite, ne renvoyez jamais du code HTML directement depuis la vue comme nous le faisons ici. Passez toujours par des templates, ce que nous introduirons au chapitre suivant. Il s'agit de respecter l'architecture du framework dont nous avons parlé dans la partie précédente afin de bénéficier de ses avantages (la structuration du code notamment). Nous n'avons utilisé cette méthode que dans un but pédagogique et afin de montrer les choses une par une.
Fatigué(e) de lire sur un écran ? Découvrez ce cours en livre.
Nous avons désormais une vue opérationnelle, il n'y a plus qu'à l'appeler depuis une URL. Mais comment ? En effet, nous n'avons pas encore défini vers quelle URL pointait cette fonction. Pour ce faire, il faut modifier le fichier urls.py de votre projet (ici crepes_bretonnes/urls.py). Par défaut, ce fichier contient une aide basique :
from django.conf.urls import patterns, include, url
# Uncomment the next two lines to enable the admin:
# from django.contrib import admin
# admin.autodiscover()
urlpatterns = patterns('',
# Examples:
# url(r'^$', 'crepes.views.home', name='home'),
# url(r'^crepes/', include('crepes.foo.urls')),
# Uncomment the admin/doc line below to enable admin documentation:
# url(r'^admin/doc/', include('django.contrib.admindocs.urls')),
# Uncomment the next line to enable the admin:
# url(r'^admin/', include(admin.site.urls)),
)
Quand un utilisateur appelle une page de votre site, la requête est directement prise en charge par le contrôleur de Django qui va chercher à quelle vue correspond cette URL. En fonction de l'ordre de définition dans le fichier précédent, la première vue qui correspond à l'URL demandée sera appelée, et elle retournera donc la réponse HTML au contrôleur (qui, lui, la retournera à l'utilisateur). Si aucune URL ne correspond à un schéma que vous avez défini, alors Django renverra une page d'erreur 404. Le schéma d'exécution est celui de la figure suivante.
Schéma d'exécution d'une requête (nous travaillons pour le moment sans templates et sans modèles)
Occupons-nous uniquement du tuple urlpatterns, qui permet de définir les associations entre URL et vues. Une association de routage basique se définit par un sous-tuple composé des éléments suivants :
Le pattern de l'URL : une URL peut être composée d'arguments qui permettent par la suite de retrouver des informations dans les modèles par exemple. Exemple : un titre d'article, le numéro d'un commentaire, etc. ;
Le chemin Python vers la vue correspondante.
Par exemple, en reprenant la vue définie tout à l'heure, si nous souhaitons que celle-ci soit accessible depuis l'URL http://www.crepes-bretonnes.com/accueil, il suffit de rajouter cette règle dans votre urlpatterns :
Qu'est-ce que c'est, tous ces caractères bizarres dans l'URL ?
Il s'agit d'expressions régulières (ou « regex ») qui permettent de créer des URL plus souples. Il est généralement conseillé de maîtriser au moins les bases des regex pour pouvoir écrire des URL correctes. Dans ce cas-ci :
^ indique le début de la chaîne (autrement dit, il ne peut rien y avoir avant /accueil) ;
? indique que le caractère précédent peut être absent ;
$ est le contraire de ^, il indique la fin de la chaîne.
Bien évidemment, toute expression régulière compatible avec le module re de Python sera compatible ici aussi.
Concernant le lien vers la vue, il s'agit du même type de lien utilisé lors d'une importation de module. Ici :
blog indique le module qui forme l'application « blog » ;
views indique le fichier concerné du module ;
home est la fonction du fichier views.py.
Grâce à cette règle, Django saura que lorsqu'un client demande la page http://www.crepes-bretonnes.com/accueil, il devra appeler la vue blog.views.home.
Enregistrez les modifications, lancez le serveur de développement Django et laissez-le tourner (pour rappel : python manage.py runserver), et rendez-vous sur http://localhost:8000/accueil/. Vous devriez obtenir quelque chose comme la figure suivante.
L'affichage de votre première vue
Si c'est le cas, félicitations, vous venez de créer votre première vue !
Fatigué(e) de lire sur un écran ? Découvrez ce cours en livre.
Dans la partie précédente, nous avions parlé de deux avantages importants de Django : la réutilisation d'applications et la structuration du code. Sauf qu'évidemment, un problème se pose avec l'utilisation des URL que nous avons faites : si nous avons plusieurs applications, toutes les URL de celles-ci iraient dans urls.py du projet, ce qui compliquerait nettement la réutilisation d'une application et ne structure en rien votre code.
En effet, il faudrait sans cesse recopier toutes les URL d'une application en l'incluant dans un projet, et une application complexe peut avoir des dizaines d'URL, ce qui ne facilite pas la tâche du développeur. Sans parler de la problématique qui survient lorsqu'il faut retrouver la bonne vue parmi la centaine de vues déjà écrites. C'est pour cela qu'il est généralement bien vu de créer dans chaque application un fichier également nommé urls.py et d'inclure ce dernier par la suite dans le fichier urls.py du projet.
Comment procède-t-on ?
Tout d'abord, il faut créer un fichier urls.py dans le dossier de votre application, ici blog. Ensuite, il suffit d'y réécrire l'URL que nous avons déjà écrite précédemment (ne pas oublier l'importation des modules nécessaires !) :
Maintenant, retournons à crepes_bretonnes/urls.py. Nous pouvons y enlever la règle réécrite dans blog/urls.py. Il ne devrait donc plus rester grand-chose. L'importation des règles de blogs/urls.py est tout aussi simple, il suffit d'utiliser la fonction include de django.conf.urls et d'ajouter ce sous-tuple à urlpatterns :
url(r'^blog/', include('blog.urls'))
En quoi consiste l'URL ^blog/ ici ?
Cette URL (en réalité portion d'URL), va précéder toutes les URL incluses. Autrement dit, nous avions une URL /accueil qui envoyait vers la vue blog.views.home, désormais celle-ci sera accessible depuis /blog/accueil. Et cela vaut pour toutes les futures URL importées. Cependant, rien ne vous empêche de laisser cette chaîne de caractères vide (/accueil restera /accueil), mais il s'agit d'une bonne solution pour structurer vos URL.
Nous avons scindé nos URL dans un fichier urls.py pour chaque application. Cependant, nous allons bientôt ajouter d'autres URL plus complexes dans notre blog/urls.py. Toutes ces URL seront routées vers des vues de blog.views. Au final, la variable urlpatterns de notre blog/urls.py risque de devenir longue :
Maintenant, imaginez que votre application « blog » change de nom, vous allez devoir réécrire tous les chemins vers vos vues ! Pour éviter de devoir modifier toutes les règles une à une, il est possible de spécifier un module par défaut qui contient toutes les vues. Pour ce faire, il faut utiliser le premier élément de notre tuple qui est resté une chaîne de caractères vide jusqu'à maintenant :
Tout est beaucoup plus simple et facilement éditable. Le module par défaut ici est blog.views, car toutes les vues viennent de ce fichier-là ; cela est désormais possible, car nous avons scindé notre urls.py principal en plusieurs urls.py propres à chaque application.
Finalement, notre blog/urls.py ressemblera à ceci :
Ne négligez pas cette solution, utilisez-la dès maintenant ! Il s'agit d'une excellente méthode pour structurer votre code, parmi tant d'autres que Django offre. Pensez aux éventuels développeurs qui pourraient maintenir votre projet après vous et qui n'ont pas envie de se retrouver avec une structure proche de l'anarchie.
Fatigué(e) de lire sur un écran ? Découvrez ce cours en livre.
Nous avons vu comment lier des URL à des vues et comment les organiser. Cependant, un besoin va bientôt se faire sentir : pouvoir passer des paramètres dans nos adresses directement. Si vous observez les adresses du site Instagram (qui est basé sur Django pour rappel), le lien vers une photo est construit ainsi : http://instagr.am/p/******* où ******* est une suite de caractères alphanumériques. Cette suite représente en réalité l'identifiant de la photo sur le site et permet à la vue de récupérer les informations en relation avec cette photo.
Pour passer des arguments dans une URL, il suffit de capturer ces arguments directement depuis les expressions régulières. Par exemple, si nous souhaitons sur notre blog pouvoir accéder à un certain article via l'adresse /blog/article/** où ** sera l'identifiant de l'article (un nombre unique), il suffit de fournir le routage suivant dans votre urls.py :
urlpatterns = patterns('blog.views',
url(r'^accueil/$', 'home'), # Accueil du blog
url(r'^article/(\d+)/$', 'view_article'), # Vue d'un article
url(r'^articles/(\d{4})/(\d{2})/$', 'list_articles'), # Vue des articles d'un mois précis
)
Lorsque l'URL /blog/article/42 est demandée, Django regarde le routage et exécute la fonction view_article, en passant en paramètre 42. Autrement dit, Django appelle la vue de cette manière : view_article(request, 42). Voici un exemple d'implémentation :
def view_article(request, id_article):
""" Vue qui affiche un article selon son identifiant (ou ID, ici un numéro). Son ID est le second paramètre de la fonction
(pour rappel, le premier paramètre est TOUJOURS la requête de l'utilisateur) """
text = "Vous avez demandé l'article n°{0} !".format(id_article)
return HttpResponse(text)
Il faut cependant faire attention à l'ordre des paramètres dans l'URL afin qu'il corresponde à l'ordre des paramètres de la fonction. En effet, lorsque nous souhaitons obtenir la liste des articles d'un mois précis, selon la troisième règle que nous avons écrite, il faudrait accéder à l'URL suivante pour le mois de juin 2012 : /blog/articles/2012/06.
Cependant, si nous souhaitons changer l'ordre des paramètres de l'URL pour afficher le mois, et ensuite l'année, celle-ci deviendrait /blog/articles/06/2012. Il faudra donc modifier l'ordre des paramètres dans la déclaration de la fonction en conséquence.
Pour éviter cette lourdeur et un bon nombre d'erreurs, il est possible d'associer une variable de l'URL à un paramètre de la vue. Voici la démarche :
urlpatterns = patterns('blog.views',
url(r'^home/$', 'home'), # Accueil du blog
url(r'^article/(?P<id_article>\d+)/$', 'view_article'), # Vue d'un article
url(r'^articles/(?P<year>\d{4})/(?P<month>\d{2})/$', 'list_articles'), # Vue des articles d'un mois précis
)
Et la vue correspondante :
def list_articles(request, month, year):
""" Liste des articles d'un mois précis. """
text = "Vous avez demandé les articles de {0} {1}.".format(month, year)
return HttpResponse(text)
Dans cet exemple, mois et année (month et year) ne sont pas dans le même ordre entre le urls.py et le views.py, mais Django s'en occupe et règle l'ordre des arguments en fonction des noms qui ont été donnés dans le urls.py. En réalité, le framework va exécuter la fonction de cette manière :
list_articles(request, year=2012, month=6)
Il faut juste s'assurer que les noms de variables donnés dans le fichier urls.py coïncident avec les noms donnés dans la déclaration de la vue, sans quoi Python retournera une erreur.
Pour terminer, sachez qu'il est toujours possible de passer des paramètres GET. Par exemple : http://www.crepes-bretonnes.com/blog/article/1337?ref=twitter. Django tentera de trouver le pattern correspondant en ne prenant en compte que ce qui est avant les paramètres GET, c'est-à-dire /blog/article/1337/. Les paramètres passés par la méthode GET sont bien évidemment récupérables, ce que nous verrons plus tard.
Fatigué(e) de lire sur un écran ? Découvrez ce cours en livre.
Jusqu'ici, nous avons vu comment renvoyer une page HTML standard. Cependant, il se peut que nous souhaitions renvoyer autre chose que du HTML : une erreur 404 (page introuvable), une redirection vers une autre page, etc.
Simuler une page non trouvée
Parfois, une URL correspond bien à un pattern mais ne peut tout de même pas être considérée comme une page existante. Par exemple, lorsque vous souhaitez afficher un article avec un identifiant introuvable, il est impossible de renvoyer une page, même si Django a correctement identifié l'URL et utilisé la bonne vue. Dans ce cas-là, nous pouvons le faire savoir à l'utilisateur via une page d'erreur 404, qui correspond au code d'erreur indiquant qu'une page n'a pas été trouvée. Pour ce faire, il faut utiliser une exception du framework : Http404. Cette exception, du module django.http, arrête le traitement de la vue, et renvoie l'utilisateur vers une page d'erreur.
Voici un rapide exemple d'une vue compatible avec une des règles de routage que nous avons décrites dans le sous-chapitre précédent :
from django.http import HttpResponse, Http404
def view_article(request, id_article):
if int(id_article) > 100: #Si l'ID est supérieur à 100, nous considérons que l'article n'existe pas
raise Http404
return HttpResponse('<h1>Mon article ici</h1>')
Si à l'appel de la page l'argument id_article est supérieur à 100, la page retournée sera une erreur 404 de Django, visible à la figure suivante. Il est bien entendu possible de personnaliser par la suite cette vue, avec un template, afin d'avoir une page d'erreur qui soit en accord avec le design de votre site, mais cela ne fonctionne uniquement qu'avec DEBUG = False dans le settings.py (en production donc). Si vous êtes en mode de développement, vous aurez toujours une erreur similaire à la figure suivante.
Erreur 404, page introuvable
Rediriger l'utilisateur
Le second cas que nous allons aborder concerne les redirections. Il arrive que vous souhaitiez rediriger votre utilisateur vers une autre page lorsqu'une action vient de se dérouler, ou en cas d'erreur rencontrée. Par exemple, lorsqu'un utilisateur se connecte, il est souvent redirigé soit vers l'accueil, soit vers sa page d'origine. Une redirection est réalisable avec Django via la méthode redirect qui renvoie un objet HttpResponseRedirect (classe héritant de HttpResponse), qui redirigera l'utilisateur vers une autre URL. La méthode redirect peut prendre en paramètres plusieurs types d'arguments, dont notamment une URL brute (chaîne de caractères) ou le nom d'une vue.
Si par exemple vous voulez que votre vue, après une certaine opération, redirige vos visiteurs vers le Site du Zéro, il faudrait procéder ainsi :
from django.shortcuts import redirect
def list_articles(request, year, month):
# Il veut des articles ?
return redirect("http://www.siteduzero.com") # Nous le redirigeons vers le Site du Zéro
N'oubliez pas qu'une URL valide pour accéder à cette vue serait /blog/articles/2005/05.
Cependant, si vous souhaitez rediriger votre visiteur vers une autre page de votre site web, il est plus intéressant de privilégier l'autre méthode, qui permet de garder indépendante la configuration des URL et des vues. Nous devons donc passer en argument le nom de la vue vers laquelle nous voulons rediriger l'utilisateur, avec éventuellement des arguments destinés à celle-ci.
from django.http import HttpResponse, Http404
from django.shortcuts import redirect
def view_article(request, id_article):
if int(id_article) > 100:
raise Http404
return redirect(view_redirection)
def view_redirection(request):
return HttpResponse(u"Vous avez été redirigé.")
url(r'^redirection/$', 'view_redirection'),
Ici, si l'utilisateur accède à l'URL /blog/article/101, il aura toujours une page 404. Par contre, s'il choisit un ID inférieur à 100, alors il sera redirigé vers la seconde vue, qui affiche un simple message.
Il est également possible de préciser si la redirection est temporaire ou définitive en ajoutant le paramètre permanent=True. L'utilisateur ne verra aucune différence, mais ce sont des détails que les moteurs de recherche prennent en compte lors du référencement de votre site web.
Si nous souhaitions rediriger un visiteur vers la vue view_article définie précédemment par un ID d'article spécifique, il suffirait simplement d'utiliser la méthode redirect ainsi :
Pourquoi est-ce que nous utilisons une chaîne de caractères pour désigner la vue maintenant, au lieu de la fonction elle-même ?
Il est possible d'indiquer une vue de trois manières différentes :
En passant directement la fonction Python, comme nous l'avons vu au début ;
En donnant le chemin vers la fonction, dans une chaîne de caractères (ce qui évite de l'importer si elle se situe dans un autre fichier) ;
En indiquant le nom de la vue tel qu'indiqué dans un urls.py (voir l'exemple suivant).
En réalité, la fonction redirect va construire l'URL vers la vue selon le routage indiqué dans urls.py. Ici, il va générer l'URL /blog/article/42 tout seul et rediriger l'utilisateur vers cette URL. Ainsi, si par la suite vous souhaitez modifier vos URL, vous n'aurez qu'à le faire dans les fichiers urls.py, tout le reste se mettra à jour automatiquement. Il s'agit d'une fonctionnalité vraiment pratique, il ne faut donc jamais écrire d'URL en dur, sauf quand cette méthode est inutilisable (vers des sites tiers par exemple).
Sachez qu'au lieu d'écrire à chaque fois tout le chemin d'une vue ou de l'importer, il est possible de lui assigner un nom plus court et plus facile à utiliser dans urls.py. Par exemple :
Notez le paramètre name="afficher_article" qui permet d'indiquer le nom de la vue. Avec ce routage, en plus de pouvoir passer directement la fonction ou le chemin vers celle-ci en argument, nous pouvons faire beaucoup plus court et procéder comme ceci :
Pour terminer, sachez qu'il existe également une fonction qui permet de générer simplement l'URL et s'utilise de la même façon que redirect ; il s'agit de reverse (from django.core.urlresolvers import reverse). Cette fonction ne retournera pas un objet HttpResponseRedirect, mais simplement une chaîne de caractères contenant l'URL vers la vue selon les éventuels arguments donnés. Une variante de cette fonction sera utilisée dans les templates peu après pour générer des liens HTML vers les autres pages du site.
En résumé
Le minimum requis pour obtenir une page web avec Django est une vue, associée à une URL.
Une vue est une fonction placée dans le fichier views.py d'une application. Cette fonction doit toujours renvoyer un objet HttpResponse.
Pour être accessible, une vue doit être liée à une ou plusieurs URL dans les fichiers urls.py du projet.
Les URL sont désignées par des expressions régulières, permettant la gestion d'arguments qui peuvent être passés à la vue pour rendre l'affichage différent selon l'URL visitée.
Il est conseillé de diviser le urls.py du projet en plusieurs fichiers, en créant un fichier urls.py par application.
Il existe des réponses plus spéciales permettant d'envoyer au navigateur du client les codes d'erreur 404 (page non trouvée) et 403 (accès refusé), ou encore d'effectuer des redirections.
Fatigué(e) de lire sur un écran ? Découvrez ce cours en livre.
Nous avons vu comment créer une vue et renvoyer du code HTML à l'utilisateur. Cependant, la méthode que nous avons utilisée n'est pas très pratique, le code HTML était en effet intégré à la vue elle-même ! Le code Python et le code HTML deviennent plus difficiles à éditer et à maintenir pour plusieurs raisons :
Les indentations HTML et Python se confondent ;
La coloration syntaxique de votre éditeur favori ne fonctionnera généralement pas pour le code HTML, celui-ci n'étant qu'une simple chaîne de caractères ;
Si vous avez un designer dans votre projet, celui-ci risque de casser votre code Python en voulant éditer le code HTML ;
Etc.
C'est à cause de ces raisons que tous les frameworks web actuels utilisent un moteur de templates. Les templates sont écrits dans un mini-langage de programmation propre à Django et qui possède des expressions et des structures de contrôle basiques (if/else, boucle for, etc.) que nous appelons des tags. Le moteur transforme les tags qu'il rencontre dans le fichier par le rendu HTML correspondant. Grâce à ceux-ci, il est possible d'effectuer plusieurs actions algorithmiques : afficher une variable, réaliser des conditions ou des boucles, faire des opérations sur des chaînes de caractères, etc.
Avant d'aborder le cœur même du fonctionnement des templates, retournons brièvement vers les vues. Dans la première partie, nous avons vu que nos vues étaient liées à des templates (et des modèles), comme le montre la figure suivante.
Schéma d'exécution d'une requête
C'est la vue qui se charge de transmettre l'information de la requête au template, puis de retourner le HTML généré au client. Dans le chapitre précédent, nous avons utilisé la méthode HttpResponse(text) pour renvoyer le HTML au navigateur. Cette méthode prend comme paramètre une chaîne de caractères et la renvoie sous la forme d'une réponse HTTP. La question ici est la suivante : comment faire pour appeler notre template, et générer la réponse à partir de celui-ci ? La fonction render a été conçue pour résoudre ce problème.
Nous commençons par un exemple avec une vue qui renvoie juste la date actuelle à l'utilisateur, et son fichier urls.py associé :
from datetime import datetime
from django.shortcuts import render
def tpl(request):
return render(request, 'blog/tpl.html', {'current_date': datetime.now()})
url(r'^$', 'tpl'),
Cette fonction prend en argument trois paramètres :
La requête initiale, qui a permis de construire la réponse (request dans notre cas) ;
Le chemin vers le template adéquat dans un des dossiers de templates donnés dans settings.py ;
Un dictionnaire reprenant les variables qui seront accessibles dans le template.
Ici, notre template sera tpl.html, dans le sous-dossier blog, et nous aurons accès à une seule variable : current_date qui aura comme valeur la date renvoyée par la fonction datetime.now().
Créons le template correspondant dans le dossier templates/blog/, ici nommé tpl.html :
<h1>Bienvenue sur mon blog</h1>
<p>La date actuelle est : {{ current_date }}</p>
Nous retrouvons current_date, comme passé dans render() ! Si vous accédez à cette page (après lui avoir assigné une URL), le {{ current_date }} est bel et bien remplacé par la date actuelle !
Deuxième exemple : une vue, et son template associé, qui additionne deux nombres donnés dans l'URL.
def addition(request, nombre1, nombre2):
total = int(nombre1) + int(nombre2)
# retourne nombre1, nombre2 et la somme des deux
return render(request, 'blog/addition.html', locals())
<h1>Ma super calculatrice</h1>
<p>{{ nombre1 }} + {{ nombre2 }}, ça fait <strong>{{ total }}</strong> !<br />
Nous pouvons également calculer la somme dans le template : {{ nombre1|add:nombre2 }}.
La seule différence dans la vue réside dans le deuxième argument donné à render. Au lieu de lui passer un dictionnaire directement, nous faisons appel à la fonction locals() qui va retourner un dictionnaire contenant toutes les variables locales de la fonction depuis laquelle locals() a été appelée. Les clés seront les noms de variables (par exemple total), et les valeurs du dictionnaire seront tout simplement… les valeurs des variables de la fonction ! Ainsi, si nombre1 valait 42, la valeur nombre1 du dictionnaire vaudra elle aussi 42.
Fatigué(e) de lire sur un écran ? Découvrez ce cours en livre.
Comme nous l'avons déjà expliqué, la vue transmet au template les données destinées à l'utilisateur. Ces données correspondent à des variables classiques de la vue. Nous pouvons les afficher dans le template grâce à l'expression {{ }} qui prend à l'intérieur des accolades un argument (on pourrait assimiler cette expression à une fonction), le nom de la variable à afficher. Le nom des variables est également limité aux caractères alphanumériques et aux underscores.
Bonjour {{ pseudo }}, nous sommes le {{ date }}.
Ici, nous considérons que la vue a transmis deux variables au template : pseudo et date. Ceux-ci seront affichés par le moteur de template. Si pseudo vaut « Zozor » et date « 28 décembre », le moteur de templates affichera « Bonjour Zozor, nous sommes le 28 décembre. ».
Si jamais la variable n'est pas une chaîne de caractères, le moteur de templates utilisera la méthode __str__ de l'objet pour l'afficher. Par exemple, les listes seront affichés sous la forme ['element 1', 'element 2'…], comme si vous demandiez son affichage dans une console Python. Il est possible d'accéder aux attributs d'un objet comme en Python, en les juxtaposant avec un point. Plus tard, nos articles de blog seront représentés par des objets, avec des attributs titre, contenu, etc. Pour y accéder, la syntaxe sera la suivante :
{# Nous supposons que notre vue a fourni un objet nommé article contenant les attributs titre, auteur et contenu #}
<h2>{{ article.titre }}</h2>
<p><em>Article publié par {{ article.auteur }}</em></p>
<p>{{ article.contenu }}</p>
Les filtres
Lors de l'affichage des données, il est fréquent de devoir gérer plusieurs cas. Les filtres permettent de modifier l'affichage en fonction d'une variable, sans passer par la vue. Prenons un exemple concret : sur la page d'accueil des sites d'actualités, le texte des dernières nouvelles est généralement tronqué, seul le début est affiché. Pour réaliser la même chose avec Django, nous pouvons utiliser un filtre qui limite l'affichage aux 80 premiers mots de notre article :
{{ texte|truncatewords:80 }}
Ici, le filtre truncatewords (qui prend comme paramètre un nombre, séparé par un deux-points) est appliqué à la variable texte. À l'affichage, cette dernière sera tronquée et l'utilisateur ne verra que les 80 premiers mots de celle-ci.
Ces filtres ont pour but d'effectuer des opérations de façon claire, afin d'alléger les vues, et ne marchent que lorsqu'une variable est affichée (avec la structure {{ }} donc). Il est par exemple possible d'accorder correctement les phrases de votre site avec le filtre pluralize :
Dans ce cas, un « s » sera ajouté si le le nombre de messages est supérieur à 1. Il est possible de passer des arguments au filtre afin de coller au mieux à notre chère langue française :
Il y a {{ nb_chevaux }} chev{{ nb_chevaux|pluralize:"al,aux" }} dans l'écurie.
Ici, nous aurons « cheval » si nb_chevaux est égal à 1 et « chevaux » pour le reste.
Et un dernier pour la route : imaginons que vous souhaitiez afficher le pseudo du membre connecté, ou le cas échéant « visiteur ». Il est possible de le faire en quelques caractères, sans avoir recours à une condition !
Bienvenue {{ pseudo|default:"visiteur" }}
En bref, il existe des dizaines de filtres par défaut : safe, length, etc. Tous les filtres sont répertoriés et expliqués dans la documentation officielle de Django, n'hésitez pas à y jeter un coup d'œil pour découvrir d'éventuels filtres qui pourraient vous être utiles.
Fatigué(e) de lire sur un écran ? Découvrez ce cours en livre.
Abordons maintenant le second type d'opération implémentable dans un template : les tags. C'est grâce à ceux-ci que les conditions, boucles, etc. sont disponibles.
Les conditions : {% if %}
Tout comme en Python, il est possible d'exécuter des conditions dans votre template selon la valeur des variables passées au template :
Ici, en fonction du contenu de la variable sexe, l'utilisateur ne verra pas le même texte à l'écran. Ce template est similaire au code HTML généré par la vue suivante :
def tpl(request):
sexe = "Femme"
html = "Bonjour "
if sexe == "Femme":
html += "Madame"
else:
html += "Monsieur"
html += " !"
return HttpResponse(html)
La séparation entre vue et template simplifie grandement les choses, et permet une plus grande lisibilité que lorsque le code HTML est écrit directement dans la vue !
Il est également possible d'utiliser les structures if, elif, else de la même façon :
{% if age > 25 %}
Bienvenue Monsieur, passez un excellent moment dans nos locaux.
{% elif age > 16 %}
Vas-y, tu peux passer.
{% else %}
Tu ne peux pas rentrer petit, tu es trop jeune !
{% endif %}
Les boucles : {% for %}
Tout comme les conditions, le moteur de templates de Django permet l'utilisation de la boucle for, similaire à celle de Python. Admettons que nous possédions dans notre vue un tableau de couleurs définies en Python :
Nous décidons dès lors d'afficher cette liste dans notre template grâce à la syntaxe {% for %} suivante :
Les couleurs de l'arc-en-ciel sont :
<ul>
{% for couleur in couleurs %}
<li>{{ couleur }}</li>
{% endfor %}
</ul>
Avec ce template, le moteur va itérer la liste (cela fonctionne avec n'importe quel autre type itérable), remplacer la variable couleur par l'élément actuel de l'itération et générer le code compris entre {% for %} et {% endfor %} pour chaque élément de la liste. Comme résultat, nous obtenons le code HTML suivant :
Les couleurs de l'arc-en-ciel sont :
<ul>
<li>rouge</li>
<li>orange</li>
<li>jaune</li>
<li>vert</li>
<li>bleu</li>
<li>indigo</li>
<li>violet</li>
</ul>
Il est aussi possible de parcourir un dictionnaire, en passant par la directive {% for cle, valeur in dictionnaire.items %} :
Les couleurs de l'arc-en-ciel sont :
<ul>
{% for code, nom in couleurs.items %}
<li style="color:#{{ code }}">{{ nom }}</li>
{% endfor %}
</ul>
Résultat :
<ul>
<li style="color:#ED7F10">orange</li>
<li style="color:#4B0082">indigo</li>
<li style="color:#0000FF">bleu</li>
<li style="color:#FFFF00">jaune</li>
<li style="color:#660099">violet</li>
<li style="color:#FF0000">rouge</li>
<li style="color:#00FF00">vert</li>
</ul>
Vous pouvez aussi réaliser n'importe quelle opération classique avec la variable générée par la boucle for (ici couleur) : une condition, utiliser une autre boucle, l'afficher, etc.
Enfin, il existe une troisième directive qui peut être associée au {% for %}, il s'agit de {% empty %}. Elle permet d'afficher un message par défaut si la liste parcourue est vide. Par exemple :
<h3>Commentaires de l'article</h3>
{% for commentaire in commentaires %}
<p>{{ commentaire }}</p>
{% empty %}
<p class="empty">Pas de commentaires pour le moment.</p>
{% endfor %}
Ici, s'il y a au moins un élément dans commentaires, alors une suite de paragraphes sera affichée, contenant chacun un élément de la liste. Sinon, le paragraphe « Pas de commentaires pour le moment. » sera renvoyé à l'utilisateur.
Le tag {% block %}
Sur la quasi-totalité des sites web, une page est toujours composée de la même façon : un haut de page, un menu et un pied de page. Si vous copiez-collez le code de vos menus dans tous vos templates et qu'un jour vous souhaitez modifier un élément de votre menu, il vous faudra modifier tous vos templates ! Heureusement, le tag {% block %} nous permet d'éviter cette épineuse situation. En effet, il est possible de déclarer des blocs, qui seront définis dans un autre template, et réutilisables dans le template actuel. Dès lors, nous pouvons créer un fichier, appelé usuellement base.html, qui va définir la structure globale de la page, autrement dit son squelette. Par exemple:
Ce template est composé de plusieurs éléments {% block %} :
Dans la balise <title> : {% block title %}Mon blog sur les crêpes bretonnes{% endblock %} ;
Dans la balise <nav>, qui définit un menu ;
Dans le corps de la page, qui recevra le contenu.
Tous ces blocs pourront être redéfinis ou inclus tels quels dans un autre template. Voyons d'ailleurs comment redéfinir et inclure ces blocs. Ayant été écrits dans le fichier base.html, nous appelons ce fichier dans chacun des templates de notre blog. Pour ce faire, nous utilisons le tag {% extends %} (pour ceux qui ont déjà fait de la programmation objet, cela doit vous dire quelque chose ; cette méthode peut aussi être assimilée à include en PHP). Nous parlons alors d'héritage de templates. Nous prenons la base que nous surchargeons, afin d'obtenir un résultat dérivé :
{% extends "base.html" %}
{% block title %}Ma page d'accueil{% endblock %}
{% block content %}
<h2>Bienvenue !</h2>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec rhoncus massa non tortor.
Vestibulum diam diam, posuere in viverra in, ullamcorper et libero.
Donec eget libero quis risus congue imperdiet ac id lectus.
Nam euismod cursus arcu, et consequat libero ullamcorper sit amet.
Sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Integer
sit amet diam. Vivamus imperdiet felis a enim tincidunt interdum.</p>
{% endblock %}
Dans cet exemple, nous avons défini deux blocs, title et content. Le tag extends va aller chercher dans le template donné en argument, ici base.html, et remplacer les blocs vides de ce dernier par les blocs de même nom définis dans le template appelé par la vue. Ainsi, title et content seront repris du template fils, mais nav sera le bloc nav défini dans base.html. En résumé, regardez la structure représentée dans l'image suivante :
Fonctionnement du tag {% block %}
Les liens vers les vues : {% url %}
Nous avons vu dans le chapitre précédent les fonctions redirect et reverse, qui respectivement redirige l'utilisateur et génère le lien vers une vue, selon certains paramètres. Une variante sous la forme de tag de la fonction reverse existe, il s'agit de {% url %}. Le fonctionnement de ce tag est très similaire à la fonction dont il est dérivé :
<a href="{% url "blog.views.view_article" 42 %}">Lien vers mon super article N° 42</a>
… générera le code HTML suivant :
<a href="/blog/article/42">Lien vers mon super article n° 42</a>
Ce code nous indique le chemin vers la vue ou son nom comme premier paramètre, entre guillemets. Les arguments qui suivent seront ceux de la vue (à condition de respecter le nombre et l'ordre des paramètres selon la déclaration de la vue bien entendu).
Nous aurions tout à fait pu utiliser une variable comme paramètre, que ce soit pour le nom de la vue ou les arguments :
<a href="{% url "blog.views.view_article" ID_article %}">Lien vers mon super article n° {{ ID_article }}</a>
Les commentaires : {% comment %}
Finalement, il existe un tag qui permet de définir des commentaires dans les templates. Ces commentaires sont différents des commentaires HTML : ils n'apparaîtront pas dans la page HTML. Cela permet par exemple de cacher temporairement une ligne, ou tout simplement de documenter votre template, afin de pouvoir mieux s'y retrouver par la suite.
Il existe deux syntaxes pour les commentaires : la première permet de faire un commentaire sur une ligne uniquement : {# Mon commentaire #}.
<p>Ma page HTML</p>
<!-- Ce commentaire HTML sera visible dans le code source. -->
{# Ce commentaire Django ne sera pas visible dans le code source. #}
Si vous souhaitez faire un commentaire sur plusieurs lignes, il vous faudra utiliser le tag {% comment %}.
{% comment %}
Ceci est une page d'exemple. Elle est composée de 3 tableaux :
- tableau des ventes
- locations
- retours en garantie
{% endcomment %}
Fatigué(e) de lire sur un écran ? Découvrez ce cours en livre.
Pour le moment, nous n'avons utilisé que du HTML dans nos templates. Cependant, un site web est composé aujourd'hui de nombreuses ressources : CSS, JavaScript, images, etc. Nous allons donc voir comment les intégrer dans nos templates.
Tout d'abord, créons un dossier à la racine du projet, dans lequel vous enregistrerez vos fichiers. Nous l'appellerons assets. Il faut ensuite renseigner ce dossier et lui assigner une URL dans votre settings.py. Voilà les deux variables qu'il faudra modifier, ici selon notre exemple :
La première variable indique l'URL du dossier depuis lequel vos fichiers seront accessibles. La deuxième renseigne le chemin vers ces fichiers sur votre disque dur.
Par la suite, si vous mettez une image, nommée par exemple salut.jpg, dans votre dossier assets (toujours selon notre exemple), vous pourrez l'inclure depuis votre template de la façon suivante :
Vous avez besoin de faire {% load static %} une fois au début de votre template, et Django s'occupe tout seul de fournir l'URL vers votre ressource. Il est déconseillé d'écrire en dur le lien complet vers les fichiers statiques, utilisez toujours {% static %}. En effet, si en production vous décidez que vos fichiers seront servis depuis l'URL assets.crepes-bretonnes.com, vous devrez modifier toutes vos URL si elles sont écrites en dur ! En revanche, si elles utilisent {% static %}, vous n'aurez qu'à éditer cette variable dans votre configuration, ce qui est tout de même bien plus pratique.
En réalité, Django ne doit pas s'occuper de servir ces fichiers, c'est à votre serveur web qu'incombe cette tâche. Cependant, en développement, étant donné que nous utilisons le serveur intégré fourni par défaut par Django, il est tout de même possible de s'arranger pour que le framework serve ces fichiers. Pour ce faire, il faut ajouter un routage spécifique à votre urls.py principal :
from django.conf.urls import patterns, include, url
from django.contrib.staticfiles.urls import staticfiles_urlpatterns
urlpatterns = patterns('',
# Ici vos règles classiques, comme vu au chapitre précédent
)
urlpatterns += staticfiles_urlpatterns()
Cette fonction va se baser sur les variables de votre fichier settings.py (URL et emplacement des fichiers) pour générer une règle de routage correcte et adaptée. Pour le déploiement des fichiers statiques en production, référez-vous à l'annexe consacrée à ce sujet.
En résumé
En pratique, et pour respecter l'architecture dictée par le framework Django, toute vue doit retourner un objet HttpResponse construit via un template.
Pour respecter cette règle, il existe des fonctions nous facilitant le travail, comme render, présentée tout au long de ce chapitre. Elle permet de construire la réponse HTML en fonction d'un fichier template et de variables.
Les templates permettent également de faire plusieurs traitements, comme afficher une variable, la transformer, faire des conditions... Attention cependant, ces traitements ont pour unique but d'afficher les données, pas de les modifier.
Il est possible de factoriser des blocs HTML (comme le début et la fin d'une page) via l'utilisation des tags {% block %} et {% extends %}.
Afin de faciliter le développement, Django possède un tag {% url %} permettant la construction d'URL en lui fournissant la vue à appeler et ses éventuels paramètres.
L'ajout de fichiers statiques dans notre template (images, CSS, JavaScript) peut se faire via l'utilisation du tag {% static %}.
Fatigué(e) de lire sur un écran ? Découvrez ce cours en livre.
Nous avons vu comment créer des vues et des templates. Cependant, ces derniers sont presque inutiles sans les modèles, car votre site n'aurait rien de dynamique. Autant créer des pages HTML statiques !
Dans ce chapitre, nous verrons les modèles qui, comme expliqué dans la première partie, sont des interfaces permettant plus simplement d'accéder à des données dans une base de données et de les mettre à jour.
Un modèle s'écrit sous la forme d'une classe et représente une table dans la base de données, dont les attributs correspondent aux champs de la table. Ceux-ci se rédigent dans le fichier models.py de chaque application. Il est important d'organiser correctement vos modèles pour que chacun ait sa place dans son application, et ne pas mélanger tous les modèles dans le même models.py. Pensez à la réutilisation et à la structure du code !
Tout modèle Django se doit d'hériter de la classe mère Model incluse dans django.db.models (sinon il ne sera pas pris en compte par le framework). Par défaut, le fichier models.py généré automatiquement importe le module models de django.db. Voici un simple exemple de modèle représentant un article de blog :
#-*- coding: utf-8 -*-
from django.db import models
class Article(models.Model):
titre = models.CharField(max_length=100)
auteur = models.CharField(max_length=42)
contenu = models.TextField(null=True)
date = models.DateTimeField(auto_now_add=True, auto_now=False, verbose_name="Date de parution")
def __unicode__(self):
"""
Cette méthode que nous définirons dans tous les modèles
nous permettra de reconnaître facilement les différents objets que nous
traiterons plus tard et dans l'administration
"""
return u"%s" % self.titre
Pour que Django puisse créer une table dans la base de données, il faut lui préciser le type des champs qu'il doit créer. Pour ce faire, le framework propose une liste de champs qu'il sera ensuite capable de retranscrire en langage SQL. Ces derniers sont également situés dans le module models.
Dans l'exemple précédent, nous avons créé quatre attributs avec trois types de champs différents. Un CharField (littéralement, un champ de caractères) a été assigné à titre et auteur. Ce champ permet d'enregistrer une chaîne de caractères, dont la longueur maximale a été spécifiée via le paramètre max_length. Dans le premier cas, la chaîne de caractères pourra être composée de 100 caractères maximum.
Le deuxième type de champ, TextField, permet lui aussi d'enregistrer des caractères, un peu comme CharField. En réalité, Django va utiliser un autre type de champ qui ne fixe pas de taille maximale à la chaîne de caractères, ce qui est très pratique pour enregistrer de longs textes.
Finalement, le champ DateTimeField prend comme valeur un objet DateTime du module datetime de la bibliothèque standard. Il est donc possible d'enregistrer autre chose que du texte !
Insistons ici sur le fait que les champs du modèle peuvent prendre plusieurs arguments. Certains sont spécifiques au champ, d'autres non. Par exemple, le champ DateTimeField possède un argument facultatif : auto_now_add. S'il est mis à True, lors de la création d'une nouvelle entrée, Django mettra automatiquement à jour la valeur avec la date et l'heure de la création de l'objet. Un autre argument du même genre existe, auto_now, qui permet à peu près la même chose, mais fera en sorte que la date soit mise à jour à chaque modification de l'entrée.
L'argument verbose_name en revanche est un argument commun à tous les champs de Django. Il peut être passé à un DateTimeField, CharField, TextField, etc. Il sera notamment utilisé dans l'administration générée automatiquement pour donner une précision quant au nom du champ. Ici, nous avons insisté sur le fait que la date correspond bien à la date de parution de l'article. Le paramètre null, lorsque mis à True, indique à Django que ce champ peut être laissé vide et qu'il est donc optionnel.
Il existe beaucoup d'autres champs disponibles, ceux-ci sont repris dans la documentation de Django. N'hésitez pas à la consulter en cas de doute ou question !
Pour que Django crée la table associée au modèle, il suffit de lancer la commande syncdb via manage.py :
python manage.py syncdb
Étant donné que c'est la première fois que vous lancez la commande, Django va créer d'autres tables plus générales (utilisateurs, groupes, sessions, etc.), comme à la figure suivante. À un moment, Django vous proposera de créer un compte administrateur. Répondez par yes et complétez les champs qu'il proposera par la suite. Nous reviendrons sur tout cela plus tard.
Aperçu des tables créées dans un outil de gestion de base de données
La table associée au modèle Article étant créée, nous pouvons commencer à jouer avec !
Fatigué(e) de lire sur un écran ? Découvrez ce cours en livre.
Django propose un interpréteur interactif Python synchronisé avec votre configuration du framework. Il est possible via celui-ci de manipuler nos modèles comme si nous étions dans une vue. Pour ce faire, il suffit d'utiliser une autre commande de l'utilitaire manage.py :
$ python manage.py shell
Python 2.7.3 (default, Apr 24 2012, 00:00:54)
[GCC 4.7.0 20120414 (prerelease)] on linux2
Type "help", "copyright", "credits" or "license" for more information.
(InteractiveConsole)
>>>
Commençons par importer le modèle que nous avons justement créé :
>>> from blog.models import Article
Pour ajouter une entrée dans la base de données, il suffit de créer une nouvelle instance de la classe Article et de la sauvegarder. Chaque instance d'un modèle correspond donc à une entrée dans la base de données. Nous pouvons spécifier la valeur des attributs directement pendant l'instanciation de classe, ou l'assigner par la suite :
Pourquoi n'avons-nous pas mis de valeur à l'attribut date du modèle ?
date est un DateTimeField dont le paramètre auto_now_add a été mis à True. Dès lors, Django se charge tout seul de le mettre à jour avec la bonne date et heure lors de la création. Cependant, il est tout de même obligatoire de remplir tous les champs pour chaque entrée sauf cas comme celui-là, sans quoi Django retournera une erreur !
Nous pouvons bien évidemment accéder aux attributs de l'objet comme pour n'importe quel autre objet Python :
>>> print article.auteur
Maxime
Pour sauvegarder l'entrée dans la base de données (les modifications ne sont pas enregistrées en temps réel), il suffit d'appeler la méthode save, de la classe mère Model dont hérite chaque modèle :
>>> article.save()
L'entrée a été créée et enregistrée !
Bien évidemment, il est toujours possible de modifier l'objet par la suite :
Il ne faut cependant pas oublier d'appeler la méthode save à chaque modification, sinon les changements ne seront pas sauvegardés.
Pour supprimer une entrée dans la base de données, rien de plus simple, il suffit d'appeler la méthode delete d'un objet :
>>> article.delete()
Nous avons vu comment créer, éditer et supprimer des entrées. Il serait pourtant également intéressant de pouvoir les obtenir par la suite, pour les afficher par exemple. Pour ce faire, chaque modèle (la classe, et non l'instance, attention !), possède plusieurs méthodes dans la sous-classe objects. Par exemple, pour obtenir toutes les entrées enregistrées d'un modèle, il faut appeler la méthode all() :
>>> Article.objects.all()
[]
Bien évidemment, étant donné que nous avons supprimé l'article créé un peu plus tôt, l'ensemble renvoyé est vide, créons rapidement deux nouvelles entrées :
>>> Article(auteur="Mathieu", titre=u"Les crêpes", contenu=u"Les crêpes c'est cool").save()
>>> Article(auteur="Maxime", titre="La Bretagne", contenu="La Bretagne c'est trop bien").save()
Cela étant fait, réutilisons la méthode all :
>>> Article.objects.all()
[<Article: Les crêpes>, <Article: La Bretagne>]
L'ensemble renvoyé par la fonction n'est pas une vraie liste, mais un QuerySet. Il s'agit d'un conteneur itérable qui propose d'autres méthodes sur lesquelles nous nous attarderons par la suite. Nous avons donc deux éléments, chacun correspondant à un des articles que nous avons créés.
Nous pouvons donc par exemple afficher les différents titres de nos articles :
>>> for article in Article.objects.all():
... print article.titre
Les crêpes
La Bretagne
Maintenant, imaginons que vous souhaitiez sélectionner tous les articles d'un seul auteur uniquement. La méthode filter a été conçue dans ce but. Elle prend en paramètre une valeur d'un ou plusieurs attributs et va passer en revue toutes les entrées de la table et ne sélectionner que les instances qui ont également la valeur de l'attribut correspondant. Par exemple :
>>> for article in Article.objects.filter(auteur="Maxime"):
... print article.titre, "par", article.auteur
La Bretagne par Maxime
Efficace ! L'autre article n'a pas été repris dans le QuerySet, car son auteur n'était pas Maxime mais Mathieu.
Une méthode similaire à filter existe, mais fait le contraire : exclude. Comme son nom l'indique, elle exclut les entrées dont la valeur des attributs passés en arguments coïncide :
>>> for article in Article.objects.exclude(auteur="Maxime"):
... print article.titre, "par", article.auteur
Les crêpes par Mathieu
Sachez que vous pouvez également filtrer ou exclure des entrées à partir de plusieurs champs : Article.objects.filter(titre="Coucou", auteur="Mathieu") renverra un QuerySet vide, car il n'existe aucun article de Mathieu intitulé « Coucou ».
Il est même possible d'aller plus loin, en filtrant par exemple les articles dont le titre doit contenir certains caractères (et non pas être strictement égal à une chaîne entière). Si nous souhaitons prendre tous les articles dont le titre comporte le mot « crêpe », il faut procéder ainsi :
>>> Article.objects.filter(titre__contains="crêpe")
[<Article: Les crêpes>]
Ces méthodes de recherche spéciales sont construites en prenant le champ concerné (ici titre), auquel nous ajoutons deux underscores « __ », suivis finalement de la méthode souhaitée. Ici, il s'agit donc de titre__contains, qui veut dire littéralement « prends tous les éléments dont le titre contient le mot passé en argument ».
D'autres méthodes du genre existent, notamment la possibilité de prendre des valeurs du champ (strictement) inférieures ou (strictement) supérieures à l'argument passé, grâce à la méthode lt (less than, plus petit que) et gt (greater than, plus grand que) :
>>> from datetime import datetime
>>> Article.objects.filter(date__lt=datetime.now())
[<Article: Les crêpes>, <Article: La Bretagne>]
Les deux articles ont été sélectionnés, car ils remplissent tous deux la condition (leur date de parution est inférieure au moment actuel). Si nous avions utilisé gt au lieu de lt, la requête aurait renvoyé un QuerySet vide, car aucun article n'a été publié après le moment actuel.
De même, il existe lte et gte qui opèrent de la même façon, la différence réside juste dans le fait que ceux-ci prendront tout élément inférieur/supérieur ou égal (lte : less than or equal, plus petit ou égal, idem pour gte).
Sur la page d'accueil de notre blog, nous souhaiterons organiser les articles par date de parution, du plus récent au plus ancien. Pour ce faire, il faut utiliser la méthode order_by. Cette dernière prend comme argument une liste de chaînes de caractères qui correspondent aux attributs du modèle :
>>> Article.objects.order_by('date')
[<Article: Les crêpes>, <Article: La Bretagne>]
Le tri se fait par ordre ascendant (ici du plus ancien au plus récent, nous avons enregistré l'article sur les crêpes avant celui sur la Bretagne). Pour spécifier un ordre descendant, il suffit de précéder le nom de l'attribut par le caractère « - » :
>>> Article.objects.order_by('-date')
[<Article: La Bretagne>, <Article: Les crêpes>]
Il est possible de passer plusieurs noms d'attributs à order_by. La priorité de chaque attribut dans le tri est déterminée par sa position dans la liste d'arguments. Ainsi, si nous trions les articles par nom et que deux d'entre eux ont le même nom, Django les départagera selon le deuxième attribut, et ainsi de suite tant que des attributs comparés seront identiques.
Accessoirement, nous pouvons inverser les éléments d'un QuerySet en utilisant la méthode reverse().
Finalement, dernière caractéristique importante des méthodes de QuerySet, elles sont cumulables, ce qui garantit une grande souplesse dans vos requêtes :
>>> Article.objects.filter(date__lt=datetime.now()).order_by('date','titre').reverse()
[<Article: La Bretagne>, <Article: Les crêpes>]
Pour terminer cette (longue) section, nous allons introduire des méthodes qui, contrairement aux précédentes, retournent un seul objet et non un QuerySet.
Premièrement, get, comme son nom l'indique, permet d'obtenir une et une seule entrée d'un modèle. Il prend les mêmes arguments que filter ou exclude. S'il ne retrouve aucun élément correspondant aux conditions, ou plus d'un seul, il retourne une erreur :
>>> Article.objects.get(titre="Je n'existe pas")
...
DoesNotExist: Article matching query does not exist. Lookup parameters were {'titre': "Je n'existe pas"}
>>> print Article.objects.get(auteur="Mathieu").titre
Les crêpes
>>> Article.objects.get(titre__contains="L")
...
MultipleObjectsReturned: get() returned more than one Article -- it returned 2! Lookup parameters were {'titre__contains': 'L'}
Dans le même style, il existe une méthode permettant de créer une entrée si aucune autre n'existe avec les conditions spécifiées. Il s'agit de get_or_create. Cette dernière va renvoyer un tuple contenant l'objet désiré et un booléen qui indique si une nouvelle entrée a été créée ou non :
Article.objects.get_or_create(auteur="Mathieu")
>>> (<Article: Les crêpes>, False)
Article.objects.get_or_create(auteur="Zozor", titre="Hi han")
>>> (<Article: Hi han>, True)
Fatigué(e) de lire sur un écran ? Découvrez ce cours en livre.
Il est souvent pratique de lier deux modèles entre eux, pour relier un article à une catégorie par exemple. Django propose tout un système permettant de simplifier grandement les différents types de liaison. Nous traiterons ce sujet dans ce sous-chapitre.
Reprenons notre exemple des catégories et des articles. Lorsque vous concevrez votre base de données, vous allez souvent faire des liens entre les classes (qui représentent nos tables SQL dans notre site), comme à la figure suivante.
Ici, un article peut être lié à une et une seule catégorie, et une catégorie peut être attributée à une infinité d'articles
Pour traduire cette relation, nous allons d'abord devoir créer un autre modèle représentant les catégories. Ce dernier est relativement simple :
class Categorie(models.Model):
nom = models.CharField(max_length=30)
def __unicode__(self):
return self.nom
Maintenant, créons la liaison depuis notre modèle Article, qu'il va falloir modifier en lui ajoutant un nouveau champ :
class Article(models.Model):
titre = models.CharField(max_length=100)
auteur = models.CharField(max_length=42)
contenu = models.TextField(null=True)
date = models.DateTimeField(auto_now_add=True, auto_now=False, verbose_name="Date de parution")
categorie = models.ForeignKey('Categorie')
def __unicode__(self):
return self.titre
Nous avons donc ajouté un champ ForeignKey. En français, ce terme est traduit par « clé étrangère ». Il va enregistrer une clé, un identifiant propre à chaque catégorie enregistrée (il s'agit la plupart du temps d'un nombre), qui permettra donc de retrouver la catégorie associée.
Nous avons modifié notre classe Article et allons rencontrer un des rares défauts de Django. En effet, si nous lancions maintenant manage.py syncdb, il créerait bien la table correspondant au modèle Categorie, mais n'ajouterait pas le champ ForeignKey dans la table Article pour autant. Pour résoudre ce problème, deux méthodes s'offrent à vous :
Vous créez manuellement le champ via l'interface SQLite et en langage SQL si vous en êtes capables, cette solution conserve vos données, mais c'est la plus difficile à implémenter ;
Vous supprimez le fichier SQLite dans lequel la base de données est enregistrée (dans la partie précédente, nous avons indiqué database.sql comme nom de fichier) et utilisez ensuite la commande manage.py syncdb. Les données dans vos tables seront perdues, mais les structures des tables seront à jour. Étant donné que nous n'avons pas de vraies données pour le moment, privilégiez cette solution.
La base de données étant prête, ouvrez à nouveau un shell via manage.py shell. Importons les modèles et créons une nouvelle catégorie :
>>> from blog.models import Categorie, Article
>>> cat = Categorie(nom=u"Crêpes")
>>> cat.save()
>>> art = Article()
>>> art.titre=u"Les nouvelles crêpes"
>>> art.auteur="Maxime"
>>> art.contenu=u"On a fait de nouvelles crêpes avec du trop bon rhum"
>>> art.categorie = cat
>>> art.save()
Pour accéder aux attributs et méthodes de la catégorie associée à l'article, rien de plus simple :
>>> print art.categorie.nom
Crêpes
Dans cet exemple, si un article ne peut avoir qu'une seule catégorie, une catégorie peut en revanche avoir plusieurs articles. Pour réaliser l'opération en sens inverse (accéder aux articles d'une catégorie depuis cette dernière), une sous-classe s'est créée toute seule avec la ForeignKey :
>>> cat.article_set.all()
[<Article: Les nouvelles crêpes>]
Le nom que prendra une relation en sens inverse est composé du nom du modèle source (qui a la ForeignKey comme attribut), d'un seul underscore « _ » et finalement du mot set qui signifie en anglais « ensemble ». Nous accédons donc ici à l'ensemble des articles d'une catégorie. Cette relation opère exactement comme n'importe quelle sous-classe objects d'un modèle, et renvoie ici tous les articles de la catégorie. Nous pouvons utiliser les méthodes que nous avons vues précédemment : all, filter, exclude, order_by…
Point important : il est possible d'accéder aux attributs du modèle lié par une clé étrangère depuis un filter, exclude, order_by… Nous pourrions ici par exemple filtrer tous les articles dont le titre de la catégorie possède un certain mot :
>>> Article.objects.filter(categorie__nom__contains=u"crêpes")
[<Article: Les nouvelles crêpes>]
Accéder à un élément d'une clé étrangère se fait en ajoutant deux underscores « __ », comme avec les méthodes de recherche spécifiques, suivis du nom de l'attribut recherché. Comme montré dans l'exemple, nous pouvons encore ajouter une méthode spéciale de recherche sans aucun problème !
Un autre type de liaison existe, très similaire au principe des clés étrangères : le OneToOneField. Ce dernier permet de lier un modèle à un autre tout aussi facilement, et garantit qu'une fois la liaison effectuée plus aucun autre objet ne pourra être associé à ceux déjà associés. La relation devient unique. Si nous avions utilisé notre exemple avec un OneToOneField, chaque catégorie ne pourrait avoir qu'un seul article associé, et de même pour chaque article.
Un autre bref exemple :
class Moteur(models.Model):
nom = models.CharField(max_length=25)
def __unicode__(self):
return self.nom
class Voiture(models.Model):
nom = models.CharField(max_length=25)
moteur = models.OneToOneField(Moteur)
def __unicode__(self):
return self.nom
N'oubliez pas de faire un manage.py syncdb !
Nous avons deux objets, un moteur nommé « Vroum » et une voiture nommée « Crêpes-mobile » qui est liée au moteur. Nous pouvons accéder du moteur à la voiture ainsi, depuis manage.py shell :
Ici, le OneToOneField a créé une relation en sens inverse qui ne va plus renvoyer un QuerySet, mais directement l'élément concerné (ce qui est logique, celui-ci étant unique). Cette relation inverse prendra simplement le nom du modèle, qui n'est donc plus suivi par _set.
Sachez qu'il est possible de changer le nom de la variable créée par la relation inverse (précédemment article_set et moteur). Pour ce faire, il faut utiliser l'argument related_name du ForeignKey ou OneToOneField et lui passer une chaîne de caractères désignant le nouveau nom de la variable (à condition que cette chaîne représente bien un nom de variable valide !). Cette solution est notamment utilisée en cas de conflit entre noms de variables. Accessoirement, il est même possible de désactiver la relation inverse en donnant related_name='+'.
Finalement, dernier type de liaison, le plus complexe : le ManyToManyField (traduit littéralement, « plusieurs-à-plusieurs »). Reprenons un autre exemple simple : nous construisons un comparateur de prix pour les ingrédients nécessaires à la réalisation de crêpes. Plusieurs vendeurs proposent plusieurs produits, parfois identiques, à des prix différents.
Il nous faudra trois modèles :
class Produit(models.Model):
nom = models.CharField(max_length=30)
def __unicode__(self):
return self.nom
class Vendeur(models.Model):
nom = models.CharField(max_length=30)
produits = models.ManyToManyField(Produit, through='Offre')
def __unicode__(self):
return self.nom
class Offre(models.Model):
prix = models.IntegerField()
produit = models.ForeignKey(Produit)
vendeur = models.ForeignKey(Vendeur)
def __unicode__(self):
return "{0} vendu par {1}".format(self.produit, self.vendeur)
Explications ! Les modèles Produit et Vendeur sont classiques, à l'exception du fait que nous avons utilisé un ManyToManyField dans Vendeur, au lieu d'une ForeignKey ou de OneToOneField comme précédemment. La nouveauté, en revanche, est bien le troisième modèle : Offre. C'est celui-ci qui fait le lien entre Produit et Vendeur et permet d'ajouter des informations supplémentaires sur la liaison (ici le prix, caractérisé par un IntegerField qui enregistre un nombre).
Un ManyToManyField va toujours créer une table intermédiaire qui enregistrera les clés étrangères des différents objets des modèles associés. Nous pouvons soit laisser Django s'en occuper tout seul, soit la créer nous-mêmes pour y ajouter des attributs supplémentaires (pour rappel, ici nous ajoutons le prix). Dans ce deuxième cas, il faut spécifier le modèle faisant la liaison via l'argument through du ManyToManyField et ne surtout pas oublier d'ajouter des ForeignKey vers les deux modèles qui seront liés.
Créez les tables via syncdb et lancez un shell. Enregistrons un vendeur et deux produits :
Désormais, la gestion du ManyToMany se fait de deux manières différentes. Soit nous spécifions manuellement la table intermédiaire, soit nous laissons Django le faire. Étant donné que nous avons opté pour la première méthode, tout ce qu'il reste à faire, c'est créer un nouvel objet Offre qui reprend le vendeur, le produit et son prix :
Si nous avions laissé Django générer automatiquement la table, il aurait fallu procéder ainsi :
vendeur.produits.add(p1,p2)
Pour supprimer une liaison entre deux objets, deux méthodes se présentent encore. Avec une table intermédiaire spécifiée manuellement, il suffit de supprimer l'objet faisant la liaison (supprimer un objet Offre ici), autrement nous utilisons une autre méthode du ManyToManyField :
vendeur.produits.remove(p1) # Nous avons supprimé p1, il ne reste plus que p2 qui est lié au vendeur
Ensuite, pour accéder aux objets du modèle source (possédant la déclaration du ManyToManyField, ici Vendeur) associés au modèle destinataire (ici Produit), rien de plus simple, nous obtenons à nouveau un QuerySet :
Finalement, pour supprimer toutes les liaisons d'un ManyToManyField, que la table intermédiaire soit générée automatiquement ou manuellement, nous pouvons appeler la méthode clear :
Nous avons vu comment utiliser les modèles dans la console, et d'une manière plutôt théorique. Nous allons ici introduire les modèles dans un autre milieu plus utile : les vues.
Afficher les articles du blog
Pour afficher les articles de notre blog, il suffit de reprendre une de nos requêtes précédentes, et l'incorporer dans une vue. Dans notre template, nous ajouterons un lien vers notre article pour pouvoir le lire en entier. Le problème qui se pose ici, et que nous n'avons pas soulevé avant, est le choix d'un identifiant. En effet, comment passer dans l'URL une information facile à transcrire pour désigner un article particulier ?
En réalité, nos modèles contiennent plus d'attributs et de champs SQL que nous en déclarons. Nous pouvons le remarquer depuis la commande python manage.py sql blog, qui renvoie la structure SQL des tables créées :
BEGIN;
CREATE TABLE "blog_categorie" (
"id" integer NOT NULL PRIMARY KEY,
"nom" varchar(30) NOT NULL
)
;
CREATE TABLE "blog_article" (
"id" integer NOT NULL PRIMARY KEY,
"titre" varchar(100) NOT NULL,
"auteur" varchar(42) NOT NULL,
"contenu" text NOT NULL,
"date" datetime NOT NULL,
"categorie_id" integer NOT NULL REFERENCES "blog_categorie" ("id")
)
;
COMMIT;
Note : nous n'avons sélectionné ici que les modèles Categorie et Article.
Chaque table contient les attributs définis dans le modèle, mais également un champ id qui est un nombre auto-incrémenté (le premier article aura l'ID 1, le deuxième l'ID 2, etc.), et donc unique ! C'est ce champ qui sera utilisé pour désigner un article particulier. Passons à quelque chose de plus concret, voici un exemple d'application :
from django.http import Http404
from django.shortcuts import render
from blog.models import Article
def accueil(request):
""" Afficher tous les articles de notre blog """
articles = Article.objects.all() # Nous sélectionnons tous nos articles
return render(request, 'blog/accueil.html', {'derniers_articles':articles})
def lire(request, id):
""" Afficher un article complet """
pass # Le code de cette fonction est donné un peu plus loin.
<h1>Bienvenue sur le blog des crêpes bretonnes !</h1>
{% for article in derniers_articles %}
<div class="article">
<h3>{{ article.titre }}</h3>
<p>{{ article.contenu|truncatewords_html:80 }}</p>
<p><a href="{% url "blog.views.lire" article.id %}">Lire la suite</a>
</div>
{% empty %}
<p>Aucun article.</p>
{% endfor %}
Nous récupérons tous les articles via la méthode objects.all() et nous renvoyons la liste au template. Dans le template, il n'y a rien de fondamentalement nouveau non plus : nous affichons un à un les articles. Le seul point nouveau est celui que nous avons cité précédemment : nous faisons un lien vers l'article complet, en jouant avec le champ id de la table SQL. Si vous avez correctement suivi le sous-chapitre sur les manipulations d'entrées et tapé nos commandes, vous devriez avoir un article enregistré.
Afficher un article précis
L'affichage d'un article précis est plus délicat : il faut vérifier que l'article demandé existe, et renvoyer une erreur 404 si ce n'est pas le cas. Notons déjà qu'il n'y a pas besoin de vérifier si l'ID précisé est bel et bien un nombre, cela est déjà spécifié dans urls.py.
C'est assez verbeux, or les développeurs Django sont très friands de raccourcis. Un raccourci particulièrement utile ici est get_object_or_404, permettant de récupérer un objet selon certaines conditions, ou renvoyer la page d'erreur 404 si aucun objet n'a été trouvé. Le même raccourci existe pour obtenir une liste d'objets : get_list_or_404.
À cette adresse, la vue de notre article (l'ID à la fin est variable, attention) : http://127.0.0.1:8000/blog/article/2
Des URL plus esthétiques
Comme vous pouvez le voir, nos URL contiennent pour le moment un ID permettant de déterminer quel article il faut afficher. C'est relativement pratique, mais cela a l'inconvénient de ne pas être très parlant pour l'utilisateur. Pour remédier à cela, nous voyons de plus en plus fleurir sur le web des adresses contenant le titre de l'article réécrit. Par exemple, le Site du Zéro emploie cette technique à plusieurs endroits, comme avec l'adresse de ce cours : http://www.siteduzero.com/informatique/tutoriels/creez-vos-applications-web-avec-django-1. Nous pouvons y identifier la chaîne « creez-vos-applications-web-avec-django » qui nous permet de savoir de quoi parle le lien, sans même avoir cliqué dessus. Cette chaîne est couramment appelée un slug. Et pour définir ce terme barbare, rien de mieux que Wikipédia :
Citation : Wikipédia - Slug (journalisme)
Un slug est en journalisme un label court donné à un article publié, ou en cours d'écriture. Il permet d'identifier l'article tout au long de sa production et dans les archives. Il peut contenir des informations sur l'état de l'article, afin de les catégoriser.
Nous allons intégrer la même chose à notre système de blog. Pour cela, il existe un type de champ un peu spécial dans les modèles : le SlugField. Il permet de stocker une chaîne de caractères, d'une certaine taille maximale. Ainsi, notre modèle devient le suivant :
class Article(models.Model):
titre = models.CharField(max_length=100)
slug = models.SlugField(max_length=100)
auteur = models.CharField(max_length=42)
contenu = models.TextField(null=True)
date = models.DateTimeField(auto_now_add=True, auto_now=False, verbose_name="Date de parution")
def __unicode__(self):
return self.titre
N'oubliez pas de mettre à jour la structure de votre table, comme nous l'avons déjà expliqué précédemment, et de créer une nouvelle entrée à partir de manage.py shell !
Désormais, nous pouvons aisément ajouter notre slug dans l'URL, en plus de l'ID lors de la construction d'une URL. Nous pouvons par exemple utiliser des URL comme celle-ci : /blog/article/1-titre-de-l-article. La mise en œuvre est également rapide à mettre en place :
<p><a href="{% url "blog.views.lire" article.id article.slug %}">Lire la suite</a>
L'inconvénient ici est qu'il faut renseigner pour le moment le slug à la main à la création d'un article. Nous verrons au chapitre suivant qu'il est possible d'automatiser son remplissage.
En résumé
Un modèle représente une table dans la base de données et ses attributs correspondent aux champs de la table.
Tout modèle Django hérite de la classe mère Model incluse dans django.db.models.
Chaque attribut du modèle est typé et décrit le contenu du champ, en fonction de la classe utilisée : CharField, DateTimeField, IntegerField…
Les requêtes à la base de données sur le modèle Article peuvent être effectuées via des appels de méthodes sur Article.objects, tels que all(), filter(nom="Un nom") ou encore order_by('date').
L'enregistrement et la mise à jour d'articles dans la base de données se fait par la manipulation d'objets de la classe Article, et via l'appel à la méthode save().
Deux modèles peuvent être liés ensemble par le principe des clés étrangères. La relation dépend cependant des contraintes de multiplicité qu'il faut respecter : OneToOneField, ManyToManyField.
Il est possible d'afficher les attributs d'un objet dans un template de la même façon qu'en Python via des appels du type article.nom. Il est également possible d'itérer une liste d'objets, pour afficher une liste d'articles par exemple.
Fatigué(e) de lire sur un écran ? Découvrez ce cours en livre.
Sur un bon nombre de sites, l'interface d'administration est un élément capital à ne pas négliger lors du développement. C'est cette partie qui permet en effet de gérer les diverses informations disponibles : les articles d'un blog, les comptes utilisateurs, etc. Un des gros points forts de Django est que celui-ci génère de façon automatique l'administration en fonction de vos modèles. Celle-ci est personnalisable à souhait en quelques lignes et est très puissante.
Nous verrons dans ce chapitre comment déployer l'administration et la personnaliser.
L'administration Django est optionnelle : il est tout à fait possible de développer un site sans l'utiliser. Pour cette raison, elle est placée dans le module django.contrib, contenant un ensemble d'extensions fournies par Django, réutilisables dans n'importe quel projet. Ces modules sont bien pensés et vous permettent d'éviter de réinventer la roue à chaque fois. Nous allons étudier ici le module django.contrib.admin qui génère l'administration. Il existe toutefois bien d'autres modules, dont certains que nous aborderons par la suite : django.contrib.messages (gestion de messages destinés aux visiteurs), django.contrib.auth (système d'authentification et de gestion des utilisateurs), etc.
Accédons à cette administration !
Import des modules
Ce module étant optionnel, il est nécessaire d'ajouter quelques lignes dans notre fichier de configuration pour pouvoir en profiter. Ouvrons donc notre fichier settings.py. Comme vu précédemment, la variable INSTALLED_APPS permet à Django de savoir quels sont les modules à charger au démarrage du serveur. Si django.contrib.admin n'apparaît pas, ajoutez-le (l'ordre dans la liste n'a pas d'importance). L'administration nécessite toutefois quelques dépendances pour fonctionner, également fournies dans django.contrib. Ces dépendances sont :
django.contrib.auth
django.contrib.contenttypes
django.contrib.sessions
qui sont normalement incluses de base dans INSTALLED_APPS. Au final, vous devriez avoir une variable INSTALLED_APPS qui ressemble à ceci :
INSTALLED_APPS = (
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.sites',
'django.contrib.messages',
'django.contrib.staticfiles',
'blog', #Nous avions ajouté celui-ci lors de l'ajout de l'application
# Uncomment the next line to enable the admin:
'django.contrib.admin',
# Uncomment the next line to enable admin documentation:
# 'django.contrib.admindocs',
)
Finalement, le module admin nécessite aussi l'import de middlewares, normalement inclus par défaut également :
Sauvegardez le fichier settings.py. Désormais, lors du lancement du serveur, le module contenant l'administration sera importé.
Mise à jour de la base de données
Pour fonctionner, il faut créer de nouvelles tables dans la base de données, qui serviront à enregistrer les actions des administrateurs, définir les droits de chacun, etc. Pour ce faire, il faut procéder comme avec les modèles et utiliser la commande suivante : python manage.py syncdb. À la première exécution, cette commande vous demandera de renseigner des informations pour créer un compte super-utilisateur, qui sera au début le seul compte à pouvoir accéder à l'administration. Cette opération commence notamment par la directive suivante :
You just installed Django's auth system, which means you don't have any superusers defined.
Répondez yes et insérez les informations utilisateur que Django vous demande.
Si vous sautez cette étape, il sera toujours possible de (re)créer ce compte via la commande python manage.py createsuperuser.
Intégration à notre projet : définissons-lui une adresse
Enfin, tout comme pour nos vues, il est nécessaire de dire au serveur « Quand j'appelle cette URL, redirige-moi vers l'administration. » En effet, pour l'instant nous avons bel et bien importé le module, mais nous ne pouvons pas encore y accéder.
Comme pour les vues, cela se fait à partir d'un urls.py. Ouvrez le fichier crepes_bretonnes/urls.py. Par défaut, Django a déjà indiqué plusieurs lignes pour l'administration, mais celles-ci sont commentées. Au final, après avoir décommenté ces lignes, votre fichier urls.py devrait ressembler à ceci :
from django.conf.urls import patterns, include, url
# Uncomment the next two lines to enable the admin:
from django.contrib import admin
admin.autodiscover()
urlpatterns = patterns('',
# D'autres éventuelles directives.
# Uncomment the next line to enable the admin:
url(r'^admin/', include(admin.site.urls)),
)
Nous voyons que par défaut, l'administration sera disponible à l'adresse http://localhost:8000/admin/. Une fois les lignes décommentées, lancez le serveur Django (ou relancez-le s'il est déjà lancé). Vous pouvez dès lors accéder à l'administration depuis l'URL définie (voir la figure suivante), il suffira juste de vous connecter avec le nom d'utilisateur et le mot de passe que vous avez spécifiés lors du syncdb ou createsuperuser.
L'écran de connexion de l'administration
Fatigué(e) de lire sur un écran ? Découvrez ce cours en livre.
Une fois que vous avez saisi vos identifiants de super-utilisateur, vous devez arriver sur une page semblable à la figure suivante.
Accueil de l'administration
C'est encore un peu vide, mais ne vous inquiétez pas, nous allons bientôt pouvoir manipuler nos modèles Article et Catégorie, rédigés dans le chapitre précédent. Tout d'abord, faisons un petit tour des fonctionnalités disponibles. Sur cette page, vous avez la liste des modèles que vous pouvez gérer. Ces modèles sont au nombre de 3 : Groupes, Utilisateurs et Sites. Ce sont les modèles par défaut. Chaque modèle possède ensuite une interface qui permet de réaliser les 4 opérations de base « CRUD » : Create, Read, Update, Delete (littéralement créer, lire, mettre à jour, supprimer). Pour ce faire, allons dans l'administration des comptes sur notre site, en cliquant sur Utilisateurs. Pour le moment, vous n'avez logiquement qu'un compte dans la liste, le vôtre, ainsi que vous pouvez le voir sur la figure suivante.
Liste des utilisateurs
C'est à partir d'ici que nous pouvons constater la puissance de cette administration : sans avoir écrit une seule ligne de code, il est possible de manipuler la liste des utilisateurs dans tous les sens : la filtrer selon certains paramètres, la trier avec certains champs, effectuer des actions sur certaines lignes, etc. Pour essayer ces opérations, nous allons d'abord créer un deuxième compte utilisateur. Il suffit de cliquer sur le bouton Ajouter utilisateur, disponible en haut à droite. Le premier formulaire vous demande de renseigner le nom d'utilisateur et le mot de passe. Nous pouvons déjà remarquer sur la figure suivante que les formulaires peuvent gérer des contraintes, et l'affichage d'erreurs.
Formulaire de création de comptes, après le validateur avec erreur
Une fois cela validé, vous accédez directement à un formulaire plus complet, permettant de renseigner plus d'informations sur l'utilisateur qui vient d'être créé : ses informations personnelles, mais aussi ses droits sur le site. Django fournit de base une gestion précise des droits, par groupe et par utilisateur, offrant souplesse et rapidité dans l'attribution des droits. Ainsi, ici nous pouvons voir qu'il est possible d'assigner un ou plusieurs groupes à l'utilisateur, et des permissions spécifiques. D'ailleurs, vous pouvez créer un groupe sans quitter cette fenêtre en cliquant sur le « + » vert à côté des choix (qui est vide chez vous pour le moment) ! Également, deux champs importants sont Statut équipe et Statut super-utilisateur : le premier permet de définir si l'utilisateur peut accéder au panel d'administration, et le second de donner « les pleins pouvoirs » à l'utilisateur (voir la figure suivante).
Exemple d'édition des permissions, ici j'ai créé deux groupes avant d'éditer l'utilisateur
Une fois que vous avez fini de gérer l'utilisateur, vous êtes redirigés vers la liste de tout à l'heure, avec une ligne en plus. Désormais, vous pouvez tester le tri, et les filtres qui sont disponibles à la droite du tableau ! Nous verrons d'ailleurs plus tard comment définir les champs à afficher, quels filtres utiliser, etc.
En définitive, pour finir ce rapide tour des fonctionnalités, vous avez peut-être remarqué la présence d'un bouton Historique en haut de chaque fiche utilisateur ou groupe. Ce bouton est très pratique, puisqu'il vous permet de suivre les modifications apportées, et donc de voir rapidement l'évolution de l'objet sur le site. En effet, chaque action effectuée via l'administration est inscrite dans un journal des actions. De même, sur l'index vous avez la liste de vos dernières actions, vous permettant de voir ce que vous avez fait récemment, et d'accéder rapidement aux liens, en cas d'erreur par exemple (voir la figure suivante).
Historique des modifications d'un objet utilisateur
Fatigué(e) de lire sur un écran ? Découvrez ce cours en livre.
Pour le moment, nous avons vu comment manipuler les données des objets de base de Django, ceux concernant les utilisateurs. Il serait pratique désormais de faire de même avec nos propres modèles. Comme dit précédemment, l'administration est auto-générée : vous n'aurez pas à écrire beaucoup de lignes pour obtenir le même résultat que ci-avant. En réalité, quatre lignes suffisent : créez un fichier admin.py dans le répertoire blog/ et insérez ces lignes :
from django.contrib import admin
from blog.models import Categorie, Article
admin.site.register(Categorie)
admin.site.register(Article)
Ici, nous indiquons à Django de prendre en compte les modèles Article et Catégorie dans l'administration. Rafraîchissez la page (relancez le serveur Django si nécessaire) et vous devez voir apparaître une nouvelle section, pour notre blog, semblable à la figure suivante.
La deuxième section nous permet enfin de gérer notre blog !
Les fonctionnalités sont les mêmes que celles pour les utilisateurs : nous pouvons éditer des articles, des catégories, les supprimer, consulter l'historique, etc. Vous pouvez désormais créer vos articles depuis cette interface et voir le résultat depuis les vues que nous avons créées précédemment. Comme vous pouvez le voir, l'administration prend en compte la clé étrangère de la catégorie.
Comment cela fonctionne-t-il ?
Au lancement du serveur, le framework charge le fichier urls.py et tombe sur la ligne admin.autodiscover(). Cette méthode ira chercher dans chaque application installée (celles qui sont listées dans INSTALLED_APPS) un fichier admin.py, et si celui-ci existe exécutera son contenu. Ainsi, si nous souhaitons activer l'administration pour toutes nos applications, il suffit de créer un fichier admin.py dans chacune, et d'appeler la méthode register() de admin.site sur chacun de nos modèles. Nous pouvons alors deviner que le module django.contrib.auth contient son propre fichier admin.py, qui génère l'administration des utilisateurs et des groupes. De même, le module Site, que nous avons ignoré depuis le début, fonctionne de la même façon. Ce module sert à pouvoir faire plusieurs sites, avec le même code. Il est rarement utilisé, et si vous souhaitez le désactiver, il vous suffit de commenter la ligne 5 du code ci-dessous, dans votre settings.py :
INSTALLED_APPS = (
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.sites',
'django.contrib.messages',
'django.contrib.staticfiles',
# Uncomment the next line to enable the admin:
'django.contrib.admin',
'blog',
# Uncomment the next line to enable admin documentation:
# 'django.contrib.admindocs',
)
Fatigué(e) de lire sur un écran ? Découvrez ce cours en livre.
Avant tout, créez quelques articles depuis l'administration, si ce n'est déjà fait. Cela vous permettra de tester tout au long de ce chapitre les différents exemples qui seront donnés.
Modifier l'aspect des listes
Dans un premier temps, nous allons voir comment améliorer la liste. En effet, pour le moment, nos listes sont assez vides, comme vous pouvez le constater sur la figure suivante.
Notre liste d'articles, avec uniquement le titre comme colonne
Le tableau ne contient qu'une colonne contenant le titre de notre article. Cette colonne n'est pas due au hasard : c'est en réalité le résultat de la méthode __unicode__ que nous avons définie dans notre modèle.
class Article(models.Model):
titre = models.CharField(max_length=100)
auteur = models.CharField(max_length=42)
slug = models.SlugField(max_length=100)
contenu = models.TextField()
date = models.DateTimeField(auto_now_add=True, auto_now=False, verbose_name="Date de parution")
categorie = models.ForeignKey(Categorie)
def __unicode__(self):
return self.titre
Ce résultat par défaut est assez utile, mais nous aimerions pouvoir gérer plus facilement nos articles : les trier selon certains champs, filtrer par catégorie, etc. Pour ce faire, nous devons créer une nouvelle classe dans notre fichier admin.py, contenant actuellement ceci :
from django.contrib import admin
from blog.models import Categorie, Article
admin.site.register(Categorie)
admin.site.register(Article)
Nous allons donc créer une nouvelle classe pour chaque modèle. Notre classe héritera de admin.ModelAdmin et aura principalement 5 attributs, listés dans le tableau suivant :
Nom de l'attribut
Utilité
list_display
Liste des champs du modèle à afficher dans le tableau
list_filter
Liste des champs à partir desquels nous pourrons filtrer les entrées
date_hierarchy
Permet de filtrer par date de façon intuitive
ordering
Tri par défaut du tableau
search_fields
Configuration du champ de recherche
Nous pouvons dès lors rédiger notre première classe adaptée au modèle Article :
Le tableau affiche les champs titre, auteur et date. Notez que les en-têtes sont nommés selon leur attribut verbose_name respectif.
Il est possible de filtrer selon les différents auteurs et la catégorie des articles (menu de droite).
L'ordre par défaut est la date de parution, dans l'ordre croissant (du plus ancien au plus récent).
Il est possible de chercher les articles contenant un mot, soit dans leur titre, soit dans leur contenu.
Enfin, il est possible de voir les articles publiés sur une certaine période (première ligne au-dessus du tableau).
Désormais, il faut spécifier à Django de prendre en compte ces données pour le modèle Article. Pour ce faire, modifions la ligne admin.site.register(Article), en ajoutant un deuxième paramètre :
admin.site.register(Article, ArticleAdmin)
Avec ce deuxième argument, Django prendra en compte les règles qui ont été spécifiées dans la classe ArticleAdmin.
Vous pouvez maintenant observer le résultat sur la figure suivante :
La même liste, bien plus complète, et plus pratique !
Les différents changements opérés sont désormais visibles. Vous pouvez bien sûr modifier selon vos besoins : ajouter le champ Catégorie dans le tableau, changer le tri…
Pour terminer, nous allons voir comment créer des colonnes plus complexes. Il peut arriver que vous ayez envie d'afficher une colonne après un certain traitement. Par exemple, afficher les 40 premiers caractères de notre article. Pour ce faire, nous allons devoir créer une méthode dans notre ModelAdmin, qui va se charger de renvoyer ce que nous souhaitons, et la lier à notre list_display.
Créons tout d'abord notre méthode. Celles de notre ModelAdmin auront toujours la même structure :
def apercu_contenu(self, article):
"""
Retourne les 40 premiers caractères du contenu de l'article. S'il
y a plus de 40 caractères, il faut ajouter des points de suspension.
"""
text = article.contenu[0:40]
if len(article.contenu) > 40:
return '%s...' % text
else:
return text
La méthode prend en argument l'instance de l'article, et nous permet d'accéder à tous ses attributs. Ensuite, il suffit d'exécuter quelques opérations, puis de renvoyer une chaîne de caractères. Il faut ensuite intégrer cela dans notre ModelAdmin.
Et comment l'ajoute-t-on à notre list_display ?
Il faut traiter la fonction comme un champ. Il suffit donc d'ajouter 'apercu_contenu' à la liste, Django s'occupe du reste. Pour ce qui est de l'en-tête, il faudra par contre ajouter une ligne supplémentaire pour spécifier le titre de la colonne :
# -*- coding:utf-8 -*-
from django.contrib import admin
from blog.models import Categorie, Article
class ArticleAdmin(admin.ModelAdmin):
list_display = ('titre', 'categorie', 'auteur', 'date', 'apercu_contenu')
list_filter = ('auteur','categorie',)
date_hierarchy = 'date'
ordering = ('date',)
search_fields = ('titre', 'contenu')
def apercu_contenu(self, article):
"""
Retourne les 40 premiers caractères du contenu de l'article. S'il
y a plus de 40 caractères, il faut ajouter des points de suspension.
"""
text = article.contenu[0:40]
if len(article.contenu) > 40:
return '%s...' % text
else:
return text
# En-tête de notre colonne
apercu_contenu.short_description = u'Aperçu du contenu'
admin.site.register(Categorie)
admin.site.register(Article, ArticleAdmin)
Nous obtenons notre nouvelle colonne avec les premiers mots de chaque article (voir la figure suivante).
La liste des articles est accessible depuis l'administration
Modifier le formulaire d'édition
Nous allons désormais nous occuper du formulaire d'édition. Pour le moment, comme vous pouvez le voir sur la figure suivante, nous avons un formulaire affichant tous les champs, hormis la date de publication (à cause du paramètre auto_now_add=True dans le modèle).
Le formulaire d'édition d'un article par défaut
L'ordre d'apparition des champs dépend actuellement de l'ordre de déclaration dans notre modèle. Nous allons ici séparer le contenu des autres champs. Tout d'abord, modifions l'ordre via un nouvel attribut dans notre ModelAdmin : fields. Cet attribut prend une liste de champs, qui seront affichés dans l'ordre souhaité. Cela nous permettra de cacher des champs (inutile dans le cas présent) et, bien évidemment, de changer leur ordre :
Nous observons peu de changements, à part le champ Catégorie qui est désormais au-dessus de Contenu (voir la figure suivante).
Notre formulaire, avec une nouvelle organisation des champs
Pour le moment, notre formulaire est dans un unique fieldset (ensemble de champs). Conséquence : tous les champs sont les uns à la suite des autres, sans distinction. Nous pouvons hiérarchiser cela en utilisant un attribut plus complexe que fields. À titre d'exemple, nous allons mettre les champs titre, auteur et categorie dans un fieldset et contenu dans un autre.
Nos deux éléments dans le tuple fieldset, qui correspondent à nos deux fieldsets distincts.
Chaque élément contient un tuple contenant exactement deux informations : son nom, et les informations sur son contenu, sous forme de dictionnaire.
Ce dictionnaire contient trois types de données :
fields: liste des champs à afficher dans le fieldset ;
description : une description qui sera affichée en haut du fieldset, avant le premier champ ;
classes : des classes CSS supplémentaires à appliquer sur le fieldset (par défaut il en existe trois : wide, extrapretty et collapse).
Ici, nous avons donc séparé les champs en deux fieldsets et affiché quelques informations supplémentaires pour aider à la saisie. Au final, nous avons le fichier admin.py suivant :
# -*- coding:utf-8 -*-
from django.contrib import admin
from blog.models import Categorie, Article
class ArticleAdmin(admin.ModelAdmin):
# Configuration de la liste d'articles
list_display = ('titre', 'categorie', 'auteur', 'date')
list_filter = ('auteur','categorie', )
date_hierarchy = 'date'
ordering = ('date', )
search_fields = ('titre', 'contenu')
# Configuration du formulaire d'édition
fieldsets = (
# Fieldset 1 : meta-info (titre, auteur…)
('Général', {
'classes': ['collapse',],
'fields': ('titre', 'slug', 'auteur', 'categorie')
}),
# Fieldset 2 : contenu de l'article
('Contenu de l\'article', {
'description': u'Le formulaire accepte les balises HTML. Utilisez-les à bon escient !',
'fields': ('contenu', )
}),
)
# Colonnes personnalisées
def apercu_contenu(self, article):
"""
Retourne les 40 premiers caractères du contenu de l'article. S'il
y a plus de 40 caractères, il faut rajouter des points de suspension.
"""
text = article.contenu[0:40]
if len(article.contenu) > 40:
return '%s...' % text
else:
return text
apercu_contenu.short_description = 'Aperçu du contenu'
admin.site.register(Categorie)
admin.site.register(Article, ArticleAdmin)
… qui donne la figure suivante.
Notre formulaire, mieux présenté qu'avant
Retour sur notre problème de slug
Souvenez-vous, au chapitre précédent nous avons parlé des slugs, ces chaînes de caractères qui permettent d'identifier un article dans notre URL. Dans notre zone d'administration, ce champ est actuellement ignoré… Nous souhaitons toutefois le remplir, mais en plus que cela se fasse automatiquement !
Nous avons notre champ slug que nous pouvons désormais éditer à la main. Mais nous pouvons aller encore plus loin, en ajoutant une option qui remplit instantanément ce champ grâce à un script JavaScript. Pour ce faire, il existe un attribut aux classes ModelAdmin nommé prepopulated_fields. Ce champ a pour principal usage de remplir les champs de type SlugField en fonction d'un ou plusieurs autres champs :
prepopulated_fields = {'slug': ('titre', ), }
Ici, notre champ slug est rempli automatiquement en fonction du champ titre. Il est possible bien entendu de concaténer plusieurs chaînes, si vous voulez par exemple faire apparaître l'auteur (voir la figure suivante).
Exemple d'utilisation de prepopulated_fields
En résumé
L'administration est un outil optionnel : il est possible de ne pas l'utiliser. Une fois activée, de très nombreuses options sont automatisées, sans qu'il y ait besoin d'ajouter une seule ligne de code !
Ce module requiert l'usage de l'authentification, et la création d'un super-utilisateur afin d'en restreindre l'accès aux personnes de confiance.
De base, l'administration permet la gestion complète des utilisateurs, de groupes et des droits de chacun, de façon très fine.
L'administration d'un modèle créé dans une de nos applications est possible en l'enregistrant dans le module d'administration, via admin.site.register(MonModele) dans le fichier admin.py de l'application.
Il est également possible de personnaliser cette interface pour chaque module, en précisant ce qu'il faut afficher dans les tableaux de listes, ce qui peut être édité, etc.