编写第一个Django程序(3)
接着我们的第二部分,我们继续.下面我们将要把注意力转移到创建视图部分.
原理:
在Django的程序中,一个视图就同属一类的网页.它提供特定的功能,拥有特定的模板.例如,一个博客程序你可能有以下一个视图:
- 首页,显示最近添加的文章.
- 文章的详细页面
- 基于年份的文章归档
- 基于月份的文章归档
- 基于天的文章归档
- 添加评论的动作-处理文章的评论
在我们的民意测试投票的程序中,我们需要有以下一些视图:
- index页.显示最近的民意测试投票项目
- 民意测试投票项目的详细页面,一个投票的所有问题,没有答案,只给吃一个投票的表单
- 投票的结果页
- 投票的动作,给特定的投票添加投票
在Django里,每一个视图都由一个简单Python函数来处理
设计你的URLs
开始编写视图前的第一个步应该是设计你的URL结构,你可以通过创建Python 的模块来完成.这个模块就是URLconf.URLconf就是Django用来完成具体请求和Python代码对应的功能.
当一个用户请求有Django构建的网站时,系统会首先去根目录下的settings.py文件里查看ROOT_URLCONF配置.它的值就是一个string,用Python的点语法写的.得到这个值后Django就会去指定的模块文件里查找对应的Python函数.
当在教程的第一部分中建立mysite这个网站的时候,就已经在根目录下创建了一个urls.py这个文件,它也同时被指向到了settings.py文件中的ROOT_URLCONF.
下面我们修改mysite/urls.py文件:
from django.conf.urls import patterns, include, url from django.contrib import admin admin.autodiscover() urlpatterns = patterns('', url(r'^polls/$', 'polls.views.index'), url(r'^polls/(?P<poll_id>\d+)/$', 'polls.views.detail'), url(r'^polls/(?P<poll_id>\d+)/results/$', 'polls.views.results'), url(r'^polls/(?P<poll_id>\d+)/vote/$', 'polls.views.vote'), url(r'^admin/', include(admin.site.urls)), )
这段代码需要我们来重新阅读一遍,例如当一个请求”/polls/23/”来访问网站时,Django就会加载urls.py,因为在settings.py文件里的ROOT_URLCONF就指向它,然后就会寻找到urlpatterns变量,它是一个序列,然后就会挨个的解析它,当找到与请求对应的url(r’^polls/(?P<poll_id>\d+)/$’)时,就会映射到polls目录下的views.py文件里的detail()函数.相当于最后一个http请求直接请求了这个函数.
detail(request=<HttpRequest object>, poll_id=’23’)
这个poll_id=’23’部分来自于(?P<poll_id>\d+).使用括号来包围这个样式.?P<poll_id>定义了名称用来鉴别对应的模型. \d+是一个正则表达式用来匹配参数.(例如数字).
因为url是用正则表达式写的,所有就没有任何显示对于你来说.当然也没有必要跟着文件的后缀名.例如.php.当然你想写也行:
(r'^polls/latest\.php$', 'polls.views.index')
但是还是别这样做,很傻.
注意的是这些正则不会搜索GET和POST的参数,或者域名.例如这样一个请求:http://www.example.com/myapp/ . URLconf就会对应到myapp/ .http://www.example.com/myapp/?page=3这样的请求还是会对应到myapp/ .其实就是说它的模式完全是REST风格的,而不是传统的.querystring是不起作用的!
如果需要关于正则表达式的知识请阅读Wikipedia's entry.还有关于re模块的文档最后,性能的考虑,这些正则会在URLconf模块第一个加载的时候被编译,他们相当的快!
开始写你的第一个视图:
好的,到目前为止我们还没有写过一个视图.我们只是有个URLconf文件.我们来确认Django已经加载了它.启动server:
python manage.py runserver
我们输入这个地址: “http://localhost:8000/polls/”,接着你会看见这个错误:
ViewDoesNotExist at /polls/ Tried index in module polls.views. Error was: 'module' object has no attribute 'index'
出这个错因为是你没有在polls/views.py里写index()函数.你还可以试试 “/polls/23/”, “/polls/23/results/” and “/polls/23/vote/”. 这些,同样Django尝试去寻找对于的函数,但是失败了,那是因为我们还没有给views.py中添加任何代码. 是的,是时候来在views.py里添加点代码了:
from django.http import HttpResponse def index(request): return HttpResponse("Hello, world. You're at the poll index.")
输入/polls/这可能是你见过的最简单的页面了.让我们来多创建几个视图吧,下面的可能有点不同,因为这些请求带了点参数.(都是从哪些URLconf里的正则表达式来的)
def detail(request, poll_id): return HttpResponse("You're looking at poll %s." % poll_id) def results(request, poll_id): return HttpResponse("You're looking at the results of poll %s." % poll_id) def vote(request, poll_id): return HttpResponse("You're voting on poll %s." % poll_id)
下面你在地址里输入,它将会调用detail()函数,会在页面上显示你输入的ID.试试“/polls/34/results/”和 “/polls/34/vote/” .这些会显示投票页面.
接下来让我们的views做点实事吧。
每一个视图基本上会做1到2件事情。返回http请求对应的http响应。或者就是返回一个异常,比如说404.
你的视图能够去数据库读取数据,然后可以使用Django的模板系统,或者Python的第三方模板。设置都可以不用,它能够生成PDF文件,输入XML文件,创建一个zip压缩包等等。简单说就是你能够做任何事情,使用你想要的任何Python库。
Django想要的就是一个Http响应或者就是一个异常。
我们将用Django自己的数据库API。因为它很方便使用,我们在教程的第一个部分中提到过。下面的代码是取了最近的5条民意测试。每条由逗号分隔,按照发布时间排序:
from polls.models import Poll from django.http import HttpResponse def index(request): latest_poll_list = Poll.objects.all().order_by('-pub_date')[:5] output = ', '.join([p.question for p in latest_poll_list]) return HttpResponse(output)
但是问题就来了,给页面返回的内容是我们硬编码的,如果我们想要改变一下页面的外观,还要到这个文件里重新编辑。这个时候我们就需要使用模板文件了。
from django.template import Context, loader from polls.models import Poll from django.http import HttpResponse def index(request): latest_poll_list = Poll.objects.all().order_by('-pub_date')[:5] t = loader.get_template('polls/index.html') c = Context({ 'latest_poll_list': latest_poll_list, }) return HttpResponse(t.render(c))
上面的代码中提到了一个名叫“polls/index.html”模板文件。然后把内容值给了这个页面。然后你你就可以去浏览器看看了。不过可惜的是会报错的:
TemplateDoesNotExist at /polls/ polls/index.html
为什么呢?原来我们还没有配置模板呢。怎么做呢?首先创建一个目录。位置随意,只要Django可以访问到。安全起见,别让他们给公共了。然后,编辑你的工程mysite目录下的settings.py文件。找到TEMPLATE_DIRS,然后把它指向到你刚才创建的那个模板地址.这就是告诉Django到哪里去找模板.完成之后在模板目录下新建一个polls目录.然后创建一个index.html文件。这个时候可以知道它是怎么工作的了。其实就是
loader.get_template('polls/index.html')==》"[template_directory]/polls/index.html"
然后在index.html写这些代码:
{% if latest_poll_list %} <ul> {% for poll in latest_poll_list %} <li><a href="/polls/{{ poll.id }}/">{{ poll.question }}</a></li> {% endfor %} </ul> {% else %} <p>No polls are available.</p> {% endif %}
到这里就重新去请求刚才的那个路径吧:http://localhost:8000/polls/ 就将会看见一个列表显示的民意测试结果。每一个都链接到了详细页面。
简写:render_to_response()
请求数据,然后加载模板,最后把数据返回给模板,模板做展示。这可能是最常见的想法了。同时Django还提供了一个这个过程的一个简写。那就是render_to_response()方法。看代码:
from django.shortcuts import render_to_response from polls.models import Poll def index(request): latest_poll_list = Poll.objects.all().order_by('-pub_date')[:5] return render_to_response('polls/index.html', {'latest_poll_list': latest_poll_list})
这里要注意一点就是一旦我们这样写了之后就不需要再引用loader, Context 和 HttpResponse.这个render_to_response()方法的第一个参数就是模板的地址,第二个可选的参数就是要返回的结果字典。
404页面
现在,我们来编写投票的详细页面,这个页面会显示民意调查的几个问题:
from django.http import Http404 # ... def detail(request, poll_id): try: p = Poll.objects.get(pk=poll_id) except Poll.DoesNotExist: raise Http404 return render_to_response('polls/detail.html', {'poll': p})
这个我们要解决的就是要查询的ID不存在的情况,会返回一个404的页面。我们写在polls目录下新建一个detail.html页面。待会我们再说模板怎么写,你先简单的写一个{{ poll }} 去浏览器试试看吧。
对于刚才的那个try catch块同样有一个简写就是get_object_or_404()这样一个函数。可以这样写:
from django.shortcuts import render_to_response, get_object_or_404 # ... 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() 帮助方法来替代自动捕捉异常的ObjectDoesNotExist的方式呢?或者使用对象api的Http404来替代ObjectDoesNotExist呢?
因为我们又是在一个页面视图上会返回很多的对象。为了能够解耦合,我们就这样设计了。
同样还有一个get_list_or_404()的方法。和get——object_or_404()是一样的工作原理。
使用模板系统
回到刚才的详细页面的视图文件,下面是我们修改的detail.html文件:
<h1>{{ poll.question }}</h1> <ul> {% for choice in poll.choice_set.all %} <li>{{ choice.choice }}</li> {% endfor %} </ul>
在模板系统里我们使用点操作符来访问对象的属性,在例子中,我们能够通过 {{ poll.question }}访问question属性,首先Django会去查找对象poll的字典,如果失败就会去找属性。在这个例子中,方法的调用是在{% %}代码块中。有一个for循环。 poll.choice_set.all代表的就是Python代码中的 poll.choice_set.all()。它将返回一个可以遍历的选项集合。
简化我们的URLconf配置
在我们折腾视图和模板系统的时候,你会标记URLconf文件,你可能注意到了有那么一点的重复内容存在:
urlpatterns = patterns('', url(r'^polls/$', 'polls.views.index'), url(r'^polls/(?P<poll_id>\d+)/$', 'polls.views.detail'), url(r'^polls/(?P<poll_id>\d+)/results/$', 'polls.views.results'), url(r'^polls/(?P<poll_id>\d+)/vote/$', 'polls.views.vote'), )
在什么的路径中,每次请求都会包含polls.views路径。为了解决这个常见的问题,URLconf框架提供了一个共通的前缀,你可以把它提出来让在patterns()里的第一个参数位置:
urlpatterns = patterns('', url(r'^polls/$', 'polls.views.index'), url(r'^polls/(?P<poll_id>\d+)/$', 'polls.views.detail'), url(r'^polls/(?P<poll_id>\d+)/results/$', 'polls.views.results'), url(r'^polls/(?P<poll_id>\d+)/vote/$', 'polls.views.vote'), )
也行你不想给应用的每一个url都添加上前缀,这个时候就可以把他们分开写。然后再合在一起:
from django.conf.urls.defaults import patterns, include, url from django.contrib import admin admin.autodiscover() urlpatterns = patterns('polls.views', url(r'^polls/$', 'index'), url(r'^polls/(?P<poll_id>\d+)/$', 'detail'), url(r'^polls/(?P<poll_id>\d+)/results/$', 'results'), url(r'^polls/(?P<poll_id>\d+)/vote/$', 'vote'), ) urlpatterns += patterns('', url(r'^admin/', include(admin.site.urls)), )
分解我们的url配置
到这里我们就需要花点时间来分解polls的url我们的url配置了。我们要把polls的工程的url配置从系统的urls.py文件中分离出来。Django的设计初衷就是能够实现持续的可拔插。一个应用应该很容易就迁移到别的Django工程下。
我们把mysite/urls.py拷贝一份到polls目录下。然后编辑它:(这里我们为这个目录下的所有url都加了前缀,所以我们就去掉之前的在每个url中的polls/views)
from django.conf.urls.defaults import patterns, include, url urlpatterns = patterns('polls.views', url(r'^$', 'index'), url(r'^(?P<poll_id>\d+)/$', 'detail'), url(r'^(?P<poll_id>\d+)/results/$', 'results'), url(r'^(?P<poll_id>\d+)/vote/$', 'vote'), )
这里只是polls应用的url设置。
下面我们来修改工程的url配置:(我们通过include()方法来加载polls目录下的url配置文件。)
# This also imports the include function from django.conf.urls.defaults import * from django.contrib import admin admin.autodiscover() urlpatterns = patterns('', url(r'^polls/', include('polls.urls')), url(r'^admin/', include(admin.site.urls)), )
期待下一部分(4)~