Walk by faith code, hack, curious

编写第一个Django程序(3)

接着我们的第二部分,我们继续.下面我们将要把注意力转移到创建视图部分.

原理:

在Django的程序中,一个视图就同属一类的网页.它提供特定的功能,拥有特定的模板.例如,一个博客程序你可能有以下一个视图:

  1. 首页,显示最近添加的文章.
  2. 文章的详细页面
  3. 基于年份的文章归档
  4. 基于月份的文章归档
  5. 基于天的文章归档
  6. 添加评论的动作-处理文章的评论

在我们的民意测试投票的程序中,我们需要有以下一些视图:

  1. index页.显示最近的民意测试投票项目
  2. 民意测试投票项目的详细页面,一个投票的所有问题,没有答案,只给吃一个投票的表单
  3. 投票的结果页
  4. 投票的动作,给特定的投票添加投票

在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)~