被解救的Django【5】

来源:互联网 发布:福建网络电视台投诉 编辑:程序博客网 时间:2024/05/29 02:21

视图与模板

原理:视图(view)Django应用中的一网页,它通常使用一个特定的函数提供服务,并且具有一个特定的模板。

在我们的投票应用中,将有以下四个视图:

1Question首页 ——显示最新发布的几个Question

2Question“详细页面 —— 显示单个Question的具体内容,不显示该议题的当前投票结果,而是提供一个投票的表单。

3Question“结果页面 —— 显示特定的Question的投票结果。

4、投票功能 ——处理对QuestionChoice的投票。

Django中,网页的页面和其他内容都是由视图来传递的(视图对WEB请求进行回应)。每个视图都是由一个简单的Python函数(或者是基于类的视图的方法)表示的。Django通过检查请求的URL(准确地说,是URL里域名之后的那部分)来选择使用哪个视图。

平日你上网时,可能会遇到像“ME2/Sites/dirmod.asp?sid=&type=gen&mod=Core+Pages&gid=A6CD4967199A42D9B65B1B”这样 "优美"URL。你将会愉快地了解到,Django允许我们使用更加优雅的URL模式 URL模式就是一个URL的通用形式,如: /newsarchive/<year>/<month>/Django使用叫做‘URLconfs’的配置来为URL匹配视图。 一个URLconf负责使用正则表达式URL模式匹配到视图。

编写第一个视图

 打开polls/views.py文件

fromdjango.httpimport HttpResponse
defindex(request):
    return HttpResponse("Hello, world. You're at the polls index.")

这可能是Django中最简单的视图。 为了能够调用这个视图,我们需要将这个视图映射到URL上,利用一个URLconf。要在polls目录中创建一个URLconf,创建一个名为urls.py的文件。


polls/urls.py文件中键入如下代码:



下一步,让主URLconf可以链接到polls.urls模块。在mysite/urls.py中插入一个include()



现在我们已经将一个index视图关联到URLconf中。在你的浏览器中浏览 http://localhost:8000/polls/ ,你会看到Hello, world. You’re at the polls index.”, 正如你在index 视图中定义的那样.



url()函数具有四个参数:两个必需的regex view,以及两个可选的kwargsname 到这里,就可以研究下这些参数的含义了:

url() 参数:regex

术语“regex”是一种常用的短格式,意思是正则表达式,它是用于匹配字符串中的模式的语法,或者在这种情况下是url模式。Django从第一个正则表达式开始,依次将请求的URL与每个正则表达式进行匹配,直到找到匹配的那个为止。

请注意,这些正则表达式不会检索URLGETPOST的参数以及域名。 例如,对于http://www.example.com/myapp/请求,URLconf将查找myapp/。对于http://www.example.com/myapp/?page=3请求,URLconf也将查找myapp/

如果你需要正则表达式方面的帮助,参见Wikipedia条目 re 模块的文档。另外,O’Reilly出版Jeffrey Friedl编写的“Mastering Regular Expressions”是一本极好的书。 然而实际上,你不需要成为一个正则表达式的专家,只需要知道如何捕获简单的URL模式就好了。 事实上,复杂的正则表达式的性能并不是太好,所以你并不应该太依赖于正则表达式。

最后,性能方面的一个注意点:这些正则表达式会在URLconf模块第一次载入的时候被编译。 它们超级快,只要这些正则表达式不像上面提醒的那样过于复杂。

url() 参数:view

Django找到一个匹配的正则表达式时,它就会调用view参数指定的视图函数,并将HttpRequest对象作为第一个参数,从正则表达式中捕获的其他值作为其他参数,传入到该视图函数中。如果正则表达式使用简单的捕获方式,值将作为位置参数传递; 如果使用命名的捕获方式,值将作为关键字参数传递。一会儿,我们将给出一个例子。

url() 参数:kwargs

任何关键字参数都可以以字典形式传递给目标视图。 我们在这个教程里不用Django的这个功能。

url() 参数:name

命名你的URL这样就可以在Django的其它地方尤其是模板中,通过名称来明确地引用这个URL 这个强大的特性可以使你仅仅修改一个文件就可以改变全局的URL模式。



编写更多的视图 

现在让我们给polls/views.py添加一些更多的视图。这些视图和之前的略有不同,因为它们另带了一个参数:



通过下面的url() 调用将这些新的视图和polls.urls模块关联起来:


看看你的浏览器,输入“/34/”它将运行detail()方法并显示你在URL中提供的ID 再试一下“/polls/34/results/”“/polls/34/vote/” —— 它们将显示出对应的结果界面和投票界面。





当有人请求你的网站的一个页面时—— 比如“/polls/34/”,Django将加载mysite.urls Python模块,因为ROOT_URLCONF设置指定了它。它寻找名为urlpatterns 的变量并按顺序匹配其中的正则表达式。我们使用的include()函数来只是对其它URLconfs的简单引用。注意include()函数的正则表达式的末尾没有$(字符串结束符号)而是一个斜线。每当Django遇到include()时,它会截取与该点匹配的URL的前面部分,并将剩余的字符串发送到包含的URLconf以进行进一步处理。以下是如果一个使用者访问“/polls/34/”,系统中将会发生的事:

Django发现匹配到了正则表达式'^polls/',然后,Django将去掉匹配到的文本("polls/")并将剩下的文本—— "34/" ——发送给‘polls.urls URLconf 做进一步处理,这时将匹配r'^(?P<question_id>[0-9]+)/$'并导致像下面这样调用detail()视图detail(request=<HttpRequestobject>, question_id='34')

question_id='34'部分来自(?P<question_id>[0-9]+)。使用模式周围的括号“捕获”该模式匹配的文本,并将其作为参数发送到视图函数;?P<question_id> 定义一个名字,它将用于标识匹配的模式;[0-9]+是匹配一串数字的正则表达式。


编写拥有实际功能的视图

每个视图函数只负责处理两件事中的一件:返回一个包含所请求页面内容的 HttpResponse对象,或抛出一个诸如Http404异常。该如何去做这两件事,就看你自己的想法了。你的视图可以从数据库中读取记录,或者不读取数据库。 你还可以动态地生成一个PDF文件、输出XML文件、创建一个ZIP文件或者使用你想用的Python库生成任何想要的形式。Django只要求返回的是一个HttpResponse 或者抛出一个异常。

因为方便,让我们之前介绍的Django自己的数据库API。下面是一个新的index()视图,它显示系统中最新发布的5questions记录,并用逗号分隔

polls/views.py

这里有一个问题:页面的设计被硬编码在视图中。 如果你想更改页面的外观,就得编辑这段Python代码。 因此,让我们使用Django的模板系统,通过创建一个视图能够调用的模板,将页面的设计从Python中分离出来。首先,在你的polls目录下创建一个叫做 templates的目录。Django将在这里查找模板:

组织模板

我们可以将我们所有的模板聚在一起,放在一个大的模板目录下,且可以运行地很好。然而,我们的这个模板属于投票应用,不像我们在先前教程中创建的管理站点模板,我们将把它们放在应用的模板目录下(polls/templates)而不是项目模板目录下(templates)。我们将在可重用的应用教程中详细讨论为什么我们这样做。

在你刚刚创建的templates目录中,创建另外一个目录polls,并在其中创建一个文件index.html。换句话讲,你的模板应该位于 polls/templates/polls/index.html。由于app_directories 模板加载器按照上面描述的方式工作,在Django中你可以简单地用polls/index.html引用这个模板。

模板命名空间

现在,我们可以直接将我们的模板放在polls/templates中(而不用创建另外一个polls子目录),但实际上这是个坏主意。Django将选择它找到的名字匹配的第一个模板文件,如果你在不同 的应用有相同名字的模板文件,Django将不能区分它们。我们需要将Django指向正确的模板,最简单的方式是使用命名空间。具体实现方式是,将这些模板文件放在以应用的名字来命名的另一个目录下。

将以下的代码放入模板文件:polls/templates/polls/index.html




现在让我们更新polls/views.py中的index视图来使用模板:polls/views.py




以上的代码载入polls/index.html模板,并传给它一个contextrender

将你的浏览器指向“/polls/”来加载这个页面,你应该看到一个列表。包含来自之前的"What's up" Question,其链接指向Questiondetail页面。




点击hi boys and girls?出现问题的详细页面:




快捷方式:render()

常见的习惯是载入一个模板、填充一个context然后返回一个含有模板渲染结果的HttpResponse对象。Django为此提供一个快捷方式。 下面是重写后的index()视图:

polls/views.py




注意,一旦我们在所有的视图上都应用这个快捷函数,我们将不再需要导入 loaderRequestContextHttpResponse (如果你没有改变先前的detailresults vote方法,你将需要在导入中保留HttpResponse)。render()函数将请求对象作为它的第一个参数,模板的名字作为它的第二个参数,一个字典作为它可选的第三个参数。 它返回一个HttpResponse对象,含有用给定的context渲染后的模板。


引发一个404错误

现在,让我们处理Question详细页面的视图,显示Question内容的页面: 下面是该视图:

polls/views.py



这里有一个新概念:如果没有找到所请求IDQuestion,这个视图引发一个Http404异常。我们将在以后讨论你可以在polls/detail.html模板文件里放些什么代码,但如果你想快点运行上面的例子,仅仅包含:

polls/templates/polls/detail.html

{{ question }}

快捷方式:get_object_or_404()

一种常见的习惯是使用get()并在对象不存在时引发Http404Django为此提供一个快捷方式。 下面是重写后的detail()视图:

polls/views.py




get_object_or_404() 函数将一个Django模型作为它的第一个参数,任意数量的关键字参数作为它的第二个参数,它会将这些关键字参数传递给模型管理器中的get() 函数。如果对象不存在,它就引发一个 Http404异常。


理念

为什么我们要使用一个辅助函数get_object_or_404()而不是在更高层自动捕获ObjectDoesNotExist异常,或者让模型的API引发 Http404 而不是ObjectDoesNotExist?因为那样做将会使模型层与视图层耦合在一起。 Django最重要的一个设计目标就是保持松耦合。 一些可控的耦合将会在django.shortcuts 模块中介绍。 还有一个get_list_or_404()函数,它的工作方式类似get_object_or_404()  ——差别在于它使用filter()而不是get()。如果列表为空则引发Http404


使用模板

回到我们投票应用的detail()视图。 根据context变量question,下面是polls/detail.html模板可能的样子:polls/templates/polls/detail.html


模板系统使用点号查找语法来访问变量的属性。 {{ question.question_text }}这个例子中,Django首先在question对象上做字典查询。如果失败,Django会接着尝试属性查询,在这个例子中,属性查询会成功。如果属性查询也失败,Django将尝试列表索引查询。方法调用发生在{% for %}循环中:question.choice_set.all被解释为Python的代码question.choice_set.all(),它返回一个由Choice对象组成的可迭代对象,并将其用于{% for %}标签。


移除模板中硬编码的URLs 

请记住,当我们在polls/index.html模板中编写一个指向Question的链接时,链接中一部分是硬编码的:

<li><ahref="/polls/{{ question.id }}/">{{ question.question_text}}</a></li>

这种硬编码、紧耦合的方法有一个问题,就是如果我们想在拥有许多模板文件的项目中修改URLs,那将会变得很有挑战性。 然而,因为你在polls.urls模块的url()函数中定义了name参数,你可以通过使用{% url %}模板标签来移除对你的URL配置中定义的特定的URL的依赖。

<li><ahref="{% url 'detail' question.id %}">{{ question.question_text}}</a></li>

它的工作原理是在polls.urls模块里查找指定的URL的定义。你可以看到名为‘detail’的URL的准确定义:

# the 'name' value ascalled by the {% url %} template tag

url(r'^(?P<question_id>[0-9]+)/$',views.detail, name='detail'),

如果你想把polls应用中detail视图的URL改成其它样子比如 polls/specifics/12/,就可以不必在该模板(或者多个模板)中修改它,只需要修改 polls/urls.py

# added the word'specifics'

url(r'^specifics/(?P<question_id>[0-9]+)/$',views.detail, name='detail'),


带命名空间的URL名字

教程中的这个项目只有一个应用polls。在真实的Django项目中,可能会有五个、十个、二十个或者更多的应用。 Django如何区分它们URL的名字呢? 例如,polls 应用具有一个detail 视图,相同项目中的博客应用可能也有这样一个视图。当使用模板标签{% url %}时,人们该如何做才能使得Django知道为一个URL创建哪个应用的视图?

