Documentation de Django 1.1.1

Écrire votre première application Django, partie 4

Ce tutoriel commence là où le 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 <form> :

<h1>{{ poll.question }}</h1>

{% if error_message %}<p><strong>{{ error_message }}</strong></p>{% endif
%}

<form action="/polls/{{ poll.id }}/vote/" method="post">
{% for choice in poll.choice_set.all %}
    <input type="radio" name="choice" id="choice{{ forloop.counter }}" value="{{ choice.id }}" />
    <label for="choice{{ forloop.counter }}">{{ choice.choice }}</label><br />
{% endfor %}
<input type="submit" value="Vote" />
</form>

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 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 Tutoriel 3, nous avons créé un URLconf pour l'application de sondage contenant cette ligne:

(r'^(?P<poll_id>\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 :

  • 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 request.POST sont toujours des chaînes de caractères.

    Notez que Django dispose aussi de request.GET pour accéder aux données GET de la même manière -- mais nous utilisons explicitement 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 KeyError si choice n'est pas spécifié dans les données POST. Le code ci-dessus vérifie qu'une exception 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 HttpResponseRedirect plutôt qu'une HttpResponse normale. 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 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 reverse() dans le contructeur de 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 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 HttpRequest. Pour plus d'informations sur les objets HttpRequest, voir la documentation des requêtes et réponses (en).

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 Tutoriel 3. La seule différence est le nom du template. Nous éliminerons cette redondance plus tard.

Écrivins maintenant le template results.html:

<h1>{{ poll.question }}</h1>

<ul>
{% for choice in poll.choice_set.all %}
    <li>{{ choice.choice }} -- {{ choice.votes }} vote{{ choice.votes|pluralize }}</li>
{% endfor %}
</ul>

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

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<poll_id>\d+)/$', 'detail'),
    (r'^(?P<poll_id>\d+)/results/$', 'results'),
    (r'^(?P<poll_id>\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<object_id>\d+)/$', 'django.views.generic.list_detail.object_detail', info_dict),
    url(r'^(?P<object_id>\d+)/results/$', 'django.views.generic.list_detail.object_detail', dict(info_dict, template_name='polls/results.html'), 'poll_results'),
    (r'^(?P<poll_id>\d+)/vote/$', 'mysite.polls.views.vote'),
)

Nous utilisons ici deux vues génériques : object_list() et 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 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 url() de django.conf.urls.defaults. C'est une bonne habitude d'utiliser url() lorsque vous définissez un nom de schéma comme celui-ci.

Par défaut, la vue générique object_detail() utilise un template appelé <app name>/<model name>_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 render_to_response() dans vote().

De la même façon, la vue générique object_list() utilise un template appelé <app name>/<model name>_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 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

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

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 à 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 reverse() pour éviter de coder en dur nos URLs. Depuis que nous sommes passé aux vues génériques, nous devons changer l'appel à 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).

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)