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

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

### Articles peut-être en rapport

- Le langage de template Django : Pour les auteurs de templates
- Comparaison de TurboGears et Django, deux frameworks web Python
- Rédaction de votre première appli Django, partie 4 : Conception d'un formulaire et vues génériques

### 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