Introduction
Ce tutoriel reprend là où Creating the Public Interface Views s'est arrêté. Nous continuons l'application de sondage web et nous concentrerons sur le traitement des formulaires et la réduction de notre code.
💡 Ce tutoriel est traduit par l'IA à partir de la version anglaise. Pour voir la version originale, vous pouvez cliquer ici
Ce tutoriel reprend là où Creating the Public Interface Views s'est arrêté. Nous continuons l'application de sondage web et nous concentrerons sur le traitement des formulaires et la réduction de notre code.
Mettons à jour notre modèle de détail de sondage (polls/detail.html
) du dernier tutoriel, de sorte que le modèle contienne un élément HTML <form>
:
<form action="{% url 'polls:vote' question.id %}" method="post">
{% csrf_token %}
<fieldset>
<legend><h1>{{ question.question_text }}</h1></legend>
{% if error_message %}<p><strong>{{ error_message }}</strong></p>{% endif %}
{% for choice in question.choice_set.all %}
<input type="radio" name="choice" id="choice{{ forloop.counter }}" value="{{ choice.id }}">
<label for="choice{{ forloop.counter }}">{{ choice.choice_text }}</label><br>
{% endfor %}
</fieldset>
<input type="submit" value="Vote">
</form>
Un rapide survol :
valeur
de chaque bouton radio est l'ID associé au choix de question. Le nom
de chaque bouton radio est "choice"
. Cela signifie que, lorsqu'un utilisateur sélectionne un des boutons radio et soumet le formulaire, il enverra les données POST choice=#
où ## est l'ID du choix sélectionné. Voilà le concept de base des formulaires HTML.action
du formulaire sur {% url 'polls:vote' question.id %}
, et nous avons défini méthode="post"
. Utiliser méthode="post"
(au lieu de méthode="get"
) est très important, car l'acte de soumettre ce formulaire modifiera les données côté serveur. Chaque fois que vous créez un formulaire qui modifie les données côté serveur, utilisez méthode="post"
. Ce conseil n'est pas spécifique à Django ; c'est une bonne pratique de développement web en général.forloop.counter
indique combien de fois la balise for
a parcouru sa boucle{% csrf_token %}<csrf_token>
.Maintenant, créons une vue Django qui gère les données soumises et en fait quelque chose. Rappelez-vous, dans **Creating the Public Interface Views**
, nous avons créé une configuration d'URL pour l'application de sondages qui contient cette ligne :
path("<int:question_id>/vote/", views.vote, name="vote"),
Nous avons également créé une implémentation fictive de la fonction vote()
. Créons une version réelle. Ajoutez ce qui suit à polls/views.py
:
from django.http import HttpResponse, HttpResponseRedirect
from django.shortcuts import get_object_or_404, render
from django.urls import reverse
from.models import Choice, Question
#...
def vote(request, question_id):
question = get_object_or_404(Question, pk=question_id)
try:
selected_choice = question.choice_set.get(pk=request.POST["choice"])
except (KeyError, Choice.DoesNotExist):
## Redisplay the question voting form.
return render(
request,
"polls/detail.html",
{
"question": question,
"error_message": "You didn't select a choice.",
},
)
else:
selected_choice.votes += 1
selected_choice.save()
## Always return an HttpResponseRedirect after successfully dealing
## with POST data. This prevents data from being posted twice if a
## user hits the Back button.
return HttpResponseRedirect(reverse("polls:results", args=(question.id,)))
Ce code inclut quelques éléments que nous n'avons pas encore abordés dans ce tutoriel :
request.POST <django.http.HttpRequest.POST>
est un objet ressemblant à un dictionnaire qui vous permet d'accéder aux données soumises par nom de clé. Dans ce cas, request.POST['choice']
renvoie l'ID du choix sélectionné, sous forme de chaîne de caractères. Les valeurs de request.POST <django.http.HttpRequest.POST>
sont toujours des chaînes de caractères.
Notez que Django fournit également request.GET <django.http.HttpRequest.GET>
pour accéder aux données GET de la même manière - mais nous utilisons explicitement request.POST <django.http.HttpRequest.POST>
dans notre code, pour vous assurer que les données ne sont modifiées que via un appel POST.
request.POST['choice']
lèvera une KeyError
si choice
n'est pas fourni dans les données POST. Le code ci-dessus vérifie la KeyError
et redisplaye le formulaire de question avec un message d'erreur si choice
n'est pas donné.
Après avoir incrémenté le compteur de choix, le code renvoie une ~django.http.HttpResponseRedirect
plutôt qu'une ~django.http.HttpResponse
normale. ~django.http.HttpResponseRedirect
prend un seul argument : l'URL vers laquelle l'utilisateur sera redirigé (voir le point suivant pour savoir comment nous construisons l'URL dans ce cas).
Comme le commentaire Python ci-dessus le souligne, vous devriez toujours renvoyer une ~django.http.HttpResponseRedirect
après avoir correctement traité les données POST. Ce conseil n'est pas spécifique à Django ; c'est une bonne pratique de développement web en général.
Nous utilisons la fonction ~django.urls.reverse
dans le constructeur ~django.http.HttpResponseRedirect
dans cet exemple. Cette fonction aide à éviter d'avoir à coder en dur une URL dans la fonction de vue. Elle est donnée le nom de la vue vers laquelle nous voulons passer le contrôle et la partie variable du motif d'URL qui pointe vers cette vue. Dans ce cas, en utilisant la configuration d'URL que nous avons établie dans **Creating the Public Interface Views**
, cet appel ~django.urls.reverse
renverra une chaîne de caractères comme :
"/polls/3/results/"
où le 3
est la valeur de question.id
. Cette URL redirigée appellera ensuite la vue 'results'
pour afficher la page finale.
Comme mentionné dans **Creating the Public Interface Views**
, request
est un objet ~django.http.HttpRequest
. Pour en savoir plus sur les objets ~django.http.HttpRequest
, consultez la documentation sur les requêtes et les réponses </ref/request-response>
.
Après qu'un utilisateur ait voté pour une question, la vue vote()
redirige vers la page des résultats de la question. Écrivons cette vue :
from django.shortcuts import get_object_or_404, render
def results(request, question_id):
question = get_object_or_404(Question, pk=question_id)
return render(request, "polls/results.html", {"question": question})
Cela est presque exactement le même que la vue detail()
de Creating the Public Interface Views. La seule différence est le nom du modèle. Nous corrigerons cette redondance plus tard.
Maintenant, créez un modèle polls/results.html
:
<h1>{{ question.question_text }}</h1>
<ul>
{% for choice in question.choice_set.all %}
<li>{{ choice.choice_text }} -- {{ choice.votes }} vote{{ choice.votes|pluralize }}</li>
{% endfor %}
</ul>
<a href="{% url 'polls:detail' question.id %}">Vote again?</a>
Maintenant, accédez à /polls/1/
dans votre navigateur et votez pour la question. Vous devriez voir une page de résultats qui se met à jour chaque fois que vous votez. Si vous soumettez le formulaire sans avoir choisi de choix, vous devriez voir le message d'erreur.
cd ~/project/mysite
python manage.py runserver 0.0.0.0:8080
Remarque :
Le code de notre vue vote()
a un petit problème. Il obtient d'abord l'objet selected_choice
de la base de données, puis calcule la nouvelle valeur de votes
, puis la sauvegarde dans la base de données. Si deux utilisateurs de votre site tentent de voter exactement en même temps, cela peut se mal passer : La même valeur, disons 42, sera récupérée pour votes
. Ensuite, pour les deux utilisateurs, la nouvelle valeur de 43 est calculée et enregistrée, mais 44 serait la valeur attendue.
Cela s'appelle une condition de course. Si vous êtes intéressé, vous pouvez lire avoiding-race-conditions-using-f
pour apprendre comment résoudre ce problème.
La vue detail()
(issue de **Creating the Public Interface Views**
) et la vue results()
sont très courtes - et, comme mentionné ci-dessus, redondantes. La vue index()
, qui affiche une liste de sondages, est similaire.
Ces vues représentent un cas courant du développement web de base : récupérer des données depuis la base de données selon un paramètre passé dans l'URL, charger un modèle et renvoyer le modèle rendu. Comme cela est très courant, Django propose un raccourci, appelé système de "vues génériques".
Les vues génériques abstraient les modèles communs au point où vous n'avez même pas besoin d'écrire du code Python pour écrire une application.
Convertissons notre application de sondage pour utiliser le système de vues génériques, afin que nous puissions supprimer un certain nombre de notre propre code. Nous devrons prendre quelques étapes pour effectuer la conversion. Nous allons :
Lisez ci-dessous pour plus de détails.
Pourquoi le remaniement du code?
Généralement, lorsqu'on écrit une application Django, on évaluera si les vues génériques sont appropriées pour votre problème, et on les utilisera dès le départ, plutôt que de refactoriser votre code au milieu du chemin. Mais ce tutoriel a intentionnellement mis l'accent sur l'écriture des vues "de manière difficile" jusqu'à présent, pour se concentrer sur les concepts clés.
Vous devriez connaître les mathématiques de base avant de commencer à utiliser une calculatrice.
Tout d'abord, ouvrez la configuration d'URL polls/urls.py
et modifiez-la comme suit :
from django.urls import path
from. import views
app_name = "polls"
urlpatterns = [
path("", views.IndexView.as_view(), name="index"),
path("<int:pk>/", views.DetailView.as_view(), name="detail"),
path("<int:pk>/results/", views.ResultsView.as_view(), name="results"),
path("<int:question_id>/vote/", views.vote, name="vote"),
]
Notez que le nom du motif correspondant dans les chaînes de caractères de chemin des deuxième et troisième motifs est passé de <question_id>
à <pk>
.
Ensuite, nous allons supprimer nos anciennes vues index
, detail
et results
et utiliser les vues génériques de Django à la place. Pour ce faire, ouvrez le fichier polls/views.py
et modifiez-le comme suit :
from django.http import HttpResponseRedirect
from django.shortcuts import get_object_or_404, render
from django.urls import reverse
from django.views import generic
from.models import Choice, Question
class IndexView(generic.ListView):
template_name = "polls/index.html"
context_object_name = "latest_question_list"
def get_queryset(self):
"""Retourne les cinq dernières questions publiées."""
return Question.objects.order_by("-pub_date")[:5]
class DetailView(generic.DetailView):
model = Question
template_name = "polls/detail.html"
class ResultsView(generic.DetailView):
model = Question
template_name = "polls/results.html"
def vote(request, question_id):
... ## identique à ci-dessus, pas de modifications nécessaires.
Nous utilisons ici deux vues génériques : ~django.views.generic.list.ListView
et ~django.views.generic.detail.DetailView
. respectivement, ces deux vues abstraient les concepts de "afficher une liste d'objets" et "afficher une page de détails pour un type particulier d'objet".
model
.~django.views.generic.detail.DetailView
attend que la valeur de la clé primaire capturée depuis l'URL soit appelée "pk"
, donc nous avons changé question_id
en pk
pour les vues génériques.Par défaut, la vue générique ~django.views.generic.detail.DetailView
utilise un modèle appelé <nom de l'application>/<nom du modèle>_detail.html
. Dans notre cas, il utiliserait le modèle "polls/question_detail.html"
. L'attribut template_name
est utilisé pour dire à Django d'utiliser un nom de modèle spécifique au lieu du nom de modèle par défaut autogénéré. Nous spécifions également le template_name
pour la vue de liste results
- cela garantit que la vue de résultats et la vue de détails ont une apparence différente lorsqu'elles sont rendues, même si elles sont toutes deux une ~django.views.generic.detail.DetailView
en coulisse.
De manière similaire, la vue générique ~django.views.generic.list.ListView
utilise un modèle par défaut appelé <nom de l'application>/<nom du modèle>_list.html
; nous utilisons template_name
pour dire à ~django.views.generic.list.ListView
d'utiliser notre modèle existant "polls/index.html"
.
Dans les parties précédentes du tutoriel, les modèles ont été fournis avec un contexte qui contient les variables de contexte question
et latest_question_list
. Pour DetailView
, la variable question
est fournie automatiquement - puisque nous utilisons un modèle Django (Question
), Django est capable de déterminer un nom approprié pour la variable de contexte. Cependant, pour ListView
, la variable de contexte automatiquement générée est question_list
. Pour la surcharger, nous fournissons l'attribut context_object_name
, en spécifiant que nous voulons utiliser latest_question_list
à la place. En tant qu'approche alternative, vous pourriez modifier vos modèles pour correspondre aux nouvelles variables de contexte par défaut - mais il est beaucoup plus facile de dire à Django d'utiliser la variable que vous voulez.
Exécutez le serveur et utilisez votre nouvelle application de sondage basée sur les vues génériques.
Félicitations ! Vous avez terminé le laboratoire Traitement des formulaires et Réduction de notre code. Vous pouvez pratiquer d'autres laboratoires sur LabEx pour améliorer vos compétences.