Einführung
Dieser Tutorial beginnt dort, wo Creating the Public Interface Views aufgehört hat. Wir setzen die Web-Umfrageanwendung fort und werden uns auf die Formularverarbeitung und die Reduzierung unseres Codes konzentrieren.
Schreiben eines minimalen Formulars
Lassen Sie uns die Umfrage-Detail-Vorlage (polls/detail.html) aus dem letzten Tutorial aktualisieren, sodass die Vorlage ein HTML <form>-Element enthält:
<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>
Eine schnelle Übersicht:
- Die obige Vorlage zeigt für jede Frageauswahl einen Radiobutton an. Der
valuejedes Radiobuttons ist die ID der zugehörigen Frageauswahl. Dernamejedes Radiobuttons ist"choice". Das bedeutet, wenn jemand einen der Radiobuttons auswählt und das Formular abgibt, wird es die POST-Datenchoice=#senden, wobei ## die ID der ausgewählten Auswahl ist. Dies ist das grundlegende Konzept von HTML-Formularen. - Wir setzen die
actiondes Formulars auf{% url 'polls:vote' question.id %}undmethod="post". Das Verwenden vonmethod="post"(im Gegensatz zumethod="get") ist sehr wichtig, da das Absenden dieses Formulars die Daten auf der Serverseite ändern wird. Wann immer Sie ein Formular erstellen, das die Daten auf der Serverseite ändert, verwenden Siemethod="post". Dieser Tipp ist nicht speziell für Django; es ist allgemein gute Praxis im Web-Entwicklung. forloop.countergibt an, wie oft derfor-Tag seine Schleife durchlaufen hat.- Da wir ein POST-Formular erstellen (was die Wirkung haben kann, Daten zu ändern), müssen wir uns um Cross-Site-Request-Forgery (CSRF) kümmern. Glücklicherweise müssen Sie sich nicht zu sehr darum kümmern, weil Django ein hilfreiches System zur Schutz vor diesem bietet. Kurz gesagt sollten alle POST-Formulare, die auf interne URLs gerichtet sind, das
{% csrf_token %}<csrf_token>-Vorlagentag verwenden.
Lassen Sie uns nun eine Django-Ansicht erstellen, die die abgeschickten Daten verarbeitet und damit etwas macht. Denken Sie daran, dass wir in **Creating the Public Interface Views** eine URLconf für die Umfrageanwendung erstellt haben, die diese Zeile enthält:
path("<int:question_id>/vote/", views.vote, name="vote"),
Wir haben auch eine Dummy-Implementierung der vote()-Funktion erstellt. Lassen Sie uns eine echte Version erstellen. Fügen Sie Folgendes zu polls/views.py hinzu:
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):
## Zeige das Frageabstimmungsformular erneut an.
return render(
request,
"polls/detail.html",
{
"question": question,
"error_message": "You didn't select a choice.",
},
)
else:
selected_choice.votes += 1
selected_choice.save()
## Geben Sie immer eine HttpResponseRedirect zurück, nachdem Sie
## erfolgreich mit POST-Daten umgegangen sind. Dies verhindert, dass
## Daten zweimal gesendet werden, wenn ein Benutzer die
## Zurück-Taste drückt.
return HttpResponseRedirect(reverse("polls:results", args=(question.id,)))
Dieser Code enthält ein paar Dinge, die wir in diesem Tutorial noch nicht behandelt haben:
request.POST <django.http.HttpRequest.POST>ist ein dictionary-ähnliches Objekt, das Ihnen ermöglicht, abgeschickte Daten nach Schlüsselnamen zuzugreifen. In diesem Fall gibtrequest.POST['choice']die ID der ausgewählten Auswahl als String zurück. Die Werte vonrequest.POST <django.http.HttpRequest.POST>sind immer Strings.Beachten Sie, dass Django auch `request.GET
<django.http.HttpRequest.GET>zur Zugang zu GET-Daten auf die gleiche Weise bietet - aber wir verwenden explizitrequest.POST
<django.http.HttpRequest.POST>` in unserem Code, um sicherzustellen, dass die Daten nur über einen POST-Aufruf geändert werden.
request.POST['choice']wirdKeyErrorauslösen, wennchoicein den POST-Daten nicht angegeben wurde. Der obige Code überprüft aufKeyErrorund zeigt das Frageformular erneut an, wennchoicenicht angegeben ist.Nachdem die Stimmenzahl erhöht wurde, gibt der Code eine
~django.http.HttpResponseRedirectzurück, statt einer normalen~django.http.HttpResponse.~django.http.HttpResponseRedirectnimmt einen einzelnen Parameter: die URL, zu der der Benutzer weitergeleitet wird (siehe den folgenden Punkt, wie wir in diesem Fall die URL konstruieren).Wie der Python-Kommentar oben zeigt, sollten Sie immer eine
~django.http.HttpResponseRedirectzurückgeben, nachdem Sie erfolgreich mit POST-Daten umgegangen sind. Dieser Tipp ist nicht speziell für Django; es ist allgemein gute Praxis im Web-Entwicklung.Wir verwenden in diesem Beispiel die
~django.urls.reverse-Funktion im~django.http.HttpResponseRedirect-Konstruktor. Diese Funktion hilft dabei, eine URL im View-Funktion nicht hartcodieren zu müssen. Es wird der Name der Ansicht übergeben, an die wir die Steuerung weitergeben möchten, und der variable Teil des URL-Patterns, das auf diese Ansicht zeigt. In diesem Fall wird, wenn wir die URLconf verwenden, die wir in**Creating the Public Interface Views**eingerichtet haben, dieser~django.urls.reverse-Aufruf eine Zeichenfolge wie diese zurückgeben:"/polls/3/results/"wobei die
3der Wert vonquestion.idist. Die weitergeleitete URL ruft dann die'results'-Ansicht auf, um die Endseite anzuzeigen.
Wie in **Creating the Public Interface Views** erwähnt, ist request ein ~django.http.HttpRequest-Objekt. Weitere Informationen zu ~django.http.HttpRequest-Objekten finden Sie in der request and response documentation </ref/request-response>.
Nachdem jemand in einer Frage abgestimmt hat, leitet die vote()-Ansicht zur Ergebnispage der Frage weiter. Schreiben Sie diese Ansicht:
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})
Dies ist fast genau die gleiche wie die detail()-Ansicht aus Creating the Public Interface Views. Der einzige Unterschied ist der Vorlagenname. Wir werden diese Redundanz später beheben.
Erstellen Sie nun eine polls/results.html-Vorlage:
<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>
Gehen Sie nun in Ihrem Browser zu /polls/1/ und stimmen Sie in der Frage ab. Sie sollten eine Ergebnispage sehen, die sich jedes Mal aktualisiert, wenn Sie abstimmen. Wenn Sie das Formular absenden, ohne eine Auswahl getroffen zu haben, sollten Sie die Fehlermeldung sehen.
cd ~/project/mysite
python manage.py runserver 0.0.0.0:8080

