🎵 Django#

Premier projet Django#

Note

Cette section est votre premier pas dans l’univers de Django. Ce premier projet Django va vous permettre de comprendre la structure et comment bien débuter avec le framework.

Un projet Django ?#

Un projet Django est l’entité de base qui représente une instance du framework et regroupe un ensemble de configurations et d’applications.

Avertissement

Ne confondez pas projet et application. Le projet est une coquille quasi vide qui permet d’utiliser des applications (créez par vous-même ou par d’autres). Les applications Django peuvent être utilisées dans différents projets. On ne peut pas utiliser une application sans projet.

Par exemple une application forum peut être utilisé dans différents projets.

Installation et création du projet#

L’installation de Django se fait généralement très rapidement, et ne présente pas grande difficulté. Assurez-vous néanmoins de suivre les étapes suivantes.

La méthode conseillée pour installer Django consiste à utliser l’outil pip dont la vocation est d’installer des packages Python.

  • Commencez donc par installer pip

  • Dans un terminal tapez: pip install django

Vérification de l’installation de Django

Pour vérifier que votre installation a bien fonctionné, ouvrez un terminal, lancez Python et essayez d’importer le module django :

import django

django.VERSION

Création du projet GamesPlus#

Note

Notre premier projet Django consistera en la conception et le développement d’un site web de review de jeu vidéo. Cette page est en quelque sorte notre cahier des charges « simplifié ».

Bien entendu comme il s’agit d’un projet pédagogique et que nous allons le réaliser en quelques heures.

Note

Django est livré avec une CLI utilitaire bien pratique qui vous permettra de créer automatiquement l’arborescence de votre projet. Cette commande (qui est en réalité un script Python) se nomme django-admin et s’utilise de la sorte:

$ django-admin startproject my_project

Nous allons donc taper la commande suivante:

$ django-admin startproject games_plus

C’est fait, notre projet est créé ! En d’autres termes, un répertoire games_plus a été créé et il contient quelques fichiers utiles au fonctionnement de notre projet. Voyons quel est son contenu:

games_plus
|--- manage.py
|--- games_plus
|    |--- __init__.py
|    |--- settings.py
|    |--- urls.py
|    |--- wsgi.py

manage.py

script qui permet d’exécuter des commandes utiles au sein du projet. Son utilisation sera détaillé par la suite

games_plus

sous-répertoire qui contient les fichiers propres à notre projet:

settings.py

configuration globale du projet

urls.py

contrôleur frontal du projet, c’est le chef de gare qui éguillera toutes les requêtes vers les bons contrôleurs

wsgi.py

fichier de configuration relatif au serveur qui exécutera notre projet suivant l’interface WSGI (Web Server Gateway Interface)

Indication

Notre répertoire games_plus/games_plus contient un fichier __init__.py.

Ce fichier est vide et permet d’indiquer à Python qu’un répertoire contenant des fichiers .py est un module Python.

Lancement du serveur de développement#

Maintenant que notre projet est créé, nous allons lancer le serveur de développement de Django afin de nous assuré que tout fonctionne correctement.

Placez vous dans le répertoire du projet puis tapez la commande suivante:

$ python manage.py runserver

Django devrait afficher quelque chose équivalent à cela:

Performing system checks...
System check identified no issues (0 silenced).
February 27, 2017 - 09:43:39
Django version 1.10.3, using settings 'games_plus.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CONTROL-C.

Parfait notre serveur est lancé, nous pouvons maintenant visiter l’URL: http://127.0.0.1:8000/ pour voir ce qui s’y passe !

Vous devriez donc voir ceci:

Note

Mission accomplie, nous avons créé notre premier projet Django, et il est à présent opérationnel. Le serveur se lance sans erreur, et il répond aux requêtes.

Certes, son utilité reste limitée en l’état. Nous allons voir comment y ajouter quelques éléments intéressants :

  • créer des modèles,

  • paramétrer notre contrôleur frontal

  • et faire un peu de scaffolding pour remplir notre base de données !

Création d’une application#

À faire

Nous allons maintenant créer notre première application Django au sein de notre projet. Cette application nommée games_reviews sera le coeur de notre projet et gérera l’ensemble des problèmatiques liées à la revue de jeux vidéos.

Indication

Vous vous souvenez de l’écosystème d’un projet Django: dans un projet Django, on aggrège (au sens de l’agrégation du paradigme objet) différentes applications. ceci encourage la programmation modulaire et la réutilisation des modules.

Créer une application Django est aussi simple que la création d’un projet. Voici comment faire.

Placez vous dans le répertoire du projet et tapez la commande suivante:

$ python manage.py startapp games_review

Notre application est créée. Un répertoire games_review a été créé dans le répertoire de notre projet et il contient quelques fichiers utiles au fonctionnement de notre application. Voici son contenu:

games_plus
|--- manage.py
|--- games_plus/
|--- games_review
|    |--- __init__.py
|    |--- models.py
|    |--- views.py
|    |--- tests.py

