热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

【用户指南】05教程

文章目录1. 介绍 Flaskr2. 创建文件夹3. 数据库模式4. 应用设置代码5. 数据库连接6. 创建数据库7. 视图函数7.1 显示条目7.2 添加条目7.3 登入和登出8. 登入登出8.1

文章目录

  • 1. 介绍 Flaskr
  • 2. 创建文件夹
  • 3. 数据库模式
  • 4. 应用设置代码
  • 5. 数据库连接
  • 6. 创建数据库
  • 7. 视图函数
    • 7.1 显示条目
    • 7.2 添加条目
    • 7.3 登入和登出
  • 8. 登入登出
    • 8.1 layout.html
    • 8.2 show_entries.html
    • 8.3 login.html
  • 9. 添加样式
  • 10. 福利: 应用测试

想要用 Python 和 Flask 开发一个应用?在此,你将有机会通过实例来学习。在本教程中,我们会创建一个简单的微博客应用。它只支持单用户和纯文本条目,并且没有推送或评论功能,但是它仍然有你需要开始的一切。我们将使用 Flask,采用 Python 自带的 SQLite 数据库,所以你需要其它的东西。

如果你想预先拿到完整的源码或是用于对照,请查看示例源码。

1. 介绍 Flaskr

在本教程中,我们把我们的这个博客应用称为 falskr,也可以选一个不那么 web2.0 的名字。基本上,我们希望它能做这些事情:

  1. 允许用户配置文件里指定的凭证登入登出,只支持一个用户。
  2. 当用户登录后,可以向页面添加条目。条目标题是纯文本,正文可以是一些 HTML。因信任这里的用户,这部分 HTML 不作审查。
  3. 页面倒序显示所有的条目(后来居上),并且用户登录后可以在此添加新条目。

我们将会在应用中直接采用 SQLite3,因为它足以应付这种规模的应用。对于更大型的应用,就有必要使用 SQLAlchemy,它能更加智能地处理数据库连接,允许你一次连接不同的关系数据库等等。如果你的数据更适合 NoSQL,你也可以考虑流行的 NoSQL 数据库。

这是一个应用最终效果的截图:

在这里插入图片描述

2. 创建文件夹

在我们真正开始之前,让我们创建这个应用所需的文件夹:

/flaskr/static/templates

flaskr 文件夹不是一个 Python 包,只是个我们放置文件的地方。在接下来的步骤中,我们会直接把数据库模式和主模块放在这个目录中。用户可以通过 HTTP 访问 static 文件夹中的文件,也即存放 css 和 Javascript 文件的地方。Flask 会在 templates 文件夹里寻找 Jinja2 模板,之后教程中创建的模板将会放在这个文件夹里。

3. 数据库模式

首先我们要创建数据库模式。对于这个应用来说,一张表就足够了,而且只需支持 SQLite,所以会很简单。只需要把下面的内容放进一个名为 schema.sql 的文件,放在刚才创建的 flaskr 文件夹中:

DROP TABLE IF EXISTS entries
;
CREATE TABLE entries (id INTEGER PRIMARY KEY AUTOINCREMENT, title STRING NOT NULL, text STRING NOT NULL
)
;

这个模式包含一个名为 entries 的表,该表中的每行都包含一个 id、一个 title 和一个 text。id 是一个自增的整数,也是主键;其余的两个是字符串,且不允许为空。

4. 应用设置代码

现在我们已经有了数据库模式,我们可以创建应用的模块了。让我们把它叫做 flaskr.py,并放置在 flaskr 目录下。我们从添加所需的导入语句开始和添加配置部分开始。对于小型应用,可以直接配置放在主模块里,正如我们需要要做的一样。但更简单的方案是创建独立的 .ini.py 文件,并载入或导入里面的值。

首先在 flaskr.py 里导入:

# all the imports
import os
import sqlite3
from flask import (Flask, request, session, g, redirecturl_for, abort, render_template, flash)

Config 对象的用法如同字典,所以我们可以用新值更新它。

数据库路径:

操作系统有进程当前工作目录的概念。不幸的是,你在 Web 应用中不能依赖此概念,因为你可能会在相同的进程中运行多个应用。

