Traitement des formulaires et réduction de notre code

DjangoDjangoBeginner
Pratiquer maintenant

💡 Ce tutoriel est traduit par l'IA à partir de la version anglaise. Pour voir la version originale, vous pouvez cliquer ici

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.


Skills Graph

%%%%{init: {'theme':'neutral'}}%%%% flowchart RL django(("Django")) -.-> django/CoreConfigurationandRoutingGroup(["Core Configuration and Routing"]) django(("Django")) -.-> django/DatabaseModelsandMigrationsGroup(["Database, Models, and Migrations"]) django(("Django")) -.-> django/UserInterfaceandInteractionGroup(["User Interface and Interaction"]) django/CoreConfigurationandRoutingGroup -.-> django/django_urls("Django Urls") django/DatabaseModelsandMigrationsGroup -.-> django/request_and_response("Request and Response") django/UserInterfaceandInteractionGroup -.-> django/built_in_views("Built-in Views") subgraph Lab Skills django/django_urls -.-> lab-153744{{"Traitement des formulaires et réduction de notre code"}} django/request_and_response -.-> lab-153744{{"Traitement des formulaires et réduction de notre code"}} django/built_in_views -.-> lab-153744{{"Traitement des formulaires et réduction de notre code"}} end

Écrire un formulaire minimal

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 :

  • Le modèle ci-dessus affiche un bouton radio pour chaque choix de question. La 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.
  • Nous avons défini l'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
  • Puisque nous créons un formulaire POST (qui peut avoir pour effet de modifier les données), nous devons nous soucier des falsifications de requêtes entre sites. Heureusement, vous n'avez pas à vous inquiéter trop, car Django dispose d'un système utile pour se prémunir contre cela. En bref, tous les formulaires POST visant des URL internes devraient utiliser la balise de modèle {% 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
Poll voting form interface

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.

Utiliser les vues génériques : moins de code est mieux

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 :

  1. Convertir la configuration d'URL.
  2. Supprimer certaines des anciennes vues inutiles.
  3. Introduire de nouvelles vues basées sur les vues génériques de Django.

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.

Modifier la configuration d'URL

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

Modifier les vues

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

  • Chaque vue générique doit savoir quel modèle elle va agir sur. Cela est fourni en utilisant l'attribut model.
  • La vue générique ~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.

Sommaire

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.