models.py

modèles de notre application (cf. MVC)

views.py

contrôleurs de notre application (cf. MVC)

test.py

tests unitaires de notre application

Des modèles ..#

A l’instar de nombreux frameworks web, les modèles Django sont des classes qui héritent d’une classe du framework (la classe Model).

En faisant hériter vos modèles de cette fameuse classe vous leur conférez automatiquement toutes les propriétés et méthodes relatives au modèles (gestion de l’ORM, ..).

Syntaxe de déclaration d’un modèle#

Vous l’avez d’ores et déjà compris, c’est au sein du fichier models.py de notre application que nous allons implémenter tous nos modèles. Voici la syntaxe :

models.py#
from django.db import models


class MyModel(models.Model):
    a_text_field = models.CharField(max_length=150)

Important

Par défault tout nos modèles seront créer dans le fichier models.py , il s’agit d’une convention de Django. Sachez simplement que si par la suite, notamment sur une application de grande taille, vous souhaitez diviser vos fichiers de modèles en plusieurs fichiers, c’est tout à fait possible.

Types de champs disponibles#

En créant un modèle Django, nous allons utiliser le système d’ORM du framework. Django propose ainsi quelques types de champs (~ d’attributs) disponibles pour utilisation dans nos modèles.

Voici une liste non-exhaustive:

CharField

Un champ de texte, typiquement mappé vers un VARCHAR en BDD.

TextField

Un champ de texte long, typiquement mappé vers un TEXT en BDD.

ForeignKey

Une référence à un objet d’un autre modèle. En BDD, il est représenté par un entier désignant l’ID de la ligne cible.

ManyToManyField

Une référence à plusieurs objets d’un autre modèle. En BDD, il est représenté à l’aide d’une table d’association.

BooleanField

Un champ booléen.

URLField

Un champ de texte représentant une URL, typiquement mappé vers un VARCHAR en BDD.

SlugField

Un champ de texte représentant un slug, c’est à dire une chaîne de caractère destinée à être utilisée dans une URL.

IntegerField

Un nombre entier.

DateTimeField

Une date et une heure.

Voir aussi

Nous n’allons pas lister ici tous les champs qu’il est possible d’utiliser dans un modèle. Une liste exhaustive est disponible sur la documentation officielle de Django, section Model field reference.

Indication

Chaque type de champ admet un certain nombre de paramétrages, que Django appelle des options. Nous n’allons pas non-plus tous les lister, mais voici les plus utilisés :

null=True|False

Permet de spécifier si le champ correspondant en BDD peut être NULL ou pas.

blank=True|False

Permet de spécifier si, dans les formulaires de l’application, le champ peut être laissé vide ou pas.

max_length=[in]

Permet de spécifier la longueur maximale d’un CharField (généralement, la limite est de 255 caractères).

default=[int|str|boolean|..]

Permet de spécifier une valeur par défaut pour le champ

Exemple d’utilisation de ces options dans le cadre de la déclaration d’un modèle#
from django.db import models


class MyModel(models.Model):
   a_text_field = models.CharField(max_length=150, null=True,
                                    blank=True, default="Hello")

Définir la BDD de notre projet#

Première version de nos modèles#
from __future__ import unicode_literals
from django.db import models
from django.contrib.auth.models import User


class Type(models.Model):
    name = models.CharField(max_length=150)
    slug = models.SlugField()


class Game(models.Model):
    name = models.CharField(max_length=150)
    slug = models.SlugField()
    author = models.ForeignKey(User, on_delete=models.CASCADE)
    release_date = models.DateTimeField(auto_now_add=True)
    picture = models.ImageField()
    types = models.ManyToManyField(Type)

Nous avons à présent nos modèles, mais aucune base pour stocker les données de ceux-ci. Pour pallier ce problème, nous allons faire dans le classique et nous utiliserons une base de données relationnelle.

Astuce

Pour éviter d’avoir à installer un serveur de BDD comme PostgreSQL ou MySQL, nous allons utiliser SQLite.

Nous utilisons un ORM nous serons donc libre, plus tard, notamment si le projet obtient le succès qu’il mérite, de switcher très simplement vers un autre SGBD, en modifiant juste la configuration de notre projet.

Note

Pour utiliser SQLite, nous n’avons rien de spécial à faire, si ce n’est de fournir le nom du fichier de BDD à Django dans notre fichier de configuration.

Pour préciser à Django que nous souhaitons utiliser SQLite nous n’avons rien à faire, par défault la commande startproject initialise le fichier settings.py avec la variable DATABASES:

DATABASES = {
   'default': {
      'ENGINE': 'django.db.backends.sqlite3',
      'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
   }
}

Indication

Profitons également d’être dans le fichier settings.py pour modifier quelques configurations.

Modifions la section INSTALLED_APPS pour indiquer à notre projet Django que nous souhaitons utiliser notre application games_review :

