Rédaction de votre première appli Django, partie 3 : Création des vues de l'interface publique

vignette

Ce tutoriel commence là où s'est achevé le Tutoriel 2. Nous continuons l'application Web de sondage et allons nous focaliser sur la création d'interfaces publiques -- les « vues ».

Philosophie

Une vue est un « type » de page Web dans votre application Django qui en général sert une fonctionnalité spécifique et possède un template spécifique. Par exemple, dans une application de weblog, vous pourriez avoir les vues suivantes:

  • Page d'accueil du blog -- afficher quelques unes des dernières entrées.
  • Page de « détail » d'un billet -- page au lien permanent pour un certain billet.
  • Page d'archivage par années -- affiche tous les mois ayant des entrées dans une année donnée.
  • Page d'archivage par mois -- affiche tous les jours ayant des entrées dans un mois donné.
  • Page d'archivage par jour -- affiche toutes les entrées pour un jour donné.
  • Action de commentaire -- gère l'envoi de commentaires pour un billet donné.

Dans notre application de sondage, nous aurons les quatre vues suivantes:

  • Page « d'archive » des sondages -- affiche quelques uns des derniers sondages.
  • Page « de détail » de sondage -- affiche la question d'un sondage, sans les résultats mais avec un formulaire pour voter.
  • Page « de résultat » de sondage -- affiche les résultats pour un sondage particulier.
  • Action de vote -- gère le vote d'un choix particulier pour un sondage particulier.

Dans Django, chaque vue est représentée par une simple fonction Python.

Conception de vos URLs

La première étape dans la rédaction de vues est de concevoir la structure de vos URLs. Vous faites cela en créant un module Python, appelé URLconf. Les URLconfs décrivent comment Django associe une URL donnée avec un code Python donné.

Lorsqu'un utilisateur effectue une requête pour une page gérée par Django, le système examine l'option ROOT_URLCONF, qui contient une chaîne dans la syntaxe à points de Python. Django charge ce module et recherche une variable de module appelé urlpatterns, que est une séquence de tuples dans le format suivant:

(expression rationnelle, fonction Python de rappel [, dictionnaire facultatif])

Django démarre la première expression rationnelle et parcourt la liste, en comparant l'URL de la requête avec chaque expression rationnelle jusqu'à ce qu'il en trouve une qui corresponde.

Quand il trouve une correspondance, Django appelle la fonction Python de rappel, avec un objet HTTPRequest en premier argument, toutes les valeurs « capturées » dans l'expression rationnelle sous forme d'arguments mot-clés, et, facultativement, un argument mot-clé arbitraire contenant un dictionnaire (le troisième élément facultatif du tuple).

Pour plus d'infos sur les objets HTTPRequest, lisez la documentation sur les requêtes et réponses. Pour plus de détails sur les URLconfs, lisez la documentation URLconf.

Quand vous avez lancé python manage.py startproject monsite au début du Tutoriel 1, ça a créé un URLconf par défaut dans monsite/urls.py. Ça a aussi défini automatiquement votre option ROOT_URLCONF pour pointer vers ce fichier:

ROOT_URLCONF = 'monsite.urls'

Passons à un exemple. Éditez monsite/urls.py pour qu'il ressemble à ceci:

from django.conf.urls.defaults import *

urlpatterns = patterns('',
    (r'^polls/$', 'monsite.polls.views.index'),
    (r'^polls/(?P<poll_id>\d+)/$', 'monsite.polls.views.detail'),
    (r'^polls/(?P<poll_id>\d+)/results/$', 'monsite.polls.views.results'),
    (r'^polls/(?P<poll_id>\d+)/vote/$', 'monsite.polls.views.vote'),
)

Cela mérite quelques explications. Lorsque quelqu'un effectue une requête sur une page de votre site Web -- disons, « /polls/23/ », Django va charger ce module Python, parce qu'il est pointé par l'option ROOT_URLCONF. Ça trouve la variable nommée urlpatterns et parcourt les expressions rationnelles dans l'ordre. Quand il trouve une expression rationnelle qui correspond -- r'^polls/(?P<poll_id>\d+)/$' -- il charge le module ou paquetage Python associé : monsite.polls.views.detail. Ce qui correspond à la fonction detail() dans mysite/polls/views.py.

Enfin, il appelle cette fonction detail() comme ceci:

detail(request=<HttpRequest object>, poll_id='23')

