Rédaction de votre première appli Django, partie 4 : Conception d'un formulaire et vues génériques

vignette

Ce tutoriel commence là où le Tutoriel 3 s'est achevé. Nous continuons notre application de sondage en ligne et allons nous intéresser à la génération d'un formulaire simple et au ré-arrangement de notre code.

Générer un formulaire simple

Commençons par mettre à jour notre template de détail de sondage issu du dernier tutoriel de façon à ce que le template contienne un élement 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="Voter" />
</form>

Un bref rappel :

  • Le template ci-dessus affiche un bouton radio pour chaque choix du sondage. La valeur value de chaque bouton radio est associée à l'ID de chaque choix. Le nom name de chaque bouton radio est "choice". Cela signifie que lorsque quelqu'un sélectionne l'un des boutons radio et soumet le formulaire, il envoie la donnée de type POST choice=3. C'est un formulaire HTML 101.
  • Nous avons assigné l'action du formulaire à /polls/{{ poll.id }}/vote/, et nous avons mis method="post". L'utilisation de method="post" (au contraire de method="get") est très importante, car le fait de soumettre ce formulaire va être à l'origine d'une modification des données du côté du serveur. À chaque fois que vous créez un formulaire qui modifie des données sur le serveur, utilisez method="post". Cet adage n'est pas spécifique à Django, ce sont juste de bonnes pratiques de développement web.

À présent, créons une vue Django qui récupère les données soumises et fait quelque chose avec. Rappelez-vous, dans le Tutoriel 3, nous avons créé une URLconf pour l'application de sondage qui incluait cette ligne:

(r'^(?P<poll_id>\d+)/vote/$', 'monsite.polls.views.vote'),

Donc créons une fonction vote() dans monsite/polls/views.py:

from django.shortcuts import get_object_or_404, render_to_response
from django.http import HttpResponseRedirect
from monsite.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éaffiche le formulaire de vote.
        return render_to_response('polls/detail.html', {
            'poll': p,
            'error_message': "Vous n'avez pas sélectionné de choix.",
        })
    else:
        selected_choice.votes += 1
        selected_choice.save()
        # Retourne toujours un HttpResponseRedirect après validation
        # des données POST. Ceci empèche que les données soient postées
        # deux fois si l'utilisateur clique sur le bouton Précédent.
        return HttpResponseRedirect('/polls/%s/results/' % p.id)

Ce code inclus des parties que nous n'avons pas encore étudiées dans ce tutoriel :

  • request.POST est un objet de type dictionnaire qui vous laisse accéder aux données soumises avec les mots-clé. Dans notre cas, request.POST['choice'] retourne l'ID du choix sélectionné, sous forme de chaîne de caractères. Les valeurs issues de request.POST sont toujours des chaînes de caractères.

    Notez que Django vous permet aussi d'accéder via request.GET aux données de type GET de la même façon -- mais nous utilisons explicitement request.POST dans notre code, pour nous assurer que la modification des données se fasse uniquement par un appel à POST.

  • request.POST['choice'] déclenchera une KeyError si choice n'a pas été obtenu par des données de type POST. Le code ci-dessus vérifie les KeyError et réaffiche le formulaire de sondage avec un message d'erreur si choice n'a pas été donné.

  • Après avoir incrémenté le compteur associé au choix soumis, le script retourne une HttpResponseRedirect à la place du HttpResponse habituel. HttpResponseRedirect prend un seul argument : l'URL vers laquelle l'utilisateur va être redirigé. Vous pouvez omettre de mentionner le « http:// » et le nom de domaine quand cela est possible. Cela permet à votre appli d'être portable sur différents domaines.

    Comme le commentaire Python le souligne, vous devez toujours retourner une HttpResponseRedirect après avoir traité une donnée de type POST avec succès. Cet adage n'est pas spécifique à Django, c'est juste une bonne pratique de développement web.