答案是在你的主URLconf下添加命名空间。 mysite/urls.py文件中,添加命名空间将它修改成:mysite/urls.py

 



现在将你的模板polls/index.html由:

polls/templates/polls/index.html

<li><ahref="{% url 'detail' question.id %}">{{ question.question_text}}</a></li>

修改为指向具有命名空间的详细视图:

polls/templates/polls/index.html

<li><a href="{%url 'polls:detail' question.id %}">{{ question.question_text}}</a></li>




表单处理以及如何对代码进行优化

编写一个简单的表单

让我们更新一下在上一个教程中编写的投票详细页面的模板(“polls/detail.html”),让它包含一个HTML<form> 元素:




简要说明:

  • 在detail网页模板中,我们为Question对应的每个Choice都添加了一个单选按钮用于选择。每个单选按钮的value属性是对应的各个Choice的ID。每个单选按钮的name是"choice"。这意味着,当有人选择一个单选按钮并提交表单提交时,它将发送一个POST数据choice=#,其中# 为选择的Choice的ID。这是HTML 表单的基本概念。
  • 我们设置表单的action为{% url 'polls:vote' question.id %},并设置 method="post"。使用method="post"(与其相对的是method="get")是非常重要的,因为这个提交表单的行为会改变服务器端的数据。 无论何时,当你需要创建一个改变服务器端数据的表单时,请使用 method="post"。这不是Django的特定技巧;这是优秀的网站开发实践。
  • forloop.counter指示for标签已经循环多少次。
  • 由于我们创建一个POST表单(它具有修改数据的作用),所以我们需要小心跨站点请求伪造。 谢天谢地,你不必太过担心,因为Django已经拥有一个用来防御它的非常容易使用的系统。 简而言之,所有针对内部URL的POST表单都应该使用{% csrf_token %}模板标签。



现在,我们来创建一个处理提交的数据的Django视图,并用它来处理。记住,我们为投票应用创建了一个URLconf,包含这一行:polls/urls.py

url(r'^(?P<question_id>[0-9]+)/vote/$', views.vote,name='vote'),

我们还创建了一个vote()函数的虚拟实现。让我们来创建一个真实的版本。 将下面的代码添加到polls/views.py



有些新的东西我们来解释一下:

request.POST 是一个类似字典的对象,让你可以通过关键字的名字获取提交的数据。 这个例子中,request.POST['choice'] 以字符串形式返回选择的ChoiceIDrequest.POST 的值永远是字符串。

注意,Django还以同样的方式提供request.GET用于访问GET数据——但我们在代码中显式地使用request.POST,以保证数据只能通过POST调用改动。

如果在POST数据中没有提供choicerequest.POST['choice']将引发一个KeyError。上面的代码检查KeyError,如果没有给出choice将重新显示Question表单和一个错误信息。

在增加Choice的得票数之后,代码返回一个 HttpResponseRedirect而不是常用的HttpResponseHttpResponseRedirect只接收一个参数:用户将要被重定向的URL(请继续看下去,我们将会解释如何构造这个例子中的URL)。

正如上面的Python注释指出的,你应该在成功处理POST数据后总是返回一个HttpResponseRedirect 这不是Django的特定技巧; 这是那些优秀网站在开发实践中形成的共识。

在这个例子中,我们在HttpResponseRedirect的构造函数中使用reverse()函数。这个函数避免了我们在视图函数中硬编码URL。它需要我们给出我们想要跳转的视图的名字和该视图所对应的URL模式中需要给该视图提供的参数。 在本例中,使用在教程3中设定的URLconf reverse() 调用将返回一个这样的字符串:

'/polls/3/results/'

... 其中3p.id的值。重定向的URL将调用'results'视图来显示最终的页面。

当有人对Question进行投票后,vote()视图将请求重定向到Question的结果界面。让我们来编写这个视图:polls/views.py



这和detail()视图几乎一模一样。唯一的不同是模板的名字。 我们将在稍后解决这个冗余问题。

下面创建一个polls/results.html模板:




现在,在你的浏览器中访问/polls/1/然后为Question投票。你应该看到一个投票结果页面,并且在你每次投票之后都会更新。 如果你提交时没有选择任何Choice,你应该看到错误信息。





















原创粉丝点击