- 引言
- 本书涉及的内容
- 第 1 部分 Python 开发入门
- 第 1 章 Python 入门
- 第 2 章 开发 Web 应用
- 第 3 章 Python 项目的结构与包的创建
- 第 4 章 面向团队开发的工具
- 第 5 章 项目管理与审查
- 第 6 章 用 Mercurial 管理源码
- 第 7 章 完备文档的基础
- 第 8 章 模块分割设计与单元测试
- 第 9 章 Python 封装及其运用
- 第 10 章 用 Jenkins 持续集成
- 第 11 章 环境搭建与部署的自动化
- 第 12 章 应用的性能改善
- 第 13 章 让测试为我们服务
- 第 14 章 轻松使用 Django
- 第 15 章 方便好用的 Python 模块
- 附录 A VirtualBox 的设置
- 附录 B OS(Ubuntu)的设置
2.6 实现功能
终于到了编写 Python 程序的阶段。我们准备让服务器端的程序来实现这个应用的必备功能。这里将优先实现比较重要的功能,或者能提高其他部分实现速度的功能。
保存和读取用户提交的数据是这个应用的核心部分,所以我们先从它下手。
2.6.1 保存留言数据
提交功能不但要能保存表单传来的名字和留言,还得能将提交日期及时间存储下来,供显示时使用。
这里我们用 Python 的标准模块 shelve 来存储数据。shelve 能够像 Python 字典对象一样操作数据,将对象持久化。也就是说,shelve 会将提交的数据转换成字典对象,以列表形式保存多个字典,然后将这些字典保存在 shelve 中。
下面来编写留言板的脚本文件。guestbook.py 实现了负责保存数据的 save_data 函数,具体代码如 LIST 2.11 所示。
LIST 2.11 guestbook.py
# coding: utf-8 import shelve DATA_FILE = 'guestbook.dat' def save_data(name, comment, create_at): """ 保存提交的数据 """ # 通过shelve 模块打开数据库文件 database = shelve.open(DATA_FILE) # 如果数据库中没有greeting_list,就新建一个表 if 'greeting_list' not in database: greeting_list = [] else: # 从数据库获取数据 greeting_list = database['greeting_list'] # 将提交的数据添加到表头 greeting_list.insert(0, { 'name': name, 'comment': comment, 'create_at': create_at, }) # 更新数据库 database['greeting_list'] = greeting_list # 关闭数据库文件 database.close()
然后再来看看 save_data 函数的运行情况。我们通过终端在 guestbook.py 文件所在的目录下启动 Python shell,然后像 LIST 2.12 这样通过 Python shell 加载并执行 guestbook 模块的 save_data 函数。
LIST 2.12 save_data 函数的运行测试
$ ls # 确认guestbook.py 位于当前目录下 guestbook.py $ python # 启动Python shell >>> import datetime >>> from guestbook import save_data >>> save_data('test', 'test comment', ... datetime.datetime(2014, 10, 31, 10, 0, 0)) >>>
这里我们将 datetime 模块的日期时间对象传递给传值参数 create_at,以此来保存提交的日期和时间。在这个阶段,我们只能看出它的运行是否报错,至于数据是不是真的被保存下来了,还要等取出数据的功能实现之后才能知道。
2.6.2 获取已保存的留言列表
实际上,我们在保存数据的时候就从 shelve 模块中取出过数据,现在只把这部分代码单独拿出来做成函数即可。
在 guestbook.py 中添加 load_data 函数(LIST 2.13)。
LIST 2.13 guestbook.py
def load_data(): """ 返回已提交的数据 """ # 通过shelve 模块打开数据库文件 database = shelve.open(DATA_FILE) # 返回greeting_list。如果没有数据则返回空表 greeting_list = database.get('greeting_list', []) database.close() return greeting_list
这里同样通过 Python shell 查看其运行情况。启动 Python shell,加载函数并运行(LIST 2.14)。
LIST 2.14 load_data 函数的运行测试
>>> from guestbook import load_data >>> load_data() [{'comment': 'test comment', 'name': 'test', 'create_at': datetime.datetime (2014, 10, 31, 10, 0)}]
如果运行正常,那就表示我们能获取 save_data 函数保存下来的数据。
2.6.3 用模板引擎显示页面
从文件中取出数据之后,为了将其显示到页面上,我们要使用模板引擎。模板引擎可以将模板(程序的雏形)与要植入模板内的数据合并输出。Flask 标准支持 Jinja2 模板引擎。
接下来创建 templates 目录,然后将前面已经写好的 HTML 文件放到该目录下(LIST 2.15)。这样一来,我们就可以以它为模板生成 HTML 了。
LIST 2.15 放置模板
$ mkdir templates $ mv index.html templates/
下面从程序端入手,用这个 HTML 文件(模板)来完成页面的显示。先添加代码,让 guestbook.py 调用 Flask,然后再添加用来显示首页的函数以及用来启动 Web 服务器的代码(LIST 2.16)。
LIST 2.16 guestbook.py
# coding: utf-8 import shelve from flask import Flask, request, render_template, redirect, escape, Markup application = Flask(__name__) DATA_FILE = 'guestbook.dat' def save_data(name, comment, create_at): """ 保存提交的数据 """ # 省略 def load_data(): """ 返回已提交的数据 """ # 省略 @application.route('/') def index(): """ 首页 使用模板显示页面 """ return render_template('index.html') if __name__ == '__main__': # 在IP 地址127.0.0.1 的8000 端口运行应用程序 application.run('127.0.0.1', 8000, debug=True)
我们将 Flask 类的实例赋给变量 application,传值参数指定为 __name__ 变量的模块名。方法 route 是一个装饰器,负责注册针对特定 URL 执行的函数。这里我们让主页的 URL 对应执行 index 函数。render_template 函数负责将指定文件用作模板,再通过模板引擎进行输出。
Flask 类的 run 方法用于启动 Web 服务器并执行应用程序,传值参数用来指定要绑定的 IP 地址及端口。另外,将 debug 选项指定为 True 时,一旦应用程序出错,Web 浏览器端就会启动可用的调试程序。
接下来用 python 命令运行 guestbook.py(LIST 2.17)。
LIST 2.17 运行已开发完毕的 Web 应用
$ python guestbook.py * Running on http://127.0.0.1:8000/ * Restarting with reloader
运行 guestbook.py 之后,在该环境的(这里是 VirtualBox 上的 Ubuntu)127.0.0.1 地址的 8000 端口等待请求的应用服务器(Web 服务器)就会启动。我们可以在终端同时按下 Ctrl 键和 C 键,关闭这个服务器。
现在我们需要设置好 8000 端口的端口转发,然后在 Web 浏览器中打开 http://127.0.0.1:8000/ 。如何,看到页面没有?
我们会发现 CSS 文件并没有生效,所以需要重新调整一下 CSS 文件的位置,让它能够被读取。Flask 会公开 static 目录下存放的静态文件。接下来我们创建一个 static 目录,把 main.css 文件放进去(LIST 2.18)。
LIST 2.18 放置静态文件
$ mkdir static $ mv main.css static/
另外,还要将 templates/index.html 中的 CSS 文件引用位置改为 /static/main.css(LIST 2.19、LIST 2.20)。
LIST 2.19 templates/index.html(更改前)
<link rel="stylesheet" href="main.css" type="text/css">
LIST 2.20 templates/index.html(更改后)
<link rel="stylesheet" href="/static/main.css" type="text/css">
现在再用 Web 浏览器打开该页面,就会发现 CSS 文件这时已经生效了。
接下来,我们把从数据库中取出的内容显示在页面上。修改 guestbook.py 文件,让 index 函数调用 load_data 函数,同时使模板能够使用 load_data 函数取出来的数据(LIST 2.21)。
LIST 2.21 guestbook.py
@application.route('/') def index(): """ 首页 使用模板显示页面 """ # 读取已提交的数据 greeting_list = load_data() return render_template('index.html', greeting_list=greeting_list)
render_template 函数可以将关键字传值参数所指定的值用作模板变量。比如本例就使用了名为 greeting_list 的模板变量。
此外,我们还要修改模板 templates/index.html,使其能够使用模板变量进行显示。现在对 HTML 中的显示留言部分作如下修改(LIST 2.22)。
LIST 2.22 templates/index.html
<div class="entries-area"> <h2> 留言记录</h2> {% for greeting in greeting_list %} <h3>{{ greeting.name }} 的留言({{ greeting.create_at }}):</h3> <p>{{ greeting.comment }}</p> {% endfor %} </div>
模板内可以使用一种特殊的描述方式,即模板语言。Jinja2 的模板可以通过 {%…%} 的形式使用 if 或 for 等控制语句。植入有模板变量的部分用 {{…}} 的形式描述。在上述模板中,程序会从 greeting_list 中逐一取值并赋给模板变量 greeting,然后使用从 for 到 endfor 之间的模板变量进行循环输出。
这样一来,应用就能将 save_data 函数保存的数据显示在页面上了。
2.6.4 准备评论接收方的 URL
下一步我们用 save_data 函数保存表单提交来的数据。由于模板文件中表单的 action 值为 /post,所以我们就做出这个 URL。
这里,我们将 post 函数添加到 guestbook.py 的 if __main__ ... 前面(LIST 2.23)。
LIST 2.23 guestbook.py
@application.route('/post', methods=['POST']) def post(): """ 用于提交评论的URL """ # 获取已提交的数据 name = request.form.get('name') # 名字 comment = request.form.get('comment') # 留言 create_at = datetime.now() # 投稿时间(当前时间) # 保存数据 save_data(name, comment, create_at) # 保存后重定向到首页 return redirect('/')
在 Flask 中,可以用 request.form 引用表单发来的数据。此外,由于保存数据后需要重新显示首页,所以我们要返回 redirect 函数的结果并进行重定向。
post 函数使用了 datetime 模块,所以需要在文件开头添加如 LIST 2.24 所示代码,以导入该模块。
LIST 2.24 导入 datetime 模块(guestbook.py)
# coding: utf-8 import shelve from datetime import datetime # 添加此行
至此,保存数据的功能也实现了。应用的运行部分基本完工。
2.6.5 调整模板的输出
程序运行所需的功能现在已经基本上都实现了,但这里至少还有两点需要注意。
· 表单提交多行留言时,无法正常显示留言
· 显示的时间精确到了毫秒
要想解决这两个问题需创建一个模板过滤器。模板过滤器会对模板变量的值加以转换并输出。接下来,我们在 guestbook.py 的 if __main__ ... 前添加如 LIST 2.25 所示代码,将模板过滤器导入模板。
LIST 2.25 guestbook.py
@application.template_filter('nl2br') def nl2br_filter(s): """ 将换行符置换为br 标签的模板过滤器 """ return escape(s).replace('\n', Markup('<br>')) @application.template_filter('datetime_fmt') def datetime_fmt_filter(dt): """ 使datetime 对象更容易分辨的模板过滤器 """ return dt.strftime('%Y/%m/%d %H:%M:%S')
Flask 类的 template_filter 方法是一个装饰器,它负责将函数注册为指定名称的模板过滤器。程序运行时,指定了模板过滤器的模板变量会被作为传值参数传递给相应函数,而函数的返回值则为最后输出的值。在上述源码中,我们注册了 nl2br 和 datetime_fmt 两个模板过滤器。
接下来,我们修改一下 templates/index.html 文件,让模板能够使用这两个模板过滤器(LIST 2.26)。
LIST 2.26 templates/index.html
<div class="entries-area"> <h2> 留言记录</h2> {% for greeting in greeting_list %} <h3>{{ greeting.name }} 的留言({{ greeting.create_at|datetime_fmt }}):</h3> <p>{{ greeting.comment|nl2br }}</p> {% endfor %} </div>
在模板内指定模板过滤器的方法很简单,只需要在模板变量的名称后面加管道符“| ”再加模板过滤器名即可。在本次的模板中,我们给 greeting.create_at 指定了 datetime_fmt 过滤器,给 greeting.comment 指定了 nl2br 过滤器。
至此,服务器端的功能、功能与页面的对接已经全部完工。从 Web 浏览器看到的效果如图 2.5 所示。显示留言部分 2 显示了我们已提交的内容。
2 留言记录部分的 tokibito 为本章撰写者冈野真也先生的 Twitter 用户名。——编者注
图 2.5 植入功能后的页面
整个应用的开发过程到这里就结束了,接下来就是确认该应用的运行是否正常,以及看一看成品是否能满足我们当初定下的需求。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论