接上一篇Django入門-從0開始編寫一個(gè)投票網(wǎng)站(一)
開始筆記的part3-4。
part3
- 添加更多的頁面,這些頁面有一些不一樣,在
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)
- 把這些頁面跟polls.urls鏈接起來:
在瀏覽器輸入from django.conf.urls import url from . import views urlpatterns = [ url(r'^$', views.index, name='index'), url(r'^(?P<question_id>[0-9]+)/$', views.detail, name='detail'), url(r'^(?P<question_id>[0-9]+)/results/$', views.results, name='results'), url(r'^(?P<question_id>[0-9]+)/vote/$', views.vote, name='vote'), ]'polls/12'、'polls/12/results/'、'polls/12/vote/'可以查看結(jié)果。這里的過程是這樣的:
有人用'polls/12/'請(qǐng)求你的網(wǎng)站時(shí),django會(huì)根據(jù)settings里的ROOT_URLCONF的值mysite.urls去加載mysite/urls.py模塊,在這個(gè)模塊里如果發(fā)現(xiàn)urlpatterns變量那么會(huì)按順序傳入去匹配正則。當(dāng)匹配'^polls/'時(shí),匹配成功會(huì)去掉polls/,把剩下的12/發(fā)送到include('polls.urls')里指定的polls/urls來進(jìn)一步處理。在polls/urls中,12會(huì)匹配r'^(?P<question_id>[0-9]+)/$',然后會(huì)這樣調(diào)用detail()方法:
這里的detail(request=<HttpRrquest object>, question_id='12')question_id='12'來自于(?P<question_id>[0-9]+),使用小括號(hào)會(huì)把括號(hào)里面正則匹配出來的結(jié)果“捕捉”起來做為方法的參數(shù)。?P<question_id>定義了匹配出來的結(jié)果的name,[0-9]+就是一般的匹配一串?dāng)?shù)字的正則
- 寫一些實(shí)際做事情的view
django里每一個(gè)view都要做這樣一件事:返回HttpResponse對(duì)象或者拋出異常。就像這樣polls/views.py:
這里有個(gè)問題,頁面的樣式在這里是硬編碼,如果要改變頁面樣式就要改這里的代碼,按照一般的需求根本不可能這樣,所以要把頁面和代碼分開。這里可以使用django的模版系統(tǒng)。from django.http import HttpResponse from .models import Question def index(request): latest_qeustion_list = Question.objects.order_by('-pub_date')[:5] output = ','.join([q.question_text for q in latest_qeustion_list]) return HttpResponse(output)- 首先在
polls目錄下創(chuàng)建一個(gè)templates目錄。settings.py里的TEMPLATES描述了django加載和渲染模版的方法:有個(gè)叫DjangoTemplates的按照約定會(huì)在每一個(gè)INSTALLED_APPS下尋找templates文件夾。 - 在
templates目錄下再創(chuàng)建一個(gè)polls文件夾,在這個(gè)polls目錄下創(chuàng)建一個(gè)index.html文件,換句話說,這個(gè)html文件的路徑是這樣的:polls/templates/polls/index.html。
這個(gè)怪怪的二不像是模版語法,下面會(huì)講到,先抄著。{% 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 %}
然后在views里使用這個(gè)html:
在瀏覽器訪問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))'/polls/'就是這樣:
tempalte_index.png - 首先在
- 簡(jiǎn)便函數(shù)
render()。
上面的index函數(shù)可以用render()簡(jiǎn)寫成這樣: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)
- 拋404錯(cuò)誤。
在templates下新建一個(gè)detail.html:
在<p>{{ question.question_text}}</p>views.py里
這個(gè)拋404也有簡(jiǎn)便函數(shù)from django.http import Http404 def detail(request, question_id): try: question = Question.objects.get(pk=question_id) exception Question.DoesNotExist: raise Http404('Question does not exist') return render(request, 'polls/detail.html', {'question': question})get_object_or_404(),第一個(gè)參數(shù)是模型的class,其它的是任意數(shù)量的關(guān)鍵字參數(shù):
還有一個(gè)from django.shorcuts import get_object_or_404, render def detail(request, question_id): question = get_object_or_404(Question, pk=question_id) return render(request, 'polls/detail.html', {'question': question})get_list_or_404(),如果list為空就返回404。
- 使用模版系統(tǒng)。
先改寫一下detail.html:
django的模版系統(tǒng)使用<h1>{{ question.question_text }}<h1> <ul> {% for choice in question.choice_set.all %} <li>{{ choice.choice_text }}</li> {% endfor %} </ul>.去查找。在上面的例子里,{{question.question_text}}先當(dāng)成字典查找key,失敗了就當(dāng)對(duì)象查找屬性,這里成功了。如果還失敗,那就當(dāng)成list查找索引。
模版語法里{{}}里面包的是變量,{%%}是塊標(biāo)簽。上面的for循環(huán)里,question.choice_set.all會(huì)被解釋成question.choice_set.all(),返回的是可迭代對(duì)象。
- 移除templates里的硬編碼。
在polls/index.html里
中<li><a href="/polls/{{question.id}}">{{question.question_text}}</a></li><a>標(biāo)簽的屬性里有硬編碼,如果以后工程有大量的view的地址需要更改那會(huì)比較麻煩,所以更好的方式是這樣寫:
模版標(biāo)簽會(huì)在<li><a href="{% url 'detail' question_id %}">{{question.question_text}}</a></li>polls/urls里定義的URL去找name是detail的url()并傳入使用。這樣的話如果要更改view的url,比如改成polls/specifics/12,那么只要在polls/urls.py里這么改:url(r'^specifics/(?P<question_id>[0-9]+)/$', views.detail, name='detail')
- URL的命名空間
真實(shí)的django應(yīng)用會(huì)有好幾個(gè)app而不是像現(xiàn)在這樣只有一個(gè)polls,所以為了使django更好的區(qū)分不同應(yīng)用中可能出現(xiàn)的名字相同的頁面,就需要在urls.py里增加命名空間,像這樣:
增加命名空間以后,from django.conf.urls import url from . import views app_name = 'polls' urlpatterns = [ ... ]{% url %}就要改一下:<li><a href="{% url 'polls:detail' question.id %}">{{question.question_text}}</a></li>
part 4
- 編寫簡(jiǎn)單的表單。在
detail.html里,加入<form>元素
在這里,每一個(gè)選項(xiàng)有一個(gè)單選按鈕,選擇選項(xiàng)提交表單以后會(huì)發(fā)送post請(qǐng)求到<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.choice_text }}"/> <label for="choice{{ forloop.counter }}">{{ choice.choice_text }}</label><br /> {% endfor %} <input type="submit" value="Vote"/> </form>{% url 'polls: vote' question.id%},經(jīng)過urlpatterns匹配后調(diào)用views里的vote函數(shù)。
這里<label>的for表示綁定到哪個(gè)表單元素,for屬性的值就設(shè)置成這個(gè)元素的id。
forloop.counter的值是到目前為止循環(huán)了幾次。
在使用post請(qǐng)求時(shí),django為了防止跨域偽造請(qǐng)求(cross site request forgeries, XSRF),提供了{% csrf_toekn% }標(biāo)簽。
創(chuàng)建一個(gè)view函數(shù)(就是上面提到的vote函數(shù))來處理提交的數(shù)據(jù)。首先修改一下views.py里的vote():
分析一波: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): return render(request, 'polls/detail.html', {'question': question, 'error_message': "You didn't select a choice."}) else: selected_choice.votes += 1 selected_choice.save() return HttpResponseRedirect(reverse('polls: results', args=[question.id, ]))request.POST有點(diǎn)像字典,可以通過key存取數(shù)據(jù),并且永遠(yuǎn)返回字符串,這里request.POST['choice']返回choice的id的字符串。
如果POST的數(shù)據(jù)里choice為空那么拋出KeyError的錯(cuò)誤。
返回的HttpResponseRedirect函數(shù)只接收一個(gè)參數(shù):將要訪問的url。
所有情況下,post請(qǐng)求都要返回HttpResponseRedirect對(duì)象,防止用戶點(diǎn)擊返回造成2次提交。
reverse()函數(shù)避免了url的硬編碼,這里傳的參數(shù)是一個(gè)name,參考views.py里定義的;一個(gè)是這個(gè)url里的可變部分,對(duì)于這里就是question.id。
投票以后,vote()會(huì)跳轉(zhuǎn)到結(jié)果頁,在polls/views.py里
創(chuàng)建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})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>{xx|xx}是過濾器寫法,pluralize表示前面的列表或數(shù)字不是1時(shí),返回s,否則返回空字符串(因?yàn)槲覀兿M@示的時(shí)候,1 vote,0 votes這樣比較規(guī)范的單復(fù)數(shù)形式)。
- 使用
generic view來減少代碼量
到目前我們的views.py里的detail()、results()、index()都比較簡(jiǎn)單,而且歸納起來都在做這樣一件事:通過傳過來的url去數(shù)據(jù)庫撈數(shù)據(jù)---->加載模版---->返回渲染好的模版,對(duì)于這種比較單一普通的情況,django提供了一個(gè)叫做generic views的系統(tǒng)。
使用它大致分為以下3步:- 轉(zhuǎn)化URLconf。
- 刪除無用的views。
- 引入基于
generic system的新views
下面開始
- 改進(jìn)URLconf,在
polls/urls.py里
注意下這里的detail跟results的from django.conf.urls import url from . import views app_name = 'polls' urlpatterns = [ url(r'^$', views.IndexView.as_view(), name="index"), url(r'^(?P<pk>[0-9]+)/$', views.DetailView.as_view(), name="detail"), url(r'^(?P<pk>[0-9]+)/results/$', views.ResultsView.as_view(), name="results"), url(r'^(?P<question_id>[0-9]+)/vote/$', views.vote, name="vote"), ]question_id被改成了pk。
- 改進(jìn)views。這里要移除老的
detail()、results()、index(),使用通用的views。在views.py里:
這里使用了2個(gè)generic view:ListView和DetailView,一個(gè)用來展示列表,一個(gè)用來詳細(xì)描述對(duì)象,每一個(gè)通用的view需要給model屬性賦值一個(gè)它將要起作用的模型class,這里是... from django.views import generic class Index IndexView(generic.ListView): model = Question template_name = 'polls/index.html' context_object_name='latest_question_list' def get_queryset(self): return Question.objects.order('-pub_date')[:5] class DetailView(generic.View): model = Question template_name = 'polls/detail.html' class ResultsView(generic.View): model = Question template_name = 'polls/results.html' ... # vote() 不用改Question。
DetailView希望從url捕捉下來的id的值的名字叫做pk,所以u(píng)rlpatterns里的question_id要改成pk。
DetailView默認(rèn)使用一個(gè)這樣格式名字的模版:<app name>/<model name>_detail.html,對(duì)應(yīng)到我們的例子里就是polls/question_detail.html,然而我們已經(jīng)有寫好的模版了,所以要替換掉默認(rèn)的,方法就是給template_name賦值我們希望它渲染的模版。
ListView也一樣會(huì)使用默認(rèn)的,所以也要改。
前面的教程里,我們給模版提供了一個(gè)context對(duì)象,里面包裝了question或者latest_question_list。對(duì)于DetailView,question對(duì)象是自動(dòng)提供的。因?yàn)镼uestion是一個(gè)django模型,django可以為context指定合適的key的name;但是對(duì)于ListView,django會(huì)指定的name在這里叫做question_list,然而我們的index模版里的叫做latest_question_list,所以就要通過給context_object_name賦值來手動(dòng)指定。
訪問/polls/看看。
戳這里查看下面的教程:Django入門-從0開始編寫一個(gè)投票網(wǎng)站(三):part5-6
