.. _intro-tutorial04: ================================================== Écrire votre première application Django, partie 4 ================================================== Ce tutoriel commence là où le :ref:`Tutoriel 3 ` s'achève. Nous continuons l'application de sondage Web et allons nous focaliser la gestion des formulaires et sur la réduction du code. Écrire un simple formulaire =========================== Nous allons mettre à jour le template de la page de détail ("polls/details.html") du dernier tutoriel, de manière que le template contienne une balise HTML ``
`` : .. code-block:: html+django

{{ poll.question }}

{% if error_message %}

{{ error_message }}

{% endif %} {% for choice in poll.choice_set.all %}
{% endfor %}
Un résumé rapide : * Ce template affiche un bouton radio pour chaque choix. L'attribut ``value`` de chaque bouton radio correspond à l'ID du vote choisi. Le nom (``name``) de chaque bouton radio est ``"choice"``. Cela signifie que lorsque quelqu'un sélectionne l'un des boutons radio et valide le formulaire, les données POST ``choice=3`` seront envoyées. Ce sont les formulaires HTML 101. * Nous avons défini ``/polls/{{ poll.id }}/vote/`` comme attribut ``action`` du formulaire, et nous précisons ``method=post``. L'utilisation de ``method="post"`` (par opposition à ``method="get"``) est très importante, puisque le fait de valider ce formualire va entrainer des modifications de données sur le serveur. À chaque fois qu'un formulaire modifie des données sur le serveur, vous devez utiliser ``method="post"``. Cela ne concerne pas uniquement Django ; c'est une bonne pratique à adopter en tant que développeur Web. * ``forloop.counter`` indique combien de fois la balise :ttag:`for` a exécuté sa boucle. Maintenant, nous allons créer une vue qui récupère les données envoyées pour nous permettre de faire des choses avec. Souvenez-vous, dans le :ref:`Tutoriel 3 `, nous avons créé un URLconf pour l'application de sondage contenant cette ligne:: (r'^(?P\d+)/vote/$', 'vote'), Écrivons donc une fonction ``vote()`` dans ``mysite/polls/views.py``:: from django.shortcuts import get_object_or_404, render_to_response from django.http import HttpResponseRedirect from django.core.urlresolvers import reverse from mysite.polls.models import Choice, Poll # ... def vote(request, poll_id): p = get_object_or_404(Poll, pk=poll_id) try: selected_choice = p.choice_set.get(pk=request.POST['choice']) except (KeyError, Choice.DoesNotExist): # Réafficher le formulaire de vote du sondage. return render_to_response('polls/detail.html', { 'poll': p, 'error_message': "Veuillez sélectionner une réponse.", }) else: selected_choice.votes += 1 selected_choice.save() # Toujours renvoyer une HttpResponseRedirect après avoir # correctement traité les données POST. Cela empêche de poster les # données deux fois si l'utilisateur appuie sur le bouton # "précedent". return HttpResponseRedirect(reverse('mysite.polls.views.results', args=(p.id,))) Ce code contient quelques points encore non traités dans ce tutoriel : * :attr:`request.POST ` est un objet similaire à un dictionnaire qui vous permet d'accéder aux données envoyées par leurs clés. Dans ce cas, ``request.POST['choice']`` renvoie l'ID du choix sélectionné, sous forme d'une chaîne de caractères. Les valeurs dans :attr:`request.POST ` sont toujours des chaînes de caractères. Notez que Django dispose aussi de :attr:`request.GET ` pour accéder aux données GET de la même manière -- mais nous utilisons explicitement :attr:`request.POST ` dans notre code, pour s'assurer que les données ne sont modifiées que par des requêtes POST. * ``request.POST['choice']`` lèvera une exception :exc:`KeyError` si ``choice`` n'est pas spécifié dans les données POST. Le code ci-dessus vérifie qu'une exception :exc:`KeyError` n'est pas levée et réaffiche le formulaire de sondage avec un message d'erreur si ``choice`` n'est pas rempli. * Après l'incrémentation du numéro du choix, le code renvoie une :class:`~django.http.HttpResponseRedirect` plutôt qu'une :class:`~django.http.HttpResponse` normale. :class:`~django.http.HttpResponseRedirect` prend un seul argument : l'URL vers laquelle l'utilisateur va être redirigé (voir le point suivant pour la manière de construire cette URL dans ce cas). Comme le commentaire Python l'indique, vous devez systématiquement renvoyer une :class:`~django.http.HttpResponseRedirect` après avoir correctement traité les données POST. Ceci n'est pas valable uniquement avec Django, c'est une bonne pratique du développement Web. * Dans cet exemple, nous utilisons la fonction :func:`~django.core.urlresolvers.reverse` dans le contructeur de :class:`~django.http.HttpResponseRedirect`. Cette fonction nous évite de coder en dur une URL dans une vue. On lui donne en paramètre la vue vers laquelle nous voulons rediriger et la partie variable de l'URL qui pointe vers cette vue. Dans ce cas, en utilisant l'URLconf défini dans la partie 3 de ce tutoriel, l'appel de la fonction :func:`~django.core.urlresolvers.reverse` va retourner une chaîne de caractères:: '/polls/3/results/' ... où ``3`` est la valeur de ``p.id``. Cette URL de redirection va être ensuite appeler la vue ``'results'`` pour afficher la page finale. Notez que vous devez utiliser le nom complet de la vue, préfixe compris. Comme expliqué dans la partie 3 de ce tutoriel, ``request`` est un objet :class:`~django.http.HttpRequest`. Pour plus d'informations sur les objets :class:`~django.http.HttpRequest`, voir la `documentation des requêtes et réponses`_ (en). .. _documentation des requêtes et réponses: http://docs.djangoproject.com/en/dev/ref/request-response/#ref-request-response Après le vote d'une personne dans un sondage, la vue ``vote()`` redirige vers la page de résultats du sondage. Écrivons cette vue : :: def results(request, poll_id): p = get_object_or_404(Poll, pk=poll_id) return render_to_response('polls/results.html', {'poll': p}) C'est presque exactement la même que la vue ``detail()`` du :ref:`Tutoriel 3 `. La seule différence est le nom du template. Nous éliminerons cette redondance plus tard. Écrivins maintenant le template ``results.html``: .. code-block:: html+django

