.. _intro-tutorial03: ================================================== Écrire votre première application Django, partie 3 ================================================== Ce tutoriel commence là où le :ref:`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". Philosophie =========== 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 : * La page d'accueil du blog -- affiche quelques-uns des derniers billets. * La page de "détail" d'un Billet -- lien permanent vers un seul billet. * La page d'archives pour une année -- affiche tous les mois avec des billets pour une année donnée. * La page d'archives pour un mois -- affiche tous les jours avec des billets pour un mois donné. * La page d'archives pour un jour -- affiche tous les billets pour un jour donné. * Actions de commentaire -- gère l'écriture de commentaire sur un billet donné. Dans notre application de sondage, nous aurons les vues suivantes : * La page "d'archives" des sondages -- affiche quelques-uns des derniers sondages. * La page de "détail" d'un sondage -- affiche la question d'un sondage, sans les résultats mais avec un formulaire pour voter. * La page des "résultats" d'un sondage -- affiche les résultats d'un sondage en particulier. * Action de vote -- gère le vote pour un choix particulier dans un sondage précis. Dans Django, chaque vue est représentée par une simple fonction Python. Concevez vos URL ================ 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 :setting:`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 :class:`~django.http.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 :class:`~django.http.HttpRequest`, voir les `objets de requête/réponse`_. Pour plus de détails sur les URLconfs, voir le `dispatcher d'URL`_. .. _objets de requête/réponse: http://docs.djangoproject.com/en/dev/ref/request-response/#ref-request-response .. _dispatcher d'URL: http://docs.djangoproject.com/en/dev/topics/http/urls/#topics-http-urls 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 :setting:`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\d+)/$', 'mysite.polls.views.detail'), (r'^polls/(?P\d+)/results/$', 'mysite.polls.views.results'), (r'^polls/(?P\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 :setting:`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\d+)/$'`` -- il charge la fonction ``detail()`` dans ``mysite/pools/views.py``. Enfin, il appelle cette fonction ``detail()`` de cette manière : :: detail(request=, poll_id='23') La partie ``poll_id='23'`` viens de ``(?P\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`` 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. .. _article Wikipedia: http://en.wikipedia.org/wiki/Regular_expression .. _documentation de Python: http://docs.python.org/library/re.html Écrivez votre première vue ========================== 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: .. code-block:: bash 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. Écrire des vues qui font des choses réelles =========================================== Chaque vue a la charge de faire une ou deux choses : renvoyer un objet :class:`~django.http.HttpResponse` contenant le contenu de la page demandée, ou levant une exception comme :exc:`~django.http.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 : :class:`~django.http.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 :ref:`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 :setting:`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 : .. code-block:: html+django {% if latest_poll_list %}
    {% for poll in latest_poll_list %}
  • {{ poll.question }}
  • {% endfor %}
{% else %}

Aucun sondage n'est disponible.

{% endif %} Chargez la page dans votre navigateur, et vous devriez voir une liste à puces contenant le sondage "Quoi de neuf" du Tutoriel 1. Un raccourci : render_to_response() ----------------------------------- Il est très courant de charger un template, remplir un contexte et renvoyer un objet :class:`~django.http.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 :mod:`~django.template.loader`, :class:`~django.template.Context` et :class:`~django.http.HttpResponse`. La fonction :func:`~django.shortcuts.render_to_response` prend comme premier argument un nom de template et un dictionnaire comme second argumant optionnel. Elle retourne un objet :class:`~django.http.HttpResponse` du template interprété avec le contexte donné. Lever une erreur 404 ==================== 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 :exc:`~django.http.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. Un raccourci : get_object_or_404() ---------------------------------- Il est très courant d'utiliser :meth:`~django.db.models.QuerySet.get` et de lever une exception :exc:`~django.http.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 :func:`~django.shortcuts.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 :meth:`~django.db.models.QuerySet.get` du module. Elle lève une exception :exc:`~django.http.Http404` si l'objet n'existe pas. .. admonition:: Philosophie Pourquoi utiliser une fonction auxiliaire :func:`~django.shortcuts.get_object_or_404` plutôt que d'intercepter automatiquement une exception :exc:`~django.core.exceptions.ObjectDoesNotExist` à un plus haut niveau, ou laisser l'API modèles lever une exception :exc:`~django.http.Http404` à la place de :exc:`~django.core.exceptions.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 :func:`~django.shortcuts.get_list_or_404`, qui fonctionne comme :func:`~django.shortcuts.get_object_or_404` -- sauf qu'elle utilise :meth:`~django.db.models.QuerySet.filter` au lieu de la méthode :meth:`~django.db.models.QuerySet.get`. Elle lève une exception :exc:`~django.http.Http404` si la liste est vide. Écrire une vue 404 (page non trouvée) ===================================== Lorsque vous levez une exception :exc:`~django.http.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 à :func:`django.views.defaults.page_not_found`. Quatre choses supplémentaires à savoir sur les vues 404 : * Si :setting:`DEBUG` est défini à ``True`` (dans votre module de préférences), alors votre vue 404 ne sera jamais utilisée, et le traceback sera affiché en lieu et place. * La vue 404 est également appelée si Django ne trouve pas de correspondance après avoir vérifié chaque expression régulière dans l'URLconf. * Si vous ne définissez pas votre propre vue 404 -- et utilisez simplement la vue par défaut, ce qui est recommandé -- vous avez toujours une obligation : créer un template ``404.html`` dans la racine de votre répertoire de template. La vue 404 par défaut utilisera ce template pour toutes vos erreurs 404. * Si :setting:`DEBUG` est défini à ``False`` (dans votre module de préférences) et que vous ne créez pas de template ``404.html``, une erreur ``Http500`` est levée à la place. Donc souvenez-vous qu'il faut créer un template ``404.html``. Écrire une vue 500 (server error) ================================= 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. Utiliser les template ===================== 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: .. code-block:: html+django

{{ poll.question }}

    {% for choice in poll.choice_set.all %}
  • {{ choice.choice }}
  • {% endfor %}
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. .. _guide des templates: http://docs.djangoproject.com/en/dev/topics/templates/#topics-templates Simplifier les URLconfs ======================= 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\d+)/$', 'mysite.polls.views.detail'), (r'^polls/(?P\d+)/results/$', 'mysite.polls.views.results'), (r'^polls/(?P\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 :func:`~django.conf.urls.defaults.patterns`, comme ceci :: urlpatterns = patterns('mysite.polls.views', (r'^polls/$', 'index'), (r'^polls/(?P\d+)/$', 'detail'), (r'^polls/(?P\d+)/results/$', 'results'), (r'^polls/(?P\d+)/vote/$', 'vote'), ) Le comportement est le même que la version précédente. C'est juste un peu plus propre. Découpler les URLconfs ======================= 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 :func:`~django.conf.urls.defaults.include` : :: (r'^polls/', include('mysite.polls.urls')), Trés simplement, :func:`~django.conf.urls.defaults.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 :func:`~django.conf.urls.defaults.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/" : * Django trouve une correspondance à ``'^polls/'`` * Puis, Django supprime la portion qui correspondait (``"polls/"``) et envoie le texte restant -- ``"34/"`` -- à l'URLconf 'mysite.polls.urls' pour traitement final. 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\d+)/$', 'detail'), (r'^(?P\d+)/results/$', 'results'), (r'^(?P\d+)/vote/$', 'vote'), ) L'idée derrière la fonction :func:`~django.conf.urls.defaults.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 :ref:`partie 4 de ce tutoriel ` pour apprendre la gestion de formulaires simples et les vues génériques.