锻造车间 - Django 视图和模板

作者:Reuven M. Lerner

上个月,我们开始研究 Django,这是一个流行的 Python Web 应用程序框架。Django 因其是“Python 世界的 Ruby on Rails”而闻名,这种描述是有一定道理的。尽管 Django 与 Rails 并行开发,并且具有许多区别于其 Ruby 对等物的独特功能,但很难避免将两者进行比较。

在上个月对 Django 的介绍中,我们看到了这两个系统之间一些细微但重要的差异。Django 一次处理一个“项目”,每个项目可能包含一个或多个应用程序。这与 Rails 的方法形成对比,Rails 的方法中没有真正等同于 Django 项目的概念,因为一切都是应用程序。

我们还看到 Django 自带一个基于 Web 的管理系统。只需稍微修改一下配置文件即可激活此管理系统,但它为任何使用它的系统提供了大量好处。

尽管存在差异,Django 和 Rails 都使用一种最著名的称为 MVC(模型、视图、控制器)的方法,该方法在 Smalltalk 社区中开发,但此后被许多其他语言和框架采用。这些术语在 Rails 世界中原封不动地使用,在 Django 世界中被称为模型、模板和视图,它们构成了基于 Django 的网站的大部分。

本月,我们将使用模板和视图,忽略模型和数据库 API,直到下个月。但是,请不要担心;Django 的模板非常强大,它们提供了一个关于约束的有趣课程。

创建应用程序

我们的第一步是在我们的项目中创建一个新的应用程序。出于多种原因,包括我们可以轻松创建页面的简易性,我们的示例应用程序是一个非常简单的博客程序。我们通过切换到我们的项目目录,然后使用我们的管理程序 manage.py 来创建我们的博客应用程序

$ cd /opt/atf/mysite
$ python manage.py startapp blog

如果执行成功,Django 不会打印任何消息。相反,我们可以获取当前目录的列表,该列表现在应包含一个 blog 子目录

$ ls blog
__init__.py  models.py  views.py

Django 非常智能,可以阻止我们重复创建应用程序,如果我们尝试这样做

$ python manage.py startapp blog
Error: [Errno 17] File exists: '/opt/atf/mysite/blog'

现在,让我们创建一个简单的“Hello, world”视图,只是为了演示一切都在正常工作。因此,我们打开 blog/views.py,它开始时是空的,除了一个注释,指示这是定义视图的文件。

最简单的“Hello, world”视图将被称为 index,其作用类似于 Rails 中的 index 方法或网站上的 index.html 文件——当没有为应用程序调用特定方法时提供默认内容。我们还必须导入定义 Django HTTP 响应功能的 Python 模块。完成后,整个 views.py 文件如下所示

from django.http import HttpResponse

def index(request):
return HttpResponse("Hello, world.")

如果我们的服务器尚未运行,我们可以使用以下命令启动它

python manage.py runserver

或者,如果我们想在公共可访问的 IP 地址(而不是 127.0.0.1(“localhost”))上运行,我们可以这样做

python manage.py runserver 69.55.232.87:8000
URLConf

为了查看我们视图的输出,让我们将浏览器指向以下 URL,期望看到“Hello, world”:http://69.55.232.87:8000/blog/。相反,我们收到一条错误消息,表明 Django 不知道我们在说什么。好消息是,此错误消息仅在我们开发应用程序时才会打开,它告诉我们我们做错了什么——即我们的 URLConf 定义未能包含 blog 的任何条目。

这是 Django 和 Rails 之间的一个根本区别,它反映了两个社区的不同理念。在 Rails 中,您期望系统默认情况下做正确的事情;只有当它偏离规范时,您才应该说些什么。相比之下,Django 假设您希望一切都明确。

您需要明确说明的事情之一是将 URL 转换为方法调用的方式。Django 用于执行此操作的系统称为 URLConf,它是一组为整个项目定义的正则表达式。(它的功能类似于 Rails 中的路由。)如果您熟悉正则表达式,那么添加或修改 URLConf 应该很容易。