La partie poll_id='23' vient de (?P<poll_id>\d+). Mettre les parenthèses autour d'un masque (pattern) « capture » le texte correspondant à ce masque et l'envoie en argument à la fonction de vue. Le ?P<poll_id> définit le nom qui sera utilisé pour identifier le masque correspondant et \d+ est une expression rationnelle pour trouver une séquence de chiffres (c'est-à-dire, un nombre).

Parce que les masques d'URL sont des expressions rationnelles, il n'y a vraiment aucune limite sur ce que vous pouvez faire avec eux. Et il n'est pas nécessaire 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$', 'monsite.polls.views.index'),

Mais, ne faites pas ça. C'est stupide.

Notez que ces expressions rationnelles ne recherchent pas dans les paramètres GET et POST, ni dans le nom de domaine. Par exemple, dans une requête vers http://www.exemple.com/monappli/, l'URLconf va rechercher dans /monappli/. Dans une requête vers http://www.exemple.com/monappli/?page=3, l'URLconf va rechercher dans /myapp/.

Si vous avez besoin d'aide sur les expressions rationnelles, lisez l'article de Wikipedia et la documentation Python. De plus, le livre O'Reilly « Mastering Regular Expressions » de Jeffrey Friedl est fantastique.

Pour finir, une note sur la performance: ces expressions rationnelles sont compilées la première fois que le modul URLconf est chargé. Ça les rend très rapides.

Écrire votre première vue

Bien, nous n'avons toujours pas créé de vue -- nous avons juste touché à URLconf. Mais assurons-nous que Django suit l'URLconf comme il faut.

Lancez le serveur Web de développement de Django:

python manage.py runserver

Maintenant, allez sur « http://localhost:8000/polls/ » avec votre navigateur Web. Vous devriez obtenir une page d'erreur agréablement 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 survient parce que vous n'avez pas écrit la fonction index() dans le module monsite/polls/views.py.

Essayez « /polls/23/ », « /polls/23/results/ » et « /polls/23/vote/ ». Les messages d'erreur vous disent quelle vue Django essaie de charger (et n'arrive pas à trouver, parce que vous n'avez pas encore écrit les vues).

Il est temps d'écrire la première vue. Ouvrez le fichier monsite/polls/views.py et mettez-y le code Python suivant:

from django.http import HttpResponse

def index(request):
    return HttpResponse("Bonjour tout le monde. Vous est dans l'index des sondages.")

Voilà la vue la plus simple possible. Allez dans « /polls/ » avec votre navigateur, et vous devriez voir votre texte.

À présent, ajoutez la vue suivante. C'est un petit peu différent, parce qu'il prend un argument (qui, rappelez-vous, est passé depuis ce qui a été capturé par l'expression rationnelle dans l'URLconf):


def detail(request, poll_id):
    return HttpResponse("Vous consultez le sondage %s." % poll_id)

Jetez à oeil à votre navigateur, à « /polls/34/ ». Ça affichera l'ID que vous avez fournie dans l'URL.

Écrire des vues qui font quelque chose

Chaque vue a la responsabilité de faire l'une des deux choses suivantes : Retourner un objet HttpResponse renfermant le contenu de la page demandée, ou lever une exception telle que Http404. Le reste est pour vous.

Votre vue peut lire ou non les enregistrements d'une base de données. Il peut utiliser ou non un système de template tel que celui de Django -- ou un système tiers de template Python. Il peut générer un fichier PDF, une sortie XML, créer un fichier ZIP à la volée, tout ce que vous voulez, en utilisant toutes les bibliothèques Python que vous désirez.

Tout ce que Django veut, c'est HttpResponse. Ou une exception.

Parce que c'est plus pratique, utilisons l'API de base de données propre à Django, que nous avons abordée dans le Tutoriel 1. Voici un premier jet de la vue index(), qui affiche les 5 dernières questions de sondage du système, séparées par des virgules, triées par date de publication:

from monsite.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)

Nous avons ici un problème, à savoir : Le design de la page est codée en dure dans la vue. Si vous voulez changer l'apparence de la page, vous devrez éditer ce code Python. Donc utilisons plutôt le système de template de Django pour la présentation des données:

from django.template import Context, loader
from monsite.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 la template appelé « polls/index.html » et lui passe un contexte. Le contexte est un dictionnaire indexant les objets Python selon des noms de variable de template.

Rechargez la page. Maintenant, vous verrez une erreur:

TemplateDoesNotExist at /polls/
polls/index.html

Ah. Il n'y a pas encore de template. En premier lieu, créez un répertoire, quelque part dans votre système de fichers, auquel Django a accès (Django s'exécute avec les droits de l'utilisateur qui a lancé le serveur). Cependant, ne les mettez pas à la racine de vos documents. Vous ne devriez probablement pas les rendre publics, juste par mesure de sécurité.

