跳转至

Python 开源框架 —— Flask

介绍

Flask 是一个轻量级的 Python Web 框架,被广泛应用于构建 Web 应用程序和 RESTful API。它简洁而灵活,具有易学易用的特点,适合于快速开发原型和构建小型到中型规模的 Web 项目。 Flask 的主要特点包括:

  1. 简单易用: Flask 的设计简洁,学习曲线较低,开发者可以快速上手并迅速构建出功能完善的 Web 应用。
  2. 轻量级: Flask 框架本身功能精简,没有过多的依赖,因此性能较高,适合于开发小型应用或微服务。
  3. 灵活性: Flask 提供了丰富的扩展机制和组件,开发者可以根据项目需求灵活选择并集成需要的功能,如数据库集成、认证授权、表单处理等。
  4. 可扩展性: Flask 支持通过 Flask 扩展实现功能的扩展,这些扩展提供了丰富的功能,如用户认证、数据库集成、表单验证等,使得开发者能够快速实现各种需求。
  5. 模板引擎: Flask 默认使用 Jinja2 模板引擎,支持模板继承、控制结构、过滤器等功能,方便开发者构建动态的 HTML 页面。
  6. RESTful 支持: Flask 对构建 RESTful API 提供了良好的支持,开发者可以轻松地设计和实现符合 RESTful 风格的 API 接口。

Flask 是一个灵活、简单且功能强大的 Web 框架,适用于各种规模的 Web 开发项目,无论是个人项目、企业应用还是微服务架构都能发挥出良好的效果。

安装 Flask

安装 Flask 的方法通常有两种:使用 pip 安装和使用虚拟环境。

pip 安装

在命令行中执行以下命令安装 Flask:

pip install Flask

这将会从 Python Package Index (PyPI) 中下载并安装 Flask 框架及其依赖。

虚拟环境安装

使用虚拟环境可以避免不同项目之间的依赖冲突,并使项目的环境更加清洁和独立。

Step 1:首先,确保你已经安装了 virtualenv 工具。如果没有安装,可以通过以下命令安装:

pip install virtualenv

Step 2:创建一个新的虚拟环境,例如在项目目录下执行:

virtualenv venv

Step 3:激活虚拟环境(Windows 下的命令):

venv\Scripts\activate

或者在 Unix 或 macOS 系统中使用:

source venv/bin/activate

Step 4:激活后,你的命令行提示符会变成 (venv),表示当前处于虚拟环境中。

Step 5:然后使用 pip 安装 Flask:

pip install Flask

Step 6:这样就在虚拟环境中安装了 Flask,你可以在该环境中进行开发和测试。

创建 Flask 应用

Hello Flask!

创建一个 hello.py 文件,编写如下的内容:

from flask import Flask

app = Flask(__name__)


@app.route("/")
def hello_world():
    return "<p>Hello, World!</p>"

这段代码做了什么?

  • 首先,我们导入了 Flask 类。这个类的一个实例将成为我们的 WSGI 应用程序。

  • 接下来,我们创建了这个类的一个实例。第一个参数是应用程序的模块或包的名称。__name__ 是一个方便的快捷方式,对于大多数情况来说都是合适的。这是必需的,这样 Flask 才知道在哪里查找诸如模板和静态文件等资源。

  • 然后,我们使用 route() 装饰器告诉 Flask 应该触发我们的函数的 URL 是什么。

  • 该函数返回我们想在用户的浏览器中显示的消息。默认的内容类型是 HTML,所以字符串中的 HTML 将由浏览器渲染。

将其保存为 hello.py 或类似的名称。确保不要将你的应用程序命名为 flask.py,因为这将与 Flask 本身冲突。要运行该应用程序,使用 flask 命令或 python -m flask。你需要使用 --app 选项告诉 Flask 你的应用程序在哪里。

flask --app hello run
 * Serving Flask app 'hello'
 * Running on http://127.0.0.1:5000 (Press CTRL+C to quit)

运行完成后,通过浏览器访问 http://127.0.0.1:5000 就可以看到输出的 Hello, World! 了。

Pycharm 安装

在 Pycharm 的 Professional 版本中,可以很方便的创建 Flask 应用:

Pycharm 创建 Flask 应用

基本概念

Flask 的基本概念如下:

  • 请求-响应模型: 是 Web 应用程序的基本工作方式。客户端(通常是浏览器)向服务器发送 HTTP 请求,服务器接收请求并处理,然后返回 HTTP 响应给客户端。
  • 路由: 在 Flask 中,路由指的是将 URL 地址和特定的处理函数(视图函数)相绑定的过程。通过路由,Flask 知道在接收到特定 URL 请求时应该调用哪个视图函数来处理。
  • 视图函数: 是 Flask 应用程序中用于处理请求并生成响应的函数。当路由匹配到一个 URL 时,Flask 调用相应的视图函数来处理该请求,然后视图函数返回一个响应给客户端。
  • 模板: Flask 使用 Jinja2 模板引擎来渲染动态内容。模板是带有特殊标记的 HTML 文件,允许在其中插入动态数据或逻辑。在视图函数中,可以使用模板引擎将数据传递给模板,并生成最终的 HTML 页面。
  • 静态文件: 通常包括 CSS、JavaScript、图片等文件,这些文件不需要动态生成,而是直接从服务器端发送给客户端。在 Flask 中,可以通过 url_for('static', filename='filename') 来引用静态文件。

路由和视图函数

路由