INSTALLED_APPS = [
   'django.contrib.admin',
   'django.contrib.auth',
   'django.contrib.contenttypes',
   'django.contrib.sessions',
   'django.contrib.messages',
   'django.contrib.staticfiles',
   'games_review',
]

Ajoutez à la fin du fichier de configuration les élèments suivants permettant la gestion des fichiers statiques (css, js, etc.) et les médias (fichiers uploadés):

STATIC_URL = '/_static/'
STATIC_ROOT = os.path.join(BASE_DIR, 'static')
MEDIA_URL = '/media/'
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')

Nous allons également ajouter les urls vers les fichiers statiques et les médias (fichiers uploadés via le back end) dans le fichier urls.py :

from django.urls import include, path
from django.contrib import admin
from django.conf import settings
from django.conf.urls.static import static


urlpatterns = [
   path('admin/', admin.site.urls),
]


urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

Migrations#

Django sait maintenant où écrire ses données !

Nous allons maintenant lui demander de créer les tables de la base de données pour nous, à partir des informations que nous lui avons fournies dans les modèles de nos applications (en l’occurrence, de notre application, puisque notre projet n’en comporte qu’une pour le moment).

Nous allons encore une fois utiliser la commande python manage.py , avec l’argument makemigrations :

$ python manage.py makemigrations

Voici le retour de cette commande :

Migrations for 'games_review':
  games_review/migrations/0001_initial.py:
    - Create model Game
    - Create model Type
    - Add field types to game

Avertissement

A cette étape aucune données n’a été écrite en base, nous venons simplement de créer une migration (c’est à dire un script python qui décrit les opérations à effecturer sur la base de données) qui permettra de modifier la structure de notre base de données en conséquence.

Une fois la migration réalisée, il faut l’appliquer.

$ python manage.py migrate

Voici le retour de Django:

Operations to perform:
  Apply all migrations: admin, auth, contenttypes, games_review, sessions
Running migrations:
  Applying contenttypes.0001_initial... OK
  Applying auth.0001_initial... OK
  Applying admin.0001_initial... OK
  Applying admin.0002_logentry_remove_auto_add... OK
  Applying contenttypes.0002_remove_content_type_name... OK
  Applying auth.0002_alter_permission_name_max_length... OK
  Applying auth.0003_alter_user_email_max_length... OK
  Applying auth.0004_alter_user_username_opts... OK
  Applying auth.0005_alter_user_last_login_null... OK
  Applying auth.0006_require_contenttypes_0002... OK
  Applying auth.0007_alter_validators_add_error_messages... OK
  Applying auth.0008_alter_user_username_max_length... OK
  Applying games_review.0001_initial... OK
  Applying sessions.0001_initial... OK

Important

Notre base de données est à présent créée, et comporte les tables nécessaires au fonctionnement de notre application. Si vous listez le contenu du répertoire de votre projet, vous devez y voir un nouveau fichier, nommé db.sqlite3. C’est notre base de données SQLite !

Note

Vous avez remarqué que Django a créé d’autres tables que celles relatives à notre application ! Ce sont les tables utiles aux autres applications mentionnées dans notre fichier settings.py.

Astuce

Par curiosité, vous pouvez ouvrir votre base de données SQLite pour voir ce qu’elle contient. Plusieurs outils existent : à moindre frais, vous pouvez utiliser SQLite Manager !

Note

Notre projet et notre application prennent forme ! Nous allons maintenant voir comment disposer, en quelques secondes, d’une interface d’administration puissante permettant d’éditer nos données : c’est l’objet du tutoriel sur le scaffolding avec Django.

Le Scaffolding avec Django#

Note

Le scaffolding est une fonctionnalité proposée par certains frameworks de développement, permettant de créer des interfaces d’administration en prenant en charge les fastidieuses interfaces de CRUD : création, lecture, mise à jour et suppression. Django possède une fonctionnalité de scaffolding très puissante.

Les CRUDs c’est chiant !

Quoi de plus ennuyeux que de développer des centaines de fois le même genre de fonctionnalité permettant de mettre à jour les données d’une application. Des formulaires pour créer des choses, les modifier, etc. Des tableaux pour visualiser des listes. Bref, rien de bien folichon.

Depuis très longtemps, les devs ont cherché à écrire des briques génériques permettant de prendre en charge ces fonctionnalités, sans avoir à réinventer la roue à chaque projet. C’est là qu’intervient le scaffolding.

Scaffolding de base#

Vous l’avez compris depuis le début de cette section, Django est modulaire et tout fonctionne par applications.

C’est donc tout naturellement que le scaffolding est une application sous Django. Nous allons vérifier que celle-ci ( django.contrib.admin ) est bien mentionnée dans la variable INSTALLED_APPS de notre fichier settings.py.

Pour l’utiliser nous avons besoin d’une URL que nous avons déjà paramétrer au sein du fichier urls.py (celui du projet). Il nous suffit donc de se rendre sur l’URL http://127.0.0.1:8000/admin.

Astuce

