唐抉的个人博客

Flask Web开发学习笔记(二)

字数统计: 3.3k阅读时长: 14 min
2022/10/20

创建应用教程

创建文件夹

先创建应用所需的文件夹,接下来会直接把数据库模式和主模块放在这个目录中。用户可以通过HTTP访问static文件夹中的文件,即存放css和javascript文件的地方:

1
2
3
/tutorweb
/static
/templates

创建数据库模式

创建schema.sql文件,文件里编写以下内容,放到tutorweb文件中:

1
2
3
4
5
6
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是一个自增的整数,也是主键,其余的两个是字符串且不允许为空。

应用设置代码

创建应用模块

创建应用的模块tutorweb.py并放置在tutorweb目录下。从添加所需的导入语句和添加配置部分开始。对于小型应用,可以直接把配置放在主模块里,但更简洁的方案是创建独立的.ini.py文件,并载入里面的值。

首先在tutorweb.py里导入内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#导入所有模块
import os
import sqlite3
from flask import Flask,request,session,g,redirect,url_for,abort,\
render_template,flash

#配置文件
DATABASE='./sqlite_db'
USERNAME='admin'
PASSWORD='default'
DEBUG=True

#创建应用
app=Flask(__name__)
app.config.from_object(__name__)
app.secret_key='develop key'

数据库路径

操作系统有进程当前工作目录的概念,但由于在Web应用中相同的进程运用多个应用,因此不能依赖此概念。Flask提供了app.root_path属性以获取应用的路径,配合os.path模块的使用,可以轻松到达任意文件。在本例中,数据库放在根目录下。

通常只能加载一个单独的、环境特定的配置文件。Flask中使用from_envvar()方法可以实现允许导入多份配置文件,并使用最后的导入的设置。

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

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

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

secret_key是保证客户端会话安全的重要所在。尽量选择一个尽可能难猜测、尽可能复杂的密钥。

调试标志关系交互式调试器的开启。永远不要在生产系统中激活调试模式,因为它将允许用户在服务器上执行代码。

添加一个连接到指定数据的方法是,用于请求时开启一个数据库连接,并在交互式Python shell和脚本中也能使用。在tutorweb.py文件中创建一个简单的SQLite数据库的连接,并让它用sqlite3.Row表示数据库中的行,使得可以通过字典而不是元组的形式访问行:

1
2
3
4
5
def connect_db():
#连接特定的数据库
rv=sqlite3.connect(app.config['DATABASE'])
rv.row_factory=sqlite3.Row
return rv

最后,若想要把这个文件当作独立应用来运行,只需在可启动服务器文件tutorweb.py的末尾加上这行代码即可运行这个应用:

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

此时在浏览器中访问服务器会遇到404 page not found错误,这是因为还没有创建任何视图的缘故。在创建视图之前,应该先让数据库工作起来。

数据库连接

Flask提供了两种环境:应用环境和请求环境。不同环境有不同的特殊变量。例如request变量与当前请求的请求对象有关,而g是当前应用环境有关的通用变量。

在tutorweb.py文件中可以写一个辅助函数把数据库连接存放在g对象上。这个函数首次调用时会为当前的函数创建一个数据库连接,调用成功后返回已经建立好的连接:

1
2
3
4
5
def get_db():
#打开一个新数据库连接
if not hasattr(g,'sqlite_db'):
g.sqlite_db=connect_db()
return g.sqlite_db

Flask提供了teardown_appcontext()装饰器来断开数据库连接,它将在每次应用环境销毁时执行:

1
2
3
4
5
@app.teardown_appcontext
def close_db(error):
#断开数据库连接
if hasattr(g,'sqlite_db'):
g.sqlite_db.close()

本质上,应用环境在请求传入前创建,每当请求结束时销毁。销毁有两种原因:一切正常(错误参数是None)或发送异常,后者错误会被传递给销毁函数。

创建数据库

Flask是一个由关系数据库驱动的应用。关系数据库系统需要一个模式来决定存储信息的方式。

创建模式

可以通过管道把schema.sql作为sqlite3命令的输入来创建这个模式:

1
sqlite3 /tmp/flaskr.db < schema.sql

这种方法的缺点是需要安装sqlite3命令,且必须提供数据库的路径,否则会报错。因此比较推荐使用函数来初始化数据库。

创建一个名为init_db的函数来初始化数据库,只需要把这个函数放在tutorweb.py里的connect_db函数后面并调用init_db()函数来创建数据库:

1
2
3
4
5
6
7
8
9
def init_db():
#建立应用环境,在with内部,g对象与app关联,在语句结束出会释放这个关联并执行所有销毁函数,即数据库连接会在提交后断开
with app.app_context():
db=get_db()
#open_resource可以打开应用提供的资源,在tutorweb文件夹打开文件并允许读取它,用它来在数据库连接上执行脚本
with app.open_resource('schema.sql',mode='r') as f:
db.cursor().executescript(f.read())
db.commit()
init_db()

应用环境在每次请求传入时创建,这里并没有请求,故需要手动创建一个应用环境。g在应用环境外无法获知它属于哪个应用,因为可能会有多个应用同时存在。

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

视图函数

数据库连接正常工作后,共需要写四个视图函数。

显示条目

这个视图显示数据库中存储的所有条目,它绑定在应用的根地址,并从数据库查询出文章的标题和正文。id值最大的条目(最新的条目)会显示在最上方。从指针返回的行是按select语句中声明的列组织元组。

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

1
2
3
4
5
6
7
@app.route('/') 
def show_entries():
#显示条目
g.db=get_db()
cur=g.db.execute('select title, text from entries order by id desc')
entries=[dict(title=row[0],text=row[1]) for row in cur.fetchall()]
return render_template('show_entries.html',entries=entries)