路由(Route)是指将 URL 请求映射到相应的处理程序或视图函数的过程。在 Web 应用程序中,当用户访问特定的 URL 时,服务器需要知道如何处理该请求,并返回相应的内容。路由就是帮助服务器确定如何处理这些 URL 请求的机制。

普通路由

在大多数 Web 框架中,路由通常由开发者定义,以确定哪个 URL 触发哪个处理程序或视图函数。这种定义通常通过装饰器或配置文件来实现。在 Flask 中,使用 @app.route() 装饰器可以将 URL 与视图函数关联起来。例如,@app.route('/hello') 将告诉 Flask 当用户访问 /hello 路径时应该调用哪个视图函数来处理请求。

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


@app.route('/hello')
def hello():
    return 'Hello, World'

变量路由

变量路由可以把路由局部作为参数传递到方法中。

from markupsafe import escape


@app.route('/user/<username>')
def show_user_profile(username):
    ## show the user profile for that user
    return f'User {escape(username)}'


@app.route('/post/<int:post_id>')
def show_post(post_id):
    ## show the post with the given id, the id is an integer
    return f'Post {post_id}'


@app.route('/path/<path:subpath>')
def show_subpath(subpath):
    ## show the subpath after /path/
    return f'Subpath {escape(subpath)}'

Flask 支持如下的路由数据类型:

string (default) accepts any text without a slash
int accepts positive integers
float accepts positive floating point values
path like string but also accepts slashes
uuid accepts UUID strings

注意路由后缀

Flask 会自动处理路由 URL 末尾带斜线或不带斜线的情况。当没有主动处理末尾带斜线的情况时,Flask 的跳转可能存在问题。

from flask import Flask

app = Flask(__name__)


@app.route("/hello")
def hello_world():
    return "<p>Hello, World!</p>"

当浏览器访问 127.0.0.1/hello 时,可以得到正确的响应。但是访问 127.0.0.1/hello/ 时,则无法得到响应。

不同的理由得到的响应

想要解决这个问题,只要再多配置一个路由即可:

from flask import Flask

app = Flask(__name__)


@app.route("/hello")
@app.route("/hello/")
def hello_world():
    return "<p>Hello, World!</p>"

虽然访问 /hello/hello/ 都可以得到正确的响应,但是这样做对搜索引擎而言会导致同一个页面被搜索两次,对于 SEO 是不利的。实际上,Flask 本身就支持对 URL 路径末尾带斜线和不带斜线都可以访问到同一页面,只需要使用 /hello/ 一个路由修饰器。

from flask import Flask

app = Flask(__name__)


@app.route("/hello/")
def hello_world():
    return "<p>Hello, World!</p>"

从 Chrome 浏览器可以看到 Flask 通过 308 状态码重定向的方式实现了该功能。如果访问的是 /hello 则会自动返回 308 状态码,重定向到 /hello/ 路径。这种方式既可以实现同时支持 URL 路径末尾带斜线又支持不带斜线的两种路径,同时还不会影响搜索引擎的 SEO。

404 页面

在 Flask 中,你可以自定义 404 错误页面,以提供统一的用户体验。要实现这一点,你可以使用 errorhandler 装饰器来处理 404 错误。

下面是一个示例,展示了如何在 Flask 应用中定义一个自定义的 404 错误页面:

from flask import Flask, render_template

app = Flask(__name__)


## 自定义404错误页面
@app.errorhandler(404)
def page_not_found(error):
    return render_template('404.html'), 404


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

在这个示例中,errorhandler 装饰器用于指定当出现 404 错误时要调用的函数。在这里,我们定义了一个名为 page_not_found 的函数来处理 404 错误。在这个函数中,我们使用 render_template 函数来渲染一个名为 404.html 的模板文件,并将 HTTP 状态码设置为 404。

接下来,你需要在你的 Flask 应用中创建一个名为 404.html 的模板文件,用来显示自定义的 404 错误页面。你可以在这个模板文件中添加任何你想要展示的内容,比如一些友好的错误提示或者导航链接。

这样,当用户访问了一个不存在的页面时,Flask 将会返回自定义的 404 错误页面,提供更好的用户体验。

获取视图参数

视图函数可以接受参数来获取用户请求中的数据。在 Flask 中常用的获取视图参数的方式有 URL 参数、查询参数、表单数据和 JSON 数据。

URL 参数

通过路由 URL 获取定义的参数,例如 /user/<username> 定义了一个 username 变量的 URL 参数

@app.route('/user/<username>')
def show_user_profile(username):
    ## show the user profile for that user
    return f'User {escape(username)}'

查询参数

查询参数通常出现在 URL 中,例如 ?key1=value1&key2=value2。可以使用 request.args 来获取查询参数。

@app.route('/query-example')
def query_example():
    ## 获取名为 'key' 的查询参数的值
    key_value = request.args.get('key')
    return 'Query Parameter Value: {}'.format(key_value)

表达数据

当客户端通过 POST 请求提交表单时,你可以使用 request.form 来获取表单数据。

@app.route('/form-example', methods=['POST'])
def form_example():
    ## 获取名为 'username' 的表单字段的值
    username = request.form.get('username')
    return 'Form Field Value: {}'.format(username)

JSON 数据

当客户端通过 POST 请求提交 JSON 数据时,你可以使用 request.json 来获取 JSON 数据。

@app.route('/json-example', methods=['POST'])
def json_example():
    ## 获取名为 'key' 的 JSON 字段的值
    key_value = request.json.get('key')
    return 'JSON Field Value: {}'.format(key_value)

模板语言

