Django采用MVC的结构。其中的View通常来实现一定功能,并且有一个模板。在Django中,每一个view用一个函数来表示。例如在我们接下来要实现的例子中,将有4个view:index页面,显示最新的几个Poll,Poll detail页面,结果页面,和投票页面。
在Django中,编写view的第一步是配置URL结构,通过URLconf模块来实现页面到python代码的映射。当接到一个HTTP请求的时候,django首先检查ROOT_URLCONF 的配置,这是一个模块的名字,django会在这个模块中寻找urlpatterns这个变量,urlpatterns由一组tuple组成,每个tuple的内容是:
(regular expression, Python callback function [, optional dictionary])
含义是:符合第一个正则式的url将会调用第二参数中的python函数,第一个参数是一个HttpRequest对象,其他参数在最后的dictionary中。在我们的例子中,django在settings.py中自动生成了如下配置:
ROOT_URLCONF = 'mysite.urls'
在mystie目录下有个urls.py,其中有urlpatterns变量:
urlpatterns = patterns('', # Example: # (r'^mysite/', include('mysite.foo.urls')), # Uncomment the admin/doc line below to enable admin documentation: # (r'^admin/doc/', include('django.contrib.admindocs.urls')), # Uncomment the next line to enable the admin: (r'^admin/', include(admin.site.urls)), )
按照提示,改成我们需要的:
urlpatterns = patterns('', (r'^polls/$','polls.views.index'), (r'^polls/(?P<poll_id>\d+)/$','polls.views.detail'), (r'^polls/(?P<poll_id>\d+)/results/$','polls.views.results') (r'^polls/(?P<poll_id>\d+)/vote/$','polls.views.vote') (r'^admin/', include(admin.site.urls)), )
django会依次检查每个正则表达式,然后调用相应的方法。例如,如果有个请求/polls/23/,第三条规则导致的函数调用如下:
detail(request=<HttpRequest object>, poll_id='23')
注意,这些正则表达式忽略GET参数,也就是url中?之后的内容。
接下来开始实现view,在view.py中定义如下方法:
from django.http import HttpResponse def index(request): return HttpResponse("Hello world. You're at the poll index")
启动服务器,在浏览器中访问http://127.0.0.1:8000/polls/ 就可以看到上面那句Hello world。说明配置的url映射已经起效。为了生成一个完整的html,最好采用模板的形式,目前还没有模板,在polls目录中新建一个index.html,内容如下:
{% if pollList %} <ul > {% for poll in pollList %} <li > <a href="/polls/{{poll.id}}/" >{{poll.question}} </a > </li > {% endfor%} </ul > {% else %} <p >No polls </p > {% endif %}
模板文件的语法和其他类型的脚本语言很相似。为了让dajango找到模板文件,需要一点配置,在settings.py中有个TEMPLATE_DIR变量,将模板文件的位置指定下。下面看如何在python中用数据填充这个模板。在views.py中写入如下代码:
from django.template import Context, loader from polls.models import Poll from django.http import HttpResponse def index(request): pollList=Poll.objects.all().order_by('-pub_date')[:5] t=loader.get_template('polls/index.html') c=Context({'pollList':pollList}) return HttpResponse(t.render(c))
通过loader加载模板,通过Context将数据赋给模板,这样以后,再访问/polls/就可以看到:
下面再介绍下URL配置的简化和优化,urlpatterns的第一个参数是正则表达式的公共部分,例如这个例子中的url.py可以改为:
urlpatterns = patterns('polls.views', (r'^polls/$', 'index'), (r'^polls/(?P\d+)/$', 'detail'), (r'^polls/(?P \d+)/results/$', 'results'), (r'^polls/(?P \d+)/vote/$', 'vote'), ) urlpatterns += patterns('', (r'^admin/', include(admin.site.urls)), )
python中的app的url映射规则还可以独立开来,可以在polls下新建一个urls.py,其中描述了polls的映射规则:
from django.conf.urls.defaults import * urlpatterns = patterns('polls.views', (r'^$', 'index'), (r'^(?P\d+)/$', 'detail'), (r'^(?P \d+)/results/$', 'results'), )
在mysite目录下的urls.py中,使用include来把这个文件包括进来:
from django.conf.urls.defaults import * from django.contrib import admin admin.autodiscover() urlpatterns = patterns('', (r'^polls/',include('polls.urls')), (r'^admin/', include(admin.site.urls)), )
对于使用include的元组,系统找到匹配其正则式的部分,然后把剩余部分转交给include进来的文件中的元组处理。这样可以使得每个app的url映射规则中没有全局url的部分,更加方便复用。
接下来来完成Poll的详细页面。类似的,我们需要一个template文件如下:
<h1 >{{poll.question}} </h1 > {% if error_message %} <p >{{ error_message }} </p > {% endif %} <form action="/polls/{{poll.id}}/vote" method="post" > {% csrf_token %} {% for choice in poll.choice_set.all %} <input type="radio" name="choice" id="choice{{forloop.counter}}" value="{{choice.id}}"/ > <label for="choice{{forloop.counter}}" >{{choice.choice}} </label > <br/ > {% endfor %} <input type="submit" value="vote"/ > </form >
这个template文件很简单,就是根据一个Poll,显示它的choice。再实现它的view中对应的方法,和index页面是类似的,不过这里采用一种更加简单的写法,为此需要引入:
from django.shortcuts import *
方法如下:
def detail(request, poll_id): p=get_object_or_404(Poll,pk=poll_id) return render_to_response('polls/detail.html',{'poll':p})
get_object_or_404方法可以获得一个对象,如果没有找到,则导到404页面。这样就可以访问detail页面了。但是vote按钮还没实现,现在来实现vote的代码:
def vote(request,poll_id): p=get_object_or_404(Poll,pk=poll_id) try: selected=p.choice_set.get(pk=request.POST['choice']) except (KeyError,Choice.DoesNotExist): return render_to_response('polls/detail.html',{'poll':p, 'error_message':"Please Choose one option."}, context_instance=RequestContext(request)) else: selected.votes+=1 selected.save() return HttpResponseRedirect(reverse('polls.views.results',args=(p.id,)))
利用request.POST对象可以获得post参数。添加了vote方法之后,点击vote按钮会报错,这是由于django的安全机制造成的,在details页面中的form内,有一个{% csrf_token %},是用来防止跨站请求伪造的,这是一个和sessionID有关的值,需要通过request对象来获得,通常,在template页面中无法得到request对象,为此,需要将detail页面的改为:
def detail(request, poll_id): p=get_object_or_404(Poll,pk=poll_id) return render_to_response('polls/detail.html',{'poll':p},context_instance=RequestContext(request))
RequestContext位于django.template模块中。csrf_token的具体原理参考RequestContext
vote方法的最后,是将页面重定向到结果显示页面。其中reverse 方法可以避免直接拼接url。最后完成results的模板和方法:
<h1 >{{poll.question}} </h1 > <ul > {% for choice in poll.choice_set.all %} <li >{{choice.choice}}--{{choice.votes}} vote{{choice.votes|pluralize}} </li > {% endfor %} </ul > <a href="/polls/{{poll.id}}/" >Vote again? </a >
results方法:
def results(request,poll_id): p=get_object_or_404(Poll,pk=poll_id) return render_to_response('polls/results.html',{'poll':p})
至此一个基本的投票系统已经完成,点击vote,可以看到如下页面:
注意到results和detail这两个view非常简单,而且重复。Django有一种Generic View来完成这些简单重复的工作,修改polls/urls.py如下:
from django.conf.urls.defaults import * from django.views.generic import DetailView, ListView from polls.models import Poll urlpatterns = patterns('', (r'^$', ListView.as_view(queryset=Poll.objects.order_by('-pub_date')[:5], context_object_name='pollList', template_name='polls/index.html')), (r'^(?P\d+)/$', DetailView.as_view(model=Poll,template_name='polls/detail.html')), url(r'^(?P \d+)/results/$', DetailView.as_view(model=Poll,template_name='polls/results.html'),name='poll_results'), (r'^(?P \d+)/vote/$', 'polls.views.vote'),
在这里使用了DetailView和ListView两个通用视图。
PS:这两个视图是在Django 1.3中才引入的,以前的版本写法有些不同。要升级Django要将老版本删掉,方法就是将python/lib/site-packages目录中django相关的内容直接删除,然后再安装新版本。
这两个View的使用非常简单,只需要把数据源和模板赋给他就可以了。注意到第三条规则中给url取了一个名字poll_results,这是为了给Vote方法中的reverse方法使用的,把vote中的最后一行代码改为:
return HttpResponseRedirect(reverse('poll_results',args=(p.id,)))
这样,一个更加简单的投票应用完成了。