Django gère pour vous les problématiques d’authentification relatives à ce genre d’interface (imaginez un peu une interface d’administration sans authentification ..). Afin de vous identifiez il va nous falloir créer un compte pour ce faire nous alons encore utiliser la ligne de commande:

$ python manage.py createsuperuser

Celle-ci est suffisament parlante pour ne pas avoir à décrire son fonctionnement.

Connectez-vous en entrant les identifiant/mot de passe que vous avez saisis lors de la création du compte. Vous devriez arriver sur l’écran d’accueil de votre interface d’administration :

Vous pouvez désormais administrer les comptes utilisateurs de votre projet.

Administration de notre application#

Tout ceci est très sympathique, mais ce serait encore mieux si nous pouvions aussi mettre à jour les données relatives à notre application.

Pour cela, nous devons signaler à Django pour quels models nous voulons disposer d’une interface d’administration. Nous allons le faire en créant un fichier admin.py dans le répertoire de notre application games_review.

Au sein du répertoire de l’application games_review, et créez-y un fichier admin.py.

admin.py#
from django.contrib import admin
from games_review.models import *


admin.site.register(Type)
admin.site.register(Game)

Si nous rafraîchissons notre interface d’administration:

Nous avons maintenant la possibilité d’administrer l’ensemble de nos objets métiers.

Note

Pas mal n’est-ce pas ? Nous avons écrit une dizaine de lignes de code (de paramétrage), aucune logique (donc aucun bug), et nous disposons d’une application fonctionnelle pour l’administration de notre projet.

Paramétrage de la langue#

Vous aurez remarqué que par défaut, notre interface d’administration est en anglais. C’est n’est pas un problème lorsque l’on est parfaitement bilingue mais cela peut être rebutant pour certains, notamment pour les utilisateurs potentiels de notre application.

Nous allons simplement spécifier à Django que nous souhaitons que la langue du projet soit le français.

settings.py#
TIME_ZONE = 'Europe/Paris'
LANGUAGE_CODE = 'fr-FR'

Indication

Les plus tatillons dirons que l’interface est en français, mais les noms de nos modèles restent en anglais. Pour corriger cela, nous allons paramétrer nos modèles de manière à ce que leur nom affiché le soit en français :

class Type(models.Model):
   name = models.CharField(max_length=150)
   slug = models.SlugField()

   class Meta:
      verbose_name = 'Catégorie'
      verbose_name_plural = 'Catégories'

Nous avons ici défini de quelle manière doit s’afficher le nom du modèle, à l’infinitif et au pluriel.

Personnalisation des listes#

Dans toute interface d’administration, il est question de présenter des listes d’enregistrements (qui reflètent généralement les lignes des tables d’une base de données). Les back-offices générés par Django ne dérogent pas à cette règle.

On retrouve une présentation classique comportant une liste d’objets , un bouton d’ajout d’un nouvel objet, et la possibilité de sélectionner une ou plusieurs lignes pour y appliquer des actions (ex. : suppression).

Le problème est que la présentation de la liste n’est pas folichonne : chaque ligne est nommée Game object, ce qui n’est absolument pas parlant. D’autre part, on aimerait disposer de quelques outils pratiques, notamment pour trier les objets, réaliser des recherche textuelles, etc. Voyons à présent comment mettre en place tout ceci.

Un nom parlant#

Notre premier objectif est que Django n’affiche plus Game object comme ancre de lien vers la fiche d’un jeu, mais plutôt le nom de celui-ci. Pour cela, nous allons définir, dans notre modèle Game, une méthode __str__ , qui aura pour mission de retourner le nom à afficher pour un jeu donné.

models.py#
class Game(models.Model):
    name = models.CharField(max_length=150)
    slug = models.SlugField()
    author = models.ForeignKey(User)
    release_date = models.DateTimeField(auto_now_add=True)
    picture = models.ImageField()
    types = models.ManyToManyField(Type)

    def  __str__(self):
        return self.name

Nous disposons désormais d’un affichage beaucoup plus lisible.

Gestion des colonnes#

Pour l’instant une seule colonne est affichée.

Nous souhaitons pouvoir consulter directement dans la liste d’autres informations sur nos jeux.

Pour ce faire nous allons modifier le fichier admin.py de notre application games_review afin de surcharger quelques éléments de l’interface d’administration.

Pour chaque modèle pris en charge par le scaffolding de Django il est possible de définir une classe fille de ModelAdmin décrivant le comportement de l’interface d’administration.

games_review/admin.py#
class GameAdmin(admin.ModelAdmin):
    list_display = ('name', 'release_date', )

Nous avons ici simplement spécifié à Django que la vue de liste doit comporter deux colonnes, name et release_date (les deux champs de notre modèle Game).

Il nous reste à lui demander de prendre en compte ce paramétrage, en précisant le nom de notre classe lors de la déclaration du modèle en tant que candidat à l’administration :

admin.site.register(Game, GameAdmin)

Voir aussi

La liste compète des options est disponibles à cette adresse : section ModelAdmin options.

