Flask


Flask

Flask 是一款优秀的 Python Web 应用框架,以其轻量级、灵活性和强大的社区支持,在 Web 开发领域占据重要地位,适用于各种规模和类型的项目。

1. 路由

在 Flask 中,路由函数是实现 Web 应用功能的核心部分,它将特定的 URL 路径与对应的处理逻辑关联起来。

1. 基本路由定义

在 Flask 里,使用 @app.route 装饰器来定义路由。这个装饰器的作用是把一个 URL 路径和一个函数绑定,当客户端访问该 URL 时,就会执行对应的函数。下面是一个简单示例:

from flask import Flask

app = Flask(__name__)

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

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

在这个例子中,@app.route('/') 把根路径 '/'index 函数绑定在一起。当用户访问应用的根 URL 时,index 函数会被调用,并且返回字符串 'Hello, World!'

2. 动态路由

Flask 允许你在 URL 里使用动态部分,也就是 URL 变量。这些变量会被当作参数传递给对应的路由函数。示例如下:

from flask import Flask

app = Flask(__name__)

@app.route('/user/<username>')
def show_user_profile(username):
    return f'User {username}'

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

在这个例子中,/<username> 就是一个动态部分。当用户访问 /user/john 时,john 会作为参数传递给 show_user_profile 函数,最终返回 'User john'

3. 路由中的数据类型

你可以为动态路由指定数据类型,这样 Flask 会自动对传入的参数进行类型转换。常见的数据类型有 intfloatpath。示例如下:

from flask import Flask

app = Flask(__name__)

@app.route('/post/<int:post_id>')
def show_post(post_id):
    return f'Post {post_id}'

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

在这个例子中,/<int:post_id> 表明 post_id 应该是一个整数。如果用户访问 /post/123123 会被转换为整数类型并传递给 show_post 函数。

4. HTTP 请求方法

默认情况下,@app.route 只响应 GET 请求。不过,你可以通过 methods 参数指定路由支持的 HTTP 请求方法,像 POSTPUTDELETE 等。示例如下:

from flask import Flask, request

app = Flask(__name__)

@app.route('/login', methods=['GET', 'POST'])
def login():
    if request.method == 'POST':
        return 'Doing login...'
    else:
        return 'Show login form...'

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

在这个例子中,/login 路由既支持 GET 请求也支持 POST 请求。当是 GET 请求时,会显示登录表单;当是 POST 请求时,会执行登录操作。

5. 路由别名

你可以使用 endpoint 参数为路由指定一个别名,之后可以通过 url_for 函数来生成对应的 URL。示例如下:

from flask import Flask, url_for

app = Flask(__name__)

@app.route('/user/<username>', endpoint='user_profile')
def show_user_profile(username):
    return f'User {username}'

@app.route('/')
def index():
    return url_for('user_profile', username='john')

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

在这个例子中,/user/<username> 路由的别名是 user_profile。在 index 函数里,通过 url_for('user_profile', username='john') 可以生成 /user/john 这个 URL。

6. 错误处理路由

Flask 允许你定义错误处理路由,用于处理特定的 HTTP 错误。示例如下:

from flask import Flask, abort

app = Flask(__name__)

@app.errorhandler(404)
def page_not_found(error):
    return 'This page does not exist', 404

@app.route('/error')
def trigger_error():
    abort(404)

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

在这个例子中,@app.errorhandler(404) 定义了一个 404 错误处理函数。当访问 /error 时,会触发 404 错误,进而调用 page_not_found 函数来处理。

2. 模板

在一般的 Web 程序里,访问一个地址通常会返回一个包含各类信息的 HTML 页面。因为我们的程序是动态的,页面中的某些信息需要根据不同的情况来进行调整,比如对登录和未登录用户显示不同的信息,所以页面需要在用户访问时根据程序逻辑动态生成。

按照默认的设置,Flask 会从程序实例所在模块同级目录的 templates 文件夹中寻找模板,在 Flask 应用中,可使用 render_template 函数来渲染模板。

我们把包含变量和运算逻辑的 HTML 或其他格式的文本叫做模板,执行这些变量替换和逻辑计算工作的过程被称为渲染,这个工作由模板渲染引擎——Jinja2 来完成。

