flask笔记
时间:2022-08-10 18:30:00
包括用户提交的信息 账号、文章 等等,需要能够保存这些数据
三种持久存储方法:
- 文件:shelve(pickle 和 DBM 组合)等,提供类似字典的对象接口
- 关系数据库(SQL)
- 非关系数据库(NoSQL)
- 其他
通常使用数据库保存信息,并查询数据库获取信息
SQL,关系数据库
在程序中,关系数据库将数据存储在表中 Python 类别实现。例如,订单管理程序数据库中可能有表 customers、products 和 orders。
列数固定,行数可变。
列
定义表中表示的实体数据属性
。例如,customers表中可能有 name、address、phone 等列。表中的行
定义每列对应的真实数据
。
表中有一个特殊的列,叫做主键
,其值为表中各行各业唯一的标识符
。也可以在表中称为外键
的列,引用同一表或不同表
中某行的主键
。行之间的这种联系称为行之间的这种联系关系
,这是关系数据库模型的基础。
从这个例子可以看出,关系数据库存储数据非常有效,避免了重复
。重新命名这个数据库中的用户角色也很简单,因为角色名只出现在一个地方。一旦在那里 roles 在表中修改角色名称,所有通过 role_id 引用这个角色的用户可以立即看到更新。
但另一方面,将数据存储在多个表中仍然非常复杂。生成一个包含角色的用户列表会遇到一个小问题,因为在此之前,用户和用户角色应该从两个表中读取,然后连接起来。关系数据库引擎为连接操作提供了必要的支持。
将数据单独存储在多个表中,并通过外部键建立连接。减少数据重复。查询更麻烦,但修改更方便。
关系数据库包括:
MySQL
PostgreSQL
SQLite
它是一个存储在硬盘上单个文件中的数据库。每个数据库的所有数据都用一个文件保存。Python 自带。但同一时间只能有一个连接访问。所以强烈建议不要在一个生产环境的web在应用中使用。
等
NoSQL,非关系数据库
- 键值对
键-值对数据存储是基于散列映射的数据结构。
- 面向文档的
MongoDB
Riak
Apache CouchDB
访问关系型数据库
Python 可以通过数据库接口程序(DB-API)
或对象关系映射(ORM)
访问关系数据库。
DB-API
Python 程序可以通过 API 连接到目标数据库, 并用 SQL 语句进行数据读取操作
connect(),创建连接 close(),关闭数据库连接 commit(),提交 rollback(),回滚/取消当前
Python 的官方规范 PEP 0249
MySQL 和 PostgreSQL 是最常见的存储 Python web 应用数据的开源数据库。
- MySQL
唯一的 MySQL API:MySQLdb
- PostgreSQL
有至少三个接口程序
- SQLite
sqlite3
基本 SQL 语句
创建数据库、将数据库的权限赋给某个/全部用户
CREATT DATABASE test;
GRANT ALL ON test.* to user;选择要使用的数据库
USE test;删除数据库
DROP DATABASE test;创建表
CREAT TABLE users;删除表
DROP TABLE users;插入行
INSERT INTO users VALUES();更新行
UPDATE users SET XXX;删除行
DELETE FROM users ;
ORM
使用DB-API
访问数据库,需要懂 SQL 语言,能够写 SQL 语句,如果不想懂 SQL,又想使用关系型数据库,可以使用 ORM
对象关系映射(Object Relational Mapping,简称ORM)
一个 ORM , 它的一端连着 Database, 一端连着 Python DataObject 对象。有了 ORM,可以通过对 Python 对象的操作,实现对数据库的操作,不需要直接写 SQL 语句。ORM 会自动将 Python 代码转换成对应的 SQL 语句。其余的操作,包括数据检查,生成 SQL 语句、事务控制、回滚等交由 ORM 框架来完成。
DataObject 可以通过 Query 执行产生,也可以通过用户自己创建产生。
当然,ORM 还是可以执行原始的 SQL 语句,以便执行一些复杂的/特别的操作。
查找角色为 "User" 的所有用户: >>> user_role = Role(name='User') >>> User.query.filter_by(role=user_role).all() # [u'susan'>, u'david'>]
若要查看 SQLAlchemy 为查询生成的原生 SQL 查询语句,只需`把 query 对象转换成字符串` : >>> str(User.query.filter_by(role=user_role)) 'SELECT users.id AS users_id, users.username AS users_username, users.role_id AS users_role_id FROM users WHERE :param_1 = users.role_id'
数据库将很多 SQL 的功能抽象为 Python 对象,这样,不需要写 SQL 也能完成对数据库的操作。
在Flask 中通过 Python 的类定义数据库的表
from flask.ext.sqlalchemy import SQLAlchemy # 从 flask 扩展中导入 SQLAlchemy db = SQLAlchemy() class Post(db.Model): __tablename__ = 'posts' id = db.Column(db.Integer, primary_key=True) body = db.Column(db.Text) # 博客正文,不限长度 timestamp = db.Column(db.DateTime, index=True, default=datetime.utcnow) # 发布博文的时间 body_html = db.Column(db.Text) # 存放转换后的 HTML 代码 author_id = db.Column(db.Integer, db.ForeignKey('users.id')) # 外键使用 ForeignKey,指向 User 表的 id comments = db.relationship('Comment', backref='post', lazy='dynamic')
ORM 类似标准的数据库接口,但很多工作由 ORM 代为处理了,不需要直接使用接口。
Python 的 ORM 模块:SQLAlchemy 等
一些大型 web 开发工具/框架 有自己的 ORM 组件。
import os basedir = os.path.abspath(os.path.dirname(__file__)) # 项目根目录 SQLALCHEMY_DATABASE_URI = 'sqlite:///' + os.path.join(basedir, 'app.db') # 数据库文件的路径、文件名 # print SQLALCHEMY_DATABASE_URI # sqlite:Users/chao/Desktop/projects/flask/flask_blog/app.db SQLALCHEMY_MIGRATE_REPO = os.path.join(basedir, 'db_repository') # 文件夹,保存`SQLAlchemy-migrate`数据文件,也就是迁移策略文件 # print SQLALCHEMY_MIGRATE_REPO # /Users/chao/Desktop/projects/flask/flask_blog/db_repository
数据库引擎的配置
hello.py
from flask.ext.sqlalchemy import SQLAlchemy # 从 flask 扩展中导入 SQLAlchemy db = SQLAlchemy() # 创建数据库实例`db`
如何选择
你要考虑以下几个因素。
- 易用性
如果直接比较API
和ORM
,显然后者取胜。对象关系映射(Object-Relational Mapper,ORM)
在用户不知觉的情况下把高层的面向对象操作转换成低层的数据库指令
。 - 性能
ORM 把对象业务转换成数据库业务会有一定的损耗。大多数情况下,这种性能的降低微不足道,但也不一定都是如此。一般情况下,ORM 对生产率的提升远远超过了这一丁点儿的性能降低,所以性能降低这个理由不足以说服用户完全放弃 ORM。真正的关键点在于如何选择一个能直接操作低层数据库的抽象层,以防特定的操作需要直接使用数据库原生指令优化
。 - 可移植性
选择数据库时,必须考虑其是否能在你的开发平台和生产平台中使用。例如,如果你打算利用云平台托管程序,就要知道这个云服务提供 了哪些数据库可供选择。可移植性还针对 ORM。尽管有些 ORM 只为一种数据库引擎提供抽象层,但其他 ORM 可能做了更高层的抽象,它们支持不同的数据库引擎,而且都使用相同的面向对象接口。SQLAlchemy ORM 就是一个很好的例子,它支持很多关系型数据库引擎,包 括流行的 MySQL、Postgres 和 SQLite。 - FLask集成度
选择框架时,你不一定非得选择已经集成了 Flask 的框架,但选择这些框架可以节省你编写集成代码的时间。使用集成了 Flask 的框架可以简化配置和操作,所以专门为 Flask 开发的扩展是你的首选。
基于以上因素,本书选择使用的数据库框架是 Flask-SQLAlchemy,这个 Flask 扩展包装了SQLAlchemy框架。
数据库模型
定义模型
在 ORM 中,模型
一般是一个 Python 类
, 代表数据库中的一张表, 类中的属性
对应数据库表中的列
。
Flask-SQLAlchemy 创建的数据库实例
为模型提供了一个基类db.Model
以及一系列辅助类和辅助函数,可用于定义 模型/表 的结构。
下面的例子定义了两个表,一个是用户角色,一个是用户信息
hello.py
class Role(db.Model): __tablename__ = 'roles' id = db.Column(db.Integer, primary_key=True) name = db.Column(db.String(64), unique=True) def __repr__(self): return '' % self.name class User(db.Model): __tablename__ = 'users' id = db.Column(db.Integer, primary_key=True) username = db.Column(db.String(64), unique=True, index=True) def __repr__(self): return '' % self.username
类变量__tablename__
定义在数据库中使用的表名
。如果没有定义__tablename__
,Flask-SQLAlchemy 会使用一个默认名字,但默认的表名没有遵守 使用复数形式进行命名(加 s ) 的约定, 所以最好由我们自己来指定表名。
其余的类变量都是该 模型的属性/表的列,被定义为 db.Column 类的实例。
db.Column 类构造函数的第一个参数是数据库表列/模型属性 的类型
。
更多类型
db.Column 中其余的参数指定属性的配置选项
。
选项名 | 说 明 |
---|---|
primary_key | 如果设为 True,这列就是表的主键 |
unique | 如果设为 True,这列不允许出现重复的值 |
index | 如果设为 True,为这列创建索引,提升查询效率 |
nullable | 如果设为 True,这列允许使用空值;如果设为 False,这列不允许使用空值 |
default | 为这列定义默认值 |
Flask-SQLAlchemy 要求每个模型都要定义主键,这一列经常命名为 id。ID 由 Flask-SQLAlchemy 控制。
其他配置选项
虽然没有强制要求,但这两个模型都定义了__repr()__
方法,返回一个 具有可读性的字符串 表示 模型,可在调试和测试时使用。
数据库操作
学习如何使用模型的最好方法是在 Python shell 中实际操作。
- 创建表
首先,我们要让 Flask-SQLAlchemy 根据模型类创建数据库
。方法是使用 db.create_all() 函数:
(venv) $ python hello.py shell # 进入 Python shell >>> from hello import db # 从`hello.py`导入创建的数据库实例 >>> db.create_all()
如果你查看程序目录,会发现新建了一个名为app.db
的文件。这个 SQLite 数据库文件
的名字就是在配置中指定的。如果数据库表已经存在于数据库中,那么 db.create_all() 不会重新创建或者更新这个表。如果在模型中做了修改,想要把改动应用到现有的数据库中,这一特性会带来不便。
更新现有数据库表的粗暴方式是先删除旧表
再重新创建:
>>> db.drop_all() >>> db.create_all()
遗憾的是,这个方法有个我们不想看到的副作用,它把数据库中原有的数据都销毁
了。末尾将会介绍一种称为数据库迁移
的方式用于更新数据库。
- 插入行
>>> from hello import Role, User >>> admin_role = Role(name='Admin') >>> mod_role = Role(name='Moderator') >>> user_role = Role(name='User') >>> user_john = User(username='john', role=admin_role) >>> user_susan = User(username='susan', role=user_role) >>> user_david = User(username='david', role=user_role)
模型的构造函数接受的参数是使用关键字参数指定的模型属性初始值
。注意,role 属性也可使用,虽然它不是真正的数据库列,但却是一对多关系的高级表示。这些新建对象的 id 属性并没有明确设定,因为主键是由 Flask-SQLAlchemy 管理
的。现在这些对象只存在于 Python 中
,还未写入数据库
。因此id 尚未赋值
:
>>> print(admin_role.id) None >>> print(mod_role.id) None >>> print(user_role.id) None
通过数据库会话
管理对数据库所做的改动,在 Flask-SQLAlchemy 中,会话由 db.session 表示
。准备把对象写入数据库之前,先要将其添加到会话中
:
>>> db.session.add(admin_role) >>> db.session.add(mod_role) >>> db.session.add(user_role) >>> db.session.add(user_john) >>> db.session.add(user_susan) >>> db.session.add(user_david)
或者简写成:
>>> db.session.add_all([admin_role, mod_role, user_role, ... user_john, user_susan, user_david])
为了把对象写入数据库
,我们要调用 commit() 方法提交会话
:
>>> db.session.commit()
再次查看 id 属性,现在它们已经赋值了:
>>> print(admin_role.id) 1 >>> print(mod_role.id) 2 >>> print(user_role.id) 3
数据库会话能保证数据库的一致性。提交操作使用原子方式把会话中的对象全部写入数据 库。如果在写入会话的过程中发生了错误,整个会话都会失效。如果你始终把相关改动放 在会话中提交,就能避免因部分更新导致的数据库不一致性
。 一致性:数据库中数据与实际保存的数据不一致。
数据库会话也可回滚。调用 db.session.rollback() 后,添加到
数据库会话
中、还未提交的所有对象都会还原到它们在数据库中
的版本。
- 修改行
在数据库会话
上调用 add() 方法
也能更新模型
。我们继续在之前的 shell 会话中进行操作
下面这个例子把 "Admin" 角色重命名为 "Administrator":
>>> admin_role.name = 'Administrator' >>> db.session.add(admin_role) >>> db.session.commit()
- 删除行
数据库会话还有个 delete() 方法。下面这个例子把 "Moderator" 角色从数据库中删除:
>>> db.session.delete(mod_role) >>> db.session.commit()
注意,删除
与插入
和更新
一样,提交数据库会话
后才会执行。
- 查询行
Flask-SQLAlchemy 为每个模型类都提供了 query 对象
。最基本的模型查询是取回对应表中的所有记录:

>>> Role.query.all() [u'Administrator'>, u'User'>] >>> User.query.all() [u'john'>, u'susan'>, u'david'>]
使用过滤器
可以配置 query 对象进行更精确的数据库查询
。下面这个例子查找角色为 "User" 的所有用户:
>>> User.query.filter_by(role=user_role).all() # user_role = Role(name='User'), role=user_role [u'susan'>, u'david'>]
若要查看 SQLAlchemy 为查询生成的原生 SQL 查询语句,只需把 query 对象转换成字符串
:
>>> str(User.query.filter_by(role=user_role)) 'SELECT users.id AS users_id, users.username AS users_username, users.role_id AS users_role_id FROM users WHERE :param_1 = users.role_id'
如果你退出了 shell 会话,前面这些例子中创建的对象就不会以 Python 对象的形式存在,而是作为各自数据库表中的行。如果你打开了一个新的 shell 会话,就要从数据库中读取行, 再重新创建 Python 对象。
下面这个例子发起了一个查询,加载名为 "User" 的用户角色:
>>> user_role = Role.query.filter_by(name='User').first()
filter_by() 等过滤器在 query 对象上调用,返回一个更精确的 query 对象。多个过滤器可以一起调用,直到获得所需结果。
可在 query 对象上调用的常用过滤器。
过滤器 | 说明 |
---|---|
filter() | 把过滤器添加到原查询上,返回一个新查询 |
filter_by() | 把等值过滤器添加到原查询上,返回一个新查询 |
limit() | 使用指定的值限制原查询返回的结果数量,返回一个新查询 |
offset() | 偏移原查询返回的结果,返回一个新查询 |
order_by() | 根据指定条件对原查询结果进行排序,返回一个新查询 |
group_by() | 根据指定条件对原查询结果进行分组,返回一个新查询 |
在查询上应用指定的过滤器后
,通过调用 all() 执行查询,以列表的形式返回结果。除了 all() 之外,还有其他方法能触发查询执行
。
常用查询执行函数
方法 | 说明 |
---|---|
all() | 以列表形式返回查询的所有结果 |
first() | 返回查询的第一个结果,如果没有结果,则返回 None |
first_or_404() | 返回查询的第一个结果,如果没有结果,则终止请求,返回 404 错误响应 |
get() | 返回指定主键对应的行,如果没有对应的行,则返回 None |
get_or_404() | 返回指定主键对应的行,如果没找到指定的主键,则终止请求,返回 404 错误响应 |
count() | 返回查询结果的数量 |
paginate() | 返回一个 Paginate 对象,它包含指定范围内的结果 |
关系和查询的处理方式类似。
完整的列表参见SQLAlchemy query
下面这个例子分别从关系的两端查询角色和用户之间的一对 多关系:
>>> users = user_role.users
>>> users
[u'susan'>, u'david'>] >>> users[0].role u'User'>
这个例子中的 user_role.users 查询有个小问题。执行 user_role.users 表达式时,隐含的查询会调用 all() 返回一个用户列表。query 对象是隐藏的,因此无法指定更精确的查询 过滤器。就这个特定示例而言,返回一个按照字母顺序排序的用户列表可能更好。
在示例 5-4中,我们修改了关系的设置,加入了lazy = 'dynamic'参数,从而禁止自动执行查询。
class Role(db.Model): # ... users = db.relationship('User', backref='role', lazy='dynamic') # ...
这样配置关系之后,user_role.users 会返回一个尚未执行的查询,因此可以在其上添加过 滤器:
>>> user_role.users.order_by(User.username).all()
[u'david'>, u'susan'>] >>> user_role.users.count() 2
在视图函数中操作数据库
在 Python shell 中做过练习后,可以直接在视图函数中进行数据库的操作了。
@app.route('/', methods=['GET', 'POST']) def index(): form = NameForm() if form.validate_on_submit(): user = User.query.filter_by(username=form.name.data).first() if user is None: user = User(username = form.name.data) db.session.add(user) # 没有提交?? 配置对象中有一个选项,即 SQLALCHEMY_COMMIT_ON_TEARDOWN 键,将其设为 True 时,`每次请求结束后都会自动提交数据库中的变动` session['known'] = False else: session['known'] = True session['name'] = form.name.data form.name.data = '' return redirect(url_for('index')) return render_template('index.html', form = form, name = session.get('name'), known = session.get('known', False))
提交表单后,程序会使用filter_by()
查询过滤器在数据库中查找提交的名字。变量 known 被写入用户会话中,因此重定向之后,可以把数据传给模板, 用来显示自定义的欢迎消息。注意,要想让程序正常运行,你必须按照前面介绍的方法, 在 Python shell 中创建数据库表。
对应的模板新版本。这个模板使用 known 参数在欢迎消息中加入了第二行,从而对已知用户和新用户显示不同的内容。
{% extends "base.html" %} {% import "bootstrap/wtf.html" as wtf %} {% block title %}Flasky{% endblock %} {% block page_content %} <div class="page-header"> <h1>Hello, {% if name %}{
{ name }}{% else %}Stranger{% endif %}!h1> {% if not known %} <p>Pleased to meet you!p> {% else %} <p>Happy to see you again!p> {% endif %} div> {
{ wtf.quick_form(form) }} {% endblock %}
对象关系教程
SQLAlchemy 官方文档
建立一个关系
翻译自Building a Relationship
>>> from sqlalchemy import Column, Integer, String >>> class User(Base): ... __tablename__ = 'users' ... ... id = Column(Integer, primary_key=True) ... name = Column(String) ... fullname = Column(String) ... password = Column(String) ... ... def __repr__(self): ... return "" % ( self.name, self.fullname, self.password)
让我们考虑第二个表与User
关联,可以被映射和查询。Users 在可以存储任意数量的电子邮件地址关联的用户名。这意味着一个从users
到一个存储电子邮件地址的新表Addresses
的一对多
关联。我们在Address
中使用声明定义这张表与User
的映射:
>>> from sqlalchemy import ForeignKey >>> from sqlalchemy.orm import relationship, backref >>> class Address(Base): ... __tablename__ = 'addresses' ... id = Column(Integer, primary_key=True) ... email_address = Column(String, nullable=False) ... user_id = Column(Integer, ForeignKey(元器件数据手册、IC替代型号,打造电子元器件IC百科大全!