在 Flask 中使用模板可以方便地将动态数据呈现给用户,并且可以将 HTML 页面和 Python 代码分离,提高代码的可维护性。Flask 使用 Jinja2 作为其默认的模板引擎。

Hello World

  1. 创建模板文件: 首先,你需要创建一个模板文件,通常存放在应用程序目录下的 templates 文件夹中。例如,创建一个名为 index.html 的模板文件,内容如下:

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Flask Template Example</title>
    </head>
    <body>
        <h1>Hello, {{ name }}!</h1>
    </body>
    </html>
    

在这个模板中,我们使用了 Jinja2 的语法 {{ name }} 来渲染动态内容。当模板被渲染时,{{ name }} 将被替换为相应的值。

  1. 更新 Flask 应用程序: 现在,让我们更新 Flask 应用程序,以使用我们刚刚创建的模板文件。

    from flask import Flask, render_template
    
    app = Flask(__name__)
    
    @app.route('/')
    def index():
        ## 在渲染模板时,传递一个名为 'name' 的参数
        return render_template('index.html', name='World')
    
    if __name__ == '__main__':
        app.run(debug=True)
    

在这个示例中,我们使用 render_template 函数来渲染模板文件 index.html。我们还将一个名为 name 的参数传递给模板,其值为 'World'。在模板中,{{ name }} 将被替换为 'World'

  1. 运行应用程序: 运行 Flask 应用程序,访问 http://127.0.0.1:5000/,你将看到页面上显示着 "Hello, World!"。

Jinja2

Jinja2 是 Flask 中默认使用的模板引擎,也可以用于其他 Python Web 框架以及独立的 Python 应用程序中。它提供了一种灵活且功能丰富的模板语言,用于在 HTML 文件中嵌入动态内容。

变量替换

使用双大括号 {{ 变量名 }} 来在模板中嵌入变量,并在渲染时替换为相应的值。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Welcome</title>
</head>
<body>
<h1>Hello, {{ name }}!</h1>
</body>
</html>

控制结构

使用 {% ... %} 来包含控制结构,如条件语句和循环语句。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>User List</title>
</head>
<body>
<ul>
    {% for user in users %}
    <li>{{ user }}</li>
    {% endfor %}
</ul>
</body>
</html>
条件语句

条件语句用于根据特定条件在模板中渲染不同的内容。

{% if condition %}
    Content to display if condition is true.
{% elif another_condition %}
    Content to display if another_condition is true.
{% else %}
    Content to display if none of the conditions are true.
{% endif %}
循环结构

循环结构用于在模板中遍历集合或迭代器,并为每个元素渲染特定的内容。

{% for item in collection %}
    Content to display for each item in the collection.
{% endfor %}

条件语句和循环结构可以相互嵌套,以实现更复杂的逻辑。

{% for user in users %}
    {% if user.is_active %}
        <p>{{ user.username }} is active.</p>
    {% else %}
        <p>{{ user.username }} is inactive.</p>
    {% endif %}
{% endfor %}

想要获取到循环结构的索引可以使用 loop.index (索引从 1 开始)和 loop.index0 (索引从 0 开始)的方式获取。

<ul>
    {% for fruit in fruits %}
        <li>{{ loop.index }}. {{ fruit }}</li>
    {% endfor %}
</ul>
<ul>
    {% for fruit in fruits %}
        <li>{{ loop.index0 }}. {{ fruit }}</li>
    {% endfor %}
</ul>

过滤器

使用管道符 | 来应用过滤器对变量进行处理。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Formatted Date</title>
</head>
<body>
<p>Today's date is: {{ today | date('Y-m-d') }}</p>
</body>
</html>

注释

