接上一篇Django入門-從0開始編寫一個投票網(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/'
可以查看結果同云。這里的過程是這樣的:
有人用'polls/12/'
請求你的網(wǎng)站時糖权,django會根據(jù)settings里的ROOT_URLCONF
的值mysite.urls
去加載mysite/urls.py
模塊,在這個模塊里如果發(fā)現(xiàn)urlpatterns
變量那么會按順序傳入去匹配正則炸站。當匹配'^polls/'
時星澳,匹配成功會去掉polls/
,把剩下的12/
發(fā)送到include('polls.urls')
里指定的polls/urls
來進一步處理旱易。在polls/urls
中禁偎,12
會匹配r'^(?P<question_id>[0-9]+)/$'
盒至,然后會這樣調用detail()
方法:
這里的detail(request=<HttpRrquest object>, question_id='12')
question_id='12'
來自于(?P<question_id>[0-9]+)
李命,使用小括號會把括號里面正則匹配出來的結果“捕捉”起來做為方法的參數(shù)。?P<question_id>
定義了匹配出來的結果的name,[0-9]+
就是一般的匹配一串數(shù)字的正則
- 寫一些實際做事情的view
django里每一個view都要做這樣一件事:返回HttpResponse對象或者拋出異常笆制。就像這樣polls/views.py
:
這里有個問題匆篓,頁面的樣式在這里是硬編碼,如果要改變頁面樣式就要改這里的代碼,按照一般的需求根本不可能這樣熟掂,所以要把頁面和代碼分開誉券。這里可以使用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)建一個templates
目錄。settings.py
里的TEMPLATES
描述了django加載和渲染模版的方法:有個叫DjangoTemplates
的按照約定會在每一個INSTALLED_APPS
下尋找templates
文件夾。 - 在
templates
目錄下再創(chuàng)建一個polls
文件夾,在這個polls
目錄下創(chuàng)建一個index.html
文件,換句話說陵像,這個html文件的路徑是這樣的:polls/templates/polls/index.html
。
這個怪怪的二不像是模版語法,下面會講到,先抄著。{% 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里使用這個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 - 首先在
- 簡便函數(shù)
render()
锨用。
上面的index函數(shù)可以用render()
簡寫成這樣: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錯誤掌栅。
在templates
下新建一個detail.html
:
在<p>{{ question.question_text}}</p>
views.py
里
這個拋404也有簡便函數(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()
,第一個參數(shù)是模型的class铅搓,其它的是任意數(shù)量的關鍵字參數(shù):
還有一個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}}
先當成字典查找key,失敗了就當對象查找屬性伙菊,這里成功了。如果還失敗,那就當成list查找索引癌淮。
模版語法里{{}}
里面包的是變量夕膀,{%%}
是塊標簽。上面的for循環(huán)里擦囊,question.choice_set.all
會被解釋成question.choice_set.all()
买鸽,返回的是可迭代對象看幼。
- 移除templates里的硬編碼棚唆。
在polls/index.html
里
中<li><a href="/polls/{{question.id}}">{{question.question_text}}</a></li>
<a>
標簽的屬性里有硬編碼,如果以后工程有大量的view的地址需要更改那會比較麻煩,所以更好的方式是這樣寫:
模版標簽會在<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的命名空間
真實的django應用會有好幾個app而不是像現(xiàn)在這樣只有一個polls骏掀,所以為了使django更好的區(qū)分不同應用中可能出現(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
- 編寫簡單的表單穷劈。在
detail.html
里,加入<form>
元素
在這里片酝,每一個選項有一個單選按鈕囚衔,選擇選項提交表單以后會發(fā)送post請求到<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匹配后調用views里的vote函數(shù)雕沿。
這里<label>
的for表示綁定到哪個表單元素练湿,for屬性的值就設置成這個元素的id。
forloop.counter
的值是到目前為止循環(huán)了幾次审轮。
在使用post請求時肥哎,django為了防止跨域偽造請求(cross site request forgeries, XSRF),提供了{% csrf_toekn% }
標簽疾渣。
創(chuàng)建一個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
有點像字典,可以通過key存取數(shù)據(jù)榴捡,并且永遠返回字符串杈女,這里request.POST['choice']
返回choice的id的字符串。
如果POST的數(shù)據(jù)里choice為空那么拋出KeyError
的錯誤吊圾。
返回的HttpResponseRedirect函數(shù)只接收一個參數(shù):將要訪問的url达椰。
所有情況下,post請求都要返回HttpResponseRedirect對象项乒,防止用戶點擊返回造成2次提交啰劲。
reverse()
函數(shù)避免了url的硬編碼,這里傳的參數(shù)是一個name檀何,參考views.py
里定義的蝇裤;一個是這個url里的可變部分廷支,對于這里就是question.id。
投票以后栓辜,vote()
會跳轉到結果頁恋拍,在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時啃憎,返回s
芝囤,否則返回空字符串(因為我們希望顯示的時候似炎,1 vote辛萍,0 votes這樣比較規(guī)范的單復數(shù)形式)。
- 使用
generic view
來減少代碼量
到目前我們的views.py
里的detail()
羡藐、results()
贩毕、index()
都比較簡單,而且歸納起來都在做這樣一件事:通過傳過來的url去數(shù)據(jù)庫撈數(shù)據(jù)---->加載模版---->返回渲染好的模版仆嗦,對于這種比較單一普通的情況辉阶,django提供了一個叫做generic views
的系統(tǒng)。
使用它大致分為以下3步:- 轉化URLconf瘩扼。
- 刪除無用的views谆甜。
- 引入基于
generic system
的新views
下面開始
- 改進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
集绰。
- 改進views规辱。這里要移除老的
detail()
、results()
栽燕、index()
罕袋,使用通用的views。在views.py
里:
這里使用了2個generic view:ListView和DetailView碍岔,一個用來展示列表浴讯,一個用來詳細描述對象,每一個通用的view需要給model屬性賦值一個它將要起作用的模型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
,所以urlpatterns里的question_id
要改成pk
捏肢。
DetailView默認使用一個這樣格式名字的模版:<app name>/<model name>_detail.html奈籽,對應到我們的例子里就是polls/question_detail.html
,然而我們已經(jīng)有寫好的模版了猛计,所以要替換掉默認的唠摹,方法就是給template_name
賦值我們希望它渲染的模版。
ListView也一樣會使用默認的奉瘤,所以也要改勾拉。
前面的教程里煮甥,我們給模版提供了一個context對象,里面包裝了question或者latest_question_list藕赞。對于DetailView成肘,question對象是自動提供的。因為Question是一個django模型斧蜕,django可以為context指定合適的key的name双霍;但是對于ListView,django會指定的name在這里叫做question_list
批销,然而我們的index模版里的叫做latest_question_list
洒闸,所以就要通過給context_object_name
賦值來手動指定。
訪問/polls/
看看均芽。
戳這里查看下面的教程:Django入門-從0開始編寫一個投票網(wǎng)站(三):part5-6