Un champ de recherche#

Nous aimerions disposer d’un champ de recherche textuelle, pour trouver un ou des jeu(x) grâce à leurs noms.

Pour ce faire, nous ajoutons simplement un attribut search_fields à notre classe GameAdmin, qui représente un tuple de champs candidats pour la recherche textuelle :

class GameAdmin(admin.ModelAdmin):
    list_display = ('name', 'release_date', )
    search_fields = ('name', )

Mise en place de filtre#

En plus de la recherche et du tri par colonnes, il est parfois intéressant de disposer de filtres sur les listes. Leur mise en place est tout aussi facile que le reste !

Définissons pour cela un attribut list_filter, qui représente un tuple de noms de champs candidats au filtrage :

class GameAdmin(admin.ModelAdmin):
    list_display = ('name', 'release_date', )
    search_fields = ('name', )
    list_filter = ('types', )

Nous disposons désormais d’une administration tout à fait fonctionnelle.

Contrôleur frontal#

Définition

Un contrôleur frontal est un composant dont le rôle est d’aiguiller les requêtes émises par les utilisateurs d’une application vers les contrôleurs applicatifs adéquats.

Pour réaliser cette tâche les contrôleurs frontaux, que l’on appelle aussi parfois dispatchers, associent des patterns d’URL avec des noms de contrôleurs.

Dans notre application games_review , les utilisateurs pourront accéder à la page d’accueil qui listera l’ensemble des revues de jeux ainsi qu’aux différentes revues. Pour cela il auront à leur disposition ces deux patterns d’URL:

/

devra afficher la page d’accueil.

/jeux/[slug_du_jeu]/

devra afficher la revue du jeu ayant le slug slug_du_jeu.

Bien entendu l’affichage d’un jeu et de la page d’accueil feront l’objet d’un contrôleur respectif.

Notre contrôleur frontal (front controller) devra donc rediriger les requêtes / vers le contrôleur gérant l’affichage de la page d’accueil et les requêtes /jeux/[slug_du_jeu]/ vers notre contrôleur gérant l’affichage des revues de jeu.

Le fichier urls.py#

Sous Django, chaque projet dispose d’un contrôleur frontal.

Dans le cadre de notre projet games_plus, un fichier urls.py existe donc par défaut dans le répertoire games_plus/games_review.

Délégation aux applications#

Astuce

Pour rendre les applications très modulaires et réutilisables, il est possible de faire en sorte que le projet délègue une partie de la gestion des règles d’aiguillages à différentes applications. Ainsi, chaque application est responsable du routage interne des URL la concernant.

Dans le cadre de notre projet, l’ensemble des règles relatives aux jeux, page d’accueil, etc. sera géré par l’application games_review.

Django implémente ceci de manière très élégante, en permettant la création d’un fichier urls.py dans le répertoire de l’application elle-même, puis en aiguillant un ensemble de requêtes depuis le contrôleur frontal d’un projet vers le contrôleur frontal de l’application.

Écriture du contrôleur frontal du projet#

Commençons par écrire le contrôleur frontal du projet ou plutôt le compléter, car nous l’avons déjà édité pour activer l’interface d’administration par scaffolding/

games_plus/games_plus/urls.py#
from django.urls import path, include
from django.contrib import admin
from django.conf import settings
from django.conf.urls.static import static


urlpatterns = [
    path('admin/', admin.site.urls),
    path('', include('games_review.urls')),
]


if settings.DEBUG:
    urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
    urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

Note

Nous avons simplement ajouté une ligne au fichier existant. Cette ligne permet en substance de dire à Django :

Je veux que tu rediriges toutes les requêtes faites à la racine vers le contrôleur frontal de l’application games_review qui est défini dans le module games_review.urls.

Il nous reste maintenant à définir nos routes vers les contrôleurs dédiés. Nous allons le faire dans le fichier urls.py de l’application.

Écriture du contrôleur frontal d’une application#

Note

Pour votre culture générale, avant la version 2.0 Django utilisé massivement les regex pour la configuration d’url. Depuis la version 2, nous pouvons utiliser les path !

Avant la version 2.0

games_review/urls.py#
from django.urls import url
from games_review import views
app_name = 'games_review'


urlpatterns = [
    url(r'^$', views.index, name='index'),
    url(r'^jeu/(?P<slug>[\w-]+)/$', views.game, name='game'),
]

Comme vous le remarquez, chaque règle de routage est définie selon la syntaxe url(pattern, contrôleur de destination, nom)

  • Le pattern est en fait une expression régulière (comme c’est souvent le cas dans les frameworks MVC).

  • Le contrôleur de destination est une fonction définie dans le fichier des contrôleurs (views) de l’application.

  • Le nom de la règle de routage est utilisé pour le routage inverse.

Après la version 2.0

from django.urls import path
from games_review import views
app_name = 'games_review'


urlpatterns = [
    path('', views.index, name='index'),
    path('jeu/<slug:slug>', views.game, name='game'),
]