Hinweis:
Der Code unserer vote()-Ansicht hat ein kleines Problem. Er ruft zunächst das selected_choice-Objekt aus der Datenbank ab, berechnet dann den neuen Wert von votes und speichert ihn dann wieder in der Datenbank. Wenn zwei Benutzer Ihrer Website genau zur gleichen Zeit versuchen, abzustimmen, kann das schiefgehen: Für votes wird derselbe Wert, sagen wir 42, abgerufen. Dann wird für beide Benutzer der neue Wert von 43 berechnet und gespeichert, aber 44 wäre der erwartete Wert.
Dies wird als Race Condition bezeichnet. Wenn Sie interessiert sind, können Sie avoiding-race-conditions-using-f lesen, um zu lernen, wie Sie dieses Problem lösen können.
Verwenden von generischen Ansichten: Weniger Code ist besser
Die detail()-Ansicht (aus **Creating the Public Interface Views**) und die results()-Ansicht sind sehr kurz - und, wie oben erwähnt, redundant. Die index()-Ansicht, die eine Liste von Umfragen anzeigt, ist ähnlich.
Diese Ansichten repräsentieren einen häufigen Fall der grundlegenden Webentwicklung: das Abrufen von Daten aus der Datenbank gemäß einem Parameter, der in der URL übergeben wird, das Laden einer Vorlage und das Zurückgeben der gerenderten Vorlage. Da dies so häufig vorkommt, bietet Django einen Kurzweg, den "generischen Ansichten"-System.
Generische Ansichten abstrahieren häufige Muster bis zu einem Punkt, wo Sie sogar kein Python-Code schreiben müssen, um eine App zu schreiben.
Lassen Sie uns unsere Umfrage-App umstellen, um das generische Ansichten-System zu verwenden, so dass wir eine Menge unseren eigenen Codes löschen können. Wir müssen einige Schritte unternehmen, um die Umstellung durchzuführen. Wir werden:
- Die URLconf umstellen.
- Einige der alten, unnötigen Ansichten löschen.
- Neue Ansichten auf der Grundlage von Djangos generischen Ansichten einführen.
Lesen Sie weiter für Details.
Warum der Code-Umzug?
Allgemein gesehen werden Sie bei der Schreibung einer Django-App evaluieren, ob generische Ansichten für Ihr Problem geeignet sind, und Sie werden sie von Anfang an verwenden, anstatt Ihren Code halbwegs umzuarbeiten. Aber in diesem Tutorial wurde bislang bewusst darauf fokussiert, die Ansichten "auf die schwere Weise" zu schreiben, um auf die Kernkonzepte zu fokussieren.
Sie sollten Grundrechnen können, bevor Sie einen Taschenrechner verwenden.
URLconf anpassen
Öffnen Sie zunächst die URLconf polls/urls.py und ändern Sie sie wie folgt:
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"),
]
Beachten Sie, dass der Name des gematchten Musters in den Pfadzeichenfolgen des zweiten und dritten Musters von <question_id> zu <pk> geändert ist.
Ansichten anpassen
Als nächstes werden wir unsere alten index, detail und results-Ansichten entfernen und stattdessen Djangos generische Ansichten verwenden. Um dies zu tun, öffnen Sie die Datei polls/views.py und ändern Sie sie wie folgt:
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):
"""Return the last five published questions."""
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):
... ## wie oben, keine Änderungen erforderlich.
Wir verwenden hier zwei generische Ansichten: ~django.views.generic.list.ListView und ~django.views.generic.detail.DetailView. Die beiden Ansichten abstrahieren jeweils die Konzepte "Zeige eine Liste von Objekten an" und "Zeige eine Detailseite für einen bestimmten Objekttyp an".
- Jede generische Ansicht muss wissen, auf welchem Modell sie arbeiten wird. Dies wird mithilfe des
model-Attributs bereitgestellt. - Die
~django.views.generic.detail.DetailView-generische Ansicht erwartet, dass der Primärschlüsselwert, der aus der URL abgerufen wird,"pk"genannt wird, daher haben wirquestion_idfür die generischen Ansichten inpkgeändert.
Standardmäßig verwendet die ~django.views.generic.detail.DetailView-generische Ansicht eine Vorlage namens <app name>/<model name>_detail.html. Im unserem Fall würde sie die Vorlage "polls/question_detail.html" verwenden. Das template_name-Attribut wird verwendet, um Django mitzuteilen, dass eine bestimmte Vorlagenname statt des automatisch generierten Standardvorlagennamens verwendet werden soll. Wir geben auch den template_name für die results-Listenansicht an - dies gewährleistet, dass die Ergebnisansicht und die Detailansicht bei der Darstellung ein unterschiedliches Aussehen haben, obwohl sie beide hinter den Kulissen eine ~django.views.generic.detail.DetailView sind.
Ähnlich verwendet die ~django.views.generic.list.ListView-generische Ansicht eine Standardvorlage namens <app name>/<model name>_list.html; wir verwenden template_name, um ~django.views.generic.list.ListView mitzuteilen, dass unsere vorhandene "polls/index.html"-Vorlage verwendet werden soll.
In früheren Teilen des Tutorials wurden den Vorlagen ein Kontext zur Verfügung gestellt, der die question und latest_question_list-Kontextvariablen enthält. Für DetailView wird die question-Variable automatisch bereitgestellt - da wir ein Django-Modell (Question) verwenden, kann Django einen passenden Namen für die Kontextvariable bestimmen. Für ListView ist jedoch die automatisch generierte Kontextvariable question_list. Um dies zu überschreiben, geben wir das context_object_name-Attribut an und geben an, dass wir latest_question_list statt dessen verwenden möchten. Als alternative Methode könnten Sie Ihre Vorlagen ändern, um den neuen Standardkontextvariablen zu entsprechen - aber es ist einfacher, Django mitzuteilen, welche Variable Sie möchten verwenden.
Starten Sie den Server und verwenden Sie Ihre neue Umfrage-App, die auf generischen Ansichten basiert.
Zusammenfassung
Herzlichen Glückwunsch! Sie haben das Lab "Form Processing and Cutting Down Our Code" abgeschlossen. Sie können in LabEx weitere Labs absolvieren, um Ihre Fähigkeiten zu verbessern.