W3Cschool
恭喜您成為首批注冊(cè)用戶
獲得88經(jīng)驗(yàn)值獎(jiǎng)勵(lì)
讓我們更新一下在上一個(gè)教程中編寫(xiě)的投票詳細(xì)頁(yè)面的模板 ("polls/detail.html") ,讓它包含一個(gè) 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>
簡(jiǎn)要說(shuō)明:
Choice
?前添加一個(gè)單選按鈕。 每個(gè)單選按鈕的 ?value
?屬性是對(duì)應(yīng)的各個(gè) ?Choice
?的 ID。每個(gè)單選按鈕的 ?name
?是 ?"choice"
? 。這意味著,當(dāng)有人選擇一個(gè)單選按鈕并提交表單提交時(shí),它將發(fā)送一個(gè) POST 數(shù)據(jù) ?choice=#
? ,其中# 為選擇的 Choice 的 ID。這是 HTML 表單的基本概念。action
設(shè)置為 ?{% url 'polls:vote' question.id %}
?,并設(shè)置 ?method="post"
?。使用 ?method="post"
? (而不是 ?method="get"
? )是非常重要的,因?yàn)樘峤贿@個(gè)表單的行為將改變服務(wù)器端的數(shù)據(jù)。當(dāng)你創(chuàng)建一個(gè)改變服務(wù)器端數(shù)據(jù)的表單時(shí),使用 ?method="post"
?。這不是 Django 的特定技巧;這是優(yōu)秀的網(wǎng)站開(kāi)發(fā)技巧。forloop.counter
? 指示 ?for
?標(biāo)簽已經(jīng)循環(huán)多少次。{% csrf_token %}
? 模板標(biāo)簽。現(xiàn)在,讓我們來(lái)創(chuàng)建一個(gè) Django 視圖來(lái)處理提交的數(shù)據(jù)。記住,在 教程第 3 部分 中,我們?yōu)橥镀睉?yīng)用創(chuàng)建了一個(gè) URLconf ,包含這一行:
path('<int:question_id>/vote/', views.vote, name='vote'),
我們還創(chuàng)建了一個(gè) ?vote()
? 函數(shù)的虛擬實(shí)現(xiàn)。讓我們來(lái)創(chuàng)建一個(gè)真實(shí)的版本。 將下面的代碼添加到 ?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,)))
以上代碼中有些內(nèi)容還未在本教程中提到過(guò):
request.POST
? 是一個(gè)類字典對(duì)象,讓你可以通過(guò)關(guān)鍵字的名字獲取提交的數(shù)據(jù)。 這個(gè)例子中, ?request.POST['choice']
? 以字符串形式返回選擇的 Choice 的 ID。 ?request.POST
? 的值永遠(yuǎn)是字符串。注意,Django 還以同樣的方式提供 ?request.GET
? 用于訪問(wèn) GET 數(shù)據(jù) —— 但我們?cè)诖a中顯式地使用 ?request.POST
? ,以保證數(shù)據(jù)只能通過(guò) POST 調(diào)用改動(dòng)。request.POST['choice']
? 數(shù)據(jù)中沒(méi)有提供 ?choice
?, POST 將引發(fā)一個(gè) ?KeyError
?。上面的代碼檢查 ?KeyError
?,如果沒(méi)有給出 ?choice
?將重新顯示 Question 表單和一個(gè)錯(cuò)誤信息。HttpResponseRedirect
?而不是常用的 ?HttpResponse
?、 ?HttpResponseRedirect
?只接收一個(gè)參數(shù):用戶將要被重定向的 URL(請(qǐng)繼續(xù)看下去,我們將會(huì)解釋如何構(gòu)造這個(gè)例子中的 URL)。正如上面的 Python 注釋指出的,在成功處理 POST 數(shù)據(jù)后,你應(yīng)該總是返回一個(gè) ?HttpResponseRedirect
?。這不是 Django 的特殊要求,這是那些優(yōu)秀網(wǎng)站在開(kāi)發(fā)實(shí)踐中形成的共識(shí)。HttpResponseRedirect
?的構(gòu)造函數(shù)中使用 ?reverse()
? 函數(shù)。這個(gè)函數(shù)避免了我們?cè)谝晥D函數(shù)中硬編碼 URL。它需要我們給出我們想要跳轉(zhuǎn)的視圖的名字和該視圖所對(duì)應(yīng)的 URL 模式中需要給該視圖提供的參數(shù)。 在本例中,使用在 教程第 3 部分 中設(shè)定的 ?URLconf
?, ?reverse()
? 調(diào)用將返回一個(gè)這樣的字符串:'/polls/3/results/'
其中 3 是 question.id 的值。重定向的 URL 將調(diào)用 'results' 視圖來(lái)顯示最終的頁(yè)面。
正如在 教程第 3 部分 中提到的,?HttpRequest
?是一個(gè) ?HttpRequest
?對(duì)象。
當(dāng)有人對(duì) Question 進(jìn)行投票后, ?vote()
? 視圖將請(qǐng)求重定向到 Question 的結(jié)果界面。讓我們來(lái)編寫(xiě)這個(gè)視圖:
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})
這和 教程第 3 部分 中的 ?detail()
? 視圖幾乎一模一樣。唯一的不同是模板的名字。 我們將在稍后解決這個(gè)冗余問(wèn)題。
現(xiàn)在,創(chuàng)建一個(gè) ?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>
現(xiàn)在,在你的瀏覽器中訪問(wèn) ?/polls/1/
? 然后為 Question 投票。你應(yīng)該看到一個(gè)投票結(jié)果頁(yè)面,并且在你每次投票之后都會(huì)更新。 如果你提交時(shí)沒(méi)有選擇任何 Choice,你應(yīng)該看到錯(cuò)誤信息。
注意:
我們的 vote() 視圖代碼有一個(gè)小問(wèn)題。代碼首先從數(shù)據(jù)庫(kù)中獲取了 ?selected_choice
對(duì)象,接著計(jì)算 ?vote
的新值,最后把值存回?cái)?shù)據(jù)庫(kù)。如果網(wǎng)站有兩個(gè)方可同時(shí)投票在 同一時(shí)間 ,可能會(huì)導(dǎo)致問(wèn)題。同樣的值,42,會(huì)被 ?votes
返回。然后,對(duì)于兩個(gè)用戶,新值43計(jì)算完畢,并被保存,但是期望值是44。這個(gè)問(wèn)題被稱為 競(jìng)爭(zhēng)條件 。
?detail()
?(在 教程第 3 部分 中)和 ?results()
? 視圖都很精簡(jiǎn) —— 并且,像上面提到的那樣,存在冗余問(wèn)題。用來(lái)顯示一個(gè)投票列表的 ?index()
?視圖(也在 教程第 3 部分 中)和它們類似。
這些視圖反映基本的網(wǎng)絡(luò)開(kāi)發(fā)中的一個(gè)常見(jiàn)情況:根據(jù) URL 中的參數(shù)從數(shù)據(jù)庫(kù)中獲取數(shù)據(jù)、載入模板文件然后返回渲染后的模板。 由于這種情況特別常見(jiàn),Django 提供一種快捷方式,叫做 “通用視圖” 系統(tǒng)。
通用視圖將常見(jiàn)的模式抽象化,可以使你在編寫(xiě)應(yīng)用時(shí)甚至不需要編寫(xiě)Python代碼。
讓我們將我們的投票應(yīng)用轉(zhuǎn)換成使用通用視圖系統(tǒng),這樣我們可以刪除許多我們的代碼。我們僅僅需要做以下幾步來(lái)完成轉(zhuǎn)換,我們將:
首先,打開(kāi) ?polls/urls.py
? 這個(gè) URLconf 并將它修改成:
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'),
]
注意,第二個(gè)和第三個(gè)匹配準(zhǔn)則中,路徑字符串中匹配模式的名稱已經(jīng)由 ?<question_id>
? 改為 ?<pk>
?。
下一步,我們將刪除舊的 ?index
?, ?detail
?, 和 ?results
?視圖,并用 Django 的通用視圖代替。打開(kāi) ?polls/views.py
? 文件,并將它修改成:
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.
我們?cè)谶@里使用兩個(gè)通用視圖: ?ListView
?和 ?DetailView
?。這兩個(gè)視圖分別抽象“顯示一個(gè)對(duì)象列表”和“顯示一個(gè)特定類型對(duì)象的詳細(xì)信息頁(yè)面”這兩種概念。
model
?屬性提供。DetailView
?期望從 URL 中捕獲名為 ?"pk"
? 的主鍵值,所以我們?yōu)橥ㄓ靡晥D把 ?question_id
?改成 ?pk
?。默認(rèn)情況下,通用視圖 ?DetailView
?使用一個(gè)叫做 ?<app name>/<model name>_detail.html
?的模板。在我們的例子中,它將使用 ?"polls/question_detail.html"
?模板。?template_name
? 屬性是用來(lái)告訴 Django 使用一個(gè)指定的模板名字,而不是自動(dòng)生成的默認(rèn)名字。 我們也為 ?results
列表視圖指定了 ?template_name
? —— 這確保 results 視圖和 detail 視圖在渲染時(shí)具有不同的外觀,即使它們?cè)诤笈_(tái)都是同一個(gè) ?DetailView
?。
類似地,?ListView
?使用一個(gè)叫做 ?<app name>/<model name>_list.html
? 的默認(rèn)模板;我們使用 ?template_name
來(lái)告訴 ?ListView
使用我們創(chuàng)建的已經(jīng)存在的 ?"polls/index.html"
? 模板。
在之前的教程中,提供模板文件時(shí)都帶有一個(gè)包含 ?question
?和 ?latest_question_list
?變量的 context。對(duì)于 ?DetailView
?, ?question
?變量會(huì)自動(dòng)提供—— 因?yàn)槲覀兪褂?Django 的模型(Question), Django 能夠?yàn)?context 變量決定一個(gè)合適的名字。然而對(duì)于 ListView, 自動(dòng)生成的 context 變量是 ?question_list
?。為了覆蓋這個(gè)行為,我們提供 ?context_object_name
?屬性,表示我們想使用 ?latest_question_list
?。作為一種替換方案,你可以改變你的模板來(lái)匹配新的 context 變量 —— 這是一種更便捷的方法,告訴 Django 使用你想使用的變量名。
啟動(dòng)服務(wù)器,使用一下基于通用視圖的新投票應(yīng)用。
Copyright©2021 w3cschool編程獅|閩ICP備15016281號(hào)-3|閩公網(wǎng)安備35020302033924號(hào)
違法和不良信息舉報(bào)電話:173-0602-2364|舉報(bào)郵箱:jubao@eeedong.com
掃描二維碼
下載編程獅App
編程獅公眾號(hào)
聯(lián)系方式:
更多建議: