自己动手写Django app,第三部分【完】

来源:互联网 发布:程序员都有什么app 编辑:程序博客网 时间:2024/06/06 17:59

原文地址:https://docs.djangoproject.com/en/1.4/intro/tutorial03/

这个教程是从教程2留下的地方开始的。我们继续这个基于网络调查应用程序并且将关注与创建一个公共接口——“view”。

一、哲学

一个视图是你Django应用程序中(一种特别的功能和一个特别的模板)“一种”网页。比如说,在一个网络博客应用程序中,你可能有下面的视图:

    博客页面——显示最近一些记录;

    进入“细节”页面——单个记录的永久链接;

    基于年存档的页面——显示给定年份带有记录的所有月;

    基于月存档的页面——显示给定月份带有记录的所有日;

    基于日存档的页面——显示给定日期的所有记录;

    评论动作——处理给定记录的留言;

在我们的调查应用程序中,我们有下面的四个视图:

    调查“索引”页面——显示最近的一些调查;

    调查“细节”页面——显示一个没有结果但是带有投票的表单的调查问题;

    调查“结果”页面——显示某一个调查的结果;

    投票动作——处理某一个调查的某一个投票;

Django中,每个视图由一个简单的python函数表示。

二、设计你的URLs

写视图的第一步是设计你的URL结构。你通过创建一个叫URLconf的python模块来实现它。URLconfs是Django把给定的URL和一个给定的python代码联系起来。

当一个用户要求一个Django支持的页面,系统查看ROOT_URLCONF设置,它包含了一个python点缀语法的字符串。Django载入那个模块然后查找一个叫urlpatterns的模块层变量,它是下列各式的一列元组:

1(regular expression, Python callback function [, optional dictionary])
Django从第一个正则表达式开始一直往下搜索,然后一直把请求的URL和每条正则表达式比较,直到找到和他匹配的一个。

当你找到匹配的哪个是,Django调用一个带有HttpResponse对象作为第一个参数的python回调函数,正则表达式中任何“捕获”的值都作为关键字参数,并且任意关键字参数都可以来自字典(一个元组中可选的第三个参数)。

关于更多关于HttpResponse对象的信息,请查看请求和响应对象。更多关于URLconfs,请查看URL分配器。

当你在教程1开头的地方运行django-admin.pu startproject mysite,系统在mysite/urls.py中创建了一个默认的URLconf。它也自动把你的ROOT_URLCONF设置(在setting.py中)指到那个文件:

1ROOT_URLCONF = 'mysite.urls'
是时候来个例子了。编辑mysite/urls.py让它看起来这样:
01from django.conf.urls import patterns, include, url
02 
03from django.contrib import admin
04admin.autodiscover()
05 
06urlpatterns = patterns('',
07    url(r'^polls/$''polls.views.index'),
08    url(r'^polls/(?P<poll_id>\d+)/$''polls.views.detail'),
09    url(r'^polls/(?P<poll_id>\d+)/results/$''polls.views.results'),
10    url(r'^polls/(?P<poll_id>\d+)/vote/$''polls.views.vote'),
11    url(r'^admin/', include(admin.site.urls)),
12)
这是一个有价值的回顾。当有人从你的网站请求一个页面——比如说,“/poll/23/”,Django会载入这个python模块,因为它被指向了ROOT_URLCONF设置。它找到变量名叫urlpatterns然后按顺序穿过正则表达式。当它找打了一个匹配的正则表达式——r'^poll/(?P<poll_id>\d+)/$'——它从polls/views.py中调用一个detail()函数。最后,它像这样调用detail()函数:
1detail(request=<HttpRequest object>, poll_id='23')
poll_id='23'部分来自(?P<poll_id>\d+)。在一个模式周围用括号来“捕获”和模式匹配的文本,然后把它作为一个参数传递给视图函数。?P<poll_id>定义了被用来识别匹配模式的名字,\d+是匹配一列数字的正则表达式。

因为URL模式是一个正则表达式,你对它们做的一切都没有限制。因此这里没有必要增加像.php这样的URL令人讨厌的东西——除非你有一个与众不同的幽默感,这种情况下,你可以这样做:

1(r'^polls/latest\.php$''polls.views.index'),
但是,现在不需要这样做,这看起来真的很蠢。

注意这些正则表达式不搜索GET和POST参数,或者域名。比如说,一个请求:http://www.example.com/myapp/,URLconf将会查找myapp/,在一个请求:http://example.com/myapp/?page=3,URLconf将会查找myapp/。

如果在正则表达式方面需要帮助,请查看维基百科的介绍和re模块的文档。同时,Jeffrey Friedl写的O'Reilly出版的书“掌握正则表达式”也非常精彩。

最后,一个性能注意:这些正则表达式是在你URLconf模块第一次载入的时候编译的,它们的速度是非常快的。

三、写你自己的视图

不过到目前为止我们还没有创建自己的视图——我们只是有URLconf。但是我们让Django来合适的遵循URLconf。

启动Django开发网络服务器:python manage.py runserver。

现在在你的浏览器中浏览“http://localhost:8000/polls/”。你应该看到下面带有鲜艳颜色错误的页面:

1ViewDoesNotExist at /polls/
2 
3Could not import polls.views.index. View does not exist in module polls.views.
这个错误出现的原因是你还没有写polls/views.py的index函数。

尝试“/polls/23/”,“/polls/23/results/”和“/polls/23/vote/”都一样的,错误消息告诉你Django没有找到视图(查找视图失败,因为你还没有写任何视图)。

显示是时候写第一个视图了。打开polls/views.py文件然后输入下面的python代码:

1from django.http import HttpResponse
2 
3def index(request):
4    return HttpResponse("Hello, world. You're at the poll index.")
这可能是最简单的视图。在你的浏览器去看看“/polls/”你的文本。

现在让我们增加一些更多的视图。这些视图有一点不同,因为他们带有一个参数(记住,这是从URLconf中正则表达式捕获的传递进来的):

1def detail(request, poll_id):
2    return HttpResponse("You're looking at poll %s." % poll_id)
3 
4def results(request, poll_id):
5    return HttpResponse("You're looking at the results of poll %s." % poll_id)
6 
7def vote(request, poll_id):
8    return HttpResponse("You're voting on poll %s." % poll_id)

在你的浏览器中查看“/polls/34/”,它运行detail()方法然手显示你在URL中提供的ID。尝试“/polls/34/results/”和“/polls/34/vote/”也是一样的——它们显示结果和投票页面的占的地方。

四、写一个实际能做一些事情的视图

每个视图都做两件事情中的一件:返回一个包含请求页面内容的HttpResponse对象,或者产生一个错误比如说Http404。剩下的就取决于你。

你的视图可以从数据库读取记录,或者不可以。它可以用一个模板系统比如说Django自带的或者第三方python模板系统,或者不可以。他用你想用的python库可以生成一个PDF文件,输出XML,创建一个动态的ZIP文件,你想的一切都可以。

所有的Django期待的是一个HttpResponse或者是一个溢出。

因为这很方便,让我们用Django自己的数据库API,这个我们在教程1中已经涉及到了。这儿是index()视图的一个尝试,它显示系统中按照公开时间的最近五个用逗号分开的调查问题:

1from polls.models import Poll
2from django.http import HttpResponse
3 
4def index(request):
5    latest_poll_list = Poll.objects.all().order_by('-pub_date')[:5]
6    output = ', '.join([p.question for in latest_poll_list])
7    return HttpResponse(output)
但是这里有一个问题:视图中页面的设计硬编码的。如果你想改变页面的外观,你必须编辑这个python代码。因此让我们用Django的模板系统来设计从python中分离出来:
01from django.template import Context, loader
02from polls.models import Poll
03from django.http import HttpResponse
04 
05def index(request):
06    latest_poll_list = Poll.objects.all().order_by('-pub_date')[:5]
07    = loader.get_template('polls/index.html')
08    = Context({
09        'latest_poll_list': latest_poll_list,
10    })
11    return HttpResponse(t.render(c))
这些代码载入一个叫“polls/index.html”的模板然后把它传递给上下文。上下文是一个把模板变量名字映射到python对象的字典。

重新载入这个页面,现在你会看到一个错误:

1TemplateDoesNotExist at /polls/
2polls/index.html
啊哈,到目前为止还没有模板。首先在你系统的某个Django可以接触到的地方创建一个目录(不管你运行的是什么服务器,Django都运行一样的)。但是不要把它放在你的根文档下面。为安全期间,你不能让他们公开。然后编辑settings.py中的TEMPLATE_DIRS来高速Django在哪可以找到模板——就像在教程2“低昂之管理页面的外观和感觉”的部分。

当你已经做完了这些,在你的模板目录下创建一个polls目录。在这个目录里面,创建一个叫index.html。注意我们的loader.get_template('polls/index.html')代码从上面映射到系统的“[template_directory]/polls/index.html”。

把下面的代码放到模板里:

1{% if latest_poll_list %}
2    <ul>
3    {% for poll in latest_poll_list %}
4        <li><a href="/polls/{{ poll.id }}/">{{ poll.question }}</a></li>
5    {% endfor %}
6    </ul>
7{% else %}
8    <p>No polls are available.</p>
9{% endif %}
在浏览器中载入页面,你可以看到一个包含来自教程1的“What's up”调查的布告栏列表的容器。这个链接指向调查的详细页面。

五、一个快捷方式:render_to_response()

用提供模板的结果载入一个模板,填写上下文,返回一个HttpResponse对象,这些都是很常见的习语。Django提供一个快捷方式。这是我们的全部重写的index()的视图:

1from django.shortcuts import render_to_response
2from polls.models import Poll
3 
4def index(request):
5    latest_poll_list = Poll.objects.all().order_by('-pub_date')[:5]
6    return render_to_response('polls/index.html', {'latest_poll_list': latest_poll_list})
注意我们一旦在所有视图中这样做时候,我们就不再需要载入loader,Context和HttpResponse。

render_to_response()函数带有一个模板名作为它的第一个参数和一个作为第二个可选参数的字典。它返回一个提供给定上下文的给定模板的HttpResponse对象。

六、产生404错误

现在,我们来处理调查细节的视图——显示给定调查的问题。下面是视图:

1from django.http import Http404
2# ...
3def detail(request, poll_id):
4    try:
5        = Poll.objects.get(pk=poll_id)
6    except Poll.DoesNotExist:
7        raise Http404
8    return render_to_response('polls/detail.html', {'poll': p})
这里新的内容:如果你请求一个不存的ID,视图产生Http404溢出。

我们后面讨论你可以在polls/detail.html放什么,但是如果你想快点让例子运行起来,只要:

1{{ poll }}
就能正式运行了。

七、一个快捷方式:get_object_or_404()

用get(),视图不存在时产生一个Http404错误时很常见的习语。Django提供一个快捷方式。下面是重写的detail()视图:

1from django.shortcuts import render_to_response, get_object_or_404
2# ...
3def detail(request, poll_id):
4    = get_object_or_404(Poll, pk=poll_id)
5    return render_to_response('polls/detail.html', {'poll': p})
get_object_or_404()函数把Django的模型作为它的第一个参数和一个任意数量的关键字参数,这些参数是传到模块的get()函数。如果视图不存在就产生一个Http404错误。

(哲学:为什么我们用一个get_object_or_404()帮助函数而不是在更高的层次自动捕获ObjectDoesNotExit溢出或者让模型的API产生Http404而不是ObjectDoesNotExit?因为那样做会耦合模型层和视图层。Django的一个最重要的设计就是实现松耦合。)

这里有一个get_list_or_404()函数,他就像和get_object_or_404()一样工作——除了使用filter()而不是get()。当列表是空的时候就产生Http404错误。

八、写一个404(页面没找到)视图

当你从一个视图产生Http404错误的时候,Django会载入一个致力于处理404错误的特殊视图。它通过在你的根URLconf(只在你的根URLconf下,不管在其他什么地方设置handler404都无效)查找变量handler404,它是一个python点缀语法的字符串——一般URLconf回调函数用相同的格式。一个404视图本身没有什么特别的:它只是一个普通的视图。

一般来说你不需要烦恼写404视图。如果你没有设置handler404,默认情况下使用内建的django.views.defaults.page_not_found()。这种情况下,你任然有一件事要做:在根模板目录创建一个404.html模板。默认的404视图将会调用适用于所有404错误的模板。如果DEBUG被设置成False(在你的设置文件中),并且你没有创建一个404.html文件,就会产生一个Http500。因此记住创建一个404.html。

关于404视图耦合更多的东西:

    如果DEBUG被设置成True(在你的设置文件中),你的404视图将永远不会被用到(因此404.html模板永不被提供)因为此时显示的是错误的追溯。

    404视图被称为Django在检查URLconf中正则表达式是没有找到匹配。

九、写一个500(服务器错误)视图

类似地,你的根URLconf可以定义一个handler500,防止服务器错误它指向一个视图。当你视图代码有错误时,服务器错误也可能出现。

十、使用模板系统

回到我们调查应用程序的detail()视图。鉴于上下文变量poll,下面是我们“polls/detail.html”模板可能看起来的样子:


1<h1>{{ poll.question }}</h1>
2<ul>
3{% for choice in poll.choice_set.all %}
4    <li>{{ choice.choice }}</li>
5{% endfor %}
6</ul>
模板系统用点查看语法来接触变量的属性。在{{ poll.queston }}的例子中,第一个Django在poll对象中查找字典。如果失败的话,它尝试属性查找——在这种情况下,它有效的。如果属性查找也失败了,它将尝试列表索引查找。


在{% for %}循环中会发生方法调用:poll.choice_set.all被翻译成python代码poll.choice_set.all(),它返回Choice对象的迭代并且在{% for %}标签中适合使用。更多关于模板的消息请查看模板指导。

十一、简化URLconfs

花点时间来处理视图和模板系统。就像你编辑URLconf一样,你可能注意到这里有点冗余:


1urlpatterns = patterns('',
2    url(r'^polls/$''polls.views.index'),
3    url(r'^polls/(?P<poll_id>\d+)/$''polls.views.detail'),
4    url(r'^polls/(?P<poll_id>\d+)/results/$''polls.views.results'),
5    url(r'^polls/(?P<poll_id>\d+)/vote/$''polls.views.vote'),
6)
也就是说,每个回调都在polls.views中。


因为这是一个常见的情况,URLconf框架为常见的前缀提供一个快捷方式。你可以分析出常见的前缀并且增加它们作为patterns()的第一个参数,就像下面:


1urlpatterns = patterns('polls.views',
2    url(r'^polls/$''index'),
3    url(r'^polls/(?P<poll_id>\d+)/$''detail'),
4    url(r'^polls/(?P<poll_id>\d+)/results/$''results'),
5    url(r'^polls/(?P<poll_id>\d+)/vote/$''vote'),
6)
他在功能上可之前的格式是完全一样的,它只是做了点整理。


既然你通常不想一个程序的前缀应用到URLconf的每个回调中,你可以链接多个patterns()。你mysite/urls.py的全部现在应该看起来是这样的:


01from django.conf.urls import patterns, include, url
02 
03from django.contrib import admin
04admin.autodiscover()
05 
06urlpatterns = patterns('polls.views',
07    url(r'^polls/$''index'),
08    url(r'^polls/(?P<poll_id>\d+)/$''detail'),
09    url(r'^polls/(?P<poll_id>\d+)/results/$''results'),
10    url(r'^polls/(?P<poll_id>\d+)/vote/$''vote'),
11)
12 
13urlpatterns += patterns('',
14    url(r'^admin/', include(admin.site.urls)),
15)
十二、URLconfs的去耦合


我们学习他的时候应该花点时间把我们Django项目配置的调查程序URL去耦合。Django的程序是插件式的——也就是说,每个特别地程序应该可以用最小的大惊小怪应用到另一个Django的安装。

多亏了python manage.py startapp创建的严格目录结构,我们的投票程序在这个时候是相当去耦合化的。但是它的一部分是和Django设置耦合的:URLconf。

我们已经在mysite/urls.py里编辑,但是一个程序的URL设计只针对一个应用程序,而不是Django的安装——因此让我们在应用程序目录里把URLs删掉。

把mysite/urls.py拷贝到polls/urls.py,然后改变mysite/urls.py去删除针对调查程序的URLs然后插入一个include(),让它是这样的:


1from django.conf.urls import patterns, include, url
2 
3from django.contrib import admin
4admin.autodiscover()
5 
6urlpatterns = patterns('',
7    url(r'^polls/', include('polls.urls')),
8    url(r'^admin/', include(admin.site.urls)),
9)
include()只是简单的参照另外一个URLconf。注意正则表达式没有$(匹配结束字符)但是有一个斜杠。当Django面对include()时,它砍掉在这个点上匹配的URL然后把剩下的字符串传给包含的URLconf进行更多的处理。


当一个用户访问系统的“/polls/34/”会发生下面的事情:

    Django会找到一个匹配的'^polls/';

    然后Django会去掉匹配的文本(“polls/”)然后把剩下的文本——“34”——传递给'polls.urls'URLconf进行更多的处理;

既然我们已经去耦合了,我们需要删除每行开头的“polls/”,同时也要删除注册管理站点的那一行。你的poll/urls.py文件现在应该看起来是这样的:

1from django.conf.urls import patterns, url
2 
3urlpatterns = patterns('polls.views',
4    url(r'^$''index'),
5    url(r'^(?P<poll_id>\d+)/$''detail'),
6    url(r'^(?P<poll_id>\d+)/results/$''results'),
7    url(r'^(?P<poll_id>\d+)/vote/$''vote'),
8)


include()后面的想法和URLconf去耦合是为了是让它容易变成插件和运行的URLs。 调查调查已经在URLconf中,他们可以被放置在“/polls/”下,或者“/fun_polls/”下,或者“/content/polls/”下,或者任何根路径,程序同样能工作。

所有的调查程序关心的是相对路径,而不是绝对路径。

当你对写视图感到合适的话,读教程4学习更多关于处理通用视图。

教程3结束!