Comme il est mentionné dans le Tutoriel 3, request est un objet HTTPRequest. Pour en savoir plus au sujet des objets HTTPRequest, lisez la documentation sur les requêtes et réponses.

Après qu'un vote ait été effectué 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 la même que la vue detail() du Tutoriel 3. La seule différence est le nom du template. Nous nous occuperons de la redondance plus tard.

Maintenant, créons un 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>

À présent, rendez vous à l'adresse /polls/1/ dans votre navigateur et soumettez un vote. Vous devriez voir une page de résultats qui est mise à jour à chacun de vos votes. Si vous soumettez le formulaire sans avoir spécifié de choix, vous devriez observer le message d'erreur.

Utilisez les vues génériques : Moins il y a de code mieux c'est

Les vues detail() (du Tutoriel 3) et results() sont ridiculeusement simples -- et, comme nous l'avons signalé plus haut, redondantes. La vue index() (du Tutoriel 3 aussi), qui affiche une liste de sondages, est similaire.

Ces vues correspondent à des cas courants de développement Web basic : récupérer des données de la base de données en fonction d'un paramètre passé dans l'URL, charger un template et retourner le template complété. Comme c'est si courant, Django dispose d'un raccourci, baptisé le système des « vues génériques ».

Les vues génériques fournissent une couche d'abstraction tellement importante que vous n'avez même pas besoin de rédiger du code Python pour écrire votre appli.

Convertissons notre application de sondage en utilisant le système des vues génériques, nous allons ainsi pouvoir supprimer une bonne partie du code que nous avons écrit précédemment. Il suffit de quelques étapes pour réaliser la conversion.

Pourquoi ce ré-arrangement du code ?

Géneralement, quand vous allez écrire une application Django, vous allez évaluer si les vues génériques sont une bonne solution à votre problème, et vous allez les utiliser dès le début sans avoir à refactoriser votre code en plein développement. Mais ce tutoriel s'est intentionnellement focalisé sur l'écriture de vues « de manière hardue » jusqu'à présent, de façon à ce que vous preniez conscience des concepts généraux.

Premièrement, éditez votre fichier d'URLconf polls/urls.py. Il devrait ressembler à ça, d'après les tutoriaux précédents:

from django.conf.urls.defaults import *

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

Faites le ressembler à:

from django.conf.urls.defaults import *
from monsite.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),
    (r'^(?P<object_id>\d+)/results/$', 'django.views.generic.list_detail.object_detail', dict(info_dict, template_name='polls/results.html')),
    (r'^(?P<poll_id>\d+)/vote/$', 'monsite.polls.views.vote'),
)

Nous utilisons ici deux vues génériques : object_list et object_detail. Respectivement, ces deux vues rendent abstraits les concepts « d'affichage d'une liste d'objets » et « d'affichage d'une page de détails pour un type particulier d'objet ».

  • Chaque vue générique a besoin de connaître sur quelles données est-ce qu'elle va agir. Ces données sont fournies grâce à un dictionnaire. La clé queryset de ce dictionnaire pointe sur la liste d'objets qui doit être manipulée par la vue générique.
  • La vue générique object_detail attend la valeur ID issue de l'URL qui doit être appelée "object_id", nous avons donc changé poll_id en object_id pour la vue générique.

Par défaut, la vue générique object_detail utilise un template appelé <nom_appli>/<nom_module>_detail.html. Dans notre cas, ce sera le template appelé "polls/poll_detail.html". Donc, renommons polls/detail.html en polls/poll_detail.html, et changeons la ligne render_to_response() en vote().

De façon similaire, la vue générique object_list utilise un template appelé <nom_appli>/<nom_module>_list.html. Donc, renommons polls/index.html en polls/poll_list.html.

Puisque nous avons plus d'une entrée dans l'URLconf qui utilisent object_detail pour l'application de sondage, nous spécifions manuellement un nom de template pour la vue des résultats : template_name='polls/results.html'. Sinon, les deux vues auraient utilisé le même template. Notez l'utilisation de dict() pour retourner un dictionnaire modifié à la place.