添加条目

这个视图允许已登入的用户添加新条目,并只响应POST请求,实际的表单显示在show_entries页。若一切工作正常,用flash()向下一次请求发送提示消息,并重定向回show_entries页:

1
2
3
4
5
6
7
8
9
10
11
@app.route('/add',methods=['POST'])
def add_entry():
#添加条目
g.db=get_db()
if not session.get('logged_in'):
abort(401)
g.db.execute('insert into entries (title,text) values (?,?)',
[request.form['title'],request.form['text']])
g.db.commit()
flash('New entry was successfully posted')
return redirect(url_for('show_entries'))

注意这里的用户登入检查(logged_in键在会话中存在且为True)

安全提示:

确保像上面代码一样使用问号标记来构建SQL语句。否则当使用格式化字符串构建SQL语句时,所建立的应用容易遭受SQL注入。

登入和登出

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@app.route('/login',methods=['GET','POST'])    
def login():
#登入和登出
error=None
if request.method=='POST':
if request.form['username']!=app.config['USERNAME']:
error='Invalid username'
elif request.form['password']!=app.config['PASSWORD']:
error='Invalid password'
else:
session['logged_in']=True
flash('You were logged in')
return redirect(url_for('show_entries'))
return render_template('login.html',error=error)

这个函数用来让用户登出。从会话中删除logged_in键。使用字典的pop()方法并传入第二个参数(默认),以从字典删除这个键,若这个键不存在则什么都不做。

1
2
3
4
5
6
@app.route('/logout')
def logout():
#登出
session.pop('logged_in',None)
flash('You were logged out')
return redirect(url_for('show_entries'))

模板

若现在请求URL,只会得到Flask无法找到模板的异常。模板使用Jinja2语法并默认开启自动转义。这意味着除非使用Markup标记或在模板中使用|safe过滤器,否则Jinja2会确保特殊字符,比如<>被转义为等价的XML实体。

layout.html

将下面的模板放进templates文件夹里。这个模板包含HTML主体结构、标题和一个登入链接(用户已登入则提供登出)。若有,它会显示闪现消息。

1
{% block body %}

模块可以被子模版中相同名字的块(body)替换。

session字典在模板中也是可用的,可以用它来检查用户是否已登入。在Jinja中你可以访问不存在的对象、字典属性或成员

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<!doctype html>
<title>tutorweb</title>
<link rel=stylesheet type=text/css href="{{url_for('static',filename='style.css')}}">
<div class=page>
<h1>tutorweb</h1>
<div class=metanav>
{% if not session.logged_in %}
<a href="{{url_for('login')}}">log in</a>
{% else %}
<a href="{{url_for('logout')}}">log out</a>
{% endif %}
</div>
{% for message in get_flashed_messages() %}
<div class=flash>{{message}}</div>
{% endfor %}
{% block body %}{% endblock %}
</div>

show_entries.html

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
{% extends "layout.html" %}<!--模板继承,继承layout.html的内容-->
{% block body %}
{% if session.logged_in %}<!--初次登录,session.logged_in为假,下面的内容不显示-->
<form action="{{url_for('add_entry')}}" method=post class=add-entry>
<dl>
<dt>Title:
<dd><input type=text size=30 name=title>
<dt>Text
<dd><textarea name=text rows=5 cols=40></textarea>
<dd><input type=submit value=Share>
</dl>
</form>
{% endif %}
<ul class=entries>
{% for entry in entries %}
<li><h2>{{entry.title}}</h2>{{entry.text|safe}}
{% else %}<!--此次登录默认显示以下内容-->
<li><em>Unbelievable. No entries here so far</em>
{% endfor %}
</ul>
{% endblock %}

login.html

最后是登入模板,简单地显示一个允许用户登入的表单:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
{% extends "layout.html" %}<!--模板继承layout.html的内容-->
{% block body %}<!--重载-->
<h2>Login</h2>
<!--error不为None时显示出错误原因-->
{% if error %}<p class=error><strong>Error:</strong>{{error}}{% endif %}
<form action="{{url_for('login')}}" method=post><!--action后是form表单提交的地址-->
<dl>
<dt>Username:
<dd><input type=text name=username>
<dt>Password
<dd><input type=password name=password>
<dd><input type=submit value=Login>
</dl>
</form>
{% endblock %}

添加样式

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
body{
font-family:sans-serif;
background:#eee;
}

a,h1,h2{
color:#377BAB;
}
h1,h2{
font-family:'Georgia',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 dl{
font-weight:bold;
}
.metanav{
text-align:right;
font-size:0.8em;
padding:0.3em;
margin-bottom:1em;
background:#fafafa;
}
.toturweb{
background:#CEE5F5;
padding:0.5em;;
border:1px solid #AACBE2;
}
.error{
background:#F0D6D6;
padding:0.5em;
}

最后运行tutorweb.py程序即可在127.0.0.0:5000上看到创建的应用。

CATALOG
  1. 1. 创建应用教程
    1. 1.1. 创建文件夹
    2. 1.2. 创建数据库模式
    3. 1.3. 应用设置代码
      1. 1.3.1. 创建应用模块
      2. 1.3.2. 数据库路径
    4. 1.4. 数据库连接
    5. 1.5. 创建数据库
      1. 1.5.1. 创建模式
    6. 1.6. 视图函数
      1. 1.6.1. 显示条目
    7. 1.7. 添加条目
      1. 1.7.1. 安全提示:
    8. 1.8. 登入和登出
    9. 1.9. 模板
      1. 1.9.1. layout.html
      2. 1.9.2. show_entries.html
      3. 1.9.3. login.html
    10. 1.10. 添加样式