1. Jinja2语法

Jinja2 的语法和 Python 大致相同,后面会陆续接触到一些常见的用法。在模板里,你需要添加特定的定界符将 Jinja2 语句和变量标记出来,下面是三种常用的定界符:

  • {{ ... }} 用来标记变量。

  • {% ... %} 用来标记语句,比如 if 语句,for 语句等。

  • {# ... #} 用来写注释。

1. 变量

  • 语法:使用双花括号 {{ variable }} 来表示变量,在渲染模板时,variable 会被替换为实际的值。
  • 示例
from jinja2 import Template

template = Template('Hello, {{ name }}!')
result = template.render(name='John')
print(result)  # 输出: Hello, John!

2. 语句

条件语句 if-elif-else

  • 语法
{% if condition %}
    ...
{% elif condition %}
    ...
{% else %}
    ...
{% endif %}
  • 示例
from jinja2 import Template

template = Template('''
{% if age >= 18 %}
    You are an adult.
{% elif age >= 13 %}
    You are a teenager.
{% else %}
    You are a child.
{% endif %}
''')
result = template.render(age=20)
print(result)  # 输出: You are an adult.

循环语句 for

  • 语法
{% for item in iterable %}
    ...
{% endfor %}
  • 示例
from jinja2 import Template

template = Template('''
{% for fruit in fruits %}
    {{ fruit }}
{% endfor %}
''')
result = template.render(fruits=['apple', 'banana', 'cherry'])
print(result)  # 输出: apple banana cherry

3. 过滤器

  • 语法:使用管道符号 | 来应用过滤器,格式为 {{ variable|filter }}。过滤器可以对变量进行各种转换和处理。
  • 常见filter过滤器:
  • upper:将字符串转换为大写。
  • lower:将字符串转换为小写。
  • length:返回列表、字符串等的长度。
  • join:将列表中的元素连接成一个字符串。
  • 示例
from jinja2 import Template

template = Template('{{ name|upper }} has {{ fruits|length }} fruits.')
result = template.render(name='John', fruits=['apple', 'banana', 'cherry'])
print(result)  # 输出: JOHN has 3 fruits.

4. 注释

  • 语法:使用 {# comment #} 来添加注释,注释不会在渲染结果中显示。
  • 示例
{# 这是一个注释 #}
{{ name }}

5. 宏(Macros)

宏本质上类似于 Python 里的函数,可将一段常用的模板代码封装成宏,之后在不同的模板中通过导入并调用宏来复用这段代码。这样能避免在多个地方重复编写相同的代码,减少代码冗余。

  • 语法:类似于 Python 中的函数,用于复用代码块。
{% macro input(name, value='', type='text') %}
    <input type="{{ type }}" name="{{ name }}" value="{{ value }}">
{% endmacro %}

{{ input('username') }}

6. 继承

  • 语法:通过 extendsblock 标签实现模板继承。
  • 基础模板 base.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>{% block title %}Default Title{% endblock %}</title>
</head>
<body>
    <header>
        <h1>My Website</h1>
    </header>
    <main>
        {% block content %}
            This is the default content.
        {% endblock %}
    </main>
    <footer>
        &copy; 2024 My Website
    </footer>
</body>
</html>

基础模板里,定义了两个块:titlecontent

  • 子模板 child.html
{% extends 'base.html' %}

{% block title %}Child Page{% endblock %}

{% block content %}
    <p>This is the child page content.</p>
{% endblock %}

block 标签用来在基础模板里定义可被替换或者扩展的区域。在子模板中,可借助 block 标签重新定义这些区域的内容。

除了完全覆盖块内容,还能在子模板中使用 {{ super() }} 来扩展基础模板里的块内容:

{% extends 'base.html' %}

{% block content %}
    {{ super() }}
    <p>这是额外添加的内容。</p>
{% endblock %}

7. 导入

  • 语法:使用 {% import 'macros.html' as macros %} 来导入其他模板中的宏。
  • macros.html
{% macro button(text) %}
    <button>{{ text }}</button>
{% endmacro %}
  • 使用导入的宏
{% import 'macros.html' as macros %}
{{ macros.button('Click me') }}

2. 渲染模板

使用 render_template() 函数可以把模板渲染出来,必须传入的参数为模板文件名(相对于 templates 根目录的文件路径)。为了让模板正确渲染,我们还要把模板内部使用的变量通过关键字参数传入这个函数,如下所示:

render_template(template_name_or_list, **context)
  1. template_name_or_list

  2. 这是一个必需参数。它既可以是单个模板名(字符串类型),也可以是模板名列表(列表类型)。

  3. 若传入的是单个模板名,Flask 会直接查找该模板文件并进行渲染。
  4. 若传入的是模板名列表,Flask 会按照列表顺序依次查找模板文件,一旦找到就使用该模板进行渲染。

  5. **context

  6. 这是一个可变关键字参数,也就是可以传入任意数量的键值对。

  7. 这些键值对会作为变量传递给模板,在模板中能够通过键来访问对应的值。

from flask import Flask, render_template

# ...

@app.route('/')
def index():
    return render_template('index.html', name=name, movies=movies)

html文件中包含name和movies变量,省略

在传入 render_template() 函数的关键字参数中,左边的 movies 是模板中使用的变量名称,右边的 movies 则是该变量指向的实际对象。这里传入模板的 name 是字符串,movies 是列表,但能够在模板里使用的不只这两种 Python 数据结构,你也可以传入元组、字典、函数等。

3. 静态文件

静态文件(static files)和我们的模板概念相反,指的是内容不需要动态生成的文件。比如图片、CSS 文件和 JavaScript 脚本等。

在 Flask 中,我们需要创建一个 static 文件夹来保存静态文件,它应该和程序模块、templates 文件夹在同一目录层级。

1. 生成静态文件 URL

在 HTML 文件里,引入这些静态文件需要给出资源所在的 URL。为了更加灵活,这些文件的 URL 可以通过 Flask 提供的 url_for() 函数来生成。 url_for() 函数的用法,传入端点值(视图函数的名称)和参数,它会返回对应的 URL。对于静态文件,需要传入的端点值是 static,同时使用 filename 参数来传入相对于 static 文件夹的文件路径。

假如我们在 static 文件夹的根目录下面放了一个 foo.jpg 文件,下面的调用可以获取它的 URL:

<img src="{{ url_for('static', filename='foo.jpg') }}">

花括号部分的调用会返回 /static/foo.jpg

提示 在 Python 脚本里,url_for() 函数需要从 flask 包中导入,而在模板中则可以直接使用,因为 Flask 把一些常用的函数和对象添加到了模板上下文(环境)里。

2. 添加 Favicon

Favicon(favourite icon) 是显示在标签页和书签栏的网站头像。你需要准备一个 ICO、PNG 或 GIF 格式的图片,大小一般为 16×16、32×32、48×48 或 64×64 像素。把这个图片放到 static 目录下,然后像下面这样在 HTML 模板里引入它:

templates/index.html:引入 Favicon

<head>
    ...
    <link rel="icon" href="{{ url_for('static', filename='favicon.ico') }}">
</head>

保存后刷新页面,即可在浏览器标签页上看到这个图片。

4. 模板优化

1. 模板上下文函数

使用app.context_processor 装饰器注册一个模板上下文处理函数

模板上下文处理函数的核心作用是在渲染模板之前,向模板的上下文里添加额外的变量和函数。模板上下文指的是在模板中可以使用的变量和函数集合。借助模板上下文处理函数,你无需在每个视图函数中重复传递相同的变量和函数,从而提高代码的复用性和可维护性。

5. 使用模板继承组织模板

对于模板内容重复的问题,Jinja2 提供了模板继承的支持。这个机制和 Python 类继承非常类似:我们可以定义一个父模板,一般会称之为基模板(base template)。基模板中包含完整的 HTML 结构和导航栏、页首、页脚等通用部分。在子模板里,我们可以使用 extends 标签来声明继承自某个基模板。

基模板中需要在实际的子模板中追加或重写的部分则可以定义成块(block)。块使用 block 标签创建, {% block 块名称 %} 作为开始标记,{% endblock %}{% endblock 块名称 %} 作为结束标记。通过在子模板里定义一个同样名称的块,你可以向基模板的对应块位置追加或重写内容。

6. 连接数据库(flask_sqlalchemy)

flask_sqlalchemy 是一个用于在 Flask 应用中简化 SQLAlchemy 使用的扩展库,它让你能更方便地与数据库交互。

连接数据库的步骤为:

  • 导入拓展库
 from flask_sqlalchemy import SQLAlchemy
  • 配置数据库信息并连接(app.config)
app = Flask(__name__)
# 配置数据库连接字符串,这里以SQLite为例
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///test.db'
# 关闭追踪对象的修改,以提高性能
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
  • 初始化 SQLAlchemy 类的实例
# 创建SQLAlchemy实例并关联Flask应用
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)
email = db.Column(db.String(120), unique=True, nullable=False)

def __repr__(self):
return '<User %r>' % self.username
  • 创建数据库表
with app.app_context():
db.create_all()
  • 进行数据库的操作

  • 运行Flask应用

1. 配置数据库信息

下面是 Flask-SQLAlchemy 中存在的配置值。Flask-SQLAlchemy 从 Flask 主配置中加载这些值。 注意其中的一些在创建后不能修改,所以确保尽早配置且不在运行时修改它们。

配置 作用
SQLALCHEMY_DATABASE_URI 用于连接数据的数据库。例如: sqlite:////tmp/test.db mysql://username:password@server/db
SQLALCHEMY_BINDS 一个映射绑定 (bind) 键到 SQLAlchemy 连接 URIs 的字典。 更多的信息请参阅 绑定多个数据库
SQLALCHEMY_ECHO 如果设置成 True,SQLAlchemy 将会记录所有 发到标准输出(stderr)的语句,这对调试很有帮助。
SQLALCHEMY_RECORD_QUERIES 可以用于显式地禁用或者启用查询记录。查询记录 在调试或者测试模式下自动启用。更多信息请参阅 get_debug_queries()
SQLALCHEMY_NATIVE_UNICODE 可以用于显式地禁用支持原生的 unicode。这是 某些数据库适配器必须的(像在 Ubuntu 某些版本上的 PostgreSQL),当使用不合适的指定无编码的数据库 默认值时。
SQLALCHEMY_POOL_SIZE 数据库连接池的大小。默认是数据库引擎的默认值 (通常是 5)。
SQLALCHEMY_POOL_TIMEOUT 指定数据库连接池的超时时间。默认是 10。
SQLALCHEMY_POOL_RECYCLE 自动回收连接的秒数。这对 MySQL 是必须的,默认 情况下 MySQL 会自动移除闲置 8 小时或者以上的连接。 需要注意地是如果使用 MySQL 的话, Flask-SQLAlchemy 会自动地设置这个值为 2 小时。
SQLALCHEMY_MAX_OVERFLOW 控制在连接池达到最大值后可以创建的连接数。当这些额外的 连接回收到连接池后将会被断开和抛弃。
SQLALCHEMY_TRACK_MODIFICATIONS 如果设置成 True (默认情况),Flask-SQLAlchemy 将会追踪对象的修改并且发送信号。这需要额外的内存, 如果不必要的可以禁用它。

New in version 0.8: 增加 SQLALCHEMY_NATIVE_UNICODE, SQLALCHEMY_POOL_SIZE, SQLALCHEMY_POOL_TIMEOUTSQLALCHEMY_POOL_RECYCLE 配置键。

New in version 0.12: 增加 SQLALCHEMY_BINDS 配置键。

New in version 0.17: 增加 SQLALCHEMY_MAX_OVERFLOW 配置键。

New in version 2.0: 增加 SQLALCHEMY_TRACK_MODIFICATIONS 配置键。

注意:

SQLALCHEMY_TRACK_MODIFICATIONS 配置一般设置为False以节省内存开销。

SQLALCHEMY_DATABASE_URI配置在连接不同的数据库有不同的格式:

SQLAlchemy 把一个引擎的源表示为一个连同设定引擎选项的可选字符串参数的 URI。URI 的形式是:

dialect+driver://username:password@host:port/database

该字符串中的许多部分是可选的。如果没有指定驱动器,会选择默认的(确保在这种情况下 包含 + )。

Postgres:

postgresql://scott:tiger@localhost/mydatabase

MySQL:

mysql://scott:tiger@localhost/mydatabase

Oracle:

oracle://scott:tiger@127.0.0.1:1521/sidname

SQLite (注意开头的四个斜线):

sqlite:////absolute/path/to/foo.db

示例:

app.config['SQLALCHEMY_DATABASE_URI'] = 'mysql+mysqlconnector://username:password@host:port/database_name'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False

2. 定义模型

模型是 flask_sqlalchemy 中非常重要的概念,它对应数据库中的表,每个模型类的属性对应表中的字段。模型(Model)代表数据库表结构,是对数据库表抽象的类。所有的模型类都需要继承flask-sqlalchemydb.Model基类。当没有显式的指定表名时,模型类名会当成表名,规则是大写转换为小写,并使用下划线分隔单词。 也可以在模型类中通过__tablename__属性显式指定表名。

示例:

class User(db.Model):
    # 定义主键,自增的整数类型
    id = db.Column(db.Integer, primary_key=True)
    # 定义姓名字段,字符串类型,最大长度为 80,不能为空
    name = db.Column(db.String(80), nullable=False)
    # 定义邮箱字段,字符串类型,最大长度为 120,唯一且不能为空
    email = db.Column(db.String(120), unique=True, nullable=False)

    def __repr__(self):
        # 定义对象的字符串表示形式,方便调试和打印
        return '<User %r>' % self.name

上述代码定义了一个 User 模型,包含 idnameemail 三个字段,__repr__ 方法用于返回对象的字符串表示形式。

Column 来定义一列。列名就是赋值给那个变量的名称。如果想要在表中使用不同的名称,可以提供一个想要的列名的字符串作为可选第一个参数。主键用 primary_key=True 标记。可以把多个键标记为主键,此时它们作为复合主键。

列的类型是 Column 的第一个参数。您可以直接提供它们或进一步规定(比如提供一个长度)。下面的类型是最常用的:

类型 说明
Integer 一个整数
String (size) 有长度限制的字符串
Text 一些较长的 unicode 文本
DateTime 表示为 Python datetime 对象的 时间和日期
Float 存储浮点值
Boolean 存储布尔值
PickleType 存储为一个持久化的 Python 对象
LargeBinary 存储一个任意大的二进制数据

3. 创建数据库表

在定义好模型后,需要创建对应的数据库表。可以使用 db.create_all() 方法来创建所有定义的模型对应的表。

with app.app_context():
    db.create_all()

由于 flask_sqlalchemy 的操作依赖于 Flask 应用上下文,所以需要使用 with app.app_context() 来确保操作在正确的上下文中执行。

db.create_all()dbSQLAlchemy的实例,create_all()方法会根据定义的模型类(如User类)在数据库中创建对应的表。如果表已经存在,该方法不会重复创建。

4. 数据库操作

1. 添加数据

要向数据库中添加新记录,可以创建模型对象并将其添加到会话中,最后提交会话。

with app.app_context():
    # 创建一个新的 User 对象
    new_user = User(name='John Doe', email='johndoe@example.com')
    # 将新用户对象添加到会话中
    db.session.add(new_user)
    # 提交会话,将数据保存到数据库
    db.session.commit()

db.session是 SQLAlchemy 的会话对象,用于管理数据库操作的事务。add()方法将new_user对象添加到会话中,此时数据还未真正写入数据库,只是标记为待插入。

commit()方法会提交会话中的所有更改,将new_user对象对应的数据插入到数据库的User表中。如果在提交过程中出现错误,会话会回滚到之前的状态。

2. 查询数据

可以使用 query 对象进行各种查询操作。

with app.app_context():
    # 查询所有用户
    all_users = User.query.all()
    for user in all_users:
        print(user.name)

    # 根据条件查询用户,返回第一个匹配的用户
    user = User.query.filter_by(name='John Doe').first()
    if user:
        print(user.email)

    # 根据主键查询用户
    user_by_id = User.query.get(1)
    if user_by_id:
        print(user_by_id.name)

User.queryquery是 SQLAlchemy 提供的查询对象,用于对User模型对应的表进行查询操作。

filter_by(username='testuser')filter_by()方法用于根据指定的条件过滤查询结果。这里的条件是username等于'testuser'

query对象提供了一系列操作,可用于对数据库表进行查询、过滤、排序、分组等操作。query对象常见的操作:

1. 基本查询操作

all()

返回查询结果的所有记录,以列表形式呈现。

users = User.query.all()

first()

返回查询结果的第一条记录,若未找到则返回None

user = User.query.first()

get()

依据主键值获取单条记录,若未找到则返回None

user = User.query.get(1)

2. 过滤操作

filter()

按照指定条件过滤查询结果,可使用比较运算符(如==!=><等)。

# 查询所有年龄大于18岁的用户
users = User.query.filter(User.age > 18).all()

filter_by()

根据关键字参数过滤查询结果,适用于简单的等值查询。

# 查询用户名为'testuser'的用户
user = User.query.filter_by(username='testuser').first()

like()

用于模糊查询,可搭配通配符%_

# 查询用户名以'test'开头的用户
users = User.query.filter(User.username.like('test%')).all()

in_()

查询某个字段的值在指定列表中的记录。

# 查询用户ID为1、2、3的用户
users = User.query.filter(User.id.in_([1, 2, 3])).all()

not_()

对查询条件取反。

# 查询用户名不为'testuser'的用户
users = User.query.filter(not_(User.username == 'testuser')).all()

and_()or_()

用于组合多个查询条件。

from sqlalchemy import and_, or_

# 查询年龄大于18且用户名以'test'开头的用户
users = User.query.filter(and_(User.age > 18, User.username.like('test%'))).all()

# 查询年龄大于18或用户名以'test'开头的用户
users = User.query.filter(or_(User.age > 18, User.username.like('test%'))).all()

3. 排序操作

order_by()

根据指定字段对查询结果进行排序,可使用asc()(升序)和desc()(降序)。

# 按用户ID升序排序
users = User.query.order_by(User.id.asc()).all()

# 按用户ID降序排序
users = User.query.order_by(User.id.desc()).all()

4. 分页操作

limit()

限制查询结果的记录数量。

# 查询前10条记录
users = User.query.limit(10).all()

offset()

跳过指定数量的记录。

# 跳过前10条记录,查询接下来的10条记录
users = User.query.offset(10).limit(10).all()

paginate()

对查询结果进行分页,返回一个Pagination对象。

# 获取第2页,每页10条记录
page = User.query.paginate(page=2, per_page=10)
users = page.items

5. 聚合操作

count()

统计查询结果的记录数量。

# 统计用户总数
user_count = User.query.count()

group_by()

根据指定字段对查询结果进行分组。

from sqlalchemy import func

# 按用户年龄分组,统计每个年龄的用户数量
age_count = User.query.group_by(User.age).with_entities(User.age, func.count(User.id)).all()

6. 连接操作

join()

用于多表连接查询。

# 假设存在一个Post模型,与User模型有外键关联
posts = Post.query.join(User).filter(User.username == 'testuser').all()

3. 更新数据

要更新数据库中的记录,首先查询到要更新的对象,然后修改其属性,最后提交会话。

with app.app_context():
    # 根据条件查询用户
    user = User.query.filter_by(name='John Doe').first()
    if user:
        # 修改用户的邮箱地址
        user.email = 'newemail@example.com'
        # 提交会话,将修改保存到数据库
        db.session.commit()

4. 删除数据

要删除数据库中的记录,首先查询到要删除的对象,然后将其从会话中删除,最后提交会话。

with app.app_context():
    # 根据条件查询用户
    user = User.query.filter_by(name='John Doe').first()
    if user:
        # 将用户对象从会话中删除
        db.session.delete(user)
        # 提交会话,将删除操作保存到数据库
        db.session.commit()

0 条评论

发表评论

暂无评论,欢迎发表您的观点!