Puis éditez TEMPLATE_DIRS dans votre settings.py pour dire à Django où il peut trouver les templates -- tout comme vous l'avez fait dans la section « Personnaliser l'apparence de l'interface d'admin » du Tutoriel 2.

Une fois fait, créez un répertoire polls dans votre répertoire de templates. Dedans, créez un fichier appelé index.html. Notez que notre code loader.get_template('polls/index.html') ci-dessus se réfère à "[répertoire_de_templates]/polls/index.html" dans le système de fichiers.

Placez 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 disponnible.</p>
{% endif %}

Chargez la page dans votre navigateur Web, et vous devriez voir une liste à puce contenant le sondage "Quoi de neuf" du Tutoriel 1.

Un raccourci: render_to_response()

Il est vraiment très courant de charger un template, remplir un contexte et retourner un objet HttpResponse avec le résultat du rendu de la template. Django fournit un raccourci. Voici le vue complète de index(), réécrite:

from django.shortcuts import render_to_response
from monsite.polls.models import Poll

def index(request):
    latest_poll_list = Poll.objects.all().order_by('-pub_date')
    return render_to_response('polls/index.html', {'latest_poll_list': latest_poll_list})

Notez que nous n'avons plus besoin d'importer loader, Context ni HttpResponse.

La fonction render_to_response() prend un nom de template en premier argument et un dictionnaire comme second argument facultatif. Il retourne un objet HttpResponse du template donné rendu avec le contexte donné.

Lever l'erreur 404

À présent, abordons la vue de sondage détaillé -- la page qui affiche la question d'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 l'exception django.http.Http404 si un sondage avec l'ID demandé n'existe pas.

Un raccourci : get_object_or_404()

Il est vraiment très courant d'utiliser get_object() et de déclencher 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 modèle Django en premier argument et un nombre arbitaire d'arguments mot-clés, qu'elle passe à la fonction get_object() du module. Elle déclenche Http404 si l'object n'existe pas.

Philosophie

Pourquoi utilisons-nous une fonction utilitaire get_object_or_404() au lieu de récupérer automatiquement les exceptions DoesNotExist de haut niveau, ou avoir l'API du modèle qui déclenche Http404 au lieu de DoesNotExist ?

Parce que ça serait coupler la couche du modèle avec la couche de la vue. Une des règles d'or de la conception de Django est de maintenir ce découplage entre les différentes couches.

Il y a également une fonction get_list_or_404(), qui fonctionne similairement à get_object_or_404() -- sauf qu'elle utilise filter() au lieu de get(). Elle lève Http404 si la liste est vide.

Écrire une vue 404 (page non trouvée)

Lorsque vous levez une Http404 à partir d'une vue, Django va charger une vue spéciale qui s'occupe des erreurs 404. Il la trouve en cherchant la variable handler404, qui est une chaîne de caractères utilisant la syntaxe Python à points -- le même format qui est utilisé par l'URLconf. Une vue 404 en elle-même n'a rien de particulier : c'est juste une vue classique.

Vous n'avez normalement pas à vous soucier de l'écriture des vues 404. Par défaut les URLconfs ont la ligne suivante en en-tête :

from django.conf.urls.defaults import *

Cela permet de prendre en compte le paramètre handler404 pour le module courrant. Comme vous pouvez le constater dans django/conf/urls/defaults.py, handler404 correspond par défaut à 'django.views.defaults.page_not_found'.

Encore trois remarques au sujet des vues 404 :

  • La vue 404 est aussi appelée si aucune des expressions rationnelle de l'URLconf ne correspond à l'adresse demandée.
  • Si vous ne définissez pas votre propre vue 404 -- et utilisez celle par défaut, ce qui est recommandé -- vous devez quand même créer un template 404.html à la racide de votre dossier de templates.
  • Si DEBUG est à True (dans votre module de paramètres), votre vue 404 utilisera ce template pour toutes les erreurs 404.

Écrire une vue 500 (erreur serveur)

De la même manière, URLconfs peut définir une handler500, qui pointe sur une vue appelée en cas d'erreur du serveur. Les erreurs du serveur lorsque vous avez des erreurs d'exécution dans le code de vos vues.

Utiliser le système de template

Revenons à notre vue polls.detail. Considérant la variable de contexte poll, voici le template tel qu'il pourrait être:

<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 par points pour accéder aux attributs des variables. Dans l'exemple {{ poll.question }}, Django commence par chercher la clé question du dictionnaire poll. N'y parvenant pas, il essaye avec les attributs de classe -- ce qui marche dans ce cas. Si ça n'avait pas été le cas, il aurait essayé d'appeler la méthode question() de l'objet poll.