Comme vous le remarquez, chaque règle de routage est définie selon la syntaxe path(pattern, contrôleur de destination, nom)

  • Le pattern supporte désormais la coercion de type (int, slug, etc.), dans notre exemple le premier slug correspond au type et le second au nom de la variable.

  • Le contrôleur de destination est une fonction définie dans le fichier des contrôleurs (views) de l’application.

  • Le nom de la règle de routage est utilisé pour le routage inverse.

Attention

OK, notre contrôleur frontal est en place. Essayons d’accéder à l’une des URL définies pour voir ce qui se passe.

Vous obtenez une exception c’est naturel, nous n’avons pas encore défini de fonction associé à notre path !

TDD#

Note

Cette partie a pour vocation à vous apprendre à écrire des cas de test simples et efficaces pour vos contrôleurs d’application Django. Nous écrirons pour cela des classes de test, fondées sur unittest, avec une approche TDD.

Astuce

Le TDD (test driven development ou développement dirigé par les tests en français) est à la mode tout le monde en parle, mais peu de développeurs l’utilisent réellement et efficacement.

Et pourtant, agilité rime avec TDD : quand on veut livrer souvent, vite et bien, l’une des meilleures stratégies est de se laisser guider par le contrat de service de nos composants : autrement dit, on commence par écrire les tests, on les regarde échouer lamentablement, puis on écrit le code et on espère qu’ils n’échouent plus.

Qu’à cela ne tienne : dans la suite de ce cours, nous tâcherons de faire les choses correctement, et d’adopter cette approche aussi souvent que possible !

On commence par écrire les tests !#

Avant d’écrire le moindre contrôleur (view), écrire quelques tests qui nous aideront à mettre à plat ce que nous souhaitons coder.

Mais quels tests ?#

Des tests, OK, mais lesquels ? Pour tester nos contrôleurs (views), nous devons avoir une bonne idée de ce que va devoir faire notre application, du moins dans sa toute première version.

Puisque nous sommes sportifs et agiles, nous n’allons pas essayer de spécifier l’ensemble du logiciel tout de suite, mais quelques écrans fondamentaux.

Games Review est une application de revue de jeux vidéo : elle doit a minima permettre :

  • d’afficher un écran d’accueil avec un listing des derniers jeux publiés

  • de consulter chaque fiche de jeux

C’est tout pour le moment !

Préparation du jeu de test#

Astuce

Pour tester efficacement, nous avons besoin de quelques données concrètes : on appelle cela un jeu de données ou encore jeu d’essai. C’est ce que nous allons commencer par définir !

Pour ce faire nous allons utiliser une fonctionnalités très utile du framework de test à savoir les fixtures. Pour commencer créer quelques jeux et types via l’interface d’adminsitration.

C’est fait ? Très bien. Maintenant nous allons créer un fichier JSON qui contient un dump de nos données (au format JSON). Pour ce faire nous allons utilisé la commande dumpdata

$ python manage.py dumpdata games_review > games_review/fixtures/initial.json

On demande ici au framework de faire un dump des données de nos modèles définis dans l’application games_review. Et de placer tout ceci dans un fichier initial.json.

Danger

Il est possible que le dossier fixtures n’existes pas. Pensez à le créer avant de lancer la commande.

Le fichier tests.py#

Au sein de notre application games_review (donc dans le répertoire games_review), Django a créé pour nous un fichier tests.py.

Nos premiers tests#
from django.test import TestCase
from django.urls import reverse
from django.db.models.query import QuerySet
from games_review.models import Game


class WebsiteTestCase(TestCase):
    fixtures = ['initial.json', ]

    def test_index_page(self):
        response = self.client.get(reverse('games_review:index'))
        self.assertContains(response, "Latests games")
        self.assertEqual(type(response.context['latest_games']), QuerySet)
        self.assertEqual(len(response.context['latest_games']), 3)
        self.failUnlessEqual(response.status_code, 200)
        self.assertTemplateUsed('games/list.html')

    def test_post_page(self):
        # Published news
        game = Game.objects.first()
        response = self.client.get(reverse('games_review:post', kwargs={'slug': game.slug}))
        self.assertContains(response, game.name)
        self.assertEqual(type(response.context['game']), Game)
        self.failUnlessEqual(response.status_code, 200)
        self.assertTemplateUsed('news/index.html')

Notre classe WebsiteTestCase dérive de TestCase, cette classe prend en compte notre fichier de fixtures initial.

  • Les deux méthodes permettent de tester les deux pages à minima de notre application.

  • L’objet client nous permet de simuler l’interaction entre notre application et notre serveur.

  • La fonction reverse permet d’obtenir l’url à partir d’un routing.

A partir de la réponse nous testons plusieurs éléments:

assertContains

la réponse contient le contenu de la variable

assertEqual

l’élément a est égale à l’élément b

failUnlessEqual

échoue si l’élément a n’est pas égale à l’élément b

assertTemplateUsed

