返回介绍

2.6 实现功能

发布于 2024-01-21 17:11:03 字数 8562 浏览 0 评论 0 收藏 0

终于到了编写 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 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文