为此,提供了 app.root_path 属性以获取应用的路径。配合 os.path 模块使用,轻松可达任意文件。在本例中,我们把数据库放在根目录下。

对于实际生产环境的应用,推荐使用实例文件夹。

通常,加载一个单独的、环境特定的配置文件是个好主意。Flask 允许你导入多份配置文件,并且使用最后的导入中定义的设置。这使得配置设定过程更可靠。from_envvar() 可用于达此目的。

app.config.from_envvar('FLASKR_SETTINGS', slient=True)

只需设置一个名为 FLASKR_SETTINGS 的环境变量,指向要加载的配置文件。启用静默模式告诉 Flask 在没有设置该换将变量的情况下噤声。

此外,你可以配置对象上的 from_object() 方法,并传递一个模块的导入名作为参数。Flask 会从这个模块初始化变量。注意,只有名称全为大写字母的变量才会被采用。

secret_key 是保证客户端会话的要点。正确选择一个尽可能猜测,尽可能复杂的密钥。调试标志关系交互式调试器的开启。永远不要在生产系统中激活调试模式,因为它将允许用户在服务器上执行代码。

我们还添加了一个让连接到指定数据库变得很简单的方法,这个方法用于在请求时开启一个数据库连接,并且在交互式 Python Shell 和脚本中也能使用。这位以后的操作提供了相当的便利。我们创建了一个简单的 SQLite 数据库的连接,并让它用 sqlite3.Row 表示数据库中的行,这使得我们可以通过字典而不是元组的形式访问行:

def connect_db():"""Connects to the specific database."""rv = sqlite3.connect(app.config['DATABASE'])rv.row_factory = sqlite3.Rowreturn rv

最后,如果我们想要把这个文件当做独立应用来运行,我们只需在可启动服务器文件末尾添加这一行:

if __name__ == '__main__':app.run()

如此我们便可以开始顺利运行这个应用,使用如下命令:

→ python flaskr.py

你将会看见有消息告诉你访问服务器的地址:

当你在浏览器中访问服务器遇到一个 404 page not found 错误时,是因为我们还没有任何视图。我们之后再来关注视图。首先我们应该让数据库工作起来。

5. 数据库连接

我们已经创建了一个能建立数据库连接的函数 connect_db,但它本身并不是很有用。总是创建或关闭数据库连接是相当低效的,所以我们会让连接保持更长时间。因为数据库连接封装了事务,我们也需要确保同一时刻只有一个请求使用这个连接。那么,如何用 Flask 优雅地实现呢?

这该是应用环境上场的时候了。那么,让我们开始吧。

Flask 提供了两种环境(Context): 应用环境(Application Context) 和请求环境(Request Context)。暂且你所需了解的是,不同环境有不同的特殊变量。例如 request 变量与请求对象有关,而 g 是与当前应用环境有关的通用变量。我们在之后深入了解它们。

现在你只需要知道可以安全地在 g 对象存储对象。

那么你何时把数据库连接存放在它上面?你可以写一个辅助函数。这个函数首次调用的时候会为当前环境创建一个数据库连接,调用成功后返回已经建立好的连接:

def get_db():"""Opens a new database connection if there is none yetfor the current application context."""if not hasattr(g, 'sqlite_db'):g.sqlite_db = connect_db()return g.sqlite_db

于是现在我们知道如何连接到数据库,但如何妥善断开连接呢?为此,Flask 提供了 teardown_appcontext() 装饰器。它将在每次应用环境销毁时执行:

@app.teardown_appcontext
def close_db(error):"""Closes the database again at the end of the request."""if hasattr(g, 'sqlite_db'):g.sqlite_db.close()

teardown_appcontext() 标记的函数会在每次应用环境销毁是调用。这意味着什么?本质上,应用环境在请求传入前创建,每当请求结束时销毁。销毁有两种原因: 一切正常(错误参数会是 None) 或发生异常,后者情况中,错误会被传递给销毁时函数。

提示: 我该把这些代码放在哪?

