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 会自动对传入的参数进行类型转换。常见的数据类型有 int、float 和 path。示例如下:
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/123,123 会被转换为整数类型并传递给 show_post 函数。
4. HTTP 请求方法
默认情况下,@app.route 只响应 GET 请求。不过,你可以通过 methods 参数指定路由支持的 HTTP 请求方法,像 POST、PUT、DELETE 等。示例如下:
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. 继承
- 语法:通过
extends和block标签实现模板继承。 - 基础模板
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>
© 2024 My Website
</footer>
</body>
</html>
基础模板里,定义了两个块:title 和 content。
- 子模板
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)
-
template_name_or_list: -
这是一个必需参数。它既可以是单个模板名(字符串类型),也可以是模板名列表(列表类型)。
- 若传入的是单个模板名,Flask 会直接查找该模板文件并进行渲染。
-
若传入的是模板名列表,Flask 会按照列表顺序依次查找模板文件,一旦找到就使用该模板进行渲染。
-
**context: -
这是一个可变关键字参数,也就是可以传入任意数量的键值对。
-
这些键值对会作为变量传递给模板,在模板中能够通过键来访问对应的值。
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_TIMEOUT 和 SQLALCHEMY_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-sqlalchemy的db.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 模型,包含 id、name 和 email 三个字段,__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():db是SQLAlchemy的实例,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.query:query是 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()
发表评论