例如,假设我们希望 /blog/ URL 转到我们的博客应用程序。因此,我们将打开 URLConf 文件,对于 mysite 项目,该文件将在 mysite/urls.py 中。(我们上个月已经修改了它,当时我们向我们的 Django 站点添加了管理功能。)然后我们添加一行,如下所示

(r'^blog/$', 'mysite.blog.views.index')

换句话说,如果系统看到以 blog 开头并以 / 结尾的 URL,它应该调用我们博客应用程序中 views.py 中的 index 方法。而且,果然,一旦我们将 urls.py 保存到磁盘,我们就可以重新加载我们的 URL http://69.55.232.87:8000/blog/,在我们的浏览器中,我们看到“Hello, world”。我们的 Django 应用程序开始组合在一起。

更复杂的 URL

如果只有一个方法,并且它总是做同样的事情,那么事情就没那么有趣了。对于博客应用程序,我们大概希望能够阅读特定日期或多个日期上的特定帖子或帖子。虽然我们将在下个月深入研究我们博客应用程序的模型,但这理所当然地意味着我们需要能够按个人 ID 或日期请求博客帖子。

我们当前的 URLConf 不处理这种情况。实际上,Django 要求我们明确指出用户可能请求的每个可能的 URL 以及应如何处理该 URL。因此,虽然我们已经处理了 blog/,但我们需要处理诸如以下 URL

^blog/ID

幸运的是,我们应该很容易设置一个正则表达式来捕获此类功能。如果我们再次打开 urls.py,我们可以添加另一个语句

(r'^blog/(?P<post_id>\d+)/$', 'mysite.blog.views.view_one_posting')

在这里,我们看到一些有点奇怪和不同的东西,即捕获括号的使用,以及 ?P 和尖括号内的名称。我们告诉 URLConf,每当我们收到看起来像 /blog/NUMBER 的 URL(其中 NUMBER 包含一个或多个数字)时,我们都应该调用 view_one_posting。此外,由于我们使用名称 post_id 捕获了数字(正则表达式中的 \d+),因此将使用 post_id 的附加参数调用 view_one_posting。

换句话说,一旦我们修改了 urls.py 以包含上述映射,我们现在就可以返回 views.py 并说出以下内容

def view_one_posting(request, post_id):
return HttpResponse("You asked for post '%s'" % post_id)

然后,我们可以转到 http://69.55.232.87:8000/blog/5,在我们的浏览器中,我们得到以下响应

You asked for post '5'

请注意,在 view_one_posting 方法中,我们使用了 %s(用于字符串)来呈现 post_id,而不是 %d(用于整数)。这是因为参数和 URL 作为字符串而不是整数或其他数据类型传递。当然,我们可以通过将字符串转换为整数来解决这个问题,但就我们目前的目的而言,这已经足够了。

也许这不用说,但我们 Django 应用程序中的每个方法都是一个 Python 方法,其输出恰好发送到用户的 Web 浏览器。您可以使用任意数量的 Python 库,甚至可以访问数据库、文件系统和远程计算机。只要输出作为合法的 HTTP 响应返回,您的方法就可以工作。就实现复杂性而言,我们在这些示例中构建的应用程序代表了冰山一角。

模板

如果我们愿意,我们可以仅使用我们已经看到的工具创建一个完整的网站。但是,这样做很快就会变得困难,甚至乏味,将我们所有的输出字符串作为 HttpResponse 的参数。真正的解决方案是将 HTML 放在外部文件中,根据需要插入变量值。

有很多不同的模板解决方案,每种解决方案都有其优点和缺点。最常见的类型是 ASP、PHP、JSP 和 ERb 使用的类型——ERb 是 Ruby on Rails 的一部分——其中代码与 HTML 交错在一起。这种类型的模板对于程序员来说非常容易使用,但当非编程设计人员参与时,或者如果您只想将代码和设计完全分离时,可能会引起麻烦。

Django 可以使用此类模板,但它们不是默认模板。相反,Django 使用的模板在许多方面与 PHP 的 Smarty 系统类似,该系统通过引入自己的、有意限制的语言来避免在模板中使用实际代码。只要视图可以将值传递给模板,就无需在模板内部使用功能齐全的编程语言。拥有 if/then 语句和一些基本循环可能就足够了。

这就是 Django 的默认模板系统提供的功能。为了使用模板,我们首先修改位于项目主目录中的 settings.py 文件。我们想要设置 TEMPLATE_DIRS 变量,为其提供一个或多个可能找到我们模板的目录列表。例如,我们可以将其设置为

TEMPLATE_DIRS = (
"/opt/atf/mysite/templates"
)

完成此设置后,现在我们创建相应的目录

$ mkdir -p /opt/atf/mysite/templates/blog

然后,我们在该 blog 子目录中创建一个新的 HTML 文件,我们将其命名为 view_one_posting.html

<html>
<head>
    <title>One post</title>
</head>

<body>
    <p>This is the "view_one_posting" template.</p>
</body>
</html>

现在,我们修改 views.py,以便我们的 view_one_posting 方法可以调用此模板。我们在文件顶部添加以下导入语句

from django.template import Context, loader

然后,我们修改 view_one_posting 为

def view_one_posting(request, post_id):
t = loader.get_template('blog/view_one_posting.html')
c = Context({})
return HttpResponse(t.render(c))

我们的模板被加载到变量 t 中,上下文(即我们想要传递给模板的变量值)绑定到变量 c。然后,我们告诉模板在上下文 c 中呈现自身。在这个特定的示例中,我们没有在上下文中传递任何变量,因此它由一个空字典表示。

并且,如果我们重新加载我们网站上的 URL /blog/5,我们应该在浏览器窗口中看到以下内容

This is the "view_one_posting" template.

这肯定比我们以前拥有的要好,因为模板的内容比视图方法内部的字符串更容易处理(程序员和设计人员都更容易处理)。但是,我们如何传递要插值的变量呢?

答案很简单。在视图方法中,我们可以使用上下文字典将我们喜欢的任何变量传递给模板,其中键是传递的变量名,值是传递的变量值。所以,我们可以说

def view_one_posting(request, post_id):
t = loader.get_template('blog/view_one_posting.html')
c = Context({'post_id': post_id})
return HttpResponse(t.render(c))

如果我们想在模板中看到此值,我们可以在双花括号中查看它

<html>
<head>
    <title>One post</title>
</head>

<body>
    <p>This is the "view_one_posting" template.</p>
    <p>The post_id is {{post_id}}.</p>
</body>
</html>

并且,果然,当我们呈现此模板时,我们得到

This is the "view_one_posting" template.

The post_id is 5.
结论

Django 像 Rails 和许多其他 Web 框架一样,使用 MVC 将工作划分为模型、视图和模板。本月,我们了解了如何将 URL 连接到视图,如何将一个或多个 URL 参数传递给视图,以及如何从视图调用模板。

下个月,我们将了解如何将数据库和数据模型集成到 Django 应用程序中。

资源

Django 的主要站点位于 www.djangoproject.com。该站点包含大量文档,包括教程和邮件列表的指针。

即将出版的 Django 书籍(由 Apress 出版)的预发布副本位于 www.djangobook.com/en/beta,尽管这本书在许多地方仍然未完成,但它写得很好,并且包含许多示例。

Reuven M. Lerner,一位资深的 Web/数据库顾问,是伊利诺伊州埃文斯顿西北大学学习科学专业的博士候选人。他目前与妻子和三个孩子住在伊利诺伊州斯科基。您可以在 altneuland.lerner.co.il 阅读他的博客。

加载 Disqus 评论