版權聲明:您好,轉載請留下本人部落格的位址,謝謝 https://blog.csdn.net/hongbochen1223/article/details/50329533
我們接着上一小節的末尾開始學習,在上一個小節中,我們主要是了解了Django中的模闆,即templates的使用。在這個小節中,我們主要關注于簡單的表單處理并且裁剪我們的代碼。
編寫一個簡單的表單
我們來更新一下我們的detail模闆(“polls/detail.html”),添加HTML的
<form>
元素。 <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>
簡單描述一下:
- 上面的模闆為每一個question展示了一組單選按鈕。每一個按鈕的值(value)是相關問題的選項ID。每一個按鈕的名稱是”choice”。也就是意味着,當使用者選擇其中一個選項框并且送出表單的時候,将會發送post資料
choice=#
,其中#就是對應的選擇的選項的ID。這個是HTML表單的基本概念。
- 我們設定表單的action為
{% url 'polls:vote' question.id %}
,并且我們設定
method="post"
。使用post方式(和使用get方式對應)是非常重要的,因為送出表單的行為将會改變服務端的資料。無論你什麼時候建立能夠修改服務端資料的表單的時候,一定要使用
methon="post"
。這個技巧不是Django專有的。這個僅僅就是好的網頁開發技巧。
- forloop.counter标示for标簽在這個循環中出現的次數。
- 既然我們建立了一個POST表單(該表單可能會影響修改資料),我們就需要去擔心通過站點請求的僞裝。幸運的是,我們不必太過擔心,因為Django提供了一個簡單易用的系統來阻止他。簡單來說,目标是内部URLs的所有的POST表單都應該使用
{% crsf_token %}
模闆标簽。
現在,我們來建立處理送出資料的視圖。在上一個小節中,我們建立了包含下面這一行的URLconf。
polls/urls.py
url(r'^(?P<question_id>\d+)/vote/$',views.vote,name='vote'),
我們也創造了一個vote()函數的臨時實作。現在我們來真正使用起來。向polls/views.py中添加下面的代碼:
# -*- coding:utf-8 -*-
from django.http import HttpResponse,HttpResponseRedirect
from django.shortcuts import get_object_or_404,render
from django.core.urlresolvers import reverse
from polls.models import Question
....
def vote(request,question_id):
p = get_object_or_404(Question,pk=question_id)
try:
selected_choice = p.choice_set.get(pk=request.POST['choice'])
except (KeyError, Choice.DoesNotExit):
return render(request,'polls/detail.html',{
'question':p,
'errot_message':"You did not select a choice."
})
else:
selected_choice.votes += 1
selected_choice.save()
# 每次成功處理POST資料之後一定要傳回HttpResponseRedirect。
# 這個可以避免二次送出
return HttpResponseRedirect(reverse('polls/results',args=(p.id)))
該代碼包含下面幾個事情:
- request.POST是一個類似于字典的對象,能夠使我們通過鍵名稱去通路送出的資料。在這個例子中,
傳回的是選中的選項的ID,這裡是字元串。request.POST['choice']
request.POST
值一直是字元串。
注意,Django也提供了
去通路GET資料的方式,但是對于request.GET
來說隻有在代碼中才能擷取到相應的資料,這個大家可以去了解get方式請求和post方式請求的差別。request.POST
- 如果在POST資料中沒有提供choice選項,那麼
将會抛出KeyError異常。上面的代碼檢查KeyError異常,如果沒有提供choice的話,重新顯示帶有錯誤資訊的question表單。request.POST['choice']
- 在增加了選項數量之後,代碼傳回一個HttpResponseRedirect而不是一個正常的HttpResponse。HttpResponseRedirect使用一個單一的參數:也就是使用者将要重定向的url位址。
- 在這個例子的HttpResponseRedirect結構中我們使用reverse()函數。這個函數幫助我們避免發生視圖函數中寫死一個URL位址。他被給予我們想要傳遞到的視圖名稱和指向視圖的URL正則中的變量部分。在這個例子中,使用我們在上一小節中設定的URLconf,最終reverse()調用将會傳回類似于下面的字元串:
,其中的3是'/polls/3/results'
的值。這個重定向URL然後調用’results’視圖去展示最終的頁面。p.id
在使用者對相應問題投票之後,vote()視圖将會重定向到這個問題的results頁面。現在我們來重寫這個頁面:
def results(request, question_id):
question = get_object_or_404(Question, pk=question_id)
return render(request, 'polls/results.html', {'question': question})
下面我們來添加results模闆:
建立一個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>
現在,我們在浏覽器中進入/polls/1/中去為相應的問題去投票。每次投票後你都能看到相應的結果更新。如果你沒有選擇選項而點選送出的話,你将會看到錯誤資訊。
下面來看一下我的運作結果:
使用通用視圖:減少代碼更好
detail()和results()視圖是非常簡單的 - 就像是上面提到的,太備援了。展示投票清單的index()視圖也是一樣的。
這些視圖代表着基本Web開發的通用例子:通過URL傳來的參數從資料庫擷取資料,加載模闆并且傳回一個渲染模闆。因為這些都是雷同的,是以Django提供了一個快捷方式,成為”通用視圖”系統。
通用試圖系統抽象除了通用正則,這使得我們可能不需要編寫Python代碼也能構件一個應用。
下面讓我們的應用使用通用視圖,是以我們可以删除一大些代碼了。我們僅僅需要幾步就能做出轉換。
- 轉換URLconf
- 删除一些舊的,不需要的視圖
- 基于Django的通用視圖引入新的視圖
修改URLconf
首先,打開polls/urls.py并且像下面這樣修改:
from django.conf.urls import patterns,url
from polls import views
urlpatterns = patterns('',
# ex: /polls/
url(r'^$', views.IndexView.as_view(),name='index'),
# ex: /polls/5/
url(r'^(?P<pk>\d+)/$',views.DetailView.as_view(),name='detail'),
# ex: /polls/5/results
url(r'^(?P<pk>\d+)/results/$',views.ResultsView.as_view(),name='results'),
# ex: /polls/5/vote/
url(r'^(?P<question_id>\d+)/vote/$',views.vote,name='vote'),
)
注意,正則比對中間的
<question_id>
改成了
<pk>
。
修改視圖
下一步,我們将要移除我們舊的index,detail和results視圖,然後使用Django的通用視圖。打開polls/view.py檔案,并且像下面這樣修改:
# -*- coding:utf-8 -*-
from django.http import HttpResponse,HttpResponseRedirect
from django.shortcuts import get_object_or_404,render
from django.core.urlresolvers import reverse
from django.views import generic
from polls.models import 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):
# 和原來一樣
我們使用了兩個通用視圖:ListView和DetailView。這兩個視圖抽象除了“顯示對象清單”和“現象特定對象詳細資訊”的概念。
- 每一個通用視圖都需要知道他們操作的模型是什麼。這個通過
屬性提供model
-
通用視圖需要從URL中擷取稱為”pk”的主鍵,這就是為什麼剛剛在urls.py中修改DetailView
為question_id
的原因了。pk
預設情況下,DetailView通用視圖使用稱為
<app name>/<model name>_detail.html
作為模闆。在我們的例子中,他會使用模闆”polls/question_detail.html”。屬性
template_name
被用來告訴Django使用能夠哪一個模闆代替預設模闆。我們也為results指定了template_name。
相似的,ListView通用視圖使用預設的模闆,稱為
<app name>/<model name>_list.html
,我們同樣為他指定了我們自己的模闆。
在之前的小節中,模闆已經被提供了context,也就是上下文,該上下文包含question和latest_question_list變量。對于DetailView,question已經被自動提供了。我們使用Django的模型(Question),是以Django能夠決定為上下文變量使用一個近似的名稱。然而,對于ListView,自動生成的上下文變量是question_list。為了重寫,我們可以使用
context_object_name
屬性,指定我們想要使用的
latest_question_list
。這樣的話,通過Django通用視圖提供的屬性,我們就可以指定我們想使用的變量了。
下面我們來運作一下,看一下效果:
運作效果和上面的。