Python3.0 Django2.0.4 搭建項(xiàng)目(四)

接上一篇Python3.0 Django2.0.4 搭建項(xiàng)目(三)歧斟,這一篇主要是繼續(xù)完善我們的投票應(yīng)用击你。

編寫一個(gè)簡(jiǎn)單的表單

讓我們更新一下在上一個(gè)教程中編寫的投票詳細(xì)頁(yè)面的模板 ("polls/detail.html") ,讓它包含一個(gè) HTML <form> 元素

polls/templates/polls/detail.html


<h1>{{ question.question_text }}</h1>

{% if error_message %}<p><strong>{{ error_message }}</strong></p>{% endif %}

<form action="{% url 'polls:vote' question.id %}" method="post">
{% csrf_token %}
{% 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 %}
<input type="submit" value="Vote" />
</form>

功能概述:
1.上面的模板在 Question 的每個(gè) 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。

2.我們?cè)O(shè)置表單的 action 為 {% url 'polls:vote' question.id %} 矾踱,并設(shè)置 method="post" 恨狈。使用 method="post"(與其相對(duì)的是method="get"`)是非常重要的,因?yàn)檫@個(gè)提交表單的行為會(huì)改變服務(wù)器端的數(shù)據(jù)呛讲。 無(wú)論何時(shí)禾怠,當(dāng)你需要?jiǎng)?chuàng)建一個(gè)改變服務(wù)器端數(shù)據(jù)的表單時(shí),請(qǐng)使用 ``method="post" 贝搁。

3.forloop.counter 指示 for 標(biāo)簽已經(jīng)循環(huán)多少次吗氏。

4.由于我們創(chuàng)建一個(gè) POST 表單(它具有修改數(shù)據(jù)的作用),所以我們需要小心跨站點(diǎn)請(qǐng)求偽造雷逆。 Django 擁有一個(gè)用來(lái)防御它的非常容易使用的系統(tǒng)弦讽。 簡(jiǎn)而言之,所有針對(duì)內(nèi)部 URL 的 POST 表單都應(yīng)該使用 {% csrf_token %} 模板標(biāo)簽膀哲。

在上一篇中我們已經(jīng)創(chuàng)建了投票應(yīng)用的一個(gè) URLconf往产,代碼如下:

polls/urls.py

path('<int:question_id>/vote/', views.vote, name='vote'),

實(shí)現(xiàn)vote() 函數(shù)

polls/views.py


from django.shortcuts import get_object_or_404, render
from django.http import HttpResponseRedirect, HttpResponse
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,)))

功能說(shuō)明:
1.request.POST 是一個(gè)類字典對(duì)象,讓你可以通過(guò)關(guān)鍵字的名字獲取提交的數(shù)據(jù)某宪。 這個(gè)例子中仿村, request.POST['choice'] 以字符串形式返回選擇的 Choice 的 ID。 request.POST 的值永遠(yuǎn)是字符串兴喂。

2.Django 還以同樣的方式提供 request.GET 用于訪問(wèn) GET 數(shù)據(jù) —— 但我們?cè)诖a中顯式地使用 request.POST 蔼囊,以保證數(shù)據(jù)只能通過(guò) POST 調(diào)用改動(dòng)。

3.如果在 request.POST['choice'] 數(shù)據(jù)中沒(méi)有提供 choice 衣迷, POST 將引發(fā)一個(gè) KeyError 畏鼓。上面的代碼檢查 KeyError ,如果沒(méi)有給出 choice 將重新顯示 Question 表單和一個(gè)錯(cuò)誤信息壶谒。

4.在增加 Choice 的得票數(shù)之后云矫,代碼返回一個(gè) HttpResponseRedirect 而不是常用的 HttpResponse。HttpResponseRedirect 只接收一個(gè)參數(shù):用戶將要被重定向的 URL佃迄。

5.在這個(gè)例子中泼差,我們?cè)?HttpResponseRedirect 的構(gòu)造函數(shù)中使用 reverse() 函數(shù)。這個(gè)函數(shù)避免了我們?cè)谝晥D函數(shù)中硬編碼 URL呵俏。它需要我們給出我們想要跳轉(zhuǎn)的視圖的名字和該視圖所對(duì)應(yīng)的 URL 模式中需要給該視圖提供的參數(shù)。

在本例中滔灶,使用之前設(shè)定的 URLconf普碎, reverse() 調(diào)用將返回一個(gè)這樣的字符串:

'/polls/3/results/'

其中 3 是 question.id 的值。重定向的 URL 將調(diào)用 'results' 視圖來(lái)顯示最終的頁(yè)面录平。

當(dāng)有人對(duì) Question 進(jìn)行投票后麻车, vote() 視圖將請(qǐng)求重定向到 Question 的結(jié)果界面缀皱。我們需要編寫這個(gè)視圖:

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})

創(chuàng)建一個(gè) 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>

現(xiàn)在,在瀏覽器中訪問(wèn) /polls/1/ 然后為 Question 投票动猬。應(yīng)該看到一個(gè)投票結(jié)果頁(yè)面啤斗,并且在每次投票之后都會(huì)更新。 如果提交時(shí)沒(méi)有選擇任何 Choice赁咙,應(yīng)該看到相關(guān)對(duì)的錯(cuò)誤提示钮莲。
以下就是投票成功,和沒(méi)有選擇時(shí)直接投票的兩個(gè)不同結(jié)果:

image.png
image.png

注意事項(xiàng):
我們的 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步鉴。

通用視圖:代碼越精簡(jiǎn)越好

detail() (在上篇中開(kāi)發(fā)過(guò))和 results() 視圖都很簡(jiǎn)單 —— 并且,像上面提到的那樣璃哟,存在冗余問(wèn)題氛琢。用來(lái)顯示一個(gè)投票列表的 index() 視圖(也在上一篇中)和它們類似。

這些視圖反映基本的 Web 開(kāi)發(fā)中的一個(gè)常見(jiàn)情況:根據(jù) URL 中的參數(shù)從數(shù)據(jù)庫(kù)中獲取數(shù)據(jù)随闪、載入模板文件然后返回渲染后的模板阳似。 由于這種情況特別常見(jiàn),Django 提供一種快捷方式铐伴,叫做“通用視圖”系統(tǒng)撮奏。

通用視圖將常見(jiàn)的模式抽象化,可以使你在編寫應(yīng)用時(shí)甚至不需要編寫Python代碼当宴。

讓我們將我們的投票應(yīng)用轉(zhuǎn)換成使用通用視圖系統(tǒng)畜吊,這樣我們可以刪除許多我們的代碼。我們僅僅需要做以下幾步來(lái)完成轉(zhuǎn)換户矢,我們將:

1.轉(zhuǎn)換 URLconf玲献。
2.刪除一些舊的、不再需要的視圖。
3.基于 Django 的通用視圖引入新的視圖捌年。

改良 URLconf

首先修改polls/urls.py 這個(gè) URLconf:

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'),
]

注意瓢娜,第二個(gè)和第三個(gè)匹配準(zhǔn)則中,路徑字符串中匹配模式的名稱已經(jīng)由 <question_id> 改為 <pk>礼预。

改良視圖

接下來(lái)眠砾,我們將刪除舊的 index, detail, 和 results 視圖,并用 Django 的通用視圖代替托酸。打開(kāi) polls/views.py 文件褒颈,并將它修改成:

polls/views.py


from django.shortcuts import get_object_or_404, render
from django.http import HttpResponseRedirect
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):
    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,)))

功能概述:

這里我們使用了兩個(gè)通用視圖:ListView 和 DtailView。這兩個(gè)視圖分別表示“顯示一個(gè)對(duì)象列表”和“顯示一個(gè)特定類型的詳細(xì)信息頁(yè)面”获高。
1.每個(gè)通用視圖需要知道它將作用于哪個(gè)模型哈肖。 這由 model 屬性提供。
2.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 使用你想使用的變量名吟孙。

接下來(lái)就可以直接測(cè)試一下我們的投票應(yīng)用的效果了澜倦,效果圖可以參考上面的兩張效果圖聚蝶。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末杰妓,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子碘勉,更是在濱河造成了極大的恐慌巷挥,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,729評(píng)論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件验靡,死亡現(xiàn)場(chǎng)離奇詭異倍宾,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)胜嗓,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,226評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門高职,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人辞州,你說(shuō)我怎么就攤上這事怔锌。” “怎么了变过?”我有些...
    開(kāi)封第一講書人閱讀 169,461評(píng)論 0 362
  • 文/不壞的土叔 我叫張陵埃元,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我媚狰,道長(zhǎng)岛杀,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書人閱讀 60,135評(píng)論 1 300
  • 正文 為了忘掉前任崭孤,我火速辦了婚禮类嗤,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘辨宠。我一直安慰自己遗锣,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,130評(píng)論 6 398
  • 文/花漫 我一把揭開(kāi)白布彭羹。 她就那樣靜靜地躺著黄伊,像睡著了一般。 火紅的嫁衣襯著肌膚如雪派殷。 梳的紋絲不亂的頭發(fā)上还最,一...
    開(kāi)封第一講書人閱讀 52,736評(píng)論 1 312
  • 那天,我揣著相機(jī)與錄音毡惜,去河邊找鬼拓轻。 笑死,一個(gè)胖子當(dāng)著我的面吹牛经伙,可吹牛的內(nèi)容都是我干的扶叉。 我是一名探鬼主播勿锅,決...
    沈念sama閱讀 41,179評(píng)論 3 422
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼枣氧!你這毒婦竟也來(lái)了溢十?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書人閱讀 40,124評(píng)論 0 277
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤达吞,失蹤者是張志新(化名)和其女友劉穎张弛,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體酪劫,經(jīng)...
    沈念sama閱讀 46,657評(píng)論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡吞鸭,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,723評(píng)論 3 342
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了覆糟。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片刻剥。...
    茶點(diǎn)故事閱讀 40,872評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖滩字,靈堂內(nèi)的尸體忽然破棺而出造虏,到底是詐尸還是另有隱情,我是刑警寧澤踢械,帶...
    沈念sama閱讀 36,533評(píng)論 5 351
  • 正文 年R本政府宣布酗电,位于F島的核電站,受9級(jí)特大地震影響内列,放射性物質(zhì)發(fā)生泄漏撵术。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,213評(píng)論 3 336
  • 文/蒙蒙 一话瞧、第九天 我趴在偏房一處隱蔽的房頂上張望嫩与。 院中可真熱鬧,春花似錦交排、人聲如沸划滋。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 32,700評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)处坪。三九已至,卻和暖如春架专,著一層夾襖步出監(jiān)牢的瞬間同窘,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 33,819評(píng)論 1 274
  • 我被黑心中介騙來(lái)泰國(guó)打工部脚, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留想邦,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 49,304評(píng)論 3 379
  • 正文 我出身青樓委刘,卻偏偏與公主長(zhǎng)得像丧没,于是被迫代替她去往敵國(guó)和親鹰椒。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,876評(píng)論 2 361

推薦閱讀更多精彩內(nèi)容