使用 {## ... #} 来添加注释,这些注释在渲染时会被忽略。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Welcome</title>
</head>
<body>
<h1>Hello, {{ name }}!</h1>
{## This is a comment #}
</body>
</html>

Jinja3

从2021年5月开始,Jinja 已经发展到 Jinja3 版本。Jinja3 在功能和性能上都有所改进,并且保留了与 Jinja2 兼容的语法和用法。以下是一些从 Jinja2 到 Jinja3 的改进和变化:

  1. 性能改进: Jinja3 在模板渲染速度方面进行了优化,提高了性能和效率,特别是在处理大型模板或频繁渲染的情况下。
  2. 新的功能和语法: Jinja3 引入了一些新的功能和改进,包括更强大的过滤器、更灵活的控制结构和更丰富的标准库,使得模板更加灵活和易于编写。
  3. Unicode 支持改进: Jinja3 在处理 Unicode 字符串方面更加健壮和灵活,可以更好地处理多语言和国际化的需求。
  4. 文档改进: Jinja3 提供了更新和改进的文档,使得用户更容易理解和使用 Jinja 模板引擎。
  5. 维护和支持: Jinja3 是 Jinja2 的持续改进版本,继续得到维护和支持,确保其在功能、性能和安全性方面都保持最新。

虽然 Jinja3 带来了一些改进和新功能,但它与 Jinja2 保持了很高的兼容性,因此从 Jinja2 迁移到 Jinja3 通常是相对平滑的。

Flask-WTF

Flask-WTF 是一个用于处理 Web 表单的 Flask 扩展,它基于 WTForms 库构建而成。它简化了在 Flask 应用中创建和验证表单的过程,提供了一种简单而强大的方式来处理用户提交的数据。主要功能包括:

  1. 表单类的定义: 使用 Flask-WTF,你可以通过创建表单类来定义表单及其字段。这些字段可以是文本输入、密码输入、复选框等等,你还可以定义验证规则来确保用户输入的有效性。
  2. CSRF 保护: Flask-WTF 自动为表单添加 CSRF 保护,以防止跨站请求伪造攻击。这意味着在提交表单时,会自动验证表单中的 CSRF 令牌。
  3. 表单验证: Flask-WTF 提供了对表单数据进行验证的功能。它可以验证字段是否为空、是否符合特定格式、是否满足自定义验证函数等。
  4. 表单渲染: Flask-WTF 提供了简单的方式来将表单对象渲染成 HTML 表单,以便在模板中显示给用户填写。
  5. 文件上传: Flask-WTF 支持文件上传功能,你可以通过在表单中添加 FileField 字段来实现文件上传。
  6. 国际化支持: Flask-WTF 支持国际化,你可以轻松地将表单字段的标签和错误消息翻译成不同的语言。

安装 Flask-WTF

使用如下的命令安装 Flask-WTF 表单库:

pip install Flask-WTF

简单使用

以下是一个简单的示例,演示了如何在 Flask 应用中使用 Flask-WTF 处理表单:

from flask import Flask, render_template, request
from flask_wtf import FlaskForm
from wtforms import StringField, SubmitField
from wtforms.validators import DataRequired

app = Flask(__name__)
app.config['SECRET_KEY'] = 'your_secret_key'


class MyForm(FlaskForm):
    name = StringField('Name', validators=[DataRequired()])
    submit = SubmitField('Submit')


@app.route('/', methods=['GET', 'POST'])
def index():
    form = MyForm()
    if form.validate_on_submit():
        ## 处理表单提交逻辑
        name = form.name.data
        return f'Hello, {name}!'
    return render_template('index.html', form=form)


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

在上面的示例中,我们定义了一个简单的表单类 MyForm,它包含一个文本输入字段和一个提交按钮。在视图函数 index 中,我们创建了表单对象并将其传递给模板 index.html 进行渲染。

然后,我们创建了一个模板 index.html,用于渲染表单:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Flask-WTF Example</title>
</head>
<body>
<h1>My Form</h1>
<form method="POST" action="/">
    {{ form.hidden_tag() }}
    {{ form.name.label }} <br>
    {{ form.name }} <br>
    {{ form.name.errors }} <br>
    {{ form.submit }}
</form>
</body>
</html>

这样,用户就可以在浏览器中访问该页面,并填写表单。提交表单后,Flask-WTF 会自动验证表单数据并执行相应的操作。

表单样式

Flask-WTF 并不直接处理表单的样式,但你可以使用自定义的 CSS 样式来美化你的表单。通常,你可以使用框架如 Bootstrap 或 Bulma 来轻松地为 Flask-WTF 表单添加样式。

下面是一些示例代码,演示了如何使用 Bootstrap 和 Flask-WTF 结合来创建样式美观的表单:

首先,确保你已经安装了 Bootstrap,并在你的 Flask 应用中引入它:

<!-- 在你的 base.html 或模板文件中引入 Bootstrap CSS 文件 -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/css/bootstrap.min.css" rel="stylesheet"
      integrity="sha384-B4gt1jrGC7Jh4AgTPSdUtOBvfO8shC1tg3S9k7Xs5SQx0qE/hzjNsk30lBV6cKD1" crossorigin="anonymous">

然后,你可以在你的 Flask-WTF 表单中使用 Bootstrap 类来为表单元素添加样式。例如:

from flask_wtf import FlaskForm
from wtforms import StringField, SubmitField
from wtforms.validators import DataRequired


class MyForm(FlaskForm):
    name = StringField('Name', validators=[DataRequired()], render_kw={"class": "form-control"})
    submit = SubmitField('Submit', render_kw={"class": "btn btn-primary"})

在上面的示例中,我们使用了 render_kw 参数来向字段和提交按钮添加了 Bootstrap 的类。这样,表单元素就会自动应用 Bootstrap 样式。

最后,在你的模板中,你可以简单地渲染表单元素,它们将会自动应用 Bootstrap 样式:

<form method="POST" action="/">
    {{ form.hidden_tag() }}
    <div class="mb-3">
        {{ form.name.label(class="form-label") }}
        {{ form.name(class="form-control") }}
        {% for error in form.name.errors %}
        <div class="invalid-feedback">{{ error }}</div>
        {% endfor %}
    </div>
    {{ form.submit }}
</form>

在这个示例中,我们使用了 Bootstrap 的类来添加样式,并使用了简单的条件语句来显示验证错误信息。这样,你就可以轻松地为 Flask-WTF 表单添加样式,使其看起来更加美观和专业。

表单字段

WTforms 包中包含各种表单字段的定义,WTForms 支持 HTML 的字段有:

字段 说明
BooleanField 复选框,值为True或False,相当于HTML的
DateField 文本字段, 值为datetime.date格式
DateTimeField 文本字段, 值为datetime.datetime格式
IntegerField 文本字段, 值为整数
DecimalField 用于显示带小数的数字的文本字段,值为decimal.Decimal
FloatField 文本字段, 值为浮点数
RadioField 一组单选框
FileField 文件上传字段
SelectField 下拉列表
SelectMultipleField 下拉列表, 可选择多个值
SubmitField 表单提交按钮,相当于HTML的
StringField 文本字段,相当于HTML的
TextAreaField 多行文本字段,相当于HTML的
HiddenField 隐藏文本字段,相当HTML的
FormFiled 把表单作为字段嵌入另一个表单
FieldList 子组指定类型的字段
PasswordField 密码文本字段,相当于HTML的

验证器

WTForms 支持的 validators 验证器有:

验证函数 说明
Email 验证是电子邮件地址
EqualTo 比较两个字段的值; 常用于要求输入两次信息进行确认的情况
IPAddress 验证IPv4网络地址
Length 验证输入字符串的长度
NumberRange 验证输入的值在数字范围内
Optional 无输入值时跳过其它验证函数
DataRequired 确保字段中有数据
Regexp 使用正则表达式验证输入值
URL 验证url
AnyOf 确保输入值在可选值列表中
NoneOf 确保输入值不在可选列表中

验证器的主要作用是进行表单数据的验证工作,例如需要验证 name 的数据不能为空,password 字段不能为空,且长度在 6 到 12 位数字之间。

from flask_wtf import FlaskForm
from wtforms import StringField, PasswordField
from wtforms.validators import DataRequired, length


class MyForm(FlaskForm):
    name = StringField('name', validators=[DataRequired()])
    password = PasswordField('password', validators=[DataRequired(), length(min=6, max=12)])

此时,上述代码对应的 Html 模板代码如下:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<form action="/login" method="post">
    <p>{{ myform.name }}</p>
    <p>{{ myform.password }}</p>
    <p>{{ myform.submit }}</p>
</form>
</body>
</html>

当用户点击按钮时,就通过 POST 的方式请求,路由的接受如下:

from flask import Flask, render_template
from form import MyForm

app = Flask(__name__)


@app.route('/')
def user_form():
    myform = MyForm()
    if myform.validate_on_submit():
        return '提交成功'
    return render_template('form.html', myform=myform)


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

自定义验证器

在 Flask-WTF 中,你可以使用自定义验证器来验证表单字段中的数据。自定义验证器允许你定义自己的验证函数,并将其应用于表单字段,以确保用户输入的数据符合你的特定要求。

以下是一个示例,演示了如何在 Flask-WTF 中创建自定义验证器:

from flask import Flask, render_template
from flask_wtf import FlaskForm
from wtforms import StringField, SubmitField
from wtforms.validators import DataRequired, ValidationError

app = Flask(__name__)
app.config['SECRET_KEY'] = 'your_secret_key'


## 自定义验证器函数
def validate_name(form, field):
    if field.data.lower() == 'admin':
        raise ValidationError('Username cannot be "admin".')


## 定义表单类
class MyForm(FlaskForm):
    name = StringField('Username', validators=[DataRequired(), validate_name])
    submit = SubmitField('Submit')


## 定义视图函数
@app.route('/', methods=['GET', 'POST'])
def index():
    form = MyForm()
    if form.validate_on_submit():
        return f'Hello, {form.name.data}!'
    return render_template('index.html', form=form)


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

在上面的示例中,我们定义了一个名为 validate_name 的自定义验证器函数。该函数接受表单对象和字段作为参数,并在字段数据不符合要求时引发 ValidationError 异常。然后,我们在 MyForm 类中将自定义验证器函数应用于 name 字段。当用户输入的用户名为 "admin" 时,将会触发验证错误。最后,在视图函数中,我们检查表单是否通过验证,如果通过验证,则返回一个包含用户输入的欢迎消息。

在模板中,你可以像往常一样渲染表单,Flask-WTF 将自动处理验证器并显示相应的错误消息。通过使用自定义验证器,你可以根据需要对用户输入进行更灵活和个性化的验证。

CSRF

CSRF(Cross-Site Request Forgery)是一种常见的网络安全威胁,攻击者利用用户已经认证的身份,在用户不知情的情况下执行未经授权的操作。为了防止 CSRF 攻击,Flask-WTF 提供了 CSRF 保护机制。

在 Flask-WTF 中启用 CSRF 保护非常简单,只需在应用配置中设置一个密钥。这个密钥会用于生成和验证 CSRF 令牌,以确保表单提交是来自你的应用而不是恶意网站的请求。

以下是一个示例,演示了如何在 Flask 应用中启用 CSRF 保护:

from flask import Flask, render_template
from flask_wtf import FlaskForm
from wtforms import StringField, SubmitField
from wtforms.validators import DataRequired

app = Flask(__name__)

## 设置密钥用于 CSRF 保护
app.config['SECRET_KEY'] = 'your_secret_key'
## 启动CSRF保护
csrf = CSRFProtect(app)


## 定义表单类
class MyForm(FlaskForm):
    name = StringField('Name', validators=[DataRequired()])
    submit = SubmitField('Submit')


## 定义视图函数
@app.route('/', methods=['GET', 'POST'])
def index():
    form = MyForm()
    if form.validate_on_submit():
        ## 处理表单提交
        return f'Hello, {form.name.data}!'
    return render_template('index.html', form=form)


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

在上面的示例中,我们设置了 app.config['SECRET_KEY'] 为一个随机字符串,这个字符串会被用于生成和验证 CSRF 令牌。确保这个密钥足够安全,不要泄露给任何人。Flask-WTF 会自动在表单中生成一个隐藏字段,用于存储 CSRF 令牌。在表单提交时,Flask-WTF 会验证这个令牌,以确保请求是合法的。通过启用 CSRF 保护,你可以增强你的 Flask 应用的安全性,防止 CSRF 攻击对用户数据和应用功能造成危害。

启用 CSRF 保护后,Flask-WTF 会自动在表单中生成一个隐藏字段,用于存储 CSRF 令牌。这个令牌将在每次表单提交时被发送到服务器,并在服务器端验证,以确保请求是合法的。

在 HTML 表单中不需要手动设置隐藏值,Flask-WTF 会在渲染表单时自动添加这个隐藏字段。你只需要按照通常的方式渲染表单即可,例如:

htmlCopy Code<form method="POST" action="/">
    {{ form.hidden_tag() }}
    {{ form.name.label }} {{ form.name() }}
    {{ form.submit() }}
</form>

{{ form.hidden_tag() }} 这一行会渲染出一个隐藏字段,用于存储 CSRF 令牌。当用户提交表单时,这个隐藏字段会自动包含在请求中,Flask-WTF 会在后台验证令牌的有效性。因此,只要使用了 Flask-WTF 提供的表单渲染方法,你就无需手动添加 CSRF 令牌的隐藏字段。

数据库

Flask 可以与多种类型的数据库集成,最常见的是关系型数据库(如 SQLite、MySQL、PostgreSQL)和非关系型数据库(如 MongoDB)。这里我会简要介绍如何集成 Flask 与 SQLite 和 MySQL 数据库。

集成 SQLite 数据库

SQLite 是一种轻量级的嵌入式数据库,适合小型项目或原型开发。在 Flask 中使用 SQLite 非常简单,因为它已经内置了对 SQLite 的支持。首先,确保你已经安装了 Flask-SQLAlchemy 扩展,它提供了对 SQLAlchemy ORM 的支持。

pip install Flask-SQLAlchemy

然后,你可以像下面这样配置你的 Flask 应用:

from flask import Flask
from flask_sqlalchemy import SQLAlchemy

app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///mydatabase.db'  ## 数据库文件路径
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False

db = SQLAlchemy(app)


## 定义模型类
class User(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String(80), unique=True, nullable=False)

    def __repr__(self):
        return '<User %r>' % self.username


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

集成 MySQL 数据库

如果你想要使用 MySQL 数据库,你需要安装并配置 PyMySQL 或 MySQL 客户端。

pip install pymysql

然后,你可以像下面这样配置你的 Flask 应用:

from flask import Flask
from flask_sqlalchemy import SQLAlchemy

app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'mysql+pymysql://username:password@localhost/db_name'  ## MySQL连接URL
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False

db = SQLAlchemy(app)

## 定义模型类
class User(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String(80), unique=True, nullable=False)

    def __repr__(self):
        return '<User %r>' % self.username

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

模型类

列类型和列选项

在 Flask 中,使用 SQLAlchemy 时,模型类是用来定义数据库表结构的 Python 类。每个模型类代表数据库中的一个表,类中的属性则代表表中的列。Flask-SQLAlchemy 提供了丰富的列类型和列选项,用于定义数据库表结构。下面是一些最常用的列类型和列选项:

好的,以下是常用的列类型和列选项的表格形式:

列类型(Column Types) 描述 示例用法
Integer 整数类型 id = db.Column(db.Integer, primary_key=True)
String 字符串类型,可指定最大长度 username = db.Column(db.String(80), unique=True)
Text 长文本类型 description = db.Column(db.Text)
Boolean 布尔类型 is_active = db.Column(db.Boolean, default=True)
DateTime 日期时间类型 created_at = db.Column(db.DateTime, default=datetime.utcnow)
Float 浮点数类型 price = db.Column(db.Float)
ForeignKey 外键类型,用于定义外键关联 user_id = db.Column(db.Integer, db.ForeignKey('user.id'))
Relationship 关系型列类型,用于定义模型之间的关联关系 user = db.relationship('User', backref='posts')
列选项(Column Options) 描述 示例用法
primary_key 设置列为主键 id = db.Column(db.Integer, primary_key=True)
unique 设置列的值必须唯一 username = db.Column(db.String(80), unique=True)
nullable 设置列的值是否允许为空 username = db.Column(db.String(80), nullable=False)
default 设置列的默认值 is_active = db.Column(db.Boolean, default=True)
index 为列创建索引,提高查询效率 db.Column(db.String(80), index=True)
autoincrement 设置列自动增长 id = db.Column(db.Integer, primary_key=True, autoincrement=True)
onupdate 设置列更新时的行为 updated_at = db.Column(db.DateTime, onupdate=datetime.utcnow)
ForeignKey 设置外键关联 user_id = db.Column(db.Integer, db.ForeignKey('user.id'))

类关系

SQLAlchemy 提供了丰富的关系选项,用于定义模型之间的关联关系。以下是一些常用的 SQLAlchemy 关系选项:

  1. backref:在关联的另一个模型中创建反向引用,使得可以通过反向引用方便地访问关联对象。

user = db.relationship('User', backref='posts')
这将在 User 模型中创建一个名为 posts 的属性,可以访问与该用户相关联的所有帖子对象。

  1. lazy:指定关系的加载方式,可以选择 selectjoinedsubquerydynamic 等方式。

user = db.relationship('User', lazy='select')
指定关系的加载方式是为了控制 SQLAlchemy 在访问关联对象时的查询行为,以优化性能和减少查询次数。下面是各种加载方式的作用:

1. **select**:默认加载方式。当访问关联对象时,将执行一个额外的 SQL 查询来加载关联对象。这种方式简单直接,但如果一次性加载了多个对象,可能会导致
   N+1 查询问题,性能较差。
2. **joined**:在查询主对象时,同时将关联对象的数据一起加载。这样可以通过一次 SQL 查询完成关联对象的加载,避免了 N+1
   查询问题,性能较好。但是如果关联对象数量庞大,可能会导致查询结果数据量过大。
3. **subquery**:类似于 `joined`,但是在查询时会将关联对象的数据先查询出来放入子查询中,然后再与主查询进行连接。这种方式可以减少重复的数据行,适用于关联对象数据量较大的情况。
4. **dynamic**:动态加载方式。不会立即加载关联对象,而是返回一个查询对象,当真正需要访问关联对象时,才会执行额外的 SQL
   查询来加载数据。这样可以延迟加载,避免不必要的查询,提高性能。

根据具体的场景和性能需求,可以选择合适的加载方式来优化查询性能。例如,如果关联对象数量较少且经常需要访问,则可以选择 joinedsubquery;如果关联对象数量较多或访问频率较低,则可以选择 dynamic 来延迟加载,避免不必要的查询。

  1. uselist:指定关系是否使用列表形式存储多个对象,默认为 True

    comments = db.relationship('Comment', backref='post', uselist=False)
    
    这将在 Post 模型中创建一个名为 comment 的属性,用于存储与该帖子关联的评论对象,但是该属性是单个对象而不是列表。

  2. cascade:指定级联操作的行为,可以选择 save-updatedeleteall 等。

posts = db.relationship('Post', backref='author', cascade='all, delete-orphan')
cascade 是 SQLAlchemy 中用于指定级联操作行为的选项。它定义了当对父对象执行某种操作时,如何处理与其相关联的子对象。以下是一些常用的 cascade 选项:

1. **save-update**:当父对象被插入到数据库中或者其属性被修改时,级联保存相关联的子对象。这意味着当父对象被保存时,相关联的子对象也会被保存。
2. **delete**:当父对象被删除时,级联删除与其相关联的子对象。这意味着当父对象被删除时,相关联的子对象也会被删除。
3. **all**:包括了 `save-update`  `delete`,即当父对象被保存或删除时,都会级联执行相应操作。
4. **delete-orphan**:当父对象中的子对象被移除关联时,级联删除这些孤立的子对象。例如,当一个帖子的作者被更改时,原作者不再与该帖子关联,那么该作者就成为了孤立的子对象,级联操作将会删除这些孤立的子对象。

使用 cascade 选项可以简化数据库操作,并确保父对象与子对象之间的一致性。但是需要谨慎使用,以避免意外的数据修改或删除。

  1. secondary:用于多对多关系中,指定中间表。

followers = db.relationship('User', secondary=followers,
                            primaryjoin=(followers.c.follower_id == id),
                            secondaryjoin=(followers.c.followed_id == id),
                            backref=db.backref('followed_by', lazy='dynamic'),
                            lazy='dynamic')
这将指定 followers 表为多对多关系中的中间表。

  1. primaryjoinsecondaryjoin:用于自定义多对多关系中的连接条件。

primaryjoin=(followers.c.follower_id == id),
secondaryjoin=(followers.c.followed_id == id)
这将指定多对多关系中的连接条件。

这些是一些常用的 SQLAlchemy 关系选项,可以根据具体的应用需求选择合适的选项来定义模型之间的关联关系。

增删改查

定义模型(Model)

首先,定义数据模型,可以使用 SQLAlchemy 或者其他 ORM 库来实现。例如,使用 SQLAlchemy 定义一个简单的模型:

from flask_sqlalchemy import SQLAlchemy

db = SQLAlchemy()


class User(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String(50), unique=True, nullable=False)
    email = db.Column(db.String(100), unique=True, nullable=False)

创建 Flask 应用

from flask import Flask

app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///example.db'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False

db.init_app(app)

创建数据库表

## 在 Flask Shell 中执行以下命令
from your_app import db

db.create_all()

实现增删改查操作

from flask import request, jsonify


## 添加用户
@app.route('/users', methods=['POST'])
def create_user():
    data = request.get_json()
    new_user = User(username=data['username'], email=data['email'])
    db.session.add(new_user)
    db.session.commit()
    return jsonify({'message': 'User created successfully'}), 201


## 获取所有用户
@app.route('/users', methods=['GET'])
def get_users():
    users = User.query.all()
    return jsonify([user.serialize() for user in users])


## 获取单个用户
@app.route('/users/<int:user_id>', methods=['GET'])
def get_user(user_id):
    user = User.query.get_or_404(user_id)
    return jsonify(user.serialize())


## 更新用户信息
@app.route('/users/<int:user_id>', methods=['PUT'])
def update_user(user_id):
    user = User.query.get_or_404(user_id)
    data = request.get_json()
    user.username = data['username']
    user.email = data['email']
    db.session.commit()
    return jsonify({'message': 'User updated successfully'})


## 删除用户
@app.route('/users/<int:user_id>', methods=['DELETE'])
def delete_user(user_id):
    user = User.query.get_or_404(user_id)
    db.session.delete(user)
    db.session.commit()
    return jsonify({'message': 'User deleted successfully'})

这些是一个简单的 Flask 应用中进行增删改查操作的基本步骤。在实际应用中,你可能还需要添加身份验证、错误处理等功能来完善你的应用。

get_or_404

get_or_404 是 Flask 中的一个便捷方法,用于从数据库中获取对象。它的作用是尝试从数据库中获取指定主键的对象,如果找到了,则返回该对象;如果未找到,则返回一个 404 错误页面,表示请求的资源不存在。

通常情况下,get_or_404 方法用于处理 HTTP 请求中的资源获取操作,当请求的资源不存在时,它会自动返回一个 404 错误页面,告诉用户所请求的资源未找到。

在上面的 Flask 示例中,你可以看到 get_or_404 的用法:

## 获取单个用户
@app.route('/users/<int:user_id>', methods=['GET'])
def get_user(user_id):
    user = User.query.get_or_404(user_id)
    return jsonify(user.serialize())

在这个示例中,当用户访问 /users/<user_id> 路由时,Flask 将会尝试从数据库中根据提供的 user_id 获取用户对象。如果找到了对应的用户对象,则将其序列化为 JSON 格式并返回;如果未找到对应的用户对象,则会返回一个 404 错误页面。这样可以确保在请求资源不存在时,用户能够得到一个清晰的错误提示,而不是返回一个空值或者其他不明确的结果。

认证和授权

在 Flask 中进行认证(Authentication)和授权(Authorization)是确保 Web 应用安全性的重要步骤。认证通常指的是验证用户的身份,而授权则是确定用户是否有权执行特定操作或访问特定资源。

认证

使用 Flask-Login 进行用户认证。Flask-Login 是一个常用的用于管理用户会话的扩展,可以轻松地实现用户登录功能:

pip install Flask-Login

在 Flask 应用中初始化 Flask-Login:

from flask import Flask
from flask_login import LoginManager

app = Flask(__name__)
login_manager = LoginManager(app)

定义用户模型,并实现用户加载函数:

from flask_login import UserMixin


class User(UserMixin):
    pass


@login_manager.user_loader
def load_user(user_id):
    ## 根据用户ID加载用户对象
    return User.get(user_id)

创建登录视图和认证逻辑:

from flask import request, redirect, url_for
from flask_login import login_user


@app.route('/login', methods=['GET', 'POST'])
def login():
    if request.method == 'POST':
        ## 验证用户身份,比如从数据库中验证用户名和密码
        user = User.query.filter_by(username=request.form['username']).first()
        if user and check_password_hash(user.password, request.form['password']):
            login_user(user)
            return redirect(url_for('index'))
    return render_template('login.html')

授权

路由保护

通过在路由函数上使用装饰器来限制访问权限。

from flask_login import login_required


@app.route('/dashboard')
@login_required
def dashboard():
    ## 只有经过认证的用户才能访问这个页面
    return render_template('dashboard.html')

角色控制

为了在 Flask 应用中实现角色和权限控制,你可以在用户模型中添加角色和权限字段,并在路由中根据用户的角色和权限来进行访问控制。下面是一个示例:

from flask_login import UserMixin
from werkzeug.security import generate_password_hash, check_password_hash


class User(UserMixin):
    def __init__(self, id, username, password, role):
        self.id = id
        self.username = username
        self.password = generate_password_hash(password)
        self.role = role

    ## 添加方法用于验证密码
    def check_password(self, password):
        return check_password_hash(self.password, password)

    ## 添加方法用于检查用户是否有指定权限
    def has_permission(self, permission):
        ## 在实际应用中,可以根据用户的角色来判断权限
        if self.role == 'admin':
            return True
        elif self.role == 'editor' and permission == 'edit_article':
            return True
        else:
            return False

在这个示例中,用户模型 User 包含了角色(role)字段和权限验证方法(has_permission)。你可以根据实际情况扩展这些字段和方法,以满足你的需求。

接下来,你可以在路由中使用这些信息来进行访问控制。例如:

from flask_login import current_user, login_required


@app.route('/admin')
@login_required
def admin_dashboard():
    if current_user.role == 'admin':
        ## 只有管理员才能访问管理员页面
        return render_template('admin_dashboard.html')
    else:
        return 'Unauthorized', 403


@app.route('/edit_article/<int:article_id>')
@login_required
def edit_article(article_id):
    if current_user.has_permission('edit_article'):
        ## 只有具有编辑权限的用户才能编辑文章
        ## 实际操作中,你可能会根据文章作者等信息进一步验证权限
        return render_template('edit_article.html', article_id=article_id)
    else:
        return 'Unauthorized', 403

在这个示例中,通过检查用户的角色和权限,我们可以控制用户是否有权访问特定的页面或执行特定的操作。在实际应用中,你可能需要更复杂的角色和权限控制逻辑。

中间件开发

在 Flask 中,中间件通常被称为 "Middleware" ,它们是在请求到达应用程序之前或响应发送到客户端之前执行的代码。中间件可以用于执行各种任务,例如身份验证、日志记录、错误处理等。下面是一个简单的示例,演示如何在 Flask 中开发一个自定义的中间件:

from flask import Flask, request

app = Flask(__name__)


## 自定义中间件
class CustomMiddleware:
    def __init__(self, app):
        self.app = app

    def __call__(self, environ, start_response):
        ## 在请求到达应用程序之前执行的代码
        ## 可以在这里执行身份验证、日志记录等任务
        print("Middleware: Before Request")

        ## 继续请求的传递
        response = self.app(environ, start_response)

        ## 在响应发送到客户端之前执行的代码
        ## 可以在这里执行日志记录、错误处理等任务
        print("Middleware: After Request")

        return response


## 注册中间件
app.wsgi_app = CustomMiddleware(app.wsgi_app)


## 路由
@app.route('/')
def index():
    return 'Hello, World!'


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

在这个示例中,CustomMiddleware 是一个自定义的中间件类,它接受应用程序实例作为参数,并实现了 __call__ 方法,该方法接收环境和开始响应函数作为参数。在 __call__ 方法中,我们可以编写在请求到达应用程序之前和响应发送到客户端之前执行的代码。

然后,我们将 CustomMiddleware 注册到应用程序的 WSGI 中间件列表中,这样它就会在每个请求到达应用程序之前和响应发送到客户端之前被调用。

__call__ 方法中,你可以根据需要执行各种任务,例如身份验证、日志记录、错误处理等。这只是一个简单的示例,你可以根据具体需求来扩展和定制中间件。