《flask Web 开发》读书笔记 & chapter5

chapter 2 - chapter 3 - chapter 4 - 源码

概念剖析-flask数据库操作

数据库分类

  • SQL数据库基于关系模型,用表来模拟不同的实体,列定义代表所属实体的数据属性,每个表有个特殊的列称为 主键,其值是各行的唯一标识符,不能重复,表中还有 外键,引用同一表或不同表的主键,这种联系称为 关系特点:支持联结操作,数据存储高效,数据一致性好,避免重复,但是设计比较复杂。常见:MySQL, Oracal, SQLite
  • NoSQL数据库 使用 集合 代替表,使用 文档 代替记录,特点数据重复查询效率高,一致性差。常见:MongoDB
SQL数据库

#       表 roles               表 users                   
#  ------------------      ------------------            
#    id : 主键              id : 主键
#    name                   username 
#                           password
#                           role_id : 外键
#  ------------------      ------------------ 

NoSQL 数据库

#                users
#          -----------------
#          id
#          username
#          password
#          role: 有大量重复
#          ------------------

python数据库框架

  • 数据引擎的Python包 大多数的数据库引擎都有对应的Python包。
  • 数据库抽象层SQLAlchemyMongoEngine 通过 ORM(Object-Relational Mapper)或者ODM(Object-Document Mapper) 将处理表、文档或查询语言等数据库实体操作转换成为高层的Python对象的操作,极大地提升开发效率。
  • 数据库框架评价指标:易用性,性能,可移植性。Flask集成度。本书使用的是 Flask-SQLAlchemy

使用 Flask-SQLAlchemy 管理数据库

  • 安装 pip install flask-sqlalchemy
  • 通过URL指定数据库
数据库引擎 URL
MYSQL mysql://uername:password@hostname/database
SQLite(Unix) sqlite:///absolute/path/to/database
SQLite(Win) sqlite:///c:/absolute/path/to/database
设置 SQLlite 数据库的链接

hello.py 文件, 设置app.config['SQLALCHEMY_DATABASE_URI'],app.config['SQLALCHEMY_COMMIT_ON_TEARDOWN']

## 设置 SQLite 数据库 URI
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///' + os.path.join(basedir,'data.sqlite')
## 每次请求提交后,自动提交数据库的修改
app.config['SQLALCHEMY_COMMIT_ON_TEARDOWN'] = True
## 获取数据库对象
db = SQLAlchemy(app)
定义模型

模型表示程序使用的持久化实体,在 ORM 中,模型一般是一个 Python 类,类的属性对应数据表中的列,类的操作会在底层自动转换成为对应的数据库操作。

hello.py 中,定义 Role 和 User 模型

# 定 Role 模型
class Role(db.Model):

    """ database table class Role """
    # 表名,一般采用 复数 形式
    __tablename__ = 'roles'
    # 类变量即数据表的字段,由 db.Column创建
    # primary_key = True 定义主键
    # unique = True 不允许出现重复的值
    id = db.Column(db.Integer, primary_key = True )
    name = db.Column(db.String(64), unique = True )
    
    # 返回表示模型的字符串,供调试和测试使用
    def __repr__(self):
        return '<Role %r>' % self.name

class User(db.Model):

    """ database table class User """
    __tablename__ = 'users'
    id = db.Column(db.Integer, primary_key = True )
    username = db.Column(db.String(64), unique = True, index=True )
    
    def __repr__(self):
        return '<User %r>' % self.username
创建模型之间的关系

hello.py 文件中,User中定义外键 role_id 的同时定义了关系,可以在关系的另一端Role中定义关系,添加反向引用。

class User(db.Model):
...    
# 创建外链,同时创建了关系,引用 表 roles 的 id 字段
    role_id = db.Column(db.Integer, db.ForeignKey( 'roles.id' ) )
...

class Role(db.Model):
... 
# backref 在关系的另一个模型中,添加反向引用
# 添加到 Role 中的 users 属性代表了关系的面向对象视角,
# 将返回与角色相关联的用户的列表,第一个参数 用字符串表示关系另一端的模型
# backref='role' 向User类添加了 role 属性, role_id 返回的是外键的值,
# role返回的是模型Role的对象
    users = db.relationship('User', backref='role')
...
数据库操作

