Skriva din första Django-app, del 4¶
Denna handledning börjar där Tutorial 3 slutade. Vi fortsätter med web-poll applikationen och kommer att fokusera på formulärbehandling och att minska vår kod.
Var du kan få hjälp:
Om du har problem med att gå igenom den här handledningen kan du gå till avsnittet Att få hjälp i FAQ.
Skriv ett minimalt formulär¶
Låt oss uppdatera vår mall för detaljer om omröstningen (”polls/detail.html”) från den senaste handledningen, så att mallen innehåller ett HTML-element <form>
:
polls/templates/polls/detail.html
¶<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>
En snabb genomgång:
Ovanstående mall visar en alternativknapp för varje frågealternativ. Varje alternativknapps
värde
är det associerade frågevalets ID. Namnet på varje alternativknapp är ”val”. Det betyder att när någon väljer en av alternativknapparna och skickar in formuläret, skickar den POST-datachoice=#
där # är ID för det valda valet. Detta är det grundläggande konceptet för HTML-formulär.Vi ställer in formulärets
action
till{% url 'polls:vote' question.id %}
, och vi ställer inmethod="post"
. Att användamethod="post"
(i motsats tillmethod="get"
) är mycket viktigt, eftersom handlingen att skicka in detta formulär kommer att ändra data på serversidan. När du skapar ett formulär som ändrar data på serversidan, användmethod="post"
. Detta tips är inte specifikt för Django; det är bra webbutvecklingspraxis i allmänhet.forloop.counter
anger hur många gångerfor
-taggen har gått igenom sin loopEftersom vi skapar ett POST-formulär (som kan ha effekten av att ändra data) måste vi oroa oss för Cross Site Request Forgeries. Tack och lov behöver du inte oroa dig för hårt, eftersom Django kommer med ett användbart system för att skydda mot det. Kort sagt, alla POST-formulär som är inriktade på interna webbadresser bör använda
{% csrf_token %}
malltagg.
Låt oss nu skapa en Django-vy som hanterar de inskickade uppgifterna och gör något med dem. Kom ihåg att vi i Tutorial 3 skapade en URLconf för polls-programmet som innehåller den här raden:
polls/urls.py
¶path("<int:question_id>/vote/", views.vote, name="vote"),
Vi skapade också en dummyimplementering av funktionen vote()
. Låt oss skapa en riktig version. Lägg till följande i polls/views.py
:
polls/views.py
¶from django.db.models import F
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 = F("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,)))
Den här koden innehåller några saker som vi ännu inte har gått igenom i den här handledningen:
:attr:
request.POST <django.http.HttpRequest.POST>` är ett ordboksliknande objekt som låter dig komma åt inskickade data med nyckelnamn. I det här fallet returnerar ``request.POST['choice']
ID:t för det valda valet som en sträng.request.POST
-värden är alltid strängar.Observera att Django också tillhandahåller
request.GET
för åtkomst till GET-data på samma sätt - men vi använder uttryckligenrequest.POST
i vår kod för att säkerställa att data endast ändras via ett POST-anrop.request.POST['choice']
kommer att ge upphov tillKeyError
omchoice
inte angavs i POST-data. Ovanstående kod kontrollerar förKeyError
och visar frågeformuläret på nytt med ett felmeddelande omchoice
inte har angetts.F("votes") + 1
instruerar databasen att öka antalet röster med 1.Efter att ha ökat antalet val returnerar koden en
HttpResponseRedirect
snarare än en vanligHttpResponse
.HttpResponseRedirect
tar ett enda argument: URL:en till vilken användaren kommer att omdirigeras (se följande punkt för hur vi konstruerar URL:en i det här fallet).Som Python-kommentaren ovan påpekar bör du alltid returnera en
HttpResponseRedirect
efter framgångsrik hantering av POST-data. Det här tipset är inte specifikt för Django; det är god praxis för webbutveckling i allmänhet.Vi använder funktionen
reverse()
i konstruktörenHttpResponseRedirect
i det här exemplet. Denna funktion hjälper till att undvika att behöva hårdkoda en URL i vyfunktionen. Den får namnet på den vy som vi vill skicka kontroll till och den variabla delen av URL-mönstret som pekar på den vyn. I det här fallet, med hjälp av URLconf som vi ställde in i Tutorial 3, kommer dettareverse()
-anrop att returnera en sträng som"/polls/3/results/"
där
3
är värdet avquestion.id
. Denna omdirigerade URL kommer sedan att anropa vyn'results
för att visa den slutliga sidan.
Som nämnts i Tutorial 3 är request
ett HttpRequest
-objekt. För mer information om HttpRequest
-objekt, se dokumentation om begäran och svar.
När någon har röstat på en fråga omdirigeras vyn vote()
till resultatsidan för frågan. Låt oss skriva den vyn:
polls/views.py
¶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})
Detta är nästan exakt samma sak som vyn detail()
från Tutorial 3. Den enda skillnaden är mallnamnet. Vi kommer att åtgärda denna redundans senare.
Skapa nu mallen polls/results.html
:
polls/templates/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>
Gå nu till /polls/1/
i din webbläsare och rösta på frågan. Du bör se en resultatsida som uppdateras varje gång du röstar. Om du skickar in formuläret utan att ha gjort något val bör du se ett felmeddelande.
Använd generiska vyer: Mindre kod är bättre¶
Vyerna detail()
(från Tutorial 3) och results()
är mycket korta - och, som nämnts ovan, överflödiga. Vyn index()
, som visar en lista med omröstningar, är liknande.
Dessa vyer representerar ett vanligt fall av grundläggande webbutveckling: hämta data från databasen enligt en parameter som skickas i webbadressen, ladda en mall och returnera den renderade mallen. Eftersom detta är så vanligt tillhandahåller Django en genväg, kallad ”generic views” -systemet.
Generiska vyer abstraherar vanliga mönster till den punkt där du inte ens behöver skriva Python-kod för att skriva en app. Till exempel abstraherar de generiska vyerna ListView
och DetailView
begreppen ”visa en lista med objekt” respektive ”visa en detaljsida för en viss typ av objekt”.
Låt oss konvertera vår poll-app till att använda det generiska vy-systemet, så att vi kan ta bort en hel del av vår egen kod. Vi måste ta några steg för att göra konverteringen. Vi kommer att göra det:
Konvertera URLconf.
Ta bort några av de gamla, onödiga vyerna.
Introducera nya vyer baserade på Djangos generiska vyer.
Läs vidare för mer information.
Varför kodomläggningen?
När du skriver en Django-app utvärderar du i allmänhet om generiska vyer passar bra för ditt problem, och du använder dem från början, snarare än att refaktorisera din kod halvvägs igenom. Men denna handledning har avsiktligt fokuserat på att skriva vyerna ”på det hårda sättet” fram till nu, för att fokusera på kärnkoncept.
Du bör kunna grundläggande matematik innan du börjar använda en miniräknare.
Ändra URLconf¶
Först öppnar du URLconf i polls/urls.py
och ändrar den så här:
polls/urls.py
¶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"),
]
Observera att namnet på det matchade mönstret i söksträngarna för det andra och tredje mönstret har ändrats från <question_id>
till <pk>
. Detta är nödvändigt eftersom vi kommer att använda den generiska vyn DetailView
för att ersätta våra vyer detail()
och results()
, och den förväntar sig att det primära nyckelvärdet som fångas från URL:en ska heta "pk"
.
Ändra synpunkter¶
Därefter ska vi ta bort våra gamla vyer index
, detail
och results
och använda Djangos generiska vyer istället. För att göra det, öppna filen polls/views.py
och ändra den så här:
polls/views.py
¶from django.db.models import F
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):
# same as above, no changes needed.
...
Varje generisk vy måste veta vilken modell den ska agera utifrån. Detta tillhandahålls antingen med attributet model
(i detta exempel model = Question
för DetailView
och ResultsView
) eller genom att definiera metoden get_queryset()
(som visas i IndexView
).
Som standard använder den generiska vyn DetailView
en mall som heter <app name>/<model name>_detail.html
. I vårt fall skulle den använda mallen "polls/question_detail.html"
. Attributet template_name
används för att tala om för Django att använda ett specifikt mallnamn istället för det autogenererade standardmallnamnet. Vi anger också template_name
för listvyn results
- detta säkerställer att resultatvyn och detaljvyn har ett annat utseende när de renderas, även om de båda är en DetailView
bakom kulisserna.
På samma sätt använder den generiska vyn ListView
en standardmall som heter <app name>/<model name>_list.html
; vi använder template_name
för att tala om för ListView
att använda vår befintliga mall "polls/index.html"
.
I tidigare delar av handledningen har mallarna försetts med en kontext som innehåller kontextvariablerna question
och latest_question_list
. För DetailView
tillhandahålls question
-variabeln automatiskt - eftersom vi använder en Django-modell (Question
) kan Django bestämma ett lämpligt namn för kontextvariabeln. För ListView är dock den automatiskt genererade kontextvariabeln question_list
. För att åsidosätta detta tillhandahåller vi attributet context_object_name
, som anger att vi vill använda latest_question_list
istället. Som ett alternativt tillvägagångssätt kan du ändra dina mallar för att matcha de nya standardkontextvariablerna - men det är mycket lättare att berätta för Django att använda den variabel du vill ha.
Kör servern och använd din nya polling-app baserad på generiska vyer.
För fullständig information om generiska vyer, se dokumentation om generiska vyer.
När du känner dig bekväm med formulär och generiska vyer kan du läsa del 5 av denna handledning för att lära dig hur du testar vår polls app.