échoue si le fichier de template utilisé n’est pas celui spécifié

Lancement des tests (on craint le pire)#

Il est temps à présent de lancer nos tests. Sans grand espoir de réussite : vu que nous n’avons pas écrit le code de l’application, il n’y a pas de risque qu’elle fonctionne (sinon, il faudrait revoir les tests, puisque ceci voudrait dire qu’ils sont totalement inutiles !).

Dans le répertoire de notre projet, nous allons utiliser le script manage.py pour demander le lancement des tests. Cette commande permet de lancer tous les tests, ou seulement les tests d’une application du projet, voire même d’une seule méthode de test au sein d’une application. Lançons donc tous les tests de notre projet:

$ python manage.py test

Nos deux tests échouent : pas d’inquiétude, c’est normal !

Les views Django#

Note

Nous allons voir sur cette page comment écrire des contrôleurs (views) puissants très simplement en Django, sous la forme de fonctions : un contrôleur = une fonction. Tout simplement !

Nous devons écrire deux vues qui correspondent à nos deux routes définis précédement.

Bien entendu, nos contrôleurs (views) vont avoir besoin de nos modèles, nous allons commencer par les importer dans le fichier views.py.

games_review/views.py#
from games_review.models import Game, Type

Nous sommes maintenant prêts pour écrire un premier contrôleur (view). Notre contrôleur (view) index doit récupérer les jeux pour pouvoir invoquer ensuite une vue (template) qui les affichera à l’écran. Voici sa déclaration :

games_review/views.py#
def index(request):
    games = Games.objects.all()
    return render(request, 'index.html', {'latests_games': games})

Nous avons déclaré une simple fonction index() , qui reçoit automatiquement un paramètre request.

Ce paramètre est fourni par Django au moment de l’appel de la fonction en provenance du contrôleur frontal, et comprend tout ce dont on peut avoir besoin concernant la requête de l’utilisateur (les paramètres get, post, etc.).

La dernière ligne de la fonction utilise un raccourci (shortcut) permettant de créer l’objet HttpResponse à partir de différents éléments :

  • le chemin du template à utiliser (la vue en langage MVC courant) ;

  • un dictionnaire contenant les variables que nous voulons ajouter au contexte, c’est à dire à l’ensemble des choses qui seront fournies au template.

Relancez les tests. Les deux tests sont toujours en erreur.

Si vous essayez d’accéder à l’url http://127.0.0.1:8000 vous risquez de rencontrer une exception du type TemplateDoesNotExist.

Pour éviter cette erreur, créons simplement un répertoire templates dans le dossier de notre application games_review.

ans ce répertoire, créons deux fichiers vides (pour le moment) : index.html et game.html.

Si vous essayez d’accéder à l’url http://127.0.0.1:8000, plus de soucis de template inexistant à présent, c’est assez épuré, mais ça fonctionne.

Par contre notre test toujours pas.

Écriture du contrôleur (vue) game#

games_review/views.py#
def game(request, slug):
    game = get_object_or_404(Game, slug=slug)
    return render(request, 'game.html', {'game': game})

Rien de bien nouveau ici, si ce n’est que nous souhaitons récupérer un jeu donné, repéré par un slug.

Astuce

Nous utilisons ici une fonction pratique (shortcut, une sorte de raccourcis) nommée get_object_or_404.

Cette fonction tente de récupérer un enregistrement sur la base d’une contrainte (ici sur le champ slug : slug=game_slug) et retourne un code 404 si l’objet est introuvable. Pratique !

Idem si nous relançons nos test ceux-ci échouent toujours ! Pour ce faire il faut maintenant modifier nos templates!

Note

Nous avons vu dans ce tutoriel assez dense comment créer des contrôleurs (views) avec Django, en utilisant les function-based views. Nos deux contrôleurs sont regroupés dans un seul fichier views.py, et sont très courts.

Les templates#

Important

Pensez à relancer vos tests au fur et à mesure de votre avancé pour vérifier le fonctionnement de votre application.

Le langage de templating offert par Django est sans aucun doute l’un de ses points forts face aux frameworks de même type. Élégant, puissant et très simple, le templating sous Django est un jeu d’enfant !

Comme tout langage de template (Jinja, Smarty, Jade, FreeMaker), le moteur de templating de Django permet de manipuler des variables au sein d’un contenu textuel.

Affichage d’une variable#
{{ ma_variable }}

Filtres#

Le moteur de template de Django accepte un certain nombre de filtres lors de l’utilisation de variables. Ces filtres permettent d’appliquer des traitements de mise en forme aux variables, est sont invoqués à l’aide d’un pipe :

{{ ma_variable|filtre }}

Voir aussi

Voir la liste des filtres disponibles.

Itération sur un itérable#

Il est très courant de devoir exploiter une variable de type liste ou dictionnaire. Les listes manipulées sont généralement des QuerySets, c’est à dire le résultat d’une requête en BDD. Bien souvent, il s’agit de parcourir les données pour les afficher, soit dans un tableau, soit dans une liste à puces.