git的操作十分相似

  • 创建数据表 db.create_all() 创建sqlite数据库文件data.sqlite,改动模型后,更新数据库时只能删除旧表 db.drop_all(),然后重新 db.create_all() 创建,但是会导致原有数据的丢失。
  • 插入记录 只需要调用模型的关键字参数形式的构造函数,如 admin_role = Role(name='Admin)',主键 idFlask-SQLAlchemy 管理,不需要明确设置
  • 同步模型改动到数据库 使用 db.session 数据库事务对象管理对数据库的改动,如添加记录到数据库 db.session.add(admin_role)
  • 提交操作 db.session.commit()
  • 修改记录 首先修改模型对象属性值,然后 db.session.add(), db.session.commit()
  • 删除记录 首先db.session.delete( model obj) 然后提交到仓库 db.session.commit()

hello.py文件添加

# 数据库对象的创建及初始化
def Create_database():
    # 创建数据库文件及表,
    # ? 程序如何识别所有需要创建数据表的对象 ?
    db.create_all()
    # 插入行
    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 )

    # 添加到会话
    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] )
    # 提交到数据库
    db.session.commit()
    # db.session.rollback() 将添加到数据库会话中的所有对象还原到他们在数据库中的状态,相当于git中的checkout
    # 删除数据
    # db.session.delete(mod_role)
    # db.session.commit()
数据库的查询query对象

SQLALchemy-查询篇
Python SQLAlchemy基本操作和常用技巧

Flask-SQLAlchemy 为每个模型类都提供一个query对象,
过滤器query 上调用,返回更加精确的 query 对象 ,过滤器返回 query 对象

利用关系创建的模型对象1 * User.query.filter_by(role=user_role).all(),role是关系*添加的模型对象,str( User.query.filter_by(role=user_role)) 查询ORM自动生成的查询语句
利用关系创建的模型对象2 user_role.users 默认情况下返回的是query对象的查询结果,设置 lazy = dynamic 可以得到查询对象

cmd 中 shell 方式执行查找:

>>> python hello.py shell
>>> from hello import db, Role, User
>>> User.query.all()
[<User 'john'>, <User 'susan'>, <User 'david'>]

git add. git commit -m "sqlalchemy first demo"

视图函数中调用数据库

hello.py文件

# 路由 index
@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)
            # add 到 session
            db.session.add(user)
            session['known'] = False
        else:
            session['known'] = True
        session['name'] = form.name.data
        return redirect( url_for('index'))
    return render_template('index.html', form=form, name=session.get('name'), known=session.get('known', False))

git add. git commit -m "sqlalchemy in view ", git tag 5a

集成 Python shell

可以通过Shell 来调试模块,据说,文档
每次启动 shell 都要导入数据库实例和模型,通过设置让 Flask-Scriptshell 自动导入特定的对象

from flask_script import shell

# 创建 shell 的上下文环境
def make_shell_context():
    return dict(app=app, db=db, User=User, Role=Role)
# 配置 manager 的命令行
manager.add_command("shell", Shell(make_context = make_shell_context))
数据库表的修改

开发过程中有时候需要修改数据库模型,并更新到数据库,需要用到数据库迁移框架,推荐 Alembic,及 Flask - Migrate扩展(封装了Alembic),所有的操作通过 Flask-Script完成

  • 安装 pip install flask-migrate
  • hello.py 文件修改
from flask_migrate import Migrate, MigrateCommand
...
# 创建数据库迁移对象
Migrate(app, db)
# 配置 flask_script 命令
manager.add('db', MigrateCommand)
  • 创建迁移仓库 >>> python hello.py db init,会创建 migrations文件夹,放置所有的迁移脚本,迁移仓库中的要进行版本控制

  • 数据库的迁移用 迁移脚本 表示,脚本中有两个函数 upgrade()downgrade() 前者应用修改,后者删除修改,数据库可以重置到修改历史的任一点。

    • revision 手动创建迁移,upgrade()downgrade() 都是空的,开发者需要使用 Operations对象指令实现具体操作
    • migrate 命令自动创建,会根据模型定义和数据库当前状态之间的差异自动生成 upgrade()downgrade() 的内容
  • 此处没有修改数据库,只是删除数据库 data.sqlite, 自动创建迁移脚本 >>> python hello.py db migrate -m "initial migration", 在 $project-dir \ migrations \ versions 出现文件 ab116a6e32ed_initial_migration.py 内容如下:

def upgrade():
    # ### commands auto generated by Alembic - please adjust! ###
    op.create_table('roles',
    sa.Column('id', sa.Integer(), nullable=False),
    sa.Column('name', sa.String(length=64), nullable=True),
    sa.PrimaryKeyConstraint('id'),
    sa.UniqueConstraint('name')
    )
    op.create_table('users',
    sa.Column('id', sa.Integer(), nullable=False),
    sa.Column('username', sa.String(length=64), nullable=True),
    sa.Column('role_id', sa.Integer(), nullable=True),
    sa.ForeignKeyConstraint(['role_id'], ['roles.id'], ),
    sa.PrimaryKeyConstraint('id')
    )
    op.create_index(op.f('ix_users_username'), 'users', ['username'], unique=True)
    # ### end Alembic commands ###

def downgrade():
    # ### commands auto generated by Alembic - please adjust! ###
    op.drop_index(op.f('ix_users_username'), table_name='users')
    op.drop_table('users')
    op.drop_table('roles')
    # ### end Alembic commands ###
  • 应用迁移命令, >>>python hello.py db upgrade 输出 Running upgrade -> ab116a6e32ed, initial migrateion, 删除迁移 >>>python hello.py db downgrade 输出 Running downgrade ab116a6e32ed -> , initial migrateion

PROBELM 前文说道,迁移而不是删除表格,重新创建表格能够保存数据库中的数据,那么如何指定新列于旧列的对应关系,及其他更精细的操作?

git add. git commit "flask database demo", git tag 5b

附录

常用的SQLAlchemy 的列类型

类型名称 python类型 描述
Integer int 常规整形,通常为32位
SmallInteger int 短整形,通常为16位
BigInteger int或long 精度不受限整形
Float float 浮点数
Numeric decimal.Decimal 定点数
String str 可变长度字符串
Text str 可变长度字符串,适合大量文本
Unicode unicode 可变长度Unicode字符串
Boolean bool 布尔型
Date datetime.date 日期类型
Time datetime.time 时间类型
Interval datetime.timedelta 时间间隔
Enum str 字符列表
PickleType 任意Python对象 自动Pickle序列化
LargeBinary str 二进

常用的 SQLAlchemy 的列选项

可选参数 描述
primary_key 如果设置为True,则为该列表的主键
unique 如果设置为True,该列不允许相同值
index 如果设置为True,为该列创建索引,查询效率会更高
nullable 如果设置为True,该列允许为空。如果设置为False,该列不允许空值
default 定义该列的默认值

常用的 SQLAlchemy 的关系选项

选项名 说明
backref 在关系的另一个模型中添加反向引用
primaryjoin 明确指定另个模型之间使用的关系的联结条件
lazy 指定如何加载相关记录,可选项有 select( 首次访问按需加载 ),immediate( 源对象加载后立即加载 ),joined( 加载记录且使用联结 ),subquery( 立即加载,使用子查询 ),noload( 永不加载 ),dynamic( 不加载记录,提供加载记录的查询 )
uselist 默认为真,对应一对多,返回列表,若为 False 对应一对一,返回标量值
order_by 关系中记录的排序方式
secondary 指定 多对多 关系中关系表的名字
secondaryjoin 指定 多对多 关系中 二级联结 条件

常用的 SQLAlchemy 查询过滤器

在 Flask 中使用 SQLAlchemy

过滤器 说明
filter() 将过滤器添加到原查询上,返回新查询
filter_by() 将等值过滤器添加到原查询上,返回新查询
limit() 使用指定的值限制返回的结果数量,返回新查询
offset() 偏移原查询的结果,返回新查询
oredr_by() 采用指定条件对原查询的结果排序,返回新查询
group_by 采用指定条件对原查询结果进行分组,返回新查询

常用的 query 对象的操作

操作 说明
all() 列表形式返回所有查询结果
first() 返回查询结果的第一个,如果没有返回None
first_or_404() 返回查询结果的第一个,如果没有终止请求,返回404
get() 返回主键对应的行,如果没有返回 None
get_or_404() 返回主键对应的行,如果没有,终止请求,返回404
count() 返回查询结果的数量
paginate() paginate (为书或者手稿标页数),返回一个paginate对象,包含指定范围的结果
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342

推荐阅读更多精彩内容