Dans les tutoriels précédents, les templates sont utilisés dans un contexte contenant les variables poll et latest_poll_list. Ici, les vues génériques nécessitent d'avoir les variables contextuelles object et object_list. Éditez vos templates et modifiez chaque référence à latest_poll_list par object_list, et chaque référence à poll par object.

Vous pouvez maintenant supprimer les vues index(), detail() et results() de polls/views.py. Nous n'en avons plus besoin -- elles ont été remplacées par les vues génériques.

La vue vote() est toujours requise. Néanmoins, elle doit être modifiée pour être utilisée dans les nouveaux templates et les nouvelles variables contextuelles. Changez l'appel au template polls/detail.html par polls/poll_detail.html, et passez object dans le contexte au lieu de poll.

Lancez le serveur et utilisez votre nouvelle appli de sondage avec les vues génériques.

Pour davantage de détails quant aux vues génériques, lisez la documentation sur les vues génériques.

À venir

Les tutoriels s'arrêtent ici pour le moment. Mais revenez prochainement pour la suite des réjouissances :

  • Utilisation avancée des formulaires
  • Utilisation du framework RSS
  • Utilisation du framework de cache
  • Utilisation du framework de commentaires
  • Fonctionnalités avancées d'administration : Permissions
  • Fonctionnalités avancées d'administration : Javascript

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

Play le 24/06/2006 :

David, je dis "chapeau" ! Excellents billets ! Continues comme ça ! C'est que du bon ! Django, c'est le bien :-)

Anthyme le 18/07/2006 :

Je te remerci beaucoup de tes tutoriels ... ils m'ont permis d'apprendre a une vitesse exteme les bases de Django.
Je suis impatient de voir la suite !
Encore un grand Merci !

David le 19/07/2006 :

J'attends les autres traductions avec impatience ! Good job !

David le 06/09/2006 :

Bonjour, si tu envisages une nouvelle traduction, je te propose la page sur les vues génériques, c'est vraiment pas simple à comprendre :-(
www.djangoproject.com/doc...
Sinon encore une fois très bon travail. Ton blog est la référence francophone sur Django !

See U
David bis :)

NB : à quand un tutoriel simple sur django ?

David le 06/09/2006 :

En fait est-ce que Django peut générer un formulaire automatiquement ?

Je m'explique je fais un petit site d'annonces. J'ai crée mon models.py pour générer ma bdd et l'auto admin du site. J'ai optimisé tout ça et l'admin du site est extra avec filtres et champ de recherche.

Maintenant je dois faire le front-office, et je me demande comment faire la page de recherche et de résultats sans trop de galères avec les tempates, les vues et tout ce qui passe pas encore :-/

David bis (en urgence rendu client)

David, biologeek le 06/09/2006 :

@David :
> je te propose la page sur les vues génériques

Ok, c'est noté ;-)

> NB : à quand un tutoriel simple sur django ?

Ça arrive, j'ai ma petite idée là-dessus

> En fait est-ce que Django peut générer un formulaire automatiquement ?

Oui, tu devrais jetter un œil à www.djangoproject.com/doc...

DJANGOJO le 29/01/2007 :

Bonjour,

Voilà j'ai un petit souci et je n'arrive pas à le régler. Je suis vraiment novice avec django et on me demande de faire un petit site vite fait mais bien fait.

En faite j'ai un MENU qui se compléte avec un sous menu. J'aimerai que quand je clique sur un menu, le sous menu s'affiche juste en dessous.
J'ai mis une clé primaire entre les sous menu et le menu donc j'arrive à afficher le sous menu en fonction du menu.

Le seul problème c'est d'afficher dans la même page le sous menu.

j'aimerai de l'aide svp.
MERCI.

Anthony le 05/03/2007 :

Bonjour !

