{"title":"Écriture de votre première application Django, 4ème partie","version":"2.0","locale":"fr","docname":"intro/tutorial04","url":"/fr/2.0/intro/tutorial04/","canonical":"https://djangodocs.dev/fr/2.0/intro/tutorial04/","summary":"Ce tutoriel commence là où le tutoriel 3 s’achève. Nous continuons l’application de sondage Web et allons nous focaliser sur la gestion de formulaire simple et sur…","html":"<h1>Écriture de votre première application Django, 4ème partie<a class=\"heading-anchor\" href=\"#writing-your-first-django-app-part-4\"><span class=\"visually-hidden\">Lien vers cette rubrique</span><span aria-hidden=\"true\">#</span></a></h1>\n<p>Ce tutoriel commence là où le <a class=\"reference internal\" href=\"/fr/2.0/intro/tutorial03/\"><span class=\"doc\">tutoriel 3</span></a> s’achève. Nous continuons l’application de sondage Web et allons nous focaliser sur la gestion de formulaire simple et sur la réduction du code.</p>\n<section id=\"write-a-simple-form\">\n<h2>Écriture d’un formulaire simple<a class=\"heading-anchor\" href=\"#write-a-simple-form\"><span class=\"visually-hidden\">Lien vers cette rubrique</span><span aria-hidden=\"true\">#</span></a></h2>\n<p>Nous allons mettre à jour le gabarit de la page de détail (« polls/details.html ») du tutoriel précédent, de manière à ce que le gabarit contienne une balise HTML <code class=\"docutils literal notranslate\"><span class=\"pre\">&lt;form&gt;</span></code> :</p>\n<figure class=\"code-block code-block-captioned\" data-language=\"html+django\"><figcaption class=\"code-block-caption\"><code>polls/templates/polls/detail.html</code></figcaption><div class=\"code-block-toolbar\"><span class=\"code-block-language\">Django template</span><button type=\"button\" class=\"copy-button\" data-copy hidden><span class=\"copy-button-label\">Copy</span></button></div><pre role=\"group\" tabindex=\"0\" aria-label=\"polls/templates/polls/detail.html\"><code><span class=\"p\">&lt;</span><span class=\"nt\">h1</span><span class=\"p\">&gt;</span><span class=\"cp\">{{</span> <span class=\"nv\">question.question_text</span> <span class=\"cp\">}}</span><span class=\"p\">&lt;/</span><span class=\"nt\">h1</span><span class=\"p\">&gt;</span>\n\n<span class=\"cp\">{%</span> <span class=\"k\">if</span> <span class=\"nv\">error_message</span> <span class=\"cp\">%}</span><span class=\"p\">&lt;</span><span class=\"nt\">p</span><span class=\"p\">&gt;&lt;</span><span class=\"nt\">strong</span><span class=\"p\">&gt;</span><span class=\"cp\">{{</span> <span class=\"nv\">error_message</span> <span class=\"cp\">}}</span><span class=\"p\">&lt;/</span><span class=\"nt\">strong</span><span class=\"p\">&gt;&lt;/</span><span class=\"nt\">p</span><span class=\"p\">&gt;</span><span class=\"cp\">{%</span> <span class=\"k\">endif</span> <span class=\"cp\">%}</span>\n\n<span class=\"p\">&lt;</span><span class=\"nt\">form</span> <span class=\"na\">action</span><span class=\"o\">=</span><span class=\"s\">&quot;</span><span class=\"cp\">{%</span> <span class=\"k\">url</span> <span class=\"s1\">&#39;polls:vote&#39;</span> <span class=\"nv\">question.id</span> <span class=\"cp\">%}</span><span class=\"s\">&quot;</span> <span class=\"na\">method</span><span class=\"o\">=</span><span class=\"s\">&quot;post&quot;</span><span class=\"p\">&gt;</span>\n<span class=\"cp\">{%</span> <span class=\"k\">csrf_token</span> <span class=\"cp\">%}</span>\n<span class=\"cp\">{%</span> <span class=\"k\">for</span> <span class=\"nv\">choice</span> <span class=\"k\">in</span> <span class=\"nv\">question.choice_set.all</span> <span class=\"cp\">%}</span>\n    <span class=\"p\">&lt;</span><span class=\"nt\">input</span> <span class=\"na\">type</span><span class=\"o\">=</span><span class=\"s\">&quot;radio&quot;</span> <span class=\"na\">name</span><span class=\"o\">=</span><span class=\"s\">&quot;choice&quot;</span> <span class=\"na\">id</span><span class=\"o\">=</span><span class=\"s\">&quot;choice</span><span class=\"cp\">{{</span> <span class=\"nb\">forloop</span><span class=\"nv\">.counter</span> <span class=\"cp\">}}</span><span class=\"s\">&quot;</span> <span class=\"na\">value</span><span class=\"o\">=</span><span class=\"s\">&quot;</span><span class=\"cp\">{{</span> <span class=\"nv\">choice.id</span> <span class=\"cp\">}}</span><span class=\"s\">&quot;</span> <span class=\"p\">/&gt;</span>\n    <span class=\"p\">&lt;</span><span class=\"nt\">label</span> <span class=\"na\">for</span><span class=\"o\">=</span><span class=\"s\">&quot;choice</span><span class=\"cp\">{{</span> <span class=\"nb\">forloop</span><span class=\"nv\">.counter</span> <span class=\"cp\">}}</span><span class=\"s\">&quot;</span><span class=\"p\">&gt;</span><span class=\"cp\">{{</span> <span class=\"nv\">choice.choice_text</span> <span class=\"cp\">}}</span><span class=\"p\">&lt;/</span><span class=\"nt\">label</span><span class=\"p\">&gt;&lt;</span><span class=\"nt\">br</span> <span class=\"p\">/&gt;</span>\n<span class=\"cp\">{%</span> <span class=\"k\">endfor</span> <span class=\"cp\">%}</span>\n<span class=\"p\">&lt;</span><span class=\"nt\">input</span> <span class=\"na\">type</span><span class=\"o\">=</span><span class=\"s\">&quot;submit&quot;</span> <span class=\"na\">value</span><span class=\"o\">=</span><span class=\"s\">&quot;Vote&quot;</span> <span class=\"p\">/&gt;</span>\n<span class=\"p\">&lt;/</span><span class=\"nt\">form</span><span class=\"p\">&gt;</span>\n</code></pre></figure>\n<p>Un résumé rapide :</p>\n<ul class=\"simple\">\n<li><p>Ce gabarit affiche un bouton radio pour chaque choix de question. L’attribut <code class=\"docutils literal notranslate\"><span class=\"pre\">value</span></code> de chaque bouton radio correspond à l’ID du vote choisi. Le nom (<code class=\"docutils literal notranslate\"><span class=\"pre\">name</span></code>) de chaque bouton radio est <code class=\"docutils literal notranslate\"><span class=\"pre\">&quot;choice&quot;</span></code>. Cela signifie que lorsque quelqu’un sélectionne l’un des boutons radio et valide le formulaire, les données POST <code class=\"docutils literal notranslate\"><span class=\"pre\">choice=#</span></code> (où # est l’identifiant du choix sélectionné) seront envoyées. Ce sont les concepts de base des formulaires HTML.</p></li>\n<li><p>Nous avons défini <code class=\"docutils literal notranslate\"><span class=\"pre\">{%</span> <span class=\"pre\">url</span> <span class=\"pre\">'polls:vote'</span> <span class=\"pre\">question.id</span> <span class=\"pre\">%}</span></code> comme attribut <code class=\"docutils literal notranslate\"><span class=\"pre\">action</span></code> du formulaire, et nous avons précisé <code class=\"docutils literal notranslate\"><span class=\"pre\">method=&quot;post&quot;</span></code>. L’utilisation de <code class=\"docutils literal notranslate\"><span class=\"pre\">method=&quot;post&quot;</span></code> (par opposition à <code class=\"docutils literal notranslate\"><span class=\"pre\">method=&quot;get&quot;</span></code>) est très importante, puisque le fait de valider ce formulaire va entraîner des modifications de données sur le serveur. À chaque fois qu’un formulaire modifie des données sur le serveur, vous devez utiliser <code class=\"docutils literal notranslate\"><span class=\"pre\">method=&quot;post&quot;</span></code>. Cela ne concerne pas uniquement Django ; c’est une bonne pratique à adopter en tant que développeur Web.</p></li>\n<li><p><code class=\"docutils literal notranslate\"><span class=\"pre\">forloop.counter</span></code> indique combien de fois la balise <a class=\"reference internal\" href=\"/fr/2.0/ref/templates/builtins/#std-templatetag-for\"><code class=\"xref std std-ttag docutils literal notranslate\"><span class=\"pre\">for</span></code></a> a exécuté sa boucle.</p></li>\n<li><p>Comme nous créons un formulaire POST (qui modifie potentiellement des données), il faut se préoccuper des attaques inter-sites. Heureusement, vous ne devez pas réfléchir trop longtemps car Django offre un moyen très simple à utiliser pour s’en protéger. En bref, tous les formulaires POST destinés à des URL internes doivent utiliser la balise de gabarit <a class=\"reference internal\" href=\"/fr/2.0/ref/templates/builtins/#std-templatetag-csrf_token\"><code class=\"xref std std-ttag docutils literal notranslate\"><span class=\"pre\">{%</span> <span class=\"pre\">csrf_token</span> <span class=\"pre\">%}</span></code></a>.</p></li>\n</ul>\n<p>Maintenant, nous allons créer une vue Django qui récupère les données envoyées pour nous permettre de les exploiter. Souvenez-vous, dans le <a class=\"reference internal\" href=\"/fr/2.0/intro/tutorial03/\"><span class=\"doc\">tutoriel 3</span></a>, nous avons créé un URLconf pour l’application de sondage contenant cette ligne :</p>\n<figure class=\"code-block code-block-captioned\" data-language=\"default\"><figcaption class=\"code-block-caption\"><code>polls/urls.py</code></figcaption><div class=\"code-block-toolbar\"><span class=\"code-block-language\">Code</span><button type=\"button\" class=\"copy-button\" data-copy hidden><span class=\"copy-button-label\">Copy</span></button></div><pre role=\"group\" tabindex=\"0\" aria-label=\"polls/urls.py\"><code><span class=\"n\">path</span><span class=\"p\">(</span><span class=\"s1\">&#39;&lt;int:question_id&gt;/vote/&#39;</span><span class=\"p\">,</span> <span class=\"n\">views</span><span class=\"o\">.</span><span class=\"n\">vote</span><span class=\"p\">,</span> <span class=\"n\">name</span><span class=\"o\">=</span><span class=\"s1\">&#39;vote&#39;</span><span class=\"p\">),</span>\n</code></pre></figure>\n<p>Nous avions également créé une implémentation rudimentaire de la fonction <code class=\"docutils literal notranslate\"><span class=\"pre\">vote()</span></code>. Créons maintenant une version fonctionnelle. Ajoutez ce qui suit dans le fichier <code class=\"docutils literal notranslate\"><span class=\"pre\">polls/views.py</span></code>:</p>\n<figure class=\"code-block code-block-captioned\" data-language=\"default\"><figcaption class=\"code-block-caption\"><code>polls/views.py</code></figcaption><div class=\"code-block-toolbar\"><span class=\"code-block-language\">Code</span><button type=\"button\" class=\"copy-button\" data-copy hidden><span class=\"copy-button-label\">Copy</span></button></div><pre role=\"group\" tabindex=\"0\" aria-label=\"polls/views.py\"><code><span class=\"kn\">from</span><span class=\"w\"> </span><span class=\"nn\">django.http</span><span class=\"w\"> </span><span class=\"kn\">import</span> <span class=\"n\">HttpResponse</span><span class=\"p\">,</span> <span class=\"n\">HttpResponseRedirect</span>\n<span class=\"kn\">from</span><span class=\"w\"> </span><span class=\"nn\">django.shortcuts</span><span class=\"w\"> </span><span class=\"kn\">import</span> <span class=\"n\">get_object_or_404</span><span class=\"p\">,</span> <span class=\"n\">render</span>\n<span class=\"kn\">from</span><span class=\"w\"> </span><span class=\"nn\">django.urls</span><span class=\"w\"> </span><span class=\"kn\">import</span> <span class=\"n\">reverse</span>\n\n<span class=\"kn\">from</span><span class=\"w\"> </span><span class=\"nn\">.models</span><span class=\"w\"> </span><span class=\"kn\">import</span> <span class=\"n\">Choice</span><span class=\"p\">,</span> <span class=\"n\">Question</span>\n<span class=\"c1\"># ...</span>\n<span class=\"k\">def</span><span class=\"w\"> </span><span class=\"nf\">vote</span><span class=\"p\">(</span><span class=\"n\">request</span><span class=\"p\">,</span> <span class=\"n\">question_id</span><span class=\"p\">):</span>\n    <span class=\"n\">question</span> <span class=\"o\">=</span> <span class=\"n\">get_object_or_404</span><span class=\"p\">(</span><span class=\"n\">Question</span><span class=\"p\">,</span> <span class=\"n\">pk</span><span class=\"o\">=</span><span class=\"n\">question_id</span><span class=\"p\">)</span>\n    <span class=\"k\">try</span><span class=\"p\">:</span>\n        <span class=\"n\">selected_choice</span> <span class=\"o\">=</span> <span class=\"n\">question</span><span class=\"o\">.</span><span class=\"n\">choice_set</span><span class=\"o\">.</span><span class=\"n\">get</span><span class=\"p\">(</span><span class=\"n\">pk</span><span class=\"o\">=</span><span class=\"n\">request</span><span class=\"o\">.</span><span class=\"n\">POST</span><span class=\"p\">[</span><span class=\"s1\">&#39;choice&#39;</span><span class=\"p\">])</span>\n    <span class=\"k\">except</span> <span class=\"p\">(</span><span class=\"ne\">KeyError</span><span class=\"p\">,</span> <span class=\"n\">Choice</span><span class=\"o\">.</span><span class=\"n\">DoesNotExist</span><span class=\"p\">):</span>\n        <span class=\"c1\"># Redisplay the question voting form.</span>\n        <span class=\"k\">return</span> <span class=\"n\">render</span><span class=\"p\">(</span><span class=\"n\">request</span><span class=\"p\">,</span> <span class=\"s1\">&#39;polls/detail.html&#39;</span><span class=\"p\">,</span> <span class=\"p\">{</span>\n            <span class=\"s1\">&#39;question&#39;</span><span class=\"p\">:</span> <span class=\"n\">question</span><span class=\"p\">,</span>\n            <span class=\"s1\">&#39;error_message&#39;</span><span class=\"p\">:</span> <span class=\"s2\">&quot;You didn&#39;t select a choice.&quot;</span><span class=\"p\">,</span>\n        <span class=\"p\">})</span>\n    <span class=\"k\">else</span><span class=\"p\">:</span>\n        <span class=\"n\">selected_choice</span><span class=\"o\">.</span><span class=\"n\">votes</span> <span class=\"o\">+=</span> <span class=\"mi\">1</span>\n        <span class=\"n\">selected_choice</span><span class=\"o\">.</span><span class=\"n\">save</span><span class=\"p\">()</span>\n        <span class=\"c1\"># Always return an HttpResponseRedirect after successfully dealing</span>\n        <span class=\"c1\"># with POST data. This prevents data from being posted twice if a</span>\n        <span class=\"c1\"># user hits the Back button.</span>\n        <span class=\"k\">return</span> <span class=\"n\">HttpResponseRedirect</span><span class=\"p\">(</span><span class=\"n\">reverse</span><span class=\"p\">(</span><span class=\"s1\">&#39;polls:results&#39;</span><span class=\"p\">,</span> <span class=\"n\">args</span><span class=\"o\">=</span><span class=\"p\">(</span><span class=\"n\">question</span><span class=\"o\">.</span><span class=\"n\">id</span><span class=\"p\">,)))</span>\n</code></pre></figure>\n<p>Ce code contient quelques points encore non abordés dans ce tutoriel :</p>\n<ul>\n<li><p><a class=\"reference internal\" href=\"/fr/2.0/ref/request-response/#django.http.HttpRequest.POST\" title=\"django.http.HttpRequest.POST\"><code class=\"xref py py-attr docutils literal notranslate\"><span class=\"pre\">request.POST</span></code></a> est un objet similaire à un dictionnaire qui vous permet d’accéder aux données envoyées par leurs clés. Dans ce cas, <code class=\"docutils literal notranslate\"><span class=\"pre\">request.POST['choice']</span></code> renvoie l’ID du choix sélectionné, sous forme d’une chaîne de caractères. Les valeurs dans <a class=\"reference internal\" href=\"/fr/2.0/ref/request-response/#django.http.HttpRequest.POST\" title=\"django.http.HttpRequest.POST\"><code class=\"xref py py-attr docutils literal notranslate\"><span class=\"pre\">request.POST</span></code></a> sont toujours des chaînes de caractères.</p>\n<p>Notez que Django dispose aussi de <a class=\"reference internal\" href=\"/fr/2.0/ref/request-response/#django.http.HttpRequest.GET\" title=\"django.http.HttpRequest.GET\"><code class=\"xref py py-attr docutils literal notranslate\"><span class=\"pre\">request.GET</span></code></a> pour accéder aux données GET de la même manière – mais nous utilisons explicitement <a class=\"reference internal\" href=\"/fr/2.0/ref/request-response/#django.http.HttpRequest.POST\" title=\"django.http.HttpRequest.POST\"><code class=\"xref py py-attr docutils literal notranslate\"><span class=\"pre\">request.POST</span></code></a> dans notre code, pour s’assurer que les données ne sont modifiées que par des requêtes POST.</p>\n</li>\n<li><p><code class=\"docutils literal notranslate\"><span class=\"pre\">request.POST['choice']</span></code> lèvera une exception <a class=\"reference external\" href=\"https://docs.python.org/3/library/exceptions.html#KeyError\" title=\"(disponible dans Python v3.14)\"><code class=\"xref py py-exc docutils literal notranslate\"><span class=\"pre\">KeyError</span></code></a> si <code class=\"docutils literal notranslate\"><span class=\"pre\">choice</span></code> n’est pas spécifié dans les données POST. Le code ci-dessus vérifie qu’une exception <a class=\"reference external\" href=\"https://docs.python.org/3/library/exceptions.html#KeyError\" title=\"(disponible dans Python v3.14)\"><code class=\"xref py py-exc docutils literal notranslate\"><span class=\"pre\">KeyError</span></code></a> n’est pas levée et réaffiche le formulaire de question avec un message d’erreur si <code class=\"docutils literal notranslate\"><span class=\"pre\">choice</span></code> n’est pas rempli.</p></li>\n<li><p>Après l’incrémentation du nombre de votes du choix, le code renvoie une <a class=\"reference internal\" href=\"/fr/2.0/ref/request-response/#django.http.HttpResponseRedirect\" title=\"django.http.HttpResponseRedirect\"><code class=\"xref py py-class docutils literal notranslate\"><span class=\"pre\">HttpResponseRedirect</span></code></a> plutôt qu’une <a class=\"reference internal\" href=\"/fr/2.0/ref/request-response/#django.http.HttpResponse\" title=\"django.http.HttpResponse\"><code class=\"xref py py-class docutils literal notranslate\"><span class=\"pre\">HttpResponse</span></code></a> normale. <a class=\"reference internal\" href=\"/fr/2.0/ref/request-response/#django.http.HttpResponseRedirect\" title=\"django.http.HttpResponseRedirect\"><code class=\"xref py py-class docutils literal notranslate\"><span class=\"pre\">HttpResponseRedirect</span></code></a> prend un seul paramètre : l’URL vers laquelle l’utilisateur va être redirigé (voir le point suivant pour la manière de construire cette URL dans ce cas).</p>\n<p>Comme le commentaire Python l’indique, vous devez systématiquement renvoyer une <a class=\"reference internal\" href=\"/fr/2.0/ref/request-response/#django.http.HttpResponseRedirect\" title=\"django.http.HttpResponseRedirect\"><code class=\"xref py py-class docutils literal notranslate\"><span class=\"pre\">HttpResponseRedirect</span></code></a> 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.</p>\n</li>\n<li><p>Dans cet exemple, nous utilisons la fonction <a class=\"reference internal\" href=\"/fr/2.0/ref/urlresolvers/#django.urls.reverse\" title=\"django.urls.reverse\"><code class=\"xref py py-func docutils literal notranslate\"><span class=\"pre\">reverse()</span></code></a> dans le constructeur de <a class=\"reference internal\" href=\"/fr/2.0/ref/request-response/#django.http.HttpResponseRedirect\" title=\"django.http.HttpResponseRedirect\"><code class=\"xref py py-class docutils literal notranslate\"><span class=\"pre\">HttpResponseRedirect</span></code></a>. 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 ainsi que la partie variable de l’URL qui pointe vers cette vue. Dans ce cas, en utilisant l’URLconf défini dans la <a class=\"reference internal\" href=\"/fr/2.0/intro/tutorial03/\"><span class=\"doc\">partie 3 de ce tutoriel</span></a>, l’appel de la fonction <a class=\"reference internal\" href=\"/fr/2.0/ref/urlresolvers/#django.urls.reverse\" title=\"django.urls.reverse\"><code class=\"xref py py-func docutils literal notranslate\"><span class=\"pre\">reverse()</span></code></a> va renvoyer la chaîne de caractères :</p>\n<div class=\"code-block\" data-language=\"default\"><div class=\"code-block-toolbar\"><span class=\"code-block-language\">Code</span><button type=\"button\" class=\"copy-button\" data-copy hidden><span class=\"copy-button-label\">Copy</span></button></div><pre role=\"group\" tabindex=\"0\" aria-label=\"Code code\"><code><span class=\"s1\">&#39;/polls/3/results/&#39;</span>\n</code></pre></div>\n<p>où <code class=\"docutils literal notranslate\"><span class=\"pre\">3</span></code> est la valeur de <code class=\"docutils literal notranslate\"><span class=\"pre\">question.id</span></code>. Cette URL de redirection va ensuite appeler la vue <code class=\"docutils literal notranslate\"><span class=\"pre\">'results'</span></code> pour afficher la page finale.</p>\n</li>\n</ul>\n<p>Comme expliqué dans la <a class=\"reference internal\" href=\"/fr/2.0/intro/tutorial03/\"><span class=\"doc\">partie 3 de ce tutoriel</span></a>, <code class=\"docutils literal notranslate\"><span class=\"pre\">request</span></code> est un objet <a class=\"reference internal\" href=\"/fr/2.0/ref/request-response/#django.http.HttpRequest\" title=\"django.http.HttpRequest\"><code class=\"xref py py-class docutils literal notranslate\"><span class=\"pre\">HttpRequest</span></code></a>. Pour plus d’informations sur les objets <a class=\"reference internal\" href=\"/fr/2.0/ref/request-response/#django.http.HttpRequest\" title=\"django.http.HttpRequest\"><code class=\"xref py py-class docutils literal notranslate\"><span class=\"pre\">HttpRequest</span></code></a>, voir la <a class=\"reference internal\" href=\"/fr/2.0/ref/request-response/\"><span class=\"doc\">documentation des requêtes et réponses</span></a>.</p>\n<p>Après le vote d’une personne dans une question, la vue <code class=\"docutils literal notranslate\"><span class=\"pre\">vote()</span></code> redirige vers la page de résultats de la question. Écrivons cette vue :</p>\n<figure class=\"code-block code-block-captioned\" data-language=\"default\"><figcaption class=\"code-block-caption\"><code>polls/views.py</code></figcaption><div class=\"code-block-toolbar\"><span class=\"code-block-language\">Code</span><button type=\"button\" class=\"copy-button\" data-copy hidden><span class=\"copy-button-label\">Copy</span></button></div><pre role=\"group\" tabindex=\"0\" aria-label=\"polls/views.py\"><code><span class=\"kn\">from</span><span class=\"w\"> </span><span class=\"nn\">django.shortcuts</span><span class=\"w\"> </span><span class=\"kn\">import</span> <span class=\"n\">get_object_or_404</span><span class=\"p\">,</span> <span class=\"n\">render</span>\n\n\n<span class=\"k\">def</span><span class=\"w\"> </span><span class=\"nf\">results</span><span class=\"p\">(</span><span class=\"n\">request</span><span class=\"p\">,</span> <span class=\"n\">question_id</span><span class=\"p\">):</span>\n    <span class=\"n\">question</span> <span class=\"o\">=</span> <span class=\"n\">get_object_or_404</span><span class=\"p\">(</span><span class=\"n\">Question</span><span class=\"p\">,</span> <span class=\"n\">pk</span><span class=\"o\">=</span><span class=\"n\">question_id</span><span class=\"p\">)</span>\n    <span class=\"k\">return</span> <span class=\"n\">render</span><span class=\"p\">(</span><span class=\"n\">request</span><span class=\"p\">,</span> <span class=\"s1\">&#39;polls/results.html&#39;</span><span class=\"p\">,</span> <span class=\"p\">{</span><span class=\"s1\">&#39;question&#39;</span><span class=\"p\">:</span> <span class=\"n\">question</span><span class=\"p\">})</span>\n</code></pre></figure>\n<p>C’est presque exactement la même que la vue <code class=\"docutils literal notranslate\"><span class=\"pre\">detail()</span></code> du <a class=\"reference internal\" href=\"/fr/2.0/intro/tutorial03/\"><span class=\"doc\">tutoriel 3</span></a>. La seule différence est le nom du gabarit. Nous éliminerons cette redondance plus tard.</p>\n<p>Écrivons maintenant le gabarit <code class=\"docutils literal notranslate\"><span class=\"pre\">polls/results.html</span></code> :</p>\n<figure class=\"code-block code-block-captioned\" data-language=\"html+django\"><figcaption class=\"code-block-caption\"><code>polls/templates/polls/results.html</code></figcaption><div class=\"code-block-toolbar\"><span class=\"code-block-language\">Django template</span><button type=\"button\" class=\"copy-button\" data-copy hidden><span class=\"copy-button-label\">Copy</span></button></div><pre role=\"group\" tabindex=\"0\" aria-label=\"polls/templates/polls/results.html\"><code><span class=\"p\">&lt;</span><span class=\"nt\">h1</span><span class=\"p\">&gt;</span><span class=\"cp\">{{</span> <span class=\"nv\">question.question_text</span> <span class=\"cp\">}}</span><span class=\"p\">&lt;/</span><span class=\"nt\">h1</span><span class=\"p\">&gt;</span>\n\n<span class=\"p\">&lt;</span><span class=\"nt\">ul</span><span class=\"p\">&gt;</span>\n<span class=\"cp\">{%</span> <span class=\"k\">for</span> <span class=\"nv\">choice</span> <span class=\"k\">in</span> <span class=\"nv\">question.choice_set.all</span> <span class=\"cp\">%}</span>\n    <span class=\"p\">&lt;</span><span class=\"nt\">li</span><span class=\"p\">&gt;</span><span class=\"cp\">{{</span> <span class=\"nv\">choice.choice_text</span> <span class=\"cp\">}}</span> -- <span class=\"cp\">{{</span> <span class=\"nv\">choice.votes</span> <span class=\"cp\">}}</span> vote<span class=\"cp\">{{</span> <span class=\"nv\">choice.votes</span><span class=\"o\">|</span><span class=\"nf\">pluralize</span> <span class=\"cp\">}}</span><span class=\"p\">&lt;/</span><span class=\"nt\">li</span><span class=\"p\">&gt;</span>\n<span class=\"cp\">{%</span> <span class=\"k\">endfor</span> <span class=\"cp\">%}</span>\n<span class=\"p\">&lt;/</span><span class=\"nt\">ul</span><span class=\"p\">&gt;</span>\n\n<span class=\"p\">&lt;</span><span class=\"nt\">a</span> <span class=\"na\">href</span><span class=\"o\">=</span><span class=\"s\">&quot;</span><span class=\"cp\">{%</span> <span class=\"k\">url</span> <span class=\"s1\">&#39;polls:detail&#39;</span> <span class=\"nv\">question.id</span> <span class=\"cp\">%}</span><span class=\"s\">&quot;</span><span class=\"p\">&gt;</span>Vote again?<span class=\"p\">&lt;/</span><span class=\"nt\">a</span><span class=\"p\">&gt;</span>\n</code></pre></figure>\n<p>Maintenant, rendez-vous à la page <code class=\"docutils literal notranslate\"><span class=\"pre\">/polls/1/</span></code> avec votre navigateur et votez pour la question proposée. 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.</p>\n<aside class=\"admonition admonition-note\" role=\"note\">\n<p class=\"admonition-title\">Note</p>\n<p>Le code de notre vue <code class=\"docutils literal notranslate\"><span class=\"pre\">vote()</span></code> présente un petit problème. Il obtient d’abord l’objet <code class=\"docutils literal notranslate\"><span class=\"pre\">selected_choice</span></code> depuis la base de données, puis calcule la nouvelle valeur de <code class=\"docutils literal notranslate\"><span class=\"pre\">votes</span></code>, et enregistre ensuite le résultat dans la base de données. Si deux utilisateurs du site Web essaient de voter <em>exactement au même moment</em>, cela peut mal se passer : la même valeur, disons 42, sera obtenue pour <code class=\"docutils literal notranslate\"><span class=\"pre\">votes</span></code>. Puis, la nouvelle valeur calculée sera de 43 pour les deux utilisateurs qui enregistreront cette valeur, alors qu’elle devrait être de 44 au final.</p>\n<p>On appelle cela une <em>situation de compétition</em>. Si cela vous intéresse, vous pouvez lire <a class=\"reference internal\" href=\"/fr/2.0/ref/models/expressions/#avoiding-race-conditions-using-f\"><span class=\"std std-ref\">Prévention des conflits de concurrence avec F()</span></a> pour savoir comment il est possible d’éviter ce genre de situations.</p>\n</aside>\n</section>\n<section id=\"use-generic-views-less-code-is-better\">\n<h2>Utilisation des vues génériques : moins de code, c’est mieux<a class=\"heading-anchor\" href=\"#use-generic-views-less-code-is-better\"><span class=\"visually-hidden\">Lien vers cette rubrique</span><span aria-hidden=\"true\">#</span></a></h2>\n<p>Les vues <code class=\"docutils literal notranslate\"><span class=\"pre\">detail()</span></code> (cf. <a class=\"reference internal\" href=\"/fr/2.0/intro/tutorial03/\"><span class=\"doc\">tutoriel 3</span></a>) et <code class=\"docutils literal notranslate\"><span class=\"pre\">results()</span></code> sont triviales – et comme mentionné précédemment, redondantes. La vue <code class=\"docutils literal notranslate\"><span class=\"pre\">index()</span></code> qui affiche une liste de sondages est similaire.</p>\n<p>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 gabarit et renvoyer le gabarit interprété. Ce cas est tellement classique que Django propose un raccourci, appelé le système de « vues génériques ».</p>\n<p>Les vues génériques permettent l’abstraction de pratiques communes, à un tel point que vous n’avez pas à écrire de code Python pour écrire une application.</p>\n<p>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 :</p>\n<ol class=\"arabic simple\">\n<li><p>Convertir l’URLconf.</p></li>\n<li><p>Supprimer quelques anciennes vues désormais inutiles.</p></li>\n<li><p>Introduire de nouvelles vues basées sur les vues génériques de Django.</p></li>\n</ol>\n<p>Lisez la suite pour plus de détails.</p>\n<aside class=\"admonition-why-the-code-shuffle admonition\">\n<p class=\"admonition-title\">Pourquoi ces changements de code ?</p>\n<p>En général, lorsque vous écrivez une application Django, vous devez estimer si les vues génériques correspondent bien à vos besoins et, le cas échéant, vous les utiliserez dès le début, plutôt que de réarranger votre code à mi-chemin. Mais ce tutoriel s’est concentré intentionnellement sur l’écriture des vues « à la dure » jusqu’à ce point, pour mettre l’accent sur les concepts de base.</p>\n<p>Tout comme vous devez posséder des bases de maths avant de commencer à utiliser une calculatrice.</p>\n</aside>\n<section id=\"amend-urlconf\">\n<h3>Correction de l’URLconf<a class=\"heading-anchor\" href=\"#amend-urlconf\"><span class=\"visually-hidden\">Lien vers cette rubrique</span><span aria-hidden=\"true\">#</span></a></h3>\n<p>Tout d’abord, ouvrez la configuration d’URL <code class=\"docutils literal notranslate\"><span class=\"pre\">polls/urls.py</span></code> et modifiez-la ainsi :</p>\n<figure class=\"code-block code-block-captioned\" data-language=\"default\"><figcaption class=\"code-block-caption\"><code>polls/urls.py</code></figcaption><div class=\"code-block-toolbar\"><span class=\"code-block-language\">Code</span><button type=\"button\" class=\"copy-button\" data-copy hidden><span class=\"copy-button-label\">Copy</span></button></div><pre role=\"group\" tabindex=\"0\" aria-label=\"polls/urls.py\"><code><span class=\"kn\">from</span><span class=\"w\"> </span><span class=\"nn\">django.urls</span><span class=\"w\"> </span><span class=\"kn\">import</span> <span class=\"n\">path</span>\n\n<span class=\"kn\">from</span><span class=\"w\"> </span><span class=\"nn\">.</span><span class=\"w\"> </span><span class=\"kn\">import</span> <span class=\"n\">views</span>\n\n<span class=\"n\">app_name</span> <span class=\"o\">=</span> <span class=\"s1\">&#39;polls&#39;</span>\n<span class=\"n\">urlpatterns</span> <span class=\"o\">=</span> <span class=\"p\">[</span>\n    <span class=\"n\">path</span><span class=\"p\">(</span><span class=\"s1\">&#39;&#39;</span><span class=\"p\">,</span> <span class=\"n\">views</span><span class=\"o\">.</span><span class=\"n\">IndexView</span><span class=\"o\">.</span><span class=\"n\">as_view</span><span class=\"p\">(),</span> <span class=\"n\">name</span><span class=\"o\">=</span><span class=\"s1\">&#39;index&#39;</span><span class=\"p\">),</span>\n    <span class=\"n\">path</span><span class=\"p\">(</span><span class=\"s1\">&#39;&lt;int:pk&gt;/&#39;</span><span class=\"p\">,</span> <span class=\"n\">views</span><span class=\"o\">.</span><span class=\"n\">DetailView</span><span class=\"o\">.</span><span class=\"n\">as_view</span><span class=\"p\">(),</span> <span class=\"n\">name</span><span class=\"o\">=</span><span class=\"s1\">&#39;detail&#39;</span><span class=\"p\">),</span>\n    <span class=\"n\">path</span><span class=\"p\">(</span><span class=\"s1\">&#39;&lt;int:pk&gt;/results/&#39;</span><span class=\"p\">,</span> <span class=\"n\">views</span><span class=\"o\">.</span><span class=\"n\">ResultsView</span><span class=\"o\">.</span><span class=\"n\">as_view</span><span class=\"p\">(),</span> <span class=\"n\">name</span><span class=\"o\">=</span><span class=\"s1\">&#39;results&#39;</span><span class=\"p\">),</span>\n    <span class=\"n\">path</span><span class=\"p\">(</span><span class=\"s1\">&#39;&lt;int:question_id&gt;/vote/&#39;</span><span class=\"p\">,</span> <span class=\"n\">views</span><span class=\"o\">.</span><span class=\"n\">vote</span><span class=\"p\">,</span> <span class=\"n\">name</span><span class=\"o\">=</span><span class=\"s1\">&#39;vote&#39;</span><span class=\"p\">),</span>\n<span class=\"p\">]</span>\n</code></pre></figure>\n<p>Notez que le nom du motif correspondant dans les chaînes de chemin des deuxième et troisième motifs a été modifié de <code class=\"docutils literal notranslate\"><span class=\"pre\">&lt;question_id&gt;</span></code> en <code class=\"docutils literal notranslate\"><span class=\"pre\">&lt;pk&gt;</span></code>.</p>\n</section>\n<section id=\"amend-views\">\n<h3>Correction des vues<a class=\"heading-anchor\" href=\"#amend-views\"><span class=\"visually-hidden\">Lien vers cette rubrique</span><span aria-hidden=\"true\">#</span></a></h3>\n<p>Ensuite, nous allons enlever les anciennes vues <code class=\"docutils literal notranslate\"><span class=\"pre\">index</span></code>, <code class=\"docutils literal notranslate\"><span class=\"pre\">detail</span></code> et <code class=\"docutils literal notranslate\"><span class=\"pre\">results</span></code> et utiliser à la place des vues génériques de Django. Pour cela, ouvrez le fichier <code class=\"docutils literal notranslate\"><span class=\"pre\">polls/views.py</span></code> et modifiez-le de cette façon :</p>\n<figure class=\"code-block code-block-captioned\" data-language=\"default\"><figcaption class=\"code-block-caption\"><code>polls/views.py</code></figcaption><div class=\"code-block-toolbar\"><span class=\"code-block-language\">Code</span><button type=\"button\" class=\"copy-button\" data-copy hidden><span class=\"copy-button-label\">Copy</span></button></div><pre role=\"group\" tabindex=\"0\" aria-label=\"polls/views.py\"><code><span class=\"kn\">from</span><span class=\"w\"> </span><span class=\"nn\">django.http</span><span class=\"w\"> </span><span class=\"kn\">import</span> <span class=\"n\">HttpResponseRedirect</span>\n<span class=\"kn\">from</span><span class=\"w\"> </span><span class=\"nn\">django.shortcuts</span><span class=\"w\"> </span><span class=\"kn\">import</span> <span class=\"n\">get_object_or_404</span><span class=\"p\">,</span> <span class=\"n\">render</span>\n<span class=\"kn\">from</span><span class=\"w\"> </span><span class=\"nn\">django.urls</span><span class=\"w\"> </span><span class=\"kn\">import</span> <span class=\"n\">reverse</span>\n<span class=\"kn\">from</span><span class=\"w\"> </span><span class=\"nn\">django.views</span><span class=\"w\"> </span><span class=\"kn\">import</span> <span class=\"n\">generic</span>\n\n<span class=\"kn\">from</span><span class=\"w\"> </span><span class=\"nn\">.models</span><span class=\"w\"> </span><span class=\"kn\">import</span> <span class=\"n\">Choice</span><span class=\"p\">,</span> <span class=\"n\">Question</span>\n\n\n<span class=\"k\">class</span><span class=\"w\"> </span><span class=\"nc\">IndexView</span><span class=\"p\">(</span><span class=\"n\">generic</span><span class=\"o\">.</span><span class=\"n\">ListView</span><span class=\"p\">):</span>\n    <span class=\"n\">template_name</span> <span class=\"o\">=</span> <span class=\"s1\">&#39;polls/index.html&#39;</span>\n    <span class=\"n\">context_object_name</span> <span class=\"o\">=</span> <span class=\"s1\">&#39;latest_question_list&#39;</span>\n\n    <span class=\"k\">def</span><span class=\"w\"> </span><span class=\"nf\">get_queryset</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">):</span>\n<span class=\"w\">        </span><span class=\"sd\">&quot;&quot;&quot;Return the last five published questions.&quot;&quot;&quot;</span>\n        <span class=\"k\">return</span> <span class=\"n\">Question</span><span class=\"o\">.</span><span class=\"n\">objects</span><span class=\"o\">.</span><span class=\"n\">order_by</span><span class=\"p\">(</span><span class=\"s1\">&#39;-pub_date&#39;</span><span class=\"p\">)[:</span><span class=\"mi\">5</span><span class=\"p\">]</span>\n\n\n<span class=\"k\">class</span><span class=\"w\"> </span><span class=\"nc\">DetailView</span><span class=\"p\">(</span><span class=\"n\">generic</span><span class=\"o\">.</span><span class=\"n\">DetailView</span><span class=\"p\">):</span>\n    <span class=\"n\">model</span> <span class=\"o\">=</span> <span class=\"n\">Question</span>\n    <span class=\"n\">template_name</span> <span class=\"o\">=</span> <span class=\"s1\">&#39;polls/detail.html&#39;</span>\n\n\n<span class=\"k\">class</span><span class=\"w\"> </span><span class=\"nc\">ResultsView</span><span class=\"p\">(</span><span class=\"n\">generic</span><span class=\"o\">.</span><span class=\"n\">DetailView</span><span class=\"p\">):</span>\n    <span class=\"n\">model</span> <span class=\"o\">=</span> <span class=\"n\">Question</span>\n    <span class=\"n\">template_name</span> <span class=\"o\">=</span> <span class=\"s1\">&#39;polls/results.html&#39;</span>\n\n\n<span class=\"k\">def</span><span class=\"w\"> </span><span class=\"nf\">vote</span><span class=\"p\">(</span><span class=\"n\">request</span><span class=\"p\">,</span> <span class=\"n\">question_id</span><span class=\"p\">):</span>\n    <span class=\"o\">...</span> <span class=\"c1\"># same as above, no changes needed.</span>\n</code></pre></figure>\n<p>Nous utilisons ici deux vues génériques : <a class=\"reference internal\" href=\"/fr/2.0/ref/class-based-views/generic-display/#django.views.generic.list.ListView\" title=\"django.views.generic.list.ListView\"><code class=\"xref py py-class docutils literal notranslate\"><span class=\"pre\">ListView</span></code></a> et <a class=\"reference internal\" href=\"/fr/2.0/ref/class-based-views/generic-display/#django.views.generic.detail.DetailView\" title=\"django.views.generic.detail.DetailView\"><code class=\"xref py py-class docutils literal notranslate\"><span class=\"pre\">DetailView</span></code></a>. Respectivement, ces deux vues permettent l’abstraction des concepts « afficher une liste d’objets » et « afficher une page détaillée pour un type particulier d’objet ».</p>\n<ul class=\"simple\">\n<li><p>Chaque vue générique a besoin de connaître le modèle sur lequel elle va agir. Cette information est fournie par l’attribut <code class=\"docutils literal notranslate\"><span class=\"pre\">model</span></code>.</p></li>\n<li><p>La vue générique <a class=\"reference internal\" href=\"/fr/2.0/ref/class-based-views/generic-display/#django.views.generic.detail.DetailView\" title=\"django.views.generic.detail.DetailView\"><code class=\"xref py py-class docutils literal notranslate\"><span class=\"pre\">DetailView</span></code></a> s’attend à ce que la clé primaire capturée dans l’URL s’appelle <code class=\"docutils literal notranslate\"><span class=\"pre\">&quot;pk&quot;</span></code>, nous avons donc changé <code class=\"docutils literal notranslate\"><span class=\"pre\">question_id</span></code> en <code class=\"docutils literal notranslate\"><span class=\"pre\">pk</span></code> pour les vues génériques.</p></li>\n</ul>\n<p>Par défaut, la vue générique <a class=\"reference internal\" href=\"/fr/2.0/ref/class-based-views/generic-display/#django.views.generic.detail.DetailView\" title=\"django.views.generic.detail.DetailView\"><code class=\"xref py py-class docutils literal notranslate\"><span class=\"pre\">DetailView</span></code></a> utilise un gabarit appelé <code class=\"docutils literal notranslate\"><span class=\"pre\">&lt;nom</span> <span class=\"pre\">app&gt;/&lt;nom</span> <span class=\"pre\">modèle&gt;_detail.html</span></code>. Dans notre cas, elle utiliserait le gabarit <code class=\"docutils literal notranslate\"><span class=\"pre\">&quot;polls/question_detail.html&quot;</span></code>. L’attribut <code class=\"docutils literal notranslate\"><span class=\"pre\">template_name</span></code> est utilisé pour signifier à Django d’utiliser un nom de gabarit spécifique plutôt que le nom de gabarit par défaut. Nous avons aussi indiqué le paramètre <code class=\"docutils literal notranslate\"><span class=\"pre\">template_name</span></code> pour la vue de liste <code class=\"docutils literal notranslate\"><span class=\"pre\">results</span></code>, ce qui permet de différencier l’apparence du rendu des vues « results » et « detail », même s’il s’agit dans les deux cas de vues <a class=\"reference internal\" href=\"/fr/2.0/ref/class-based-views/generic-display/#django.views.generic.detail.DetailView\" title=\"django.views.generic.detail.DetailView\"><code class=\"xref py py-class docutils literal notranslate\"><span class=\"pre\">DetailView</span></code></a> à la base.</p>\n<p>De la même façon, la vue générique <a class=\"reference internal\" href=\"/fr/2.0/ref/class-based-views/generic-display/#django.views.generic.list.ListView\" title=\"django.views.generic.list.ListView\"><code class=\"xref py py-class docutils literal notranslate\"><span class=\"pre\">ListView</span></code></a> utilise par défaut un gabarit appelé <code class=\"docutils literal notranslate\"><span class=\"pre\">&lt;nom</span> <span class=\"pre\">app&gt;/&lt;nom</span> <span class=\"pre\">modèle&gt;_list.html</span></code> ; nous utilisons  <code class=\"docutils literal notranslate\"><span class=\"pre\">template_name</span></code> pour indiquer à <a class=\"reference internal\" href=\"/fr/2.0/ref/class-based-views/generic-display/#django.views.generic.list.ListView\" title=\"django.views.generic.list.ListView\"><code class=\"xref py py-class docutils literal notranslate\"><span class=\"pre\">ListView</span></code></a> d’utiliser notre gabarit existant <code class=\"docutils literal notranslate\"><span class=\"pre\">&quot;polls/index.html&quot;</span></code>.</p>\n<p>Dans les parties précédentes de ce tutoriel, les templates ont été renseignés avec un contexte qui contenait les variables de contexte <code class=\"docutils literal notranslate\"><span class=\"pre\">question</span></code> et <code class=\"docutils literal notranslate\"><span class=\"pre\">latest_question_list</span></code>. Pour <code class=\"docutils literal notranslate\"><span class=\"pre\">DetailView</span></code>, la variable <code class=\"docutils literal notranslate\"><span class=\"pre\">question</span></code> est fournie automatiquement ; comme nous utilisons un modèle nommé <code class=\"docutils literal notranslate\"><span class=\"pre\">Question</span></code>, Django sait donner un nom approprié à la variable de contexte. Cependant, pour <code class=\"docutils literal notranslate\"><span class=\"pre\">ListView</span></code>, la variable de contexte générée automatiquement s’appelle <code class=\"docutils literal notranslate\"><span class=\"pre\">question_list</span></code>. Pour changer cela, nous fournissons l’attribut <code class=\"docutils literal notranslate\"><span class=\"pre\">context_object_name</span></code> pour indiquer que nous souhaitons plutôt la nommer <code class=\"docutils literal notranslate\"><span class=\"pre\">latest_question_list</span></code>. Il serait aussi possible de modifier les templates en utilisant les nouveaux nom de variables par défaut, mais il est beaucoup plus simple d’indiquer à Django les noms de variables que nous souhaitons.</p>\n<p>Lancez le serveur et utilisez votre nouvelle application de sondage basée sur les vues génériques.</p>\n<p>Pour plus de détails sur les vues génériques, voir la <a class=\"reference internal\" href=\"/fr/2.0/topics/class-based-views/\"><span class=\"doc\">documentation des vues génériques</span></a>.</p>\n<p>Lorsque vous êtes à l’aise avec les formulaires et les vues génériques, lisez la <a class=\"reference internal\" href=\"/fr/2.0/intro/tutorial05/\"><span class=\"doc\">5ème partie de ce tutoriel</span></a> pour apprendre comment tester notre application de sondage.</p>\n</section>\n</section>","rootId":"writing-your-first-django-app-part-4","toc":[{"title":"Écriture d’un formulaire simple","anchor":"write-a-simple-form","children":[]},{"title":"Utilisation des vues génériques : moins de code, c’est mieux","anchor":"use-generic-views-less-code-is-better","children":[{"title":"Correction de l’URLconf","anchor":"amend-urlconf","children":[]},{"title":"Correction des vues","anchor":"amend-views","children":[]}]}],"breadcrumbs":[{"docname":"intro/index","title":"Premiers pas","url":"/fr/2.0/intro/"}],"prev":{"docname":"intro/tutorial03","title":"Écriture de votre première application Django, 3ème partie","url":"/fr/2.0/intro/tutorial03/"},"next":{"docname":"intro/tutorial05","title":"Écriture de votre première application Django, 5ème partie","url":"/fr/2.0/intro/tutorial05/"},"formats":{"html":"/fr/2.0/intro/tutorial04/","markdown":"/fr/2.0/intro/tutorial04.md","json":"/fr/2.0/intro/tutorial04.json"},"source":"https://github.com/django/django/blob/stable/2.0.x/docs/intro/tutorial04.txt","official":"https://docs.djangoproject.com/fr/2.0/intro/tutorial04/","inVersions":["6.1","6.0","5.2","5.1","5.0","4.2","4.1","4.0","3.2","3.1","3.0","2.2","2.1","2.0","1.11","1.10","1.9"],"inLocales":["en","zh-hans","fr","ja","id","pt-br","ko","es","el","pl"]}