接上一篇Django入门-从0开始编写一个投票网站(一)
开始笔记的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
[0-9]+)/$', views.detail, name='detail'), url(r'^(?P [0-9]+)/results/$', views.results, name='results'), url(r'^(?P [0-9]+)/vote/$', views.vote, name='vote'), ] 'polls/12'
、'polls/12/results/'
、'polls/12/vote/'
可以查看结果。这里的过程是这样的:
有人用'polls/12/'
请求你的网站时,django会根据settings里的ROOT_URLCONF
的值mysite.urls
去加载mysite/urls.py
模块,在这个模块里如果发现urlpatterns
变量那么会按顺序传入去匹配正则。当匹配'^polls/'
时,匹配成功会去掉polls/
,把剩下的12/
发送到include('polls.urls')
里指定的polls/urls
来进一步处理。在polls/urls
中,12
会匹配r'^(?P
,然后会这样调用[0-9]+)/$' detail()
方法:
这里的detail(request=
, question_id='12') question_id='12'
来自于(?P
,使用小括号会把括号里面正则匹配出来的结果“捕捉”起来做为方法的参数。[0-9]+) ?P
定义了匹配出来的结果的name,[0-9]+
就是一般的匹配一串数字的正则
- 写一些实际做事情的view
django里每一个view都要做这样一件事:返回HttpResponse对象或者抛出异常。就像这样polls/views.py
:
这里有个问题,页面的样式在这里是硬编码,如果要改变页面样式就要改这里的代码,按照一般的需求根本不可能这样,所以要把页面和代码分开。这里可以使用django的模版系统。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
目录下创建一个templates
目录。settings.py
里的TEMPLATES
描述了django加载和渲染模版的方法:有个叫DjangoTemplates
的按照约定会在每一个INSTALLED_APPS
下寻找templates
文件夹。 - 在
templates
目录下再创建一个polls
文件夹,在这个polls
目录下创建一个index.html
文件,换句话说,这个html文件的路径是这样的:polls/templates/polls/index.html
。
这个怪怪的二不像是模版语法,下面会讲到,先抄着。{% if latest_question_list %}
-
{% for question in latest_question_list %}
- {{question.question_text}} {% endfor %}
No polls are available.
{% 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/'
就是这样:
- 首先在
- 简便函数
render()
。
上面的index函数可以用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
:
在{{ question.question_text}}
views.py
里
这个抛404也有简便函数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()
,第一个参数是模型的class,其它的是任意数量的关键字参数:
还有一个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。
- 使用模版系统。
先改写一下detail.html
:
django的模版系统使用{{ question.question_text }}
-
{% for choice in question.choice_set.all %}
- {{ choice.choice_text }} {% endfor %}
.
去查找。在上面的例子里,{{question.question_text}}
先当成字典查找key,失败了就当对象查找属性,这里成功了。如果还失败,那就当成list查找索引。
模版语法里{{}}
里面包的是变量,{%%}
是块标签。上面的for循环里,question.choice_set.all
会被解释成question.choice_set.all()
,返回的是可迭代对象。
- 移除templates里的硬编码。
在polls/index.html
里
中- {{question.question_text}}
标签的属性里有硬编码,如果以后工程有大量的view的地址需要更改那会比较麻烦,所以更好的方式是这样写:
模版标签会在- {{question.question_text}}
polls/urls
里定义的URL去找name是detail
的url()
并传入使用。这样的话如果要更改view的url,比如改成polls/specifics/12
,那么只要在polls/urls.py
里这么改:url(r'^specifics/(?P
[0-9]+)/$', views.detail, name='detail')
- URL的命名空间
真实的django应用会有好几个app而不是像现在这样只有一个polls,所以为了使django更好的区分不同应用中可能出现的名字相同的页面,就需要在urls.py
里增加命名空间,像这样:
增加命名空间以后,from django.conf.urls import url from . import views app_name = 'polls' urlpatterns = [ ... ]
{% url %}
就要改一下:- {{question.question_text}}
part 4
- 编写简单的表单。在
detail.html
里,加入元素
在这里,每一个选项有一个单选按钮,选择选项提交表单以后会发送post请求到{{ question.question_text }}
{% if error_message %}{{ error_message }}
{% endif %}{% url 'polls: vote' question.id%}
,经过urlpatterns匹配后调用views里的vote函数。
这里的for表示绑定到哪个表单元素,for属性的值就设置成这个元素的id。
forloop.counter
的值是到目前为止循环了几次。
在使用post请求时,django为了防止跨域伪造请求(cross site request forgeries, XSRF),提供了{% csrf_toekn% }
标签。
创建一个view函数(就是上面提到的vote函数)来处理提交的数据。首先修改一下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存取数据,并且永远返回字符串,这里request.POST['choice']
返回choice的id的字符串。
如果POST的数据里choice为空那么抛出KeyError
的错误。
返回的HttpResponseRedirect函数只接收一个参数:将要访问的url。
所有情况下,post请求都要返回HttpResponseRedirect对象,防止用户点击返回造成2次提交。
reverse()
函数避免了url的硬编码,这里传的参数是一个name,参考views.py
里定义的;一个是这个url里的可变部分,对于这里就是question.id。
投票以后,vote()
会跳转到结果页,在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})
results.html
模版
这里的{{ question.question_text }}
-
{% for choice in question.choice_set.all %}
- {{ choice.choice_text}} -- {{ choice.votes}} vote{{choice.votes|pluralize}} {% endfor %}
{xx|xx}
是过滤器写法,pluralize
表示前面的列表或数字不是1时,返回s
,否则返回空字符串(因为我们希望显示的时候,1 vote,0 votes这样比较规范的单复数形式)。
- 使用
generic view
来减少代码量
到目前我们的views.py
里的detail()
、results()
、index()
都比较简单,而且归纳起来都在做这样一件事:通过传过来的url去数据库捞数据---->加载模版---->返回渲染好的模版,对于这种比较单一普通的情况,django提供了一个叫做generic views
的系统。
使用它大致分为以下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
[0-9]+)/$', views.DetailView.as_view(), name="detail"), url(r'^(?P [0-9]+)/results/$', views.ResultsView.as_view(), name="results"), url(r'^(?P [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默认使用一个这样格式名字的模版:/ _detail.html,对应到我们的例子里就是 polls/question_detail.html
,然而我们已经有写好的模版了,所以要替换掉默认的,方法就是给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开始编写一个投票网站(三):part5-6