Le langage de template de Django fournit à ce propos une boucle for simple et proche de celle proposée en Python (c’est à dire fondée sur le design pattern iterateur.).

<ul>
{% for game in latests_games %}
     <li>{{ game.title }}</li>
{% endfor %}
</ul>

Conditions simples#

Autre opération que l’on doit souvent réaliser dans un template : l’évaluation de conditions. Une structure conditionnelle simple est proposée par le langage de template de Django.

Imaginons que nous souhaitions nous assurer qu’une liste contient bien des valeurs avant de l’afficher :

{% if games %}
     <ul>
    {% for game in games %}
         <li>{{ game.title }}</li>
    {% endfor %}
     </ul>
{% else %}
    Aucun jeux.
{% endif %}

Voir aussi

Pour une liste détaillée des fonctionnalités disponibles dans le langage, voyez la documentation officielle consacrée au langage de template de Django.

Extension de template#

C’est une fonctionnalité extrêmement puissante et appréciable : les templates Django peuvent être réutilisés et spécialisés par d’autres templates.

Nous commençons par définir un template de base, par exemple :

_base.html#
<!DOCTYPE html>
<html>
    <head>
        <title>{% block title %}Games Plus{% endblock %}</title>
    </head>
    <body>
        {% block body %}{% endblock %}
    </body>
</html>

Important

Vous remarquez que le template comporte des blocks : un block est une portion de contenu nommée. Notre template _base.html comporte ainsi deux blocks :

  • title

  • body

Créons à présent un autre template qui étendra notre template de base. Pour cela, nous créons un nouveau fichier index.html, dont la première ligne précisera le template de base à étendre :

index.html#
{% extends "_base.html" %}
{% block title %}{{ INDEX PAGE TITLE }}{% endblock %}
{% block body %}
    HELLO WORLD
{% endblock %}

À faire

Créer les deux templates pour la page d’accueil et la page de revue d’un jeu.

Reverse URL#

Pour créer une url, par exemple vers la page de revue d’un jeu, Django intègre un balise de template url qui permet de générer l’url vers un contrôleur.

{% url 'games_review:game' game.slug %}

Projet Microblogging en Django#

Contexte

Vous venez d’obtenir votre premier contrat qui consiste en la réalisation d’un site de Micro Blogging pour le compte d’un client local.

Après négociation avec le dit client vous avez convenu d’une réalisation d’un site web en Python utilisant le framework Django et le moteur de base de données SQLite pour une livraison à la fin du mois.

Vous devez créer une application basic de MicroBlogging nommé Microlly.

Les fonctionnalités minimun de ce site sont les suivantes:

User Story 1

En tant que visiteur je souhaite pouvoir m’inscrire dans le but de pouvoir créer un compte sur Microlly et m’y connecter.

User Story 2

En tant que visiteur je souhaite pouvoir me connecter dans le but de publier une nouvelle entrée sur Microlly.

User Story 3

En tant qu’utilisateur je souhaite pouvoir créer une publication dans le but de partager des informations avec les utilisateurs.

User Story 4

En tant qu’utilisateur je souhaite pouvoir modifier ou supprimer une de mes publications dans le but de corriger une erreur.

User Story 5

En tant que visiteur je souhaite voir un listing des dernières publications sur la page d’accueil dans le but de m’informer sur les partages des utilisateurs de la plateforme.

User Story 6

En tant que visiteur je souhaite voir l’ensemble des publications d’un utilisateur (sur plusieurs pages au besoin) dans le but de lire plus de publications de cet utilisateur.

Description du modèle de base

Voici une rapide description des données attendues pour une publication. Nous utiliserons le modéle django.contrib.auth.models.User fourni par Django pour gérer nos utilisateurs.

https://www.plantuml.com/plantuml/svg/XP6nQWCn38PtFuMuiH132rqoPIgTIfVEGMVhxjJex23BGmbvW3vNNwo-bi854Zhw_ophaxKhXcfr5bS22i63VtJbmEc8YZFq60wZvffYo98LJ36LXYZPwbQa0QlOKqxsWZF1SwjoGeE1PL0U9uF8Ii4u9-U2O2dmMdlXW4i49mS0fu59uXcCJMWBnPJJk0PzZX_Nyg3qyym-ebqjhCU5NEBpdqTynpHc7vgxSk-nskJd74cAVokabswr9QbDUAtBm5fiVuksNhSXpSYo1jxxnN3zHzrT1viDTFVTVyfR-FeCoWPjESvD70bciYd7ydi8h478xIZ5jlPl

Diagramme UML de la base de données de Microlly#

Quelques Indications

Voici quelques liens utiles (vers la documentation de Django) pour gérer quelques eléments du projet :

Indication

Pensez à commenter votre code (intelligement), au nommage de vos modules/méthode/class/variable/.. et aux tests (TDD)!

Vous êtes libre d’ajouter des fonctionnalités à votre travail cela sera bien evidemment valorisé.