Skriva din första Django-app, del 3¶
Denna tutorial börjar där Tutorial 2 slutade. Vi fortsätter med web-poll applikationen och fokuserar på att skapa det publika gränssnittet – ”views”
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.
Översikt¶
En vy är en ”typ” av webbsida i din Django-applikation som i allmänhet tjänar en specifik funktion och har en specifik mall. I en bloggapplikation kan du till exempel ha följande vyer:
Bloggens startsida - visar de senaste inläggen.
Entry ”detail” page – permalänksida för en enskild post.
Årsbaserad arkivsida - visar alla månader med poster under det angivna året.
Månadsbaserad arkivsida - visar alla dagar med poster i den angivna månaden.
Dagbaserad arkivsida - visar alla poster under den aktuella dagen.
Comment action – hanterar kommentarer till en viss post.
I vår poll-applikation kommer vi att ha följande fyra vyer:
Fråga ”index”-sida - visar de senaste frågorna.
Fråga ”detalj”-sida – visar en frågetext, utan resultat men med ett formulär för att rösta.
Frågans resultatsida - visar resultaten för en viss fråga.
Vote action – hanterar röstning för ett visst val i en viss fråga.
I Django levereras webbsidor och annat innehåll med hjälp av vyer. Varje vy representeras av en Python-funktion (eller metod, i fallet med klassbaserade vyer). Django väljer en vy genom att undersöka den URL som begärs (för att vara exakt, den del av URL:en som kommer efter domännamnet).
Nu under din tid på webben kanske du har stött på sådana skönheter som ME2/Sites/dirmod.htm?sid=&type=gen&mod=Core+Pages&gid=A6CD4967199A42D9B65B1B
. Du kommer att bli glad över att veta att Django tillåter oss mycket mer eleganta URL-mönster än så.
Ett URL-mönster är den allmänna formen av en URL - till exempel: /newsarchive/<year>/<month>/
.
För att komma från en URL till en vy använder Django så kallade ”URLconfs”. En URLconf mappar URL-mönster till vyer.
Den här handledningen ger grundläggande instruktioner om användningen av URLconfs, och du kan läsa URL-distributör för mer information.
Skriva fler visningar¶
Låt oss nu lägga till några fler vyer i polls/views.py
. Dessa vyer är lite annorlunda, eftersom de tar ett argument:
polls/views.py
¶def detail(request, question_id):
return HttpResponse("You're looking at question %s." % question_id)
def results(request, question_id):
response = "You're looking at the results of question %s."
return HttpResponse(response % question_id)
def vote(request, question_id):
return HttpResponse("You're voting on question %s." % question_id)
Koppla dessa nya vyer till modulen polls.urls
genom att lägga till följande path()
-anrop:
polls/urls.py
¶from django.urls import path
from . import views
urlpatterns = [
# ex: /polls/
path("", views.index, name="index"),
# ex: /polls/5/
path("<int:question_id>/", views.detail, name="detail"),
# ex: /polls/5/results/
path("<int:question_id>/results/", views.results, name="results"),
# ex: /polls/5/vote/
path("<int:question_id>/vote/", views.vote, name="vote"),
]
Ta en titt i din webbläsare, på ”/polls/34/”. Den kommer att köra funktionen detail()
och visa det ID som du anger i URL:en. Prova också ”/polls/34/results/” och ”/polls/34/vote/” - dessa kommer att visa platshållarresultaten och röstningssidorna.
När någon begär en sida från din webbplats - säg, ”/polls/34/”, kommer Django att ladda Python-modulen mysite.urls
eftersom den pekas ut av inställningen ROOT_URLCONF
. Den hittar variabeln med namnet urlpatterns
och går igenom mönstren i ordning. Efter att ha hittat matchningen vid 'polls/'
, tar den bort den matchande texten ("polls/"
) och skickar den återstående texten – "34/"
– till URLconf ’polls.urls’ för vidare bearbetning. Där matchar den '<int:question_id>/'
, vilket resulterar i ett anrop till vyn detail()
så här:
detail(request=<HttpRequest object>, question_id=34)
Delen question_id=34
kommer från <int:question_id>
. Med hjälp av vinkelparenteser ”fångas” en del av URL:en och skickas som ett nyckelordsargument till view-funktionen. Delen question_id
i strängen definierar det namn som ska användas för att identifiera det matchade mönstret, och delen int
är en omvandlare som avgör vilka mönster som ska matcha den här delen av URL-sökvägen. Kolonet (:
) skiljer omvandlaren och mönsternamnet åt.
Skriv åsikter som faktiskt gör något¶
Varje vy är ansvarig för att göra en av två saker: returnera ett HttpResponse
-objekt som innehåller innehållet för den begärda sidan, eller skapa ett undantag som Http404
. Resten är upp till dig.
Din vy kan läsa poster från en databas, eller inte. Den kan använda ett mallsystem som Djangos - eller ett Python-mallsystem från tredje part - eller inte. Den kan generera en PDF-fil, mata ut XML, skapa en ZIP-fil i farten, vad du vill, med hjälp av vilka Python-bibliotek du vill.
Allt Django vill ha är den där HttpResponse
. Eller ett undantag.
Eftersom det är bekvämt, låt oss använda Djangos egen databas API, som vi täckte i Tutorial 2. Här är ett försök till en ny index()
vy, som visar de senaste 5 omröstningsfrågorna i systemet, åtskilda med kommatecken, enligt publiceringsdatum:
polls/views.py
¶from django.http import HttpResponse
from .models import Question
def index(request):
latest_question_list = Question.objects.order_by("-pub_date")[:5]
output = ", ".join([q.question_text for q in latest_question_list])
return HttpResponse(output)
# Leave the rest of the views (detail, results, vote) unchanged
Det finns dock ett problem här: sidans design är hårdkodad i vyn. Om du vill ändra hur sidan ser ut måste du redigera denna Python-kod. Så låt oss använda Djangos mallsystem för att separera designen från Python genom att skapa en mall som vyn kan använda.
Först skapar du en katalog som heter templates
i din polls
-katalog. Django kommer att leta efter mallar där.
Ditt projekts TEMPLATES
-inställning beskriver hur Django ska ladda och rendera mallar. Standardinställningsfilen konfigurerar en DjangoTemplates
-backend vars APP_DIRS
-alternativ är inställt på True
. Enligt konvention letar DjangoTemplates
efter en ”templates”-underkatalog i varje INSTALLED_APPS
.
I katalogen templates
som du just har skapat, skapar du en annan katalog som heter polls
, och i den skapar du en fil som heter index.html
. Med andra ord bör din mall ligga på polls/templates/polls/index.html
. På grund av hur mallladdaren app_directories
fungerar enligt beskrivningen ovan, kan du hänvisa till denna mall inom Django som polls/index.html
.
Namnavstånd för mallar
Nu skulle vi måhända kunna komma undan med att lägga våra mallar direkt i polls/templates
(snarare än att skapa en annan polls
underkatalog), men det skulle faktiskt vara en dålig idé. Django kommer att välja den första mallen den hittar vars namn matchar, och om du hade en mall med samma namn i en annan applikation skulle Django inte kunna skilja mellan dem. Vi måste kunna peka Django på rätt mall, och det bästa sättet att säkerställa detta är genom att namespacing dem. Det vill säga genom att placera dessa mallar i en annan katalog som är namngiven för själva applikationen.
Lägg in följande kod i den mallen:
polls/templates/polls/index.html
¶{% if latest_question_list %}
<ul>
{% for question in latest_question_list %}
<li><a href="/polls/{{ question.id }}/">{{ question.question_text }}</a></li>
{% endfor %}
</ul>
{% else %}
<p>No polls are available.</p>
{% endif %}
Observera
För att göra handledningen kortare använder alla mallyxempel ofullständig HTML. I dina egna projekt bör du använda kompletta HTML-dokument.
Låt oss nu uppdatera vår vy index
i polls/views.py
så att den använder mallen:
polls/views.py
¶from django.http import HttpResponse
from django.template import loader
from .models import Question
def index(request):
latest_question_list = Question.objects.order_by("-pub_date")[:5]
template = loader.get_template("polls/index.html")
context = {"latest_question_list": latest_question_list}
return HttpResponse(template.render(context, request))
Den koden laddar mallen som heter polls/index.html
och skickar en kontext till den. Kontexten är en ordbok som mappar mallens variabelnamn till Python-objekt.
Ladda sidan genom att rikta webbläsaren mot ”/polls/”, och du bör se en punktlista som innehåller ”What’s up”-frågan från Tutorial 2. Länken pekar till frågans detaljsida.
En genväg: render()
¶
Det är ett mycket vanligt idiom att ladda en mall, fylla en kontext och returnera ett HttpResponse
-objekt med resultatet av den renderade mallen. Django tillhandahåller en genväg. Här är den fullständiga index()
-vyn, omskriven:
polls/views.py
¶from django.shortcuts import render
from .models import Question
def index(request):
latest_question_list = Question.objects.order_by("-pub_date")[:5]
context = {"latest_question_list": latest_question_list}
return render(request, "polls/index.html", context)
Observera att när vi har gjort detta i alla dessa vyer behöver vi inte längre importera loader
och HttpResponse
(du vill behålla HttpResponse
om du fortfarande har stubmetoderna för detail
, results
och vote
).
Funktionen render()
tar request-objektet som sitt första argument, ett mallnamn som sitt andra argument och en ordbok som sitt valfria tredje argument. Den returnerar ett HttpResponse
-objekt av den givna mallen renderad med den givna kontexten.
Skapa ett 404-fel¶
Låt oss nu ta itu med vyn för frågedetaljer - den sida som visar frågetexten för en viss undersökning. Här är vyn:
polls/views.py
¶from django.http import Http404
from django.shortcuts import render
from .models import Question
# ...
def detail(request, question_id):
try:
question = Question.objects.get(pk=question_id)
except Question.DoesNotExist:
raise Http404("Question does not exist")
return render(request, "polls/detail.html", {"question": question})
Det nya konceptet här: Vyn ger upphov till undantaget Http404
om en fråga med det begärda ID:t inte finns.
Vi kommer att diskutera vad du kan lägga in i mallen polls/detail.html
lite senare, men om du snabbt vill få ovanstående exempel att fungera, kan en fil som bara innehåller:
polls/templates/polls/detail.html
¶{{ question }}
kommer att hjälpa dig att komma igång för tillfället.
En genväg: get_object_or_404()
¶
Det är ett mycket vanligt idiom att använda get()
och få Http404
om objektet inte finns. Django tillhandahåller en genväg. Här är detail()
vyn, omskriven:
polls/views.py
¶from django.shortcuts import get_object_or_404, render
from .models import Question
# ...
def detail(request, question_id):
question = get_object_or_404(Question, pk=question_id)
return render(request, "polls/detail.html", {"question": question})
Funktionen get_object_or_404()
tar en Django-modell som sitt första argument och ett godtyckligt antal nyckelordsargument, som den skickar till funktionen get()
i modellens manager. Den ger upphov till Http404
om objektet inte finns.
Filosofi
Varför använder vi en hjälpfunktion get_object_or_404()
istället för att automatiskt fånga upp ObjectDoesNotExist
-undantagen på en högre nivå, eller låta modell-API:et höja Http404
istället för ObjectDoesNotExist
?
För det skulle koppla ihop modellagret med visningslagret. Ett av de främsta designmålen för Django är att upprätthålla lös koppling. En viss kontrollerad koppling införs i modulen django.shortcuts
.
Det finns också en funktion get_list_or_404()
, som fungerar precis som get_object_or_404()
- förutom att den använder filter()
istället för get()
. Det ger upphov till Http404
om listan är tom.
Använd mallsystemet¶
Tillbaka till detail()
-vyn för vår poll-applikation. Med tanke på kontextvariabeln question
, så här kan mallen polls/detail.html
se ut:
polls/templates/polls/detail.html
¶<h1>{{ question.question_text }}</h1>
<ul>
{% for choice in question.choice_set.all %}
<li>{{ choice.choice_text }}</li>
{% endfor %}
</ul>
Mallsystemet använder dot-lookup-syntax för att komma åt variabelattribut. I exemplet med {{ question.question_text }}
gör Django först en ordboksuppslagning på objektet question
. Om det misslyckas försöker den med en attributuppslagning - vilket fungerar i det här fallet. Om attributuppslagningen hade misslyckats skulle den ha försökt med en listindexuppslagning.
Metoduppringning sker i {% feller %}
-loopen: question.choice_set.all
tolkas som Python-koden question.choice_set.all()
, som returnerar en iterabel av Choice
-objekt och är lämplig för användning i taggen {% feller %}
.
Se Mallguide för mer information om mallar.
Ta bort hårdkodade webbadresser i mallar¶
Kom ihåg att när vi skrev länken till en fråga i mallen polls/index.html
, var länken delvis hårdkodad så här:
<li><a href="/polls/{{ question.id }}/">{{ question.question_text }}</a></li>
Problemet med detta hårdkodade, tätt kopplade tillvägagångssätt är att det blir svårt att ändra webbadresser i projekt med många mallar. Men eftersom du definierade argumentet name
i path()
-funktionerna i modulen polls.urls
, kan du ta bort beroendet av specifika URL-sökvägar som definieras i dina url-konfigurationer genom att använda malltaggen {% url %}
:
<li><a href="{% url 'detail' question.id %}">{{ question.question_text }}</a></li>
Det här fungerar genom att leta upp URL-definitionen som anges i modulen polls.urls
. Du kan se exakt var URL-namnet ”detail” är definierat nedan:
...
# the 'name' value as called by the {% url %} template tag
path("<int:question_id>/", views.detail, name="detail"),
...
Om du vill ändra webbadressen till detaljvyn för omröstningar till något annat, kanske till något som polls/specifics/12/
istället för att göra det i mallen (eller mallarna) skulle du ändra den i polls/urls.py
:
...
# added the word 'specifics'
path("specifics/<int:question_id>/", views.detail, name="detail"),
...
Namngivning av URL-namn¶
Självstudieprojektet har bara en app, polls
. I riktiga Django-projekt kan det finnas fem, tio, tjugo appar eller fler. Hur skiljer Django URL-namnen mellan dem? Till exempel har appen polls
en detail
vy, och det kan också en app på samma projekt som är för en blogg. Hur gör man så att Django vet vilken appvy som ska skapas för en webbadress när man använder malltaggen {% url %}
?
Svaret är att lägga till namnrymder i din URLconf. I filen polls/urls.py
, gå vidare och lägg till en app_name
för att ställa in applikationens namnrymd:
polls/urls.py
¶from django.urls import path
from . import views
app_name = "polls"
urlpatterns = [
path("", views.index, name="index"),
path("<int:question_id>/", views.detail, name="detail"),
path("<int:question_id>/results/", views.results, name="results"),
path("<int:question_id>/vote/", views.vote, name="vote"),
]
Ändra nu din polls/index.html
mall från:
polls/templates/polls/index.html
¶<li><a href="{% url 'detail' question.id %}">{{ question.question_text }}</a></li>
för att peka på den namngivna detaljvyn:
polls/templates/polls/index.html
¶<li><a href="{% url 'polls:detail' question.id %}">{{ question.question_text }}</a></li>
När du känner dig bekväm med att skriva vyer kan du läsa del 4 i denna handledning för att lära dig grunderna om formulärbehandling och generiska vyer.