如果你一直遵循教程,你应该会问从此以后的步骤产生的代码放在什么地方。逻辑上来讲,应该按照模块来组织函数,即把你新的 get_db()close_db() 函数放在之前的 connect_db 函数下面(逐行复刻教程)。

如果你需要来找准定位,可以看一下示例源码是怎么组织的。在 Flask 中,你可以把你应用中所有的代码放在一个 Python 模块里。但你无需这么做,而且在你的应用规模扩大以后,这显然不妥。


6. 创建数据库

正如之前介绍的,Flaskr 是一个数据库驱动的应用,更准确的说法是,一个由关系数据库系统驱动的应用。关系数据库系统需要一个模式来决定存储信息的方式。所以在第一次开启服务器之前,要点是创建模式。

sqlite3 /tmp/flaskr.db < schema.sql

这种方法的缺点是需要安装 sqlite3命令&#xff0c;而并不是每个系统都有安装。而且你必须提供数据库的路径&#xff0c;否则将报错。用函数将初始化时个不错的想法。

要这么做&#xff0c;我们可以创建一个名为 init_db 的函数来初始化数据库。让我们首先看看代码。只需要把这个函数放在 flaskr.py 里的 connect_db 函数的后面:

def init_db():with app.app_context():db &#61; get_db()with app.open_resource(&#39;schema.sql&#39;, mode&#61;&#39;r&#39;) as f:db,cursor().executescript(f.read())db.commit()

那么&#xff0c;这段代码会发生什么&#xff1f;还记得吗&#xff1f;上个章节中提到&#xff0c;应用环境在每次请求传入时创建。这里我们并没有请求&#xff0c;所以我们需要手动创建一个应用环境。g 在应用环境外无法获知它属于哪个应用&#xff0c;因为可能会有多个应用同时存在。

with app.app_context() 语句为我们建立了应用环境。在 with 语句的内部&#xff0c;g 对象会与 app 关联。在语句的结束处&#xff0c;会释放这个关联并执行所有销毁函数。这意味着数据库连接在提交后断开。

应用对象的 open_resource() 是一个很方便的辅助函数&#xff0c;可以打开应用提供的资源。这个函数从资源所在位置(你的 flaskr 文件夹) 打开文件夹&#xff0c;并允许你读取它。我们在此用它来在数据库连接上执行脚本。

SQLite 的数据库连接对象提供了一个游标对象。游标上有一个方法可以执行完整的脚本。最后我们只需要提交变更。SQLite3 和其他支持事务的数据库只会在你显式提交的时候提交。

现在可以在 Python shell 导入并调用这个函数来创建数据库:

>>> from flaskr import init_db
>>> init_db()

故障排除: 如果你遇到了表无法找到的异常&#xff0c;请检查你是否确实调用过 init_db 函数并且表的名称是正确的(比如弄混了单数和复数)。


7. 视图函数

现在数据库连接已经正常工作&#xff0c;我们终于可以开始写视图函数了。我们一共需要些四个:

7.1 显示条目

这个视图显示数据库中存储的所有条目。它绑定在应用的根地址&#xff0c;并从数据库查询出文章的标题和正文。id 值最大的条目(最新的条目) 会显示在最上方。从指针返回的行是按 select 语句中声明的列组织的元组。这对像我们这样的小应用已经足够了&#xff0c;但是你可能会想把转换成字典。如果你对这方面有兴趣&#xff0c;请参考简化查询的例子。

视图函数会将条目作为字典传递给 show_entries.html 模板&#xff0c;并返回渲染结果:

&#64;app.route(&#39;/&#39;)
def show_entries():cur &#61; g.db.execute(&#39;select title, text from entries order by id desc&#39;)entries &#61; [dict(title&#61;row[0], text&#61;row[1]) for row in cur.fetchall()]return redirect(url_for(&#39;show entries&#39;))

7.2 添加条目

这个视图允许已登录的用户添加新条目&#xff0c;并只响应 POST 请求&#xff0c;实际的表单显示在 show_entries 页。如果一些工作正常&#xff0c;我们会用 flash() 向下一次请求发送提示消息&#xff0c;并重定向回 show_entries 页:

&#64;app.route(&#39;/add&#39;, methods&#61;[&#39;POST&#39;])
def add_entry():if not session.get(&#39;logged_in&#39;):abort(401)g.db.execute(&#39;insert into entries (title, text) values (?, ?)&#39;,[request.form[&#39;title&#39;], request.form[&#39;text&#39;]])g.db.commit()flash(&#39;New entry was successfully posted&#39;)return redirect(url_for(&#39;show_entries&#39;))

注意这里的用户登录简检查&#xff0c;(logged_in 键在会话中存在&#xff0c;并且为 True)。

安全提示: 确保像上面例子一样&#xff0c;使用问号来构建 SQL 语句。否则&#xff0c;当你使用格式化字符串构建 SQL 语句时&#xff0c;你的应用很容易遭受 SQL 注入。更多请见在 Flask 中使用 SQLite3。


7.3 登入和登出

这些函数用来让用户登录和登出。登录通过与配置文件中的数据比较检查用户名和密码&#xff0c;并设定会话中的 logged_in 键值。如果用户登录成功&#xff0c;那么这个键值会被设置为 True&#xff0c;并跳转回 show_entries 页。此外&#xff0c;会有消息闪现来提示用户登录成功。如果发生一个错误&#xff0c;模板会通知&#xff0c;并提示重新登录。

&#64;app.route(&#39;/login&#39;, methods&#61;[&#39;POST&#39;, &#39;GET&#39;])
def login():error &#61; Noneif request.method &#61;&#61; &#39;POST&#39;:if request.form[&#39;username&#39;] !&#61; app.config[&#39;USERNAME&#39;]:error &#61; &#39;Invalid username&#39;elif request.form[&#39;password&#39;] !&#61; app.config[&#39;PASSWORD&#39;]:error &#61; &#39;Invalid password&#39;else:session[&#39;logged_in&#39;] &#61; Trueflash(&#39;You were logged in&#39;)return redirect(url_for(&#39;show_entries&#39;))return render_template(&#39;login.teml&#39;, error&#61;error)

登出函数&#xff0c;做相反的事情。从会话中删除 logged_in 键。我们这里使用了一个简洁的方法: 如果你使用字典的 pop() 方法并传入第二个参数(默认)&#xff0c;这个方法会从字段中删除这个键&#xff0c;如果这个键不存在则什么都不做。这很有用&#xff0c;因为我们不需要检查用户是否已经登录。

&#64;app.route(&#39;/logout&#39;)
def logout():session.pop(&#39;logged_in&#39;, None)flash(&#39;You were logged out&#39;)return redirect(url_for(&#39;show_entries&#39;))

8. 登入登出

接下来我们应该创建模板了。如果我们现在请求 URL&#xff0c;只会得到 Flask 无法找到模板的异常。模板使用 Jinja2 语法并默认开启自动转义。这意味着除非你使用 Markup 标记或在模板中使用 |safe 过滤器&#xff0c;否则 Jinja2 会确保特殊字符&#xff0c;比如 <> 被转义为等价的 XML 实体。

我们也会使用模板继承在网站的所有页面中重置布局。

将下面的模板放在 templates 文件夹里:

8.1 layout.html

这个模板包含 HTML 主体结构、标题和一个登入链接(用户已登录则提供登出)。如果有&#xff0c;它也会显示闪现消息。{\% block body \%} 块可以被子模块中相同名字的块(body) 替换。

session 字典在模板中也是可用的。你可以用它来检查用户是否登入。注意&#xff0c;在 Jinja 中你可以访问不存在的对象/字典属性或成员。比如下面的代码&#xff0c;即便 logged_in 键不存在&#xff0c;仍然可以正常工作:


<title>Flaskrtitle>
<link rel&#61;"stylesheet" type&#61;"text/css" href&#61;"{{ url_for(&#39;static&#39;, filename&#61;&#39;style.css&#39;) }}">
<div class&#61;"page"><h1>Flaskrh1><div class&#61;"metanav">{% if not session.logged_in %}<a href&#61;"{{ url_for(&#39;login&#39;) }}">log ina>{% else %}<a href&#61;"{{ url_for(&#39;logout&#39;) }}">log outa>{% endif %}div>{% for message in get_flashed_message() %}<div clas&#61;"flash">{{ message }}div>{% endfor %}{% block body %}{% endblock %}
div>

8.2 show_entries.html

这个模板继承了上面的 logout.html 模板来显示消息。注意 for 循环会遍历并输出所有 render_template() 函数传入的消息。我们还告诉表单的 HTTP 的 POST 方法提交信息到 add_entry 函数:

{% extends "layout.html" %}
{% block body %}
{% if session.logged_in %}<form action&#61;"{{ url_for(&#39;add_entry&#39;) }}" method&#61;"post" class&#61;"add_entry"><dl><dt>Title: <dd><input type&#61;"text" size&#61;30 name&#61;"title">dd><dt>Text: <dd><textarea name&#61;"text" rows&#61;5 cols&#61;40>textarea>dd><dd><input type&#61;"submit" value&#61;"Share">dd>dl>form>
{% endif %}
<ul class&#61;"entries">{% for entry in entries %}<li><h2>{{ entry.title }}h2>{{ entry.text|safe }}li>{% else %}<li><em>Unbelievable. No entries here so farem>li>{% endfor %}
ul>
{% endblock %}

8.3 login.html

最后是登录模板&#xff0c;只是简单地显示一个允许用户登录的表单:

{% extends "layout.html" %}
{% block body %}<h2>Loginh2>{% if error %}<p class&#61;"error"><strong>Error: strong>{{ error }}p>{% endif %}<form action&#61;"{{ url_for(&#39;login&#39;) }}" method&#61;"post"><dl><dt>Usernname:<dd><input type&#61;"text" name&#61;"usernname">dd><dt>Password:<dd><input type&#61;"password" name&#61;"password">dd><dd><input type&#61;"submit" name&#61;"Login">dl>form>
{% endblock %}

9. 添加样式

现在其他的一切都可以正常工作&#xff0c;是时候给应用添加样式了。只需要在之前创建的 static 文件夹中创建一个名为 style.css 的样式表:

body { font-family: sans-serif; background: #eee; }
a, h1, h2 { color: #377BA8; }
h1, h2 { font-family: &#39;Georgia&#39;, serif; margin: 0; }
h1 { border-bottom: 2px solid #eee; }
h2 { font-size: 1.2em; }
.page { margin: 2em auto; width: 35em; border: 5px solid #ccc;padding: 0.8em; background: white; }
.entries { list-style: none; margin: 0; padding: 0; }
.entries li { margin: 0.8em 1.2em; }
.entries li h2 { margin-left: -1em; }
.add-entry { font-size: 0.9em; border-bottom: 1px solid #ccc; }
.add-entry d1 { font-weight: bold; }
.metanav { text-align: right; font-size: 0.8em; padding: 0.3em;margin-bottom: 1em; background: #fafafa; }
.flash { background: #CEE5F5; padding: 0.5em; border: 1px solid #AACBE2; }
.error { background: #F0D6D6; padding: 0.5em; }

10. 福利: 应用测试

现在你应该完成你的应用&#xff0c;并且一切都按预期运转正常&#xff0c;对于简化未来的修改&#xff0c;添加自动测试不是一个坏主意。上面的应用将作为文档中测试Flask应用节的例子来演示如何进行单元测试。去看看测试 Flask 应用是多么简单的一件事。


推荐阅读
  • 本文分享了一个关于在C#中使用异步代码的问题,作者在控制台中运行时代码正常工作,但在Windows窗体中却无法正常工作。作者尝试搜索局域网上的主机,但在窗体中计数器没有减少。文章提供了相关的代码和解决思路。 ... [详细]
  • 本文介绍了使用Java实现大数乘法的分治算法,包括输入数据的处理、普通大数乘法的结果和Karatsuba大数乘法的结果。通过改变long类型可以适应不同范围的大数乘法计算。 ... [详细]
  • 本文讨论了如何优化解决hdu 1003 java题目的动态规划方法,通过分析加法规则和最大和的性质,提出了一种优化的思路。具体方法是,当从1加到n为负时,即sum(1,n)sum(n,s),可以继续加法计算。同时,还考虑了两种特殊情况:都是负数的情况和有0的情况。最后,通过使用Scanner类来获取输入数据。 ... [详细]
  • 本文介绍了使用postman进行接口测试的方法,以测试用户管理模块为例。首先需要下载并安装postman,然后创建基本的请求并填写用户名密码进行登录测试。接下来可以进行用户查询和新增的测试。在新增时,可以进行异常测试,包括用户名超长和输入特殊字符的情况。通过测试发现后台没有对参数长度和特殊字符进行检查和过滤。 ... [详细]
  • 知识图谱——机器大脑中的知识库
    本文介绍了知识图谱在机器大脑中的应用,以及搜索引擎在知识图谱方面的发展。以谷歌知识图谱为例,说明了知识图谱的智能化特点。通过搜索引擎用户可以获取更加智能化的答案,如搜索关键词"Marie Curie",会得到居里夫人的详细信息以及与之相关的历史人物。知识图谱的出现引起了搜索引擎行业的变革,不仅美国的微软必应,中国的百度、搜狗等搜索引擎公司也纷纷推出了自己的知识图谱。 ... [详细]
  • Spring特性实现接口多类的动态调用详解
    本文详细介绍了如何使用Spring特性实现接口多类的动态调用。通过对Spring IoC容器的基础类BeanFactory和ApplicationContext的介绍,以及getBeansOfType方法的应用,解决了在实际工作中遇到的接口及多个实现类的问题。同时,文章还提到了SPI使用的不便之处,并介绍了借助ApplicationContext实现需求的方法。阅读本文,你将了解到Spring特性的实现原理和实际应用方式。 ... [详细]
  • 个人学习使用:谨慎参考1Client类importcom.thoughtworks.gauge.Step;importcom.thoughtworks.gauge.T ... [详细]
  • 从零学Java(10)之方法详解,喷打野你真的没我6!
    本文介绍了从零学Java系列中的第10篇文章,详解了Java中的方法。同时讨论了打野过程中喷打野的影响,以及金色打野刀对经济的增加和线上队友经济的影响。指出喷打野会导致线上经济的消减和影响队伍的团结。 ... [详细]
  • 猜字母游戏
    猜字母游戏猜字母游戏——设计数据结构猜字母游戏——设计程序结构猜字母游戏——实现字母生成方法猜字母游戏——实现字母检测方法猜字母游戏——实现主方法1猜字母游戏——设计数据结构1.1 ... [详细]
  • [大整数乘法] java代码实现
    本文介绍了使用java代码实现大整数乘法的过程,同时也涉及到大整数加法和大整数减法的计算方法。通过分治算法来提高计算效率,并对算法的时间复杂度进行了研究。详细代码实现请参考文章链接。 ... [详细]
  • 前面刚有AWS开战MongoDB,双方“隔空互呛”,这厢又曝出2亿+简历信息泄露——MongoDB的这场开年似乎“充实”得过分了些。长期以来,作为“最受欢迎的NoSQL数据库”,M ... [详细]
  • 什么是堡垒机?堡垒机是一个主机系统,其自身通常经过了一定的加固,具有较高的安全性,可抵御一定的攻击,其作用主 ... [详细]
  • 本文介绍了lua语言中闭包的特性及其在模式匹配、日期处理、编译和模块化等方面的应用。lua中的闭包是严格遵循词法定界的第一类值,函数可以作为变量自由传递,也可以作为参数传递给其他函数。这些特性使得lua语言具有极大的灵活性,为程序开发带来了便利。 ... [详细]
  • Java工程师书单(初级,中级,高级)
    简介怎样学习才能从一名Java初级程序员成长为一名合格的架构师,或者说一名合格的架构师应该有怎样的技术知识体系,这是不仅一个刚刚踏入职场的初级程序员也是工作一两年之后开始迷茫的程序 ... [详细]
  • 篇首语:本文由编程笔记#小编为大家整理,主要介绍了记录一次MySQL两千万数据的大表优化解决过程,提供三种解决方案相关的知识,希望对你有一定的参考价值。 ... [详细]
author-avatar
彩色蜗牛
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有