L'appel d'une méthode se produit dans la boucle {% for %} : poll.choice_set.all est interprété comme le code poll.choice_set.all(), ce qui renvoie un iterable d'objets « Choice » ce qui est parfait pour une utilisation dans un tag {% for %}.

Lisez le guide des templates pour davantage de détails sur le fonctionnement des templates.

Simplifier URLconfs

Prennons le temps de jouer un peu avec les vues et le système de templates. Lors de l'édition d'URLconf, vous avez dû vous rendre compte de la redondance présente dans le fichier:

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'),
)

Le répertoire d'accès mysite.polls.views est dans chaque URL.

Étant un cas courrant, le framework URLconf possède un raccourci pour les préfixes communs. Vous pouvez les factoriser et les placer en premier argument de patterns() de la façon suivante:

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'),
)

C'est identique au précédent formatage. C'est juste un peu plus propre.

Découpler URLconfs

En parlant de ça, prennons le temps de découpler les URLs de notre appli de sondage à partir de la configuration de notre projet Django. Les applis Django sont censées être « pluggables » -- ce qui signifie que chaque application peut être transférée sur une autre installation de Django avec le minimum de désagréments.

Notre appli de sondage est pas mal découplée pour le moment grâce à la structure générée par python manage.py startapp. Une partie est néanmois liée aux paramètres de Django : l'URLconf.

Nous avons édité les URLs dans mysite/urls.py, mais la conception des URLs d'une appli est spécifique à l'application, pas à l'installation de Django -- déplaçons donc les URLs dans le dossier de notre appli.

Copiez le fichier mysite/urls.py vers mysite/polls/urls.py. Puis, modifiez mysite/urls.py pour retirer les URLs spécifiques au sondage et insérez un include():


(r'^polls/', include('mysite.polls.urls')),

include(), permet simplement d'établir une référence à une autre URLconf. Notez l'absence de $ (détection de fin de chaîne de caractère) dans l'expression rationnelle mais la présence d'un slash final. Lorsque Django rencontre un include(), il extrait tout ce qui suit l'URL détectée et l'envoie à l'URLconf inclus pour un traitement ultérieur.

Voici ce qu'il se passe si un utilisateur se rend sur « /polls/34/ » avec ce système :

  • Django va rencontrer '^polls/'
  • Il va enlever la partie reconnue ("polls/") et envoyer le texte restant -- "34/" -- à l'URLconf monsite.polls.urls' pour un traitement ultérieur.

Maitenant que le découplage est effectué, il est nécessaire de découpler l'URLconf de 'monsite.polls.urls' en enlevant les préfixes « polls/ » à chaque ligne:

urlpatterns = patterns('monsite.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 include() et le découplage d'URLconf est de faciliter l'aspect plug-and-play des URLs. Maintenant que les sondages ont leur propre URLconf, ils peuvent être placés à la suite de « /polls », ou de « /fun_polls », ou de « /content/polls », ou de n'importe quelle URL, et l'appli sera toujours fonctionnelle.

Toute l'appli de sondage se base sur des URL relatives, non sur des URLs absolues.

Lorsque vous avez assimilé l'écriture des vues, lisez la partie 4 de ce tutoriel de façon à en apprendre plus sur la génération de formulaires et les vues génériques.

Vous pouvez maintenant retourner à la page d'accueil des traductions de la documentation de Django.

Cette traduction correspond à la révision 3589 (post 0.95).

— 17/06/2006

Articles peut-être en rapport

Commentaires

David le 19/07/2006 :

Tu as laissé un morceau d'anglais dans la partie erreur 500 :-)

David, biologeek le 19/07/2006 :

En effet, merci c'est corrigé :)

gigi le 04/01/2007 :

Copiez le fichier mysite/urls.py vers mysite/polls/urls.py. Puis, modifiez mysite/urls.py pour retirer les URLs spécifiques au sondage et insérez un include():

(r'^polls/', include('mysite.polls.urls')),

à remplacer par :

Copiez le fichier mysite/urls.py vers mysite/polls/urls.py. Puis, modifiez mysite/urls.py comme indiquer ci-dessous

from django.conf.urls.defaults import *

urlpatterns = patterns('',
(r'', include('mysite.polls.urls')),
)

cgasmi le 24/05/2007 :

il y a une petite erreur de traduction:
une requête vers www.exemple.com/monappli/... l'URLconf va rechercher dans /myapp/.

/myapp/ a la place de /monappli/

Sinon merci pour cette doc, elle m'a beaucoup aider dans mon debut avec django.

Raysuken le 26/06/2007 :

Tres bon tuto pour commercer a utiliser le framework django !!

Merci