{{ poll.question }}

    {% for choice in poll.choice_set.all %}
  • {{ choice.choice }} -- {{ choice.votes }} vote{{ choice.votes|pluralize }}
  • {% endfor %}
Maintenant, rendez-vous à la page ``/polls/1/`` avec votre navigateur et votez au sondage proposé. Vous devriez voir une page de résultats qui sera mise à jour à chaque fois que vous voterez. Si vous validez le formulaire sans avoir coché votre choix, vous devriez voir le message d'erreur. Utilisation des vues génériques : moins de code, c'est mieux ============================================================ Les vues ``detail()`` (dans le :ref:`Tutoriel 3 `) et ``results()`` sont triviales -- et comme mentionné précédemment, redondantes. La vue ``index()`` (aussi dans le Tutoriel 3), qui affiche une liste de sondages, est similaire. Ces vues représentent un cas classique du développement Web : récupérer les données depuis la base de données suivant un paramètre contenu dans l'URL, charger un template et renvoyer le template interprété. Ce cas est tellement classique que Django propose un raccourci, appelé le système de "vues génériques". Les vues génériques permettent l'abstraction de pratiques communes, à un tel point que vous n'avez pas à écrire du code python pour écrire une application. Nous allons convertir notre application de sondage pour qu'elle utilise le système de vues génériques, nous pourrons ainsi supprimer une partie de notre code. Nous avons juste quelques pas à faire pour faire cette conversion. Nous allons: 1. Convertir l'URLconf. 2. Renommer quelques templates. 3. Supprimer quelques anciennes vues désormais inutiles. 4. Corriger la gestion des URL pour les nouvelles vues. Lisez la suite pour plus de détails. .. admonition:: Pourquoi ce mélange ? En général, lorsque vous écrirez une application Django, vous estimerez que les vues génériques correspondent bien à vos besoins, et vous les utiliserez à partir du début, plutôt que de réarranger votre code à mi-chemin. Mais ce tutoriel s'est concentré intentionellement sur l'écriture des vues "à la dure" jusque maintenant, pour mettre l'accent sur les concepts de base. Vous devez avoir des bases en maths avant de commencer à utiliser une calculatrice. Tout d'abord, ouvrez l'URLconf ``polls/urls.py``. Il ressemble à ceci, si vous avez suivi le tutoriel jusqu'ici : :: from django.conf.urls.defaults import * urlpatterns = patterns('mysite.polls.views', (r'^$', 'index'), (r'^(?P\d+)/$', 'detail'), (r'^(?P\d+)/results/$', 'results'), (r'^(?P\d+)/vote/$', 'vote'), ) Change-le en : :: from django.conf.urls.defaults import * from mysite.polls.models import Poll info_dict = { 'queryset': Poll.objects.all(), } urlpatterns = patterns('', (r'^$', 'django.views.generic.list_detail.object_list', info_dict) (r'^(?P\d+)/$', 'django.views.generic.list_detail.object_detail', info_dict), url(r'^(?P\d+)/results/$', 'django.views.generic.list_detail.object_detail', dict(info_dict, template_name='polls/results.html'), 'poll_results'), (r'^(?P\d+)/vote/$', 'mysite.polls.views.vote'), ) Nous utilisons ici deux vues génériques : :func:`~django.views.generic.list_detail.object_list` et :func:`~django.views.generic.list_detail.object_detail`. Respectivement, ces deux vues permettent l'abstraction de "afficher une liste d'objets" et "afficher une page détaillée pour un type particulier d'objet". * Chaque vue générique a besoin de connaître les données sur lesquelles elles vont agir. Ces données sont fournies dans un dictionnaire. La clé ``queryset`` de ce dictionnaire pointe vers la liste d'objets qui doivent être manipulés par la vue générique. * La vue générique :func:`~django.views.generic.list_detail.object_detail` s'attend à ce que l'ID contenu dans l'URL s'appelle ``"object_id"``, nous avons donc changé ``poll_id`` en ``object_id`` pour la vue générique. * Nous avons ajouté un nom -- ``poll_results`` -- à la vue des résultats de manière à avoir une manière de faire référence à son URL plus tard (voir la documentation à propos du `nommage des URL`_ - en - pour plus d'information). Nous utilisons aussi la fonction :func:`~django.conf.urls.default.url` de :mod:`django.conf.urls.defaults`. C'est une bonne habitude d'utiliser :func:`~django.conf.urls.defaults.url` lorsque vous définissez un nom de schéma comme celui-ci. .. _nommage des URL: http://docs.djangoproject.com/en/dev/topics/http/urls/#naming-url-patterns Par défaut, la vue générique :func:`~django.views.generic.list_detail.object_detail` utilise un template appelé ``/_detail.html``. Dans notre cas, elle va utiliser le template ``"polls/poll_detail.html"``. Ainsi, renommez votre template ``polls/detail.html`` en ``polls/poll_detail.html``, et changez la ligne :func:`~django.shortcuts.render_to_response` dans ``vote()``. De la même façon, la vue générique :func:`~django.views.generic.list_detail.object_list` utilise un template appelé ``/_list.html``. Ainsi, renommez ``polls/index.html`` en ``polls/poll_list.html``. Comme nous avons plus d'une entrée dans l'URLconf qui utilise :func:`~django.views.generic.list_detail.object_detail` pour l'application de sondages, nous précisons manuellement un nom de template pour la vue des résultats : ``template_name='polls/results.html'``. Sinon, les deux vues utiliseraient le même template. Notez que nous utilisons ``dict()`` pour renvoyer un dictionnaire altéré. .. note:: :meth:`django.db.models.QuerySet.all` est paresseux Cela pourrait paraitre un peu effrayant de voir ``Poll.objects.all()`` utilisé dans une vue de détail qui ne nécessite qu'un seul objet ``Poll``, mais ne vous inquiétez pas ; ``Poll.objects.all()`` est en fait un objet spécial appelé :class:`~django.db.models.QuerySet`, qui est "paresseux" et ne touche pas à la base de données tant qu'il n'en a pas absolument besoin. Pendant le temps de la requête sur la base de données, la vue générique :func:`~django.views.generic.list_detail.object_detail` aura réduit sa portée à un seul objet, et l'éventuelle requête ne va sélectionner qu'un seul objet dans la base de données. Si vous voulez en savoir plus sur le fonctionnement de tout ceci, la documentation de l'API de base de données de Django `explique la nature paresseuse des objets QuerySet`_ (en). .. _explique la nature paresseuse des objets QuerySet: http://docs.djangoproject.com/en/dev/topics/db/queries/#querysets-are-lazy Dans les parties précedentes de ce tutoriel, les templates ont été fournis par un contexte qui contenait les variables de contexte ``poll`` et ``latest_poll_list``. Cependant, les vues génériques fournissent les variables ``object`` et ``object_list`` comme contexte. Par conséquent, vous devez changer vos templates, et modifier toutes les références à ``latest_poll_list`` en ``object_list``, et changer les références à ``poll`` en ``object``. Vous pouvez maintenant supprimer les vues ``index()``, ``detail()`` et ``results()`` dans ``polls/views.py``. Nous n'en avons plus besoin -- elles ont été remplacées par les vues génériques. La vue ``vote()`` est encore requise. Cependant, elle doit être modifiée pour correspondre aux nouvelles variables de contexte. Dans l'appel à :func:`~django.shortcuts.render_to_response`, renommez la variable de contexte ``poll`` en ``object``. la dernière chose à faire est corriger la gestion des URL pour prendre en compte la gestion des vues génériques. Dans la vue de vote précédente, nous avons utilisé la fonction :func:`~django.core.urlresolvers.reverse` pour éviter de coder en dur nos URLs. Depuis que nous sommes passé aux vues génériques, nous devons changer l'appel à :func:`~django.core.urlresolvers.reverse` pour pointer de nouveau sur notre nouvelle vue générique. Nous ne pouvons plus utiliser la fonction de vue -- les vues génériques peuvent être (et sont) utilisées plusieurs fois -- mais nous pouvons utiliser le nom que nous avons donné : :: return HttpResponseRedirect(reverse('poll_results', args=(p.id,))) Lancez le serveur, et utilisez votre nouvelle application de sondage utilisant les vues génériques. Pour plus de détails sur les vues génériques, voir la `documentation des vues génériques`_ (en). .. _documentation des vues génériques: http://docs.djangoproject.com/en/dev/topics/http/generic-views/#topics-http-generic-views Bientôt ======= Le tutoriel s'arrête pour l'instant ici. Les prochaines mises à jour de ce tutoriel couvriront : * Gestion avancée des formulaires * Utilisation du framework RSS * Utilisation du framework de cache * Utilisation du framework de commentaires * Fonctionnalités avancées de l'interface admin : Permissions * Fonctionnalités avancées de l'interface admin : JavaScript personnalisé En attendant, vous devriez trouver quelques liens dans `où aller à partir d'ici`_ (en) .. _où aller à partir d'ici: http://docs.djangoproject.com/en/dev/intro/whatsnext/#intro-whatsnext