Hello World 之Django(下)

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,)))

这样,一个更加简单的投票应用完成了。

posted @ 2011-04-05 16:27  yinzixin  阅读(1515)  评论(0编辑  收藏  举报