本篇文章是Python Flask 建站框架入门课程_编程实战微课_w3cschool微课的学习笔记,根据课程整理而来,本人使用版本如下:

Python 3.10.0
Flask 2.2.2

简介

  • Flask是一个轻量级的可定制的web框架

  • Flask 可以很好地结合MVC模式进行开发

  • Flask还有很强的很强的扩展性和兼容性

核心函数库

Flask主要包括Werkzeug和Jinja2两个核心函数库,它们分别负责业务处理和安全方面的功能,这些基础函数为web项目开发过程提供了丰富的基础组件。

Werkzeug

Werkzeug库十分强大,功能比较完善,支持URL路由请求集成,一次可以响应多个用户的访问请求;

支持Cookie和会话管理,通过身份缓存数据建立长久连接关系,并提高用户访问速度;支持交互式Javascript调试,提高用户体验;

可以处理HTTP基本事务,快速响应客户端推送过来的访问请求。

Jinja2

Jinja2库支持自动HTML转移功能,能够很好控制外部黑客的脚本攻击;

系统运行速度很快,页面加载过程会将源码进行编译形成python字节码,从而实现模板的高效运行;

模板继承机制可以对模板内容进行修改和维护,为不同需求的用户提供相应的模板。

安装

通过pip安装即可

pip install Flask
# pip3
pip3 install Flask

目录结构

新项目创建后的结构

static文件夹:存放静态文件,比如css、js、图片等

templates文件夹:模板文件目录

app.py:应用启动程序

获取URL参数

列出所有URL参数

request.args.__str__()

from flask import Flask, request

app = Flask(__name__)


@app.route('/')
def hello_world():  # put application's code here
    return request.args.__str__()


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

在浏览器中访问http://127.0.0.1:5000/?name=Loen&age&app=ios&app=android,将显示:

ImmutableMultiDict([('name', 'Loen'), ('age', ''), ('app', 'ios'), ('app', 'android')])

列出浏览器传给我们的Flask服务的数据

from flask import Flask, request

app = Flask(__name__)


@app.route('/')
def hello_world():  # put application's code here

    # 列出访问地址
    print(request.path)

    # 列出访问地址及参数
    print(request.full_path)

    return request.args.__str__()


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

在浏览器中访问http://127.0.0.1:5000/?name=Loen&age&app=ios&app=android,控制台中显示

/
/?name=Loen&age&app=ios&app=android

获取指定的参数值

from flask import Flask, request

app = Flask(__name__)


@app.route('/')
def hello_world():  # put application's code here

    return request.args.get('name')


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

在浏览器中访问http://127.0.0.1:5000/?name=Loen&age&app=ios&app=android,将显示:

Loen

处理多值

from flask import Flask, request

app = Flask(__name__)


@app.route('/')
def hello_world():  # put application's code here
    r = request.args.getlist('app')  # 返回一个list
    return r


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

在浏览器中访问http://127.0.0.1:5000/?name=Loen&age&app=ios&app=android,将显示:

[
  "ios",
  "android"
]

获取POST方法传送的数据

作为一种HTTP请求方法,POST用于向指定的资源提交要被处理的数据。

我们在某些时候不适合将数据放到URL参数中,密或者数据太多,浏览器不一定支持太长长度的URL。这时,一般使用POST方法。

本文章使用python的requests库模拟浏览器。

安装命令:

pip install requests

看POST数据内容

app.py代码如下:

from flask import Flask, request

app = Flask(__name__)


@app.route('/register', methods=['POST'])
def register():
    print(request.headers)
    print(request.stream.read())
    return 'welcome'


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

register.py代码如下:

import requests

if __name__ == '__main__':
    user_info = {'name': 'Loen', 'password': 'loveyou'}
    r = requests.post("http://127.0.0.1:5000/register", data=user_info)
    print(r.text)

运行app.py,然后运行register.py

register.py将输出:

welcome

app.py将输出:

Host: 127.0.0.1:5000
User-Agent: python-requests/2.28.2
Accept-Encoding: gzip, deflate
Accept: */*
Connection: keep-alive
Content-Length: 26
Content-Type: application/x-www-form-urlencoded


b'name=Loen&password=loveyou'
127.0.0.1 - - [14/Feb/2023 21:12:17] "POST /register HTTP/1.1" 200 -

解析POST数据

app.py代码如下:

from flask import Flask, request

app = Flask(__name__)


@app.route('/register', methods=['POST'])
def register():
    # print(request.stream.read()) # 不要用,否则下面的form取不到数据
    print(request.form)
    print(request.form['name'])
    print(request.form.get('name'))
    print(request.form.getlist('name'))
    print(request.form.get('nickname', default='little apple'))
    return 'welcome'


if __name__ == '__main__':
    app.run(port=5000, debug=True)

register.py代码不变,运行app.py,然后运行register.py

register.py将输出:

welcome

app.py将输出:

ImmutableMultiDict([('name', 'Loen'), ('password', 'loveyou')])
Loen
Loen
['Loen']
little apple

request.form会自动解析数据。

request.form[‘name’]和request.form.get(‘name’)都可以获取name对应的值。

request.form.get()可以为参数default指定值以作为默认值。

获取POST中的列表数据

app.py代码如下:

from flask import Flask, request

app = Flask(__name__)


@app.route('/register', methods=['POST'])
def register():
    # print(request.stream.read()) # 不要用,否则下面的form取不到数据
    print(request.form.getlist('name'))
    return 'welcome'


if __name__ == '__main__':
    app.run(port=5000, debug=True)

register.py代码如下:

import requests

if __name__ == '__main__':
    user_info = {'name': ['Loen', 'Alan'], 'password': 'loveyou'}
    r = requests.post("http://127.0.0.1:5000/register", data=user_info)
    print(r.text)

运行app.py,然后运行register.py

register.py将输出:

welcome

app.py将输出:

['Loen', 'Alan']

处理和响应JSON数据

处理JSON数据

如果POST的数据是JSON格式,request.json会自动将json数据转换成Python类型(字典或者列表)。

app.py代码如下:

from flask import Flask, request

app = Flask(__name__)


@app.route('/add', methods=['POST'])
def add():
    print(type(request.json))
    print(request.json)
    result = request.json['n1'] + request.json['n2']
    return str(result)


if __name__ == '__main__':
    app.run(port=5000, debug=True)

register.py代码如下:

import requests

if __name__ == '__main__':
    json_data = {'n1': 5, 'n2': 3}
    r = requests.post("http://127.0.0.1:5000/add", json=json_data)
    print(r.text)

运行app.py,然后运行register.py

register.py将输出:

8

app.py将输出:

<class 'dict'>
{'n1': 5, 'n2': 3}

响应JSON数据(Response)

app.py代码如下:

import json

from flask import Flask, request, Response

app = Flask(__name__)


@app.route('/add', methods=['POST'])
def add():
    result = {'sum': request.json['n1'] + request.json['n2']}
    return Response(json.dumps(result), mimetype='application/json')


if __name__ == '__main__':
    app.run(port=5000, debug=True)

register.py代码如下:

import requests

if __name__ == '__main__':
    json_data = {'n1': 5, 'n2': 3}
    r = requests.post("http://127.0.0.1:5000/add", json=json_data)
    print(r.headers)
    print(r.text)

运行app.py,然后运行register.py

register.py将输出:

/home/huangge1199/PycharmProjects/flaskProject/venv/bin/python /home/huangge1199/PycharmProjects/flaskProject/register.py 
{'Server': 'Werkzeug/2.2.2 Python/3.7.3', 'Date': 'Tue, 14 Feb 2023 13:37:49 GMT', 'Content-Type': 'application/json', 'Content-Length': '10', 'Connection': 'close'}
{"sum": 8}

响应JSON数据(jsonify)

app.py中app()返回时使用下面的内容,效果同之前一样

return jsonify(result)

上传表单

用 Flask 处理文件上传很简单,只要确保你没忘记在 HTML 表单中设置 enctype=“multipart/form-data” 属性,不然你的浏览器根本不会发送文件。

安装响应的库werkzeug

pip install werkzeug

目录结构:

app.py代码如下:

from flask import Flask, request
from werkzeug.utils import secure_filename
import os

app = Flask(__name__)

# 文件上传目录
app.config['UPLOAD_FOLDER'] = 'static/uploads/'
# 支持的文件格式
app.config['ALLOWED_EXTENSIONS'] = {'png', 'jpg', 'jpeg', 'gif'}  # 集合类型


# 判断文件名是否是我们支持的格式
def allowed_file(filename):
    return '.' in filename and \
        filename.rsplit('.', 1)[1] in app.config['ALLOWED_EXTENSIONS']


@app.route('/upload', methods=['POST'])
def upload():
    upload_file = request.files['image']
    if upload_file and allowed_file(upload_file.filename):  # 上传前文件在客户端的文件名
        filename = secure_filename(upload_file.filename)
        # 将文件保存到 static/uploads 目录,文件名同上传时使用的文件名
        upload_file.save(os.path.join(app.root_path, app.config['UPLOAD_FOLDER'], filename))
        return 'info is ' + request.form.get('info', '') + '. success'
    else:
        return 'failed'


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

register.py代码如下:

import requests

if __name__ == "__main__":
    file_data = {'image': open('flask.png', 'rb')}
    user_info = {'info': 'flask'}
    r = requests.post("http://127.0.0.1:5000/upload", data=user_info, files=file_data)
    print(r.text)

运行app.py,然后运行register.py,这时候文件已经上传到了指定目录中

要控制上产文件的大小,可以设置请求实体的大小,代码如下:

app.config['MAX_CONTENT_LENGTH'] = 16 * 1024 * 1024 #16MB

获取上传文件的内容,代码如下:

file_content = request.files['image'].stream.read()

Restful URL

Restful URL可以看做是对 URL 参数的替代

变量规则

写法如下:

@app.route('/user/<username>/friends')

转换类型

使用 Restful URL 得到的变量默认为str对象。我们可以用flask内置的转换机制,即在route中指定转换类型,写法如下:

@app.route('/page/<int:num>')

有3个默认的转换器:

  • int:接受整数

  • float:同 int ,但是接受浮点数

  • path:和默认的相似,但也接受斜线

自定义转换器

自定义的转换器是一个继承werkzeug.routing.BaseConverter的类,修改to_python和to_url方法即可。

to_python方法用于将url中的变量转换后供被@app.route包装的函数使用,to_url方法用于flask.url_for中的参数转换。

下面是一个示例:

from flask import Flask, url_for
from werkzeug.routing import BaseConverter


class MyIntConverter(BaseConverter):

    def __init__(self, url_map):
        super(MyIntConverter, self).__init__(url_map)

    def to_python(self, value):
        return int(value)

    def to_url(self, value):
        return value * 2


app = Flask(__name__)
app.url_map.converters['my_int'] = MyIntConverter


@app.route('/page/<my_int:num>')
def page(num):
    print(num)
    print(url_for('page', num='145'))  # page 对应的是 page函数 ,num 对应对应`/page/<my_int:num>`中的num,必须是str
    return 'hello world'


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

运行app.py,浏览器访问http://127.0.0.1:5000/page/28后,app.py的输出信息是:

28
/page/145145

使用url_for生成链接

工具函数url_for可以让你以软编码的形式生成url,提供开发效率。

例子app.py代码如下:

from flask import Flask, url_for

app = Flask(__name__)


@app.route('/')
def hello_world():
    pass


@app.route('/user/<name>')
def user(name):
    pass


@app.route('/page/<int:num>')
def page(num):
    pass


@app.route('/test')
def test():
    print(url_for('test'))
    print(url_for('user', name='loen'))
    print(url_for('page', num=1, q='welcome to w3c 15%2'))
    print(url_for('static', filename='uploads/flask.png'))
    return 'Hello'


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

运行app.py。然后在浏览器中访问http://127.0.0.1:5000/testserver.py控制台将输出以下信息:

/test
/user/loen
/page/1?q=welcome+to+w3c+15%252
/static/uploads/flask.jpg

使用redirect重定向网址

在浏览器中访问http://127.0.0.1:5000/old,浏览器的url会变成http://127.0.0.1:5000/new,并显示,app.py代码如下:

from flask import Flask, url_for, redirect

app = Flask(__name__)


@app.route('/old')
def old():
    print('this is old')
    return redirect(url_for('new'))


@app.route('/new')
def new():
    print('this is new')
    return 'this is new'


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

运行app.py,然后在浏览器中访问http://127.0.0.1:5000/old

浏览器显示:

this is new

控制台显示:

this is old
this is new

自定义404

处理HTTP错误

要处理HTTP错误,可以使用flask.abort函数。

app.py代码如下:

from flask import Flask, abort

app = Flask(__name__)


@app.route('/user')
def user():
    abort(401)  # Unauthorized 未授权
    print('Unauthorized, 请先登录')


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

运行app.py,然后在浏览器中访问http://127.0.0.1:5000/user

浏览器显示:

自定义错误页面

page_unauthorized 函数返回的是一个元组,401 代表HTTP 响应状态码。

如果省略401,则响应状态码会变成默认的 200。

app.py代码如下:

from flask import Flask, abort, render_template_string

app = Flask(__name__)


@app.route('/user')
def user():
    abort(401)  # Unauthorized


@app.errorhandler(401)
def page_unauthorized(error):
    return render_template_string('<h1> Unauthorized </h1><h2>{{ error_info }}</h2>', error_info=error), 401


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

运行app.py,然后在浏览器中访问http://127.0.0.1:5000/user

浏览器显示:

用户会话

session 用来记录用户的登录状态,一般基于cookie实现。

app.py代码如下:

from flask import Flask, render_template_string, request, session, redirect, url_for

app = Flask(__name__)

app.secret_key = 'LoenDSdtj\9bX#%@!!*(0&^%)'


@app.route('/login')
def login():
    page = '''
    <form action="{{ url_for('do_login') }}" method="post">
        <p>name: <input type="text" name="user_name" /></p>
        <input type="submit" value="Submit" />
    </form>
    '''
    return render_template_string(page)


@app.route('/do_login', methods=['POST'])
def do_login():
    name = request.form.get('user_name')
    session['user_name'] = name
    return 'success'


@app.route('/show')
def show():
    return session['user_name']


@app.route('/logout')
def logout():
    session.pop('user_name', None)
    return redirect(url_for('login'))


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

代码的含义

app.secret_key用于给session加密。

在/login中将向用户展示一个表单,要求输入一个名字,submit后将数据以post的方式传递给/do_login,/do_login将名字存放在session中。

如果用户成功登录,访问/show时会显示用户的名字。此时,打开调试工具,选择session面板,会看到有一个cookie的名称为session。

/logout用于登出,通过将session中的user_name字段pop即可。Flask中的session基于字典类型实现,调用pop方法时会返回pop的键对应的值;如果要pop的键并不存在,那么返回值是pop()的第二个参数。

另外,使用redirect()重定向时,一定要在前面加上return。

设置session的有效时间

设置session的有效时间设置为5分钟。

代码如下:

from datetime import timedelta
from flask import session, app

session.permanent = True
app.permanent_session_lifetime = timedelta(minutes=5)

使用Cookie

Cookie是存储在客户端的记录访问者状态的数据。

常用的用于记录用户登录状态的session大多是基于cookie实现的。

cookie可以借助flask.Response来实现。

使用Response.set_cookie添加和删除cookie。

expires参数用来设置cookie有效时间,值可以是datetime对象或者unix时间戳。

res.set_cookie(key='name', value='loen', expires=time.time()+6*60)

上面的expire参数的值表示cookie在从现在开始的6分钟内都是有效的。

要删除cookie,将expire参数的值设为0即可:

res.set_cookie('name', '', expires=0)

详细的app.py代码如下:

import time

from flask import Flask, request, Response

app = Flask(__name__)


@app.route('/add')
def login():
    res = Response('add cookies')
    res.set_cookie(key='name', value='loen', expires=time.time() + 6 * 60)
    return res


@app.route('/show')
def show():
    return request.cookies.__str__()


@app.route('/del')
def del_cookie():
    res = Response('delete cookies')
    res.set_cookie('name', '', expires=0)
    return res


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

闪存系统 flashing system

Flask 的闪存系统(flashing system)用于向用户提供反馈信息,这些反馈信息一般是对用户上一次操作的反馈。

反馈信息是存储在服务器端的,当服务器向客户端返回反馈信息后,这些反馈信息会被服务器端删除。

详细的app.py代码如下:

import time

from flask import Flask, get_flashed_messages, flash

app = Flask(__name__)
app.secret_key = 'some_secret'


@app.route('/')
def index():
    return 'Hello index'


@app.route('/gen')
def gen():
    info = 'access at ' + time.time().__str__()
    flash(info)
    return info


@app.route('/show1')
def show1():
    return get_flashed_messages().__str__()


@app.route('/show2')
def show2():
    return get_flashed_messages().__str__()


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