@coder-pig
2019-01-31T12:08:29.000000Z
字数 24136
阅读 1876
2019
在上一节中,我们编写了抓取新闻的爬虫脚本,每天早上八点定时抓取新闻保存到
MySQL数据库中。直接用DataGrip连下数据库,就可以查看爬取到的新闻了。不过,
并不是我们想要的最终形态。我希望新闻筛选的工作可以直接在手机上进行,而不是
每次都要打开电脑,打开DataGrip黑乎乎的页面,筛选,复制粘贴。在写这个APP之前,
要先学点web的东东,写点接口。Python中有比较流行的两个Flask和Django,笔者选择
的比较轻量级的Flask。在写《如何用Python投机倒把几天“暴富”》,有些同学私信我有
没有Flask的教程推荐。索性本节就来过一过Flask,下一节再来利用Flask来写API接口和
静态页面,以及直接生成公号文章样式。内容较多,比较枯燥,建议先收藏后慢慢观看~
一个轻量级的“微内核”Web应用框架,基于Werkzeug实现的WSGI套件和Jinja2模板引擎,
内核精简,易于扩展,花费较少的学习成本,即可能开发一个简单的网站。
相关文档:
- Flask官网:http://flask.pocoo.org/
- Flask Github仓库:https://github.com/pallets/flask
- Flask官方文档:http://flask.pocoo.org/docs/1.0
- Flask官方文档(中文版):https://dormousehole.readthedocs.io/en/latest/
- Flask学习资源:https://github.com/humiaozuzu/awesome-flask
直接通过pip命令安装即可:
pip install flask
这里要注意「全局安装」和「虚拟环境安装」的区别,之前有很多读者都问过这样的问题:
我明明在命令行里已经执行了pip install xxx库,但是进去pycharm,还是提示模块找不到?
对此,有两种可选的解决方案:
个人更倾向于第一种,为了解决维护不同项目对应不同版本的问题,Python使用了虚拟环境的概念,
在虚拟环境中安装第三方包只会作用到虚拟环境中,全局的Python解释器不受影响。在Python3中,
虚拟环境已成为一个内置模块,创建一个带虚拟环境的文件示例如下:
mkdir Test
cd Test
python -m venv venv
执行完上述命令后,Python会运行venv包,创建一个venv的虚拟环境,上面的两个venv参数分别为:
虚拟环境创建后,需要激活后才能进入,通过下述命令「激活虚拟环境」:
source venv/bin/activate
执行完后会看到终端前缀多了个venv,激活虚拟环境后,终端会话的环境配置就会被修改,
此时键入Python或者pip,实际上调用的都是虚拟环境中的Python解释器。一个应用场景:
打开多个终端调试多个应用,每个终端窗口可以激活不同的虚拟环境,且不相互干扰。
注:如果你使用的是Python2或者Windows系统,如果想使用虚拟环境,要先通过pip命令
安装先安装一波virtualenvwrapper:pip install virtualenvwrapper。然后创建虚拟环境:
virtualenv venv,最后是激活虚拟环境:venv\Scripts\activate.bat。
新建一个hello.py的脚本,内容如下:
# coding=utf-8
from flask import Flask
app = Flask(__name__)
@app.route("/")
def hello():
return "Hello Flask!"
if __name__ == "__main__":
app.run()
在终端键入下述命令执行脚本:
python hello.py
运行后可以看到输出如下信息:
浏览器打开:http://127.0.0.1:5000,可以看到如图所示的Hello Flask!
服务启动后就会进入轮询,等待并处理请求,知道程序被终止,也可以直接按Ctrl+C停掉。
接着逐行解析一波代码:
__name__
, 该模块的作用是:通过命令行的形式来操作Flask,使用命令行传参,而不仅仅是通过
app.run()函数来传。直接通过pip命令即可安装此模块:
pip install flask-script
新建一个manage.py的文件:
from flask_script import Manager
from flask import Flask
app = Flask(__name__)
manager = Manager(app)
if __name__ == '__main__':
manager.run()
接着命令行键入:
python manage.py runserver -h ip -p 端口
除此之外的参数还有:-d(开启调试模式),-r(代码修改后自动加载),--help(查看帮助信息)。
上面这种:
@app.route("/")
def hello():
return "Hello Flask!"
定义了URL到Python函数间的映射关系,这种映射关系就叫路由。
有时,可能有一些具有相同规则的URL,比如:
app.route("/login")
app.route("/register")
我们可以把这类URL抽象成一个URL模式,示例如下:
@app.route('/<do>')
def hello(do):
return "<h1>当前请求:%s </h1>" % do
Flask支持这种动态形式的路由,动态部分默认是字符串,你也可以指定参数类型,
比如只接受整数:<int:id>,更多规则如下表所示:
字段标记 | 描述 | 示例 |
---|---|---|
string | 默认,接受任何没有斜杆"/"的文本 | <string:name> |
int | 整数 | <in:id> |
float | 浮点数 | <float:price> |
path | 和string类似,但也接受斜杠 | <path:address> |
uuid | 只接受uuid字符串 | <string:uuid> |
any | 可以指定多种路径,但需要传入参数 | <any(int,string):params> |
另外有一点要注意:唯一URL,比如下面代表的是两个不同的URL:
CodingBoy.io/article/
CodingBoy.io/article
在Flask中,可以使用url_for()函数来构造URL,接受一个视图函数名作为第一个参数,
也接受对应URL规则变量部分的命名参数,未知变量部分会被添加到URL末尾作为查询
参数。这里务必注意一点:操作的是函数,不是路由里的路径!!!!
通过函数构建,而不是直接用字符串拼接,主要出于下面两个目的:
使用示例如下:
with app.test_request_context():
print(url_for('hello', uid=1))
print(url_for('hello', uid=2, kind='测试'))
输出结果如下:
/?uid=1
/?uid=2&kind=%E6%B5%8B%E8%AF%95
如果你想生成一个绝对路径,可以添加「_external=True」参数。
注:test_request_context可以在交互模式下产生请求上下文,不用app.run()
来运行这个项目,直接执行也会有Falsk上下文
HTTP支持多种请求方法,默认情况下,路由只响应GET请求,如果不信可以自行用
PostMan对接口发起一个POST请求,结果如图所示:
响应码是405,表示不允许使用这种请求方法请求此URL,可以直接在
app.route装饰器中设置methods参数来修改。修改后的代码示例如下:
@app.route("/", methods=['GET', 'POST'])
def hello():
return "Hello Flask!"
当然也支持其他的请求方法:PUT,HEAD,DELETE,OPTIONS,PATCH,想支持多种方法用逗号隔开即可。
在Web开发中,我们经常会用到模板引擎,什么是模板?就是一个包含响应文本的文件,
用占位符(变量)标识动态部分,告诉模板引擎,其具体值需要从使用的数据中获取。
在前面的例子中,视图函数的主要作用是生成请求的响应,而实际开发中,视图函数有
两个作用:「处理业务逻辑」和「返回响应内容」。在大型项目中,如果把业务逻辑
和表现内容放在一起的话,会增加代码的复杂度和维护成本。而模板的作用就是:
「承担视图函数返回响应内容这一部分的职责,从而使得代码结构清晰,耦合度低。」
使用真实值替换变量,再(控制)返回最终得到的字符串,这个过程称作「渲染」。
Flask中默认使用「Jinja2」这个模板引擎来渲染模板。
通过一个简单的例子,引入模板,下面是一个没有使用模板的简单程序:
# coding=utf-8
from flask import Flask
app = Flask(__name__)
@app.route("/<user>")
def hello(user):
if user == 'admin':
return "<h1>管理员,您好!<h1>"
else:
return "<h1>%s, 您好!</h1>" % user
if __name__ == "__main__":
app.run()
接着我们使用Jinja2模板进行改写,默认情况下,Flask会在「项目的templates子文件夹」
中寻找模板,我们新建一个templates文件夹,然后新建一个index.html,内容如下:
"<h1>{{name}},您好!<h1>"
接着修改下hello.py文件,修改后的内容如下:
from flask import Flask, render_template
app = Flask(__name__)
@app.route("/<user>")
def hello(user):
if user == 'admin':
return render_template("index.html", user="管理员")
else:
return render_template("index.html", user=user)
if __name__ == "__main__":
app.run()
接着运行hello.py,浏览器键入以下地址,对应输出结果如下:
http://127.0.0.1:5000/admin # 输出结果:管理员,您好!
http://127.0.0.1:5000/jay # 输出结果:jay,您好!
以上就是一个简单的模板使用示例,通过调用Flask提供的render_template函数,
生成了一个模板,第一个参数是模板的名称,随后的参数是键值对,表示模板中
变量对应的真实值。接着详细讲解一波Jinja2的语法:
Jinja2使用{{变量名}}
来表示一个变量,除了基本数据类型外还可以使用列表,字段
或对象等复杂类型,示例如下:
<h1> 账号:{{ user['name'] }},密码:{{ user['passwd'] }}
Jinja2提供了多种控制结构,比如常见的判断和循环结构,用于修改模板的渲染流程。
我们把上面判断的逻辑也放到模板里,示例如下:
{# 注释,不会输出到浏览器中 #}
{% if user == 'admin' or user == '管理员' %}
<h1> 管理员,您好! </h1>
{% else %}
<h1> {{ user }},您好!</h1>
{% endif %}
{# 循环打印 #}
{% for num in num_list %}
<h2>{{ num }}</h2>
{% endfor %}
接着修改下hello.py文件:
@app.route("/<user>")
def hello(user):
return render_template("index.html", user=user, num_list=[1, 2, 3])
键入不同的URL,对应浏览器的输出结果如下:
http://127.0.0.1:5000/admin
管理员,您好!
1
2
3
http://127.0.0.1:5000/jay
jay,您好!
1
2
3
在Python中如果有一些很常用的代码,我们会习惯性地抽取成一个函数,在Jinji2中,
可以使用宏来实现。语法如下:
# 创建宏
{% macro 标签名(key=value) %}
常用代码
{% end macro %}
# 调用宏
{{ 标签名(key=value) }}
如果宏比较多,可以抽到单独的HTML中,再import进来。
{% import 'xxx.html' as 别名 %}
{{ 别名.标签名(key=value) }}
另一种代码复用的方式:模板继承,和Python中类继承一样,需要一个基模板,用
{% block XXX %}{% endblock %}
标识一个代码块,可以在子模块中重载。
代码示例如下:
# 基模板:base.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
{% block head %}
<title>{% block title %} Title {% endblock %} </title>
{% endblock %}
</head>
<body>
{% block body %}
{% endblock %}
</body>
</html>
# 子模板:son.html
{% extends "base.html" %}
{% block title %}Son{% endblock %}
{% block head %}
{{ super() }}
{% endblock %}
{% block body %}
<h1>Hello Flask!</h1>
{% endblock %}
代码简述:
第一行代码使用extends命令,表明该模板继承自base.html,接着重写了title,head和
body代码块,上面的super()函数用于获取基模板原先的内容。
可以用include语句包含一个模板,在渲染时会把include语句对应位置添加被包含的模板,
使用示例如下:
{% include 'header.html' %}
另外还可以使用「ignore missing」标记,如果模板不存在,Jinja2会忽略此语句,示例如下:
{% include 'header.html' ignore missing %}
可以使用set标签来进行赋值,示例如下:
{% set a,b = 1,2}
<h1>{{ a }}{{ b }}</h1>
过滤器的本质就是函数,有时需要修改,格式化或者执行变量间的计算,而在模板中是不能
直接调用Python中的某些方法的,可以用到过滤器。模板的过滤器支持链式调用:
{{ "hello flask" | reverse | upper}},
这串代码就是反转+转换成大写,常见的内建过滤器有字符串和列表两种。
字符串操作过滤器如下表所示:
过滤器 | 描述 |
---|---|
safe | 禁用转义 |
capitalize | 把变量值的首字母转成大写,其余字母转小写 |
lower | 把值转成小写 |
upper | 把值转成大写 |
title | 把值中的每个单词的首字母都转成大写 |
reverse | 字符串反转 |
format | 格式化输出 |
striptags | 渲染之前把值中所有的HTML标签都删掉 |
truncate | 字符串截断 |
列表操作过滤器如下表所示:
过滤器 | 描述 |
---|---|
first | 取第一个元素 |
last | 取最后一个元素 |
length | 获取列表长度 |
sum | 列表求和 |
sort | 列表排序 |
直接在py文件中编写,代码示例如下:
# 1.定义过滤器
def do_listreverse(li):
temp_li = list(li)
temp_li.reverse()
return temp_li
# 2.添加自定义过滤器
app.add_template_filter(do_listreverse, 'listreverse')
总结:
宏,继承和包含都用实现代码复用,宏功能类似于函数,可以传参,需要定义调用;
继承本质是代码替换,一般用来实现多个页面中重复不变的区域;而包含是直接将
目标模板文件整个渲染出来。
在Flask中,HTTP请求被封装成了Request对象,而HTTP响应则被封装成了Response对象。
因此Flask应用开发的逻辑处理,都是基于这两个对象。
Flask会将WSGI服务器转发的http请求数据封装为一个Request对象,这个对象中包含了请求的
相关信息,可以通过下表中的属性来获取对应的信息。
属性 | 描述 | 数据类型 |
---|---|---|
form | 记录请求中的表单数据。 | MultiDict |
args | 记录请求中的查询参数。 | MultiDict |
cookies | 记录请求中的cookie。 | Dict |
headers | 记录请求中的报文头。 | EnvironHeaders |
method | 记录请求使用的HTTP方法。 | string |
environ | 记录WSGI服务器转发的环境变量。 | Dict |
url | 记录请求的URL地址。 | string |
- 1.读取request的查询参数
浏览器以GET请求的方式提交的表单数据,Flask会将其存储在request实例的args,也可以用values属性
来查询,读取代码示例如下:
# coding=utf-8
from flask import Flask, request, json
app = Flask(__name__)
@app.route("/index")
def index():
return '''
<form method="GET" action="/login">
<input type="text" placeholder="账号" name="user"> <br />
<input type="text" placeholder="密码" name="pawd"> <br />
<input type="submit" value="登录">
</form>
'''
@app.route("/login")
def login():
msg = ""
if 'user' in request.args:
msg += request.args['user'] + ':'
msg += request.values.get('pawd','')
return msg
if __name__ == "__main__":
app.run(debug=True)
代码运行后,打开:http://127.0.0.1:5000/index,浏览器:
输入账号密码后,跳转到:http://127.0.0.1:5000/login?user=zpj&pawd=123, 浏览器:
浏览器以GET请求的方式提交的表单数据,Flask会将其存储在request实例的form中,可以使用[]操作符
读取指定键值。读取代码示例如下:
# coding=utf-8
from flask import Flask, request, json
app = Flask(__name__)
@app.route("/index")
def index():
return '''
<form method="POST" action="/login">
<input type="text" placeholder="账号" name="user"> <br />
<input type="text" placeholder="密码" name="pawd"> <br />
<input type="submit" value="登录">
</form>
'''
@app.route("/login", methods=['POST'])
def login():
msg = ""
msg += request.form['user'] + ':'
msg += request.form['pawd']
return msg
if __name__ == "__main__":
app.run(debug=True)
代码运行后,打开:http://127.0.0.1:5000/index,浏览器:
输入账号密码后,跳转到:http://127.0.0.1:5000/login,浏览器:
和Request对应,Response用于给浏览器发送响应信息,根据视图函数的返回结果。这个视图函数
就是我们路由下面的函数,视图函数的返回值会自动转换成一个响应对象,转换逻辑如下:
def hello():
resp = make_response("Hello Flask!", 250)
return resp
另外,现在的API接口都是返回JSON格式的,可以用jsonify包装下,修改后的示例代码如下:
# coding=utf-8
from flask import Flask, make_response, jsonify
from werkzeug.wrappers import Response
app = Flask(__name__)
@app.route("/", methods=['GET', 'POST'])
def hello():
resp = make_response({'code': '200', 'msg': '请求成功', 'data': [{'data_1': ['数据', '数据'], 'data_2': ['数据', '数据']}]})
return resp
class JsonResponse(Response):
@classmethod
def force_type(cls, response, environ=None):
if isinstance(response, dict):
response = jsonify(response)
return super(JsonResponse, cls).force_type(response, environ)
app.response_class = JsonResponse
if __name__ == "__main__":
app.run(debug=True)
请求接口输出如下:
{
"code": "200",
"data": [
{
"data_1": [
"数据",
"数据"
],
"data_2": [
"数据",
"数据"
]
}
],
"msg": "请求成功"
}
也可以用Flask的json模块的dumps()函数将数组或字典对象转换为JSON字符串,代码示例如下:
data = {'code': '200', 'msg': '请求成功', 'data': [{'data_1': ['数据', '数据'], 'data_2': ['数据', '数据']}]}
return json.dumps(data),200,[('Content-Type','application/json;charset=utf-8')]
set_cookie(
key, //键
value='', //值
max_age=None, //秒为单位的cookie寿命,None表示http-only
expires=None, //失效时间,datetime对象或unix时间戳
path='/', //cookie的有效路径
domain=None, //cookie的有效域
secure=None,
httponly=False)
Web开发中经常需要处理重定向和会话,Flask中内建了「redirect」和「session」来对它们进行处理。
页面重定向非常常见,最常用的就是登陆状态判断,如果没登陆将网页重定向到登录页,Flask中可以使用redirect
对象对其进行处理,状态码默认为302,可以传入code参数来修改,一般是:301,302,303,305和307,
简单的代码示例如下:
# coding=utf-8
from flask import Flask, redirect
app = Flask(__name__)
user_name = ''
@app.route('/article')
def article():
if user_name == '':
# 如果用户名为空,重定向跳转到登录页
return redirect('/login')
else:
return "文章页"
@app.route("/login")
def login():
global user_name
user_name = 'admin'
return "登录成功!"
if __name__ == "__main__":
app.run(debug=True)
运行后操作流程如下:
浏览器键入:http://127.0.0.1:5000/article
自动跳转到:http://127.0.0.1:5000/login,显示登录成功
再次访问:http://127.0.0.1:5000/article,显示文章页
我们可以把数据存储在用户会话(session)中,用户会话是一种私有存储,默认情况下保存在客户端cookie中。
会话主要为了解决两个问题:「访问者的标识和访问者信息记录」。浏览器第一次访问服务器,服务器在其
cookie中设置一个唯一的会话ID,浏览器后续对服务器的访问头中将自动包含该信息,服务器通过这个ID号来
区分不同的访问者。session依赖于cookie,一般存储在服务器,Flask提供了session对象来操作用户会话,
可以使用[]操作符读取或者设置指定键值,默认情况下,Flask将会话对象加密后存储在客户端的cookie里,
因此必须要应用实例的secret_key属性配置一个加密种子才能使用session。用法示例如下:
# 设置session
session['name'] = 'jay'
# 读取session
session.get('name')
# 配置加密种子(两种方法二选一)
app.secret_key = '123456'
app.config['SECRET_KEY']='123456'
静态文件就是那些不会被改变的文件,例如:图片,CSS样式文件,JavaScript脚本文件和字体文件等。
Flask默认会在根目录中名为static的子目录中寻找静态文件,所以如果需要用到静态文件可以创建一个
static的文件夹,然后把静态文件丢里面。可以参考下面这样的结构来组织项目:
static/
css/
lib/
bootstrap.css
style.css
home.css
js/
lib/
jquery.js
chart.js
home.js
img/
logo.svg
favicon.ico
另外,不要在模板中写死静态文件路径,应该使用url_for函数生成路径,示例如下:
url_for('static', filename='css/style.css')
当然,如果你想修改静态文件的真实目录,可以在Flask构造函数中传入参数:static_folder='文件夹名'。
另外,为了获得更好的处理能力,建议使用Nginx或其他Web服务器管理静态文件,图片这类资源可以
托管到CDN平台上。(比如七牛云)
蓝图(Blueprint),定义了可用于单个应用的视图,模板,静态文件等等的集合。通俗点理解就是
一个实现应用模块化的好工具,使用蓝图能使得项目层次更加清晰,更易于开发和维护,通常作用于
相同URL前缀的路由。先来看一个没使用蓝图的示例:
# coding=utf-8
from flask import Flask
app = Flask(__name__)
@app.route('/user/index')
def user_index():
return 'user_index'
@app.route('/user/show')
def user_show():
return 'user_show'
@app.route('/user/add')
def user_add():
return 'user_add'
@app.route('/admin/index')
def admin_index():
return 'admin_index'
@app.route('/admin/show')
def admin_show():
return 'admin_show'
@app.route('/admin/add')
def admin_add():
return 'admin_add'
if __name__ == "__main__":
app.run(debug=True)
上面的代码挺整齐的,不过有几个问题:
我们使用蓝图来把user和admin拆分成两个不同的.py文件,admin.py 文件内容如下:
# coding=utf-8
from flask import Blueprint
admin = Blueprint('admin', __name__,url_prefix='/admin')
@admin.route('/index')
def admin_index():
return 'admin_index'
@admin.route('/show')
def admin_show():
return 'admin_show'
@admin.route('/add')
def admin_add():
return 'admin_add'
user.py 文件内容如下:
# coding=utf-8
from flask import Blueprint
user = Blueprint('user',__name__)
@user.route('/index')
def user_index():
return 'user_index'
@user.route('/show')
def user_show():
return 'user_show'
@user.route('/add')
def user_add():
return 'user_add'
注册蓝图,hello.py的代码内容如下:
# coding=utf-8
from flask import Flask
from admin import *
from user import *
app = Flask(__name__)
app.register_blueprint(admin)
app.register_blueprint(user, url_prefix='/user')
if __name__ == "__main__":
print(app.url_map)
app.run(debug=True)
利用app.url_map函数,查看所有的路由,打印结果如下:
Map([<Rule '/admin/index' (GET, HEAD, OPTIONS) -> admin.admin_index>,
<Rule '/admin/show' (GET, HEAD, OPTIONS) -> admin.admin_show>,
<Rule '/admin/add' (GET, HEAD, OPTIONS) -> admin.admin_add>,
<Rule '/user/index' (GET, HEAD, OPTIONS) -> user.user_index>,
<Rule '/user/show' (GET, HEAD, OPTIONS) -> user.user_show>,
<Rule '/user/add' (GET, HEAD, OPTIONS) -> user.user_add>,
<Rule '/static/<filename>' (GET, HEAD, OPTIONS) -> static>])
url_prefix
这个参数用于设置request.url
中的url前缀,另外只有满足前缀的请求才会
通过注册的蓝图的视图方法处理请求并返回。可以写在子模块中,也可以在register_blueprint
注册蓝图的时候传入,只需传入一次!之后打开http://127.0.0.1:5000/admin/index,可以看到
如图所示的结果:
有时在处理请求前后,执行某些特定代码是非常有用的,这就用到了请求钩子,比如:请求前创建db链接,
验证用户身份等,flask提供了注册通用函数的功能,只需要写一个请求钩子——函数,整个程序实例全局都被应用,
比如请求前验证用户状态的例子,没登陆跳转登录页面等。钩子函数需要借助Flask的全局变量g,g作为中间变量,
在钩子函数和视图函数间传递数据。
g,global,g对象是专门用来保存用户数据的,存储的数据在全局都可以使用。代码示例如下:
from flask import g, request
@app.route('/')
def index():
user = request.args.get('user')
g.user = user # 保存用户数据
Flask提供下述四种钩子函数:
写一个程序来验证下钩子函数的执行流程(运行后,访问两次):
127.0.0.1 - - [30/Aug/2018 10:53:42] "GET / HTTP/1.1" 200 -
before_first_request
before_request
after_request
teardown_request
127.0.0.1 - - [30/Aug/2018 10:53:45] "GET / HTTP/1.1" 200 -
before_request
after_request
teardown_request
上下文相当于一个容器,保存了Flask程序运行过程中的一些信息,根据管理机制分为两种:
请求上下文(RequestContext)
Request:请求的对象,封装了Http请求的内容;
Session:根据请求中的cookie,重载访问者相关的会话信息。
程序上下文(AppContext)
g:处理请求时用作临时存储的对象,保存的是当前请求的全局变量,不同的请求会有不同的全局变量!
current_app:当前运行程序的程序实例,保存的是应用程序的变量,比如可以使用current_app.name获取当前应用的名称,
也可以在current_app中存储一些配置信息,变量等,使用示例:current_app.text = 'value'。
Flask中,而关于上下文的管理可以分为三个阶段:
在开发中,后台发生异常,但又不想把异常显示给用户看,或者需要同一处理的时候,可以使用abort()函数
主动抛出异常,再捕获异常,然后返回一个美化后的页面,最常见的就是404了,代码示例如下:
@app.route("/test")
def test():
abort(404)
@app.errorhandler(404)
def error(e):
return "一个精美的404页面"
代码执行后,浏览器键入:http://127.0.0.1:5000/test,可以看到如图所示的结果:
另外,你也可以把所有异常处理些写到一个蓝图中,代码示例如下:
# coding=utf-8
from flask import Blueprint, abort
exception = Blueprint('exception', __name__)
@exception.errorhandler(404)
def error(e):
return "一个精美的404页面"
# 注册蓝图
from error import exception
app.register_blueprint(exception, url_prefix='/error')
使用对象映射关系(Object-Relational Mapper)ORM框架来操作数据库。所谓的ORM框架就是:
「将底层的数据操作指令抽象成高层的面向对象操作」
不用再写繁琐的SQL操作语句,利用ORM框架可以简化成对Python对象的操作。
表映射成类
,行作为实例
,字段作为属性
.
ORM在执行对象操作时会将对应操作转换为数据库原生语句。Python中用得最广泛的ORM框架是SQLAlchemy。
直接使用pip命令安装即可
pip install flask-sqlalchemy
另外SQLAlchemy本身无法操作数据库,依赖于pymysql等第三方库,里面有个Dialect模块专门用于
和数据API交流,根据配置文件的不同而调用不同的数据库API,从而实现对数据库的操作。数据库使用
URL限定,常见的数据库引擎与其对应的URL如下表所示:
数据库引擎 | URL |
---|---|
MySQL | mysql+pymysql://username:password@hostname/database |
Postgres | postgresql://username:password@hostname/database |
SQLite (Unix,开头四个斜线) | sqlite:////absolute/path/to/database |
SQLite (Windows) | sqlite:///c:/absolute/path/to/database |
Postgres | postgresql://username:password@hostname/database |
Oracle | oracle://username:password@hostname/database |
参数简述:
连接MySQL数据库的代码示例如下:
from sqlalchemy import create_engine
engine = create_engine('mysql+pymysql://jay:zpj12345@localhost:3306/todo')
with engine.connect() as con:
rs = con.execute("SELECT 1")
print(rs.fetchone())
输出结果如下:
(1,)
另外还可以通过下面这样的方法来初始化SQLAlchemy,代码示例如下:
from flask_sqlalchemy import SQLAlchemy
db = SQLAlchemy()
app = Flask(__name__)
db.init_app(app)
sqlalchemy支持直接执行原生的SQL语句,代码示例如下:
from sqlalchemy import create_engine
engine = create_engine('mysql+pymysql://jay:zpj12345@localhost:3306/todo')
with engine.connect() as con:
con.execute("CREATE TABLE IF Not Exists note(_id INT AUTO_INCREMENT PRIMARY KEY, tran TEXT, status int)")
con.execute("INSERT INTO note(tran, status) VALUES ('吃饭', 1)")
con.execute("INSERT INTO note(tran, status) VALUES ('睡觉', 1)")
rs = con.execute("SELECT * FROM note")
for row in rs:
print(row)
代码输出结果如下:
(1, '吃饭', 1)
(2, '睡觉', 1)
演示一波Flask-SQLAlchemy的基本操作:
# models.py
from manager import db
class Note(db.Model):
__tablename__ = 'note'
_id = db.Column(db.INTEGER, primary_key=True, autoincrement=True)
tran = db.Column(db.TEXT)
status = db.Column(db.INT,default=0)
db.create_all() # 创建表
from model import Note
from manager import db
def create_note():
# 创建一个新对象
note1 = Note()
note1.tran = "吃饭"
note1.status = "1"
note2 = Note()
note2.tran = "睡觉"
note2.status = "2"
# 将新建笔记添加到数据库会话中
db.session.add(note1)
db.session.add(note2)
# 将数据库会话中的变动提交到数据库中,如果不commit,数据库内容是不会变化的
db.session.commit()
create_note()
def delete_note():
# 获取笔记对象(这里是获取_id=1的记录)
note = Note.query.filter_by(_id=1).first()
# 删除笔记
db.session.delete(note)
# 提交数据库会话
db.session.commit()
delete_note()
def update_note():
# 获取笔记对象(这里是获取_id=2的记录)
note = Note.query.filter_by(_id=2).first()
# 修改笔记内容
note.tran = "打豆豆"
# 提交数据库会话
db.session.commit()
update_note()
说到查询,必然有查询条件,SQLAlchemy提供了如下表所示的常用查询函数:
函数 | 描述 | 使用示例 |
---|---|---|
filter_by | 精确查询 | filter_by(xxx='xxx') |
filter | 模糊查询 | filter(xxx.endWith('xxx')) |
get(主键) | 根据主键查询,一般为id | get(1) |
not_() | 逻辑非,也可以直接把==换成!= | not_(xxx='xxx') |
and_() | 逻辑与 | and_(xxx='xxx') |
or_() | 逻辑或 | or_(xxx='xxx') |
in_() | 在某个范围里 | XXX.xxx.in_((1,2,3)) |
notin_() | 不在某个范围内 | XXX.xxx.notin_((1,2,3)) |
first() | 返回查询到的一个对象 | XXX.query.first() |
all() | 返回查询到的所有对象 | XXX.query.all() |
order_by() | 排序 | XXX.order_by(xxx.xxx.desc()) |
limit() | 限制返回条数 | XXX.limit(3) |
offset() | 设置偏移量 | XXX.offset() |
count() | 返回记录的总条数 | xxx.count() |
代码示例如下:
from sqlalchemy import not_, or_
def query_all():
notes = Note.query.all()
print("查询全部数据:", notes)
note = Note.query.filter(not_(or_(Note.tran == '吃饭', Note.tran == '睡觉'))).first()
print(note._id, ":", note.tran, ":", note.status)
if __name__ == '__main__':
# 先插入几条数据
create_note()
create_note()
query_all()
输出结果如下:
查询全部数据: [<Note 2>, <Note 3>, <Note 4>, <Note 5>, <Note 6>]
2 : 打豆豆 : 2
Flask中一般不会直接用原始表单,而是通过Flask-WTF扩展,它简单继承了WTForms,包括CSRF
(跨域请求伪造),验证表单数据的功能,文件上传以及Google内嵌的验证码。
字段类型 | 说明 |
---|---|
StringField | 文本字段 |
TextAreaField | 多行文本字段 |
PasswordField | 密码文本字段 |
HiddenField | 隐藏文本字段 |
DateField | 文本字段,值为datetime.date格式 |
DateTimeField | 文本字段,值为datetime.datetime格式 |
IntegerField | 文本字段,值为整数 |
DecimalField | 文本字段,值为decimal.Decimal |
FloatField | 文本字段,值为浮点数 |
BooleanField | 复选框,值为True和False |
RadioField | 一组单选框 |
SelectField | 下拉列表 |
SelectMultipleField | 下拉列表,可选择多个值 |
FileField | 文件上传字段 |
SubmitField | 表单提交按钮 |
FormField | 把表单作为字段嵌入另一个表单 |
FieldList | 一组指定类型的字段 |
验证函数 | 说明 |
---|---|
验证电子邮件地址 | |
EqualTo | 比较两个字段的值,常用于要求输入两次密码进行确认的情况 |
IPAddress | 验证IPv4网络地址 |
Length | 验证输入字符串的长度 |
NumberRange | 验证输入的值在数字范围内 |
Optional | 无输入值时跳过其他验证函数 |
Required | 确保字段中有数据 |
Regexp | 使用正则表达式验证输入值 |
URL | 验证URL |
AnyOf | 确保输入值在可选值列表中 |
NoneOf | 确保输入值不在可选列表中 |
接着我们来编写一个注册表单的例子:
# coding=utf-8
from flask import Flask, request, render_template
from flask_wtf import FlaskForm
# 导入自定义表单需要的字段
from wtforms import SubmitField, StringField, PasswordField
# 导入表单验证
from wtforms.validators import DataRequired, EqualTo
app = Flask(__name__)
app.config['SECRET_KEY'] = '123456'
# 自定义表单类
# StringField和PasswordField用于区分文本框类型
# 第一个参数是label值,第二个参数validators是要验证的内容
class RegisterForm(FlaskForm):
username = StringField('用户名:', validators=[DataRequired()])
password = PasswordField('密码:', validators=[DataRequired()])
password2 = PasswordField('确认密码:', validators=[DataRequired(), EqualTo('password', '两次输入的密码不一致!')])
submit = SubmitField('注册')
@app.route("/register", methods=['GET', 'POST'])
def register():
# 实例化注册表单类
register_from = RegisterForm()
if request.method == 'POST':
# 获取请求参数参数
username = request.form.get('username')
password = request.form.get('password')
password2 = request.form.get('password2')
# 调用validation_on_submit,一次性执行完所有验证函数的逻辑
if register_from.validate_on_submit():
return '注册成功!'
else:
return '前后密码不一致!'
return render_template('register.html', form=register_from)
if __name__ == "__main__":
print(app.url_map)
app.run(debug=True)
# templates/register.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form method="post">
{# 设置scrf_token #}
{{ form.csrf_token() }}
{{ form.username.label }} {{ form.username }}<br>
{{ form.password.label }} {{ form.password }}<br>
{{ form.password2.label }} {{ form.password2 }}<br>
<br>
{{ form.submit }}
</form>
</body>
</html>
输入一波一样的密码和不一样的密码,浏览器的输出结果如下所示:
另外有一点要注意:
使用Flask-WTF需要配置参数SECRET_KEY,CSRF_ENABLED是为了CSRF(跨站请求伪造)保护。
SECRET_KEY用于生成加密令牌,当CSRF激活的时候,该设置会根据设置的密钥生成加密令牌。
Flask基础学得差不多了,接着我们来规范下项目结构,一个比较简单通用的项目结构如图:
简述下结构:
创建流程:
在__init__.py
中初始化app实例,代码如下:
from flask import Flask
app = Flask(__name__)
views.py中写个简单的index路由:
from app import app
from flask import render_template
@app.route('/')
def index():
return render_template("index.html")
templates文件夹创建一个index.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>Hello Flask!</h1>
</body>
</html>
接着键入:python manage.py runserver,浏览器打开:http://127.0.0.1:5000/
项目结构基本就弄好了,接着我们把项目丢到远程服务器上。
有两种可选的方法,最简单的就是通过FTP/SFTP工具直接传。另外一种是把项目托管到
类似于Github这类代码托管平台,然后直接ssh连云服务器,通过git clone命令把项目
拷贝到服务器上,这里笔者直接用的第一种方法,把项目上传到云服务器上。
Nginx是一款轻量级、性能强、占用资源少,能很好的处理高并发的反向代理软件。
Flask自带的Web Server只能开单线程,自己测试还行,放到线上就不行了,这里
我们用到Nginx,直接通过apt-get命令进行安装,命令如下:
apt-get install nginx
安装完后,外网访问服务器的公网ip,出现下述页面说明安装成功:
Nginx安装完,会默认创建一个目录:/var/www/,直接通过命令把我们的
项目移动到这个路径下。
mv AutoPubNews /var/www/AutoPubNews
WSGI是一种WEB服务器,或者叫网关接口,Web服务器(如nginx)与应用服务器
(如uWSGI)通信的一种规范(协议)。而uWSGI实现了WSGI的所有接口,是一个
快速、自我修复、开发人员和系统管理员友好的服务器。uWSGI代码完全用C编写,
效率高、性能稳定。举个例子:uWSGI把HTTP协议转化成WSGI协议,让Python可以
直接使用。直接键入pip命令安装:
pip install uwsgi
一般都是能直接安装完成的,如果出错了可以试试先安装libpython3.x-dev,
比如笔者的版本是3.5:
apt-get install libpython3.5-dev
接着配置一下uwsgi,在项目里新建一个config.ini作为配置文件:
vim config.ini
添加下述内容:
[uwsgi]
# uwsgi 启动时所使用的地址与端口
socket = 127.0.0.1:8001 # 可以使用其他端口
# 指向网站目录
chdir = /var/www/AutoPubNews
# python 启动程序文件
wsgi-file = manage.py
# python 程序内用以启动的 application 变量名
callable = app
# 处理器数
processes = 4
# 线程数
threads = 2
#状态检测地址
stats = 127.0.0.1:5000
接着执行下述命令:
uwsgi config.ini
出现: Stats server enabled on 127.0.0.1:5000,代表正常启动。
接着配置下Nginx,不要去动默认的nginx.conf,直接将:/etc/nginx/sites-available/default
覆盖掉,新建default文件,添加下述内容:
server {
listen 80;
server_name _;
location / {
include uwsgi_params;
uwsgi_pass 127.0.0.1:8001; # 指向uwsgi 所应用的内部地址,所有请求将转发给uwsgi 处理
uwsgi_param UWSGI_PYHOME /home/www/AutoPubNews/venv; # 指向虚拟环境目录
uwsgi_param UWSGI_CHDIR /home/www/AutoPubNews; # 指向网站根目录
uwsgi_param UWSGI_SCRIPT manager:app; #
}
}
接着键入下述命令重启加载下nginx配置:
sudo service nginx restart
接着启动uwsgi,然后就可以通过服务器的公网ip直接访问我们的项目了:
每次访问项目都用ip,显得有些繁琐,我们可以买个域名做下映射耍耍。
域名直接买就好,需要备案,搞定后打开域名管理页,找到刚买的域名
点击解析
然后点击「添加记录」会出现如图所示的对话框,记录值那里填你的云服务器公网ip即可。
此时就可以直接通过域名来访问我们的项目了。
行吧,关于Flask速成就这么都,下节我们来利用Flask编写API接口,动态生成页面等。
有疑问的欢迎在评论区留言,谢谢~
Tips:公号目前只是坚持发早报,在慢慢完善,有点心虚,只敢贴个小图,想看早报的可以关注下~