Originaire du monde Java (les méchants), je dois avouer être impressionné par la performance de ce framework.
Un grand bravo également pour ces billets sans lesquels je ne serais certainement jamais intéressé à Django.

J'ai une question simple :
" vote{{ choice.votes|pluralize }} "
Renvoie :
- "vote" pour 1 vote
- "votes" pour 2 votes et plus
- mais "votes" pour 0 vote
A quoi ce problème est-il dû et peut-on le contourner sans avoir à utiliser de conditions ? (je trouvais cette fonction intéressante, mais si elle ne marche pas, à quoi bon ? :) )

Merci d'avance et encore bravo.
Anthony.

David, biologeek le 05/03/2007 :

Salut Anthony, et bienvenue dans le monde de Django :-)

Le mieux est d'entourer ta réponse d'un if choices.votes pour écrire une phrase adaptée du style "il n'y a pas de votes" qui est plus joli qu'un "il y a 0 votes". D'ailleurs maintenant que je l'écris, il me semble qu'il est grammaticalement correct de mettre un "s" lorsque la quantité est nulle, comme dans la première phrase.

Anthony le 06/03/2007 :

Très correct, j'avais seulement trouvé cela étrange en jouant avec le tutorial.
En regardant rapidement les bug reports de Django, j'ai trouvé que les anglo-saxons avaient demandé exactement l'inverse.
La fonction renvoyait "0 vote" à l'origine, puis ils l'ont changée pour afficher "0 votes", ce qui est effectivement valide, mais en anglais ^^
Rendez-nous Jeanne d'Arc...

Merci pour ta réponse rapide et désolé pour ma question ;)
Anthony.

JoJO le 30/03/2007 :

Bonjour à tous !!!

Voilà, j'ai vu qu'il y a pas mal de chose à faire du coté administration django, mais est-il possible d'avoir, pour un champ "TextField" une zone de texte avec possibilité de le mettre en forme, avec couleur, police , insérer une icône ... ? En breffff comme on peut le voir dans certain forum ou genre MSN !!!
Pour avoir un site complétement dynamique, de pouvoir mettre le texte totalement en forme dans la partie administration !!

Merci bien !
Jo

David, biologeek le 30/03/2007 :

@JoJO : tu devrais jeter un œil à code.djangoproject.com/wi... ou si tu utilises les newforms à code.djangoproject.com/wi...

Bonne continuation :-).

Warflo le 20/05/2007 :

Je me demande quand même quelque chose:
Django respecte le MVC ?
Le traitement des donnés avant d'être affichés sont sensé être fait par le Modèle.
Or dans Django, c'est dans la Vue que l'on traite les donnés avant de les passer au moteur de template, donc avant de définir leur mise en forme, alors que si l'on respecte le MVC, c'est dans le Modèle que les donnés sont traité.
Mon raisonement est-il bon ?

David, biologeek le 20/05/2007 :

Je ne suis pas un spécialiste du modèle MVC mais il faut déjà savoir que c'est MTV pour django donc les correspondances entre les noms ne sont pas vraiment les mêmes (cf. www2.jeffcroft.com/blog/2... ).

Concernant maintenant le traitement dans les Vues et non dans les Modèles, c'est vrai que cela ne respecte pas le MVC strict mais c'est ce qui est communément fait dans les frameworks web car le modèle joue généralement le rôle d'ORM avec la base de données (c'est d'ailleurs peut-être ce que l'on pourrait appeler traitement ce rapatriement des données). Après il faut savoir aussi que les Managers (dans les Modèles) peuvent être utilisés pour traiter en quelque sorte les données.

On peut d'ailleurs lire sur wikipédia ( fr.wikipedia.org/wiki/MVC ) : « Le MVC montre ses limites dans le cadre des applications utilisant les technologies du web, bâties à partir de serveurs d'applications. »

Donc en conclusion le raisonnement est bon mais le fait que ce soit un framework web change un peu la donne.

Warflo le 21/05/2007 :

Ok, merci des précisions ;)