Ce tutoriel commence là où le Tutoriel 2 s’achève. Nous continuons l’application de sondage Web et allons nous focaliser sur la création de l’interface publique – les “vues”.
Une vue est un “type” de page Web dans votre application Django qui sert à une fonction précise et possède un template spécifique. Par exemple, dans une application de blog, vous pouvez avoir les vues suivantes :
Dans notre application de sondage, nous aurons les vues suivantes :
Dans Django, chaque vue est représentée par une simple fonction Python.
La première étape pour écrire des vues et de concevoir votre structure d’URL. Vous faites cela en créant un module Python, appelé URLconf. Les URLconfs sont le moyen grâce auquel Django associe une URL avec un code Python donné.
Quand un utilisateur demande une page propulsée par Django, le système regarde ROOT_URLCONF, qui contient une chaîne de caractère Python avec une syntaxe à points. Django charge le module et cherche une variable-module appelée urlpatterns, qui est une séquence de tuples au format suivant :
(expression régulière, fonction réceptrice Python [, dictionnaire optionnel])
Django commence à la première expression régulière et parcoure la liste en comparant l'URL demandée avec chaque expression régulière jusqu'à ce qu'il en trouve une qui corresponde.
Quand il en a trouvé une, Django appelle la fonction réceptrice Python avec un objet HttpRequest comme premier argument, des éventuelles valeurs "capturées" par l'expression régulière comme arguments clés, et, facultativement, des arguments clés arbitraires depuis le dictionnaire (un troisième élément optionnel dans le tuple).
Pour plus d'informations sur les objets HttpRequest, voir les objets de requête/réponse. Pour plus de détails sur les URLconfs, voir le dispatcher d'URL.
Quand vous lancez python django-admin.py startproject mysite au début du tutoriel 1, cela crée un URLconf par défaut dans mysite/urls.py. Cela configure aussi automatiquement votre ROOT_URLCONF (dans settings.py) pour pointer vers ce fichier :
ROOT_URLCONF = 'mysite.urls'
C'est le moment pour un exemple. Editez mysite/urls.py pour qu'il ressemble à ceci :
from django.conf.urls.defaults import *
from django.contrib import admin
admin.autodiscover()
urlpatterns = patterns('',
(r'^polls/$', 'mysite.polls.views.index'),
(r'^polls/(?P<poll_id>\d+)/$', 'mysite.polls.views.detail'),
(r'^polls/(?P<poll_id>\d+)/results/$', 'mysite.polls.views.results'),
(r'^polls/(?P<poll_id>\d+)/vote/$', 'mysite.polls.views.vote'),
(r'^admin/', include(admin.site.urls)),
)
Cela vaut la peine de s'y intéresser. Quand quelqu'un demande une page de votre site web -- disons "/pools/23/", Django va charge ce module Python, car il est indiqué par ROOT_URLCONF. Il trouve la variable nommée urlpatterns et parcourt les expressions régulières dans l'ordre. Quand il en trouve une qui correspond -- r'^polls/(?P<poll_id>\d+)/$' -- il charge la fonction detail() dans mysite/pools/views.py. Enfin, il appelle cette fonction detail() de cette manière :
detail(request=<HttpRequest object>, poll_id='23')
La partie poll_id='23' viens de (?P<poll_id>\d+). Utiliser les parenthèses autour d'un motif "capture" le texte correspondant à ce motif et l'envoie en tant qu'argument à la fonction de la vue ; le ?P<poll_id> définit le nom qui va être utilisé pour identifier le motif trouvé, et \d+ est une expression régulière pour chercher une séquence de chiffres (c-à-d un nombre).
Parce que les motifs d'URL sont des expressions régulière, il n'y a vraiment aucune limite à ce que vous pouvez faire avec eux, et il n'y a pas besoin d'ajouter de fioritures à l'URL tel que .php -- sauf si vous avez un sens de l'humour tordu, dans lequel cas vous pouvez faire quelque chose comme ça :
(r'^polls/latest\.php$', 'mysite.polls.views.index'),
Mais ne faites pas ça. C'est stupide.
Notez que ces expressions régulières ne cherchent pas dans les paramètres GET et POST, ni dans le nom de domaine. Par exemple, dans une requête vers http://www.example.com/myapp/, l'URLconf va chercher /myapp/. Dans une requête vers http://www.example.com/myapp/?page=3, l'URLconf va chercher /myapp/.
Si vous avez besoin d'aide avec les expressions régulières, jetez un oeil à l'article Wikipedia (en) et à la documentation de Python (en). Également, le livre "Mastering Regular Expressions" écrit par Jeffrey Friedl est extraordinaire.
Enfin, une note sur pa preformance : ces expressions régulières sont compilées la première fois que l'URLconf est chargé. Elles sont extrêmement rapides.
Bon, nous n'avons pas encore créé une seule vue -- nous n'avons que l'URLconf. Mais assurons-nous que Django suit l'URLconf convenablement.
Lancez le serveur de développement de Django:
python manage.py runserver
Maintenant rendez-vous à l'adresse "http://localhost:8000/polls/" sur votre domaine dans votre navigateur. Vous devriez voir une page d'erreur joliement colorée avec le message suivant
ViewDoesNotExist at /polls/
Tried index in module mysite.polls.views. Error was: 'module'
object has no attribute 'index'
Cette erreur s'est produite parce que vous n'avez pas écrit de fonction index() dans le module mysite/polls/views.py.
Essayez "/polls/23/", "/polls/23/results/" et "/polls/23/vote/". Le message d'erreur vous dit quelle vue Django a essayée (et n'a pas trouvée, puisque vous n'avez pas encore écrit de vue).
Il est temps d'écrire la première vue. Ouvrez le fichier mysite/polls/views.py et mettez-y le code Python suivant:
from django.http import HttpResponse
def index(request):
return HttpResponse("Hello, world. Vous êtes à l'index des sondages.")
C'est la vue la plus simple possible. Rendez-vous à l'adresse "/polls/" dans votre navigateur, et vous devriez voir votre texte.
Maintenant ajoutez la vue suivante. Elle est légèrement différente, puisqu'elle prend un argument (qui est récupéré dans ce qui est capturé par l'expression régulière dans l'URLconf):
def detail(request, poll_id):
return HttpResponse("Vous êtes bien au sondage numéro %s." % poll_id)
Allez voir dans votre navigateur, à l'adresse "/polls/34/". Il affichera n'importe quel ID que vous mettrez dans l'URL.
Chaque vue a la charge de faire une ou deux choses : renvoyer un objet HttpResponse contenant le contenu de la page demandée, ou levant une exception comme Http404. Le reste dépend de ce que vous voulez faire.
Votre vue peut lire des entrées depuis une base de données, ou pas. Elle peut utiliser un système de template comme celui de Django -- ou un système de template tiers -- ou pas. Elle peut générer un fichier PDF, sortir de l'XML, créer un fichier ZIP à la volée, tout ce que vous voulez, en utilisant les bibliothèques Python que vous voulez.
Voilà tout ce que veut Django : HttpResponse. Ou une exception.
Parce que c'est pratique, nous allons utiliser l'API de base de données de Django, que nous avons vu dans le Tutoriel 1. Voici la vue index(), qui affiche les 5 derniers sondages, séparés par des virgules et ordonnés par date de publication :
from mysite.polls.models import Poll
from django.http import HttpResponse
def index(request):
latest_poll_list = Poll.objects.all().order_by('-pub_date')[:5]
output = ', '.join([p.question for p in latest_poll_list])
return HttpResponse(output)
Cependant, il y a un problème : le design de la la page est codé en dur dans la vue. Si vous voulez changer le style de la page, vous devrez éditer votre code python. Nous allons donc utiliser le système de template de Django pour séparer le design de Python :
from django.template import Context, loader
from mysite.polls.models import Poll
from django.http import HttpResponse
def index(request):
latest_poll_list = Poll.objects.all().order_by('-pub_date')[:5]
t = loader.get_template('polls/index.html')
c = Context({
'latest_poll_list': latest_poll_list,
})
return HttpResponse(t.render(c))
Ce code charge le template appelé "polls/index.html" et le passe en paramètre dans un contexte. Ce contexte est un dictionnaire qui fait correspondre des objets Python à des noms de variables de template.
Rechargez la page. Vous verrez une erreur :
TemplateDoesNotExist at /polls/
polls/index.html
Ah. Il n'y a pas encore de template. Tout d'abord, créez un dossier quelque part sur votre disque dur, et assuerz-vous que Django y a accès. (Django tourne sous l'utilisateur qui fait tourner le serveur.) Ne le mettez pas dans la racine de votre serveur. Vous ne devriez pas rendre vos templates publics, pour des questions de sécurité. Ensuite, éditez TEMPLATE_DIRS dans votre settings.py pour dire à Django où il peut trouver les templates -- exactement comme vous avez fait dans la section "Personnaliser l’apparence de l’interface d’administration" du Tutoriel 2.
Ceci fait, créez un dossier polls dans votre dossier de template. À l'intérieur, créez un fichier index.html. Notez que notre code loader.get_template('polls/index.html') correspond à "[template_directory]/polls/index.html" sur votre disque dur.
Mettez le code suivant dans ce template :
{% if latest_poll_list %}
<ul>
{% for poll in latest_poll_list %}
<li>{{ poll.question }}</li>
{% endfor %}
</ul>
{% else %}
<p>Aucun sondage n'est disponible.</p>
{% endif %}
Chargez la page dans votre navigateur, et vous devriez voir une liste à puces contenant le sondage "Quoi de neuf" du Tutoriel 1.
Il est très courant de charger un template, remplir un contexte et renvoyer un objet HttpResponse avec le résultat du template interprété. Django fournit un raccourci. Voici la vue index(), réécrite :
from django.shortcuts import render_to_response
from mysite.polls.models import Poll
def index(request):
latest_poll_list = Poll.objects.all().order_by('-pub_date')[:5]
return render_to_response('polls/index.html', {'latest_poll_list': latest_poll_list})
Notez que lorsque nous avons fait ceci dans toutes nos vues, nous n'avons plus à importer loader, Context et HttpResponse.
La fonction render_to_response() prend comme premier argument un nom de template et un dictionnaire comme second argumant optionnel. Elle retourne un objet HttpResponse du template interprété avec le contexte donné.
Attaquons-nous maintenant à la vue du détail d'un sondage -- la page qui affiche la question pour un sondage donné. Voici la vue
from django.http import Http404
# ...
def detail(request, poll_id):
try:
p = Poll.objects.get(pk=poll_id)
except Poll.DoesNotExist:
raise Http404
return render_to_response('polls/detail.html', {'poll': p})
Le nouveau concept ici : la vue lève une exception de type Http404 si un sondage avec l'ID demandé n'existe pas.
Nous parlerons un peu plus tard de ce que pouvez mettre dans le template polls/detail.html, mais si vous voulez avoir rapidement un example qui fonctionne,
{{ poll }}
vous aidera à démarrer.
Il est très courant d'utiliser get() et de lever une exception Http404 si l'objet n'existe pas. Django fournit un raccourci. Voici la vue detail() réécrite
from django.shortcuts import render_to_response, get_object_or_404
# ...
def detail(request, poll_id):
p = get_object_or_404(Poll, pk=poll_id)
return render_to_response('polls/detail.html', {'poll': p})
La fonction get_object_or_404() prend un module de modèle Django comme premier argument et un nombre arbitraire d'arguments, qu'il passe à la méthode get() du module. Elle lève une exception Http404 si l'objet n'existe pas.
Philosophie
Pourquoi utiliser une fonction auxiliaire get_object_or_404() plutôt que d'intercepter automatiquement une exception ObjectDoesNotExist à un plus haut niveau, ou laisser l'API modèles lever une exception Http404 à la place de ObjectDoesNotExist ?
Parce que cela couplerait la couche de gestion des modèles à la couche de vue. Un des but principaux de la conception de Django et de garder un couplage le plus faible possible.
Il y a aussi une fonction get_list_or_404(), qui fonctionne comme get_object_or_404() -- sauf qu'elle utilise filter() au lieu de la méthode get(). Elle lève une exception Http404 si la liste est vide.
Lorsque vous levez une exception Http404 depuis une vue, Django va charger une vue spécifique destinée à la gestion des erreurs 404. Il la trouve en cherchant la variable handler404, qui est une chaine de caractères dans la syntaxe en pointillés de Python -- le même format utilisé par les rappels URLconf normaux. Une vue 404 n'a rien de particulier, c'est une vue normale.
Vous n'avez normalement pas à vous occuper d'écrire des vues 404. par défaut, les URLconfs ont les lignes suivantes
from django.conf.urls.defaults import *
Cela prend en compte les paramètres handler404 du module actuel. Comme vous pouvez le voir dans django/conf/urls/defaults.py, handler404 correspond par défaut à django.views.defaults.page_not_found().
Quatre choses supplémentaires à savoir sur les vues 404 :
De la même façon, l'URLconf définit un handler500, qui pointe vers une vue à appeler en cas d'erreurs sur le serveur. Les erreurs du serveur se produisent lors des erreurs d'exécution dans le code des vues.
Revenons à la vue detail() de notre application de sondage. Étant donneée la variable de contexte poll, voici à quoi le template "polls/detail.html" devrait ressembler:
<h1>{{ poll.question }}</h1>
<ul>
{% for choice in poll.choice_set.all %}
<li>{{ choice.choice }}</li>
{% endfor %}
</ul>
Le système de template utilise une syntaxe d'accès aux données par points. Dans cet exemple avec {{ poll.question }}, Django commence par rechercher un dictionnaire dans l'objet poll. En cas d'échec, il cherche un attribut -- qui fonctionne dans ce cas. Si la recherche d'attribut n'avait pas fonctionné, Django aurait essayé d'appeler la méthode question() sur l'objet poll.
L'appel de méthode a lieu dans la boucle {% for %} : poll.choice_set.all est interprété comme le code python poll.choice_set.all(), qui renvoie un itérable d'objets Choice, et qui convient pour l'utilisation de la balise {% for %}.
Voir le guide des templates pour plus d'informations sur les templates.
Prenez un peu de temps pour vous familiariser avec le système de vues et de template. Lorsque vous éditez l'URLconf, vous remarquez qu'il y a un peu de redondance
urlpatterns = patterns('',
(r'^polls/$', 'mysite.polls.views.index'),
(r'^polls/(?P<poll_id>\d+)/$', 'mysite.polls.views.detail'),
(r'^polls/(?P<poll_id>\d+)/results/$', 'mysite.polls.views.results'),
(r'^polls/(?P<poll_id>\d+)/vote/$', 'mysite.polls.views.vote'),
)
À savoir, mysite.polls.views apparaît dans tous les appels.
Comme c'est un cas de figure courant, le framework des URLconf contient un raccourci pour les préfixes communs. Vous pouvez factoriser les préfixes communs et les ajouter dans le premier argument de patterns(), comme ceci
urlpatterns = patterns('mysite.polls.views',
(r'^polls/$', 'index'),
(r'^polls/(?P<poll_id>\d+)/$', 'detail'),
(r'^polls/(?P<poll_id>\d+)/results/$', 'results'),
(r'^polls/(?P<poll_id>\d+)/vote/$', 'vote'),
)
Le comportement est le même que la version précédente. C'est juste un peu plus propre.
Tant qu'on y est, prenons le temps de découpler les URL de notre application de sondages et la configuration du projet Django. Les applications Django sont censées être branchables -- cela signifie que chaque application devrait être transférable vers une autre installation de Django avec un minimum de travail.
Notre application de sondage est actuellement bien découplée, grâce à la structure de fichiers créée par python manage.py startapp, mais une partie reste couplée à la configuration de Django : l'URLconf.
Nous avons placé nos URLs dans mysite/urls.py, mais le design des URL d'une application est spécifique à cette application, et non à l'installation de Django -- déplaçons donc nos URLs dans le dossier de l'application.
Copiez le fichier mysite/urls.py vers mysite/polls/urls.py. Puis changez le fichier mysite/urls.py pour supprimer les URL spécifique aux sondages, et insérez un include() :
(r'^polls/', include('mysite.polls.urls')),
Trés simplement, include() fait référence à un autre URLconf.Notez que l'expression régulière ne contient pas de $ (caractère correspondant à la fin d'une chaîne de caractères) mais se termine bien par un slash. Lorsque Django rencontre la fonction include(), il coupe la partie de l'URL correspondant jusqu'à ce point et transmet la partie correspondante à l'URLconf inclu pour le traitement de la requête.
Voici ce qui se produit lorsqu'un utlisateur se rend à l'adresse "/polls/34/" :
Maintenant que tout cela est découplé, nous avons besoin de découpler l'URLconf 'mysite.polls.urls' en supprimant le préfixe "polls/" de chaque ligne et en supprimant les lignes qui enregistrent le site admin:
urlpatterns = patterns('mysite.polls.views',
(r'^$', 'index'),
(r'^(?P<poll_id>\d+)/$', 'detail'),
(r'^(?P<poll_id>\d+)/results/$', 'results'),
(r'^(?P<poll_id>\d+)/vote/$', 'vote'),
)
L'idée derrière la fonction include() et le découplage des URLconf est de rendre les URLs facilement plug-and-play. Maintenant que les sondages ont leur propre URLconf, ils peuvent être placés dans "/polls/", ou dans "/fun_polls/", ou encore dans "/content/polls/", bref, n'importe quel chemin. L'application fonctionnera toujours.
Toute l'application de sondage prend en compte les URLs relatives et non les URLs absolues.
Lorsque vous êtes à l'aise avec l'écriture des vues, lisez la partie 4 de ce tutoriel pour apprendre la gestion de formulaires simples et les vues génériques.
Nov 11, 2009