Django學習4:form, generic views
Django學習4
在經過前三節的學習後,基本瞭解了資料庫的連線,views的使用,但是還需要了解如何傳回資料並處理,這裡第四節學習的內容就是如何去用form來獲取資料。
step1:熟悉form
將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>
這其中需要解釋的是forloop.counter
是表示當前迴圈進行了幾次。而{% csrf_token %}
是一個Django 提供的template標記用來防止跨站偽造請求。
這之後由於表單提交給了vote頁面,所以去重寫vote的view來達到能夠登記提交的查詢的目的。
題外話,這裡可以探討一下id和name的作用,可以參考 stackoverflow the difference between id and name。
在vote這個view中就要實現:接收資訊,查詢資訊,更新資訊,顯示結果頁面,程式碼如下:
from django.shortcuts import render, get_object_or_404
# Create your views here.
from django.http import HttpResponse, HttpResponseRedirect
from .models import Question
from django.template import loader
from django.http import Http404
from django.urls import reverse
def vote(request, question_id):
#request直接接收資訊
question = get_object_or_404(Question, pk=question_id)
try:
#用get來查詢是否存在choice,並且raise自己定義的error_message
select_choice = question.choice_set.get(pk=request.POST['choice'])
except(KeyError, Choice.DoesNotExist):
return render(request, 'polls/detail.html',
{'question':question,
'error_message':"You didn't select a choice.",})
else:
#更新資料並且重定向到result頁面
select_choice.vote += 1
select_choice.save()
return HttpResponseRedirect(
reverse('polls:results', args=(question.id,))
)
上述程式碼中有一些需要解釋的程式碼:
request.post
是一個字典型的資料,可以通過key name來訪問傳入的資料,需要注意的是其中的資料皆為string型別request.post['choice']
如果沒有在傳入資料中有有效值的情況下,會raise一個keyerror錯誤,當出現這個錯誤的時候,本程式碼的處理方式就是重新返回detail.html並給出錯誤資訊。- 當處理完成post資料後,一般都會使用
HttpResponseRedirect()
來重定位頁面。這裡使用了reserve
這個函式來定向到result這個view並且給出了question_id,也就是說這裡的reserve('polls:result', args=(question.id))
相當於重定向到了"polls/question.id/results"頁面。
重寫一下result view來滿足需求:
def results(request, question_id):
question = get_object_or_404(Question, pk=question_id)
return render(quest, 'polls/results.html', {'question':question})
新增一個result的頁面:
<body>
<h1>{{ question.question_text }}</h1>
<ur>
{% for choice in question.choice_set.all %}
<li>
{{ choice.choice_text }} -- {{ choice.votes }} vote{{ choice.votes|pluralize }}
</li>
{% endfor %}
</ur>
<a href="{ url 'polls:detail' question.id %}">Vote again?</a>
</body>
上述程式碼每次就會顯示每個choice的選擇情況。值得注意學習的是這裡的{{ choice.votes|pluralize }}
是根據votes的情況來加複數"s"。
需要補充的是,這個程式是有問題的,當兩個人同時使用這個vote時,會導致資料的錯誤,可以使用F()函式來避免這一問題,詳情見F() 。
step2:通用view
我們可以看到在detail中和results中的view的程式碼大同小異,此時我們就可以來簡化程式碼,所以我們可以利用Django來做一個通用類的template來簡化程式碼。 我們需要經歷以下步驟:
- 改變urls的配置
- 刪除老的無用程式碼
- 使用新的generic views
那麼首先,需要對polls/urls.py進行修改:
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'),
]
而後修改polls/views.py檔案為:
from django.views import generic
class IndexView(generic.ListView):
template_name = 'polls/index.html'
context_object_name = 'latest_question_list'
def get_queryset(self):
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'
上述程式碼利用了兩個generic view,分別是Listview和DetailView。他們分別用於顯示一個物件的list和顯示一個具體型別的物件的detail頁面。 每一個generic都需要知道它所作用的model,所以提供了model屬性來指明model。 DetailView需要一個叫做pk的主鍵,所以在urls.py中我們使用pk來代替question_id。 在DetailView中,通常會使用"< app bane>/< model name>_detail.html"不過我們這裡用template_name來指定了頁面,同理ListView。 在DetailView中,當我們使用了model:Question,就會自動命名object的名字是question,然後我實驗了ListView好像也是自動提供的,(把question改名程question2就不行了)。同時DetailView還會自動提供context的名字為question_list不過我們可以自己用context_objetc_name來改變名字。