水军大提督 发表于 2024-9-3 09:09:01

Flask之数据库

  媒介:本博客仅作记录学习使用,部门图片出自网络,如有侵犯您的权益,请接洽删除 
https://img-blog.csdnimg.cn/direct/5e4bce067182480e91cb7c465bb5aac2.gif
目次
一、数据库的分类
1.1、SQL
1.2、NoSQL
1.3、怎样选择?
二、ORM魔法
三、使用Flask-SQLALchemy管理数据库
3.1、连接数据库服务器
3.2、界说数据库模子
3.3、创建数据库和表
四、数据库操作
4.1、CRUD
4.2、在视图函数里操作数据库
五、界说关系
5.1、配置Python Shell上下文
5.2、一对多
5.3、多对一
5.4、一对一
5.5、多对多
六、更新数据库表
6.1、重新天生表
6.2、使用Flask-Migrate迁移数据库
6.3、开发时是否要迁移?
七、数据库进阶实践
7.1、级联操作
7.2、事件监听
致谢
数据库是大多数动态Web步伐的基础设施。常见的数据库管理系统(DBMS)有:MySQL、PostgreSQL、SQLite、MongoDB等。
一、数据库的分类

数据库一般分为两种,SQL(Structured Query Language,结构化查询语言)数据库和NoSQL(Not Only SQL,泛指非关系型)数据库
1.1、SQL

SQL数据库指关系型数据库,常用的SQL DBMS主要包罗SQL Server、Oracle、MySQL、PostgreSQL、SQLite等。关系型数据库使用表来界说数据对象,差别的表之间使用关系连接。
idnamesexoccupation1NickMaleJournalist2AmyFemaleWriter 在SQL数据库中,每一行代表一条记录(record),每条记录又由差别的列(column)构成。在存储数据前,需要预先界说表模式(schema),以界说表的结构并限定列的输入数据类型。
根本概念:


[*]表(table):存储数据的特定结构
[*]模式(schema):界说表的结构信息
[*]列/字段(column/field):表中的列,存储一系列特定的数据,列构成表
[*]行/记录(raw/record):表中的行,代表一条记录
[*] 标量(scalar):指的是单一数据,与之相对的是聚集(collection)
1.2、NoSQL

NoSQL是初指No SQL或No Relational,如今NoSQL社区一般会解释为Not Only SQL。NoSQL数据库泛指不使用传统关系型数据库中的表格形式的数据库。近年来,NoSQL数据库越来越流行,被大量应用在实时Web步伐和大型步伐中。在速度和可扩展性方面有很大优势,除此之外还拥有无模式、分布式、程度伸缩等特点
最常用的两种NoSQL数据库如下:
1.2.1、文档存储(document store)

文档存储是NoSQL数据库中最流行的种类,它可作为主数据库使用。文档存储使用的文档雷同SQL数据库中的记录,文档使用类JSON格式来表示数据。常见的文档存储DBMS有MongoDB、CouchDB等。1.1的身份信息表中的第一条记录使用文档可表示为:
 {
     id: 1,
     name: "Nick",
     sex: "Male",
     occupation: "Journalist"
 } 1.2.2、键值对存储(key-value store)

键值对存储在形态上雷同Python中的字典,通过键来存取数据,在读取上非常快,通常用来存储暂时内容,作为缓存使用。常见的键值对DBMS有Redis、Riak等,其中Redis不仅可以管理键值对数据库,还可以作为缓存后端(cache backed)、图存储(graph store)等类型的NoSQL数据库。
1.3、怎样选择?



[*]NoSQL 数据库不需要界说表和列等结构,也不限定存储的数据格式,在存储方式上比力灵活,在特定的场景下服从更高。
[*]SQL 数据库稍显复杂,但不容易堕落, 能够适应大部门的应用场景
[*]大型项目通常会同时需要多种数据库,比如使用MySQL作为主数据库存储用户资料和文章,使用Redis缓存数据,使用MongoDB存储实时消息。
大多环境,SQL数据库都能满意你的需求。为便于测试,我们使用SQLite作为DBMS。
二、ORM魔法

在Web应用步伐里使用原生SQL语句操作数据库主要存在以下题目:


[*]手动编写SQL语句比力乏味,而且视图函数中加入太多SQL语句会降低代码的易读性。别的还会有安全题目,如SQL语句注入
[*]常见的开发模式是在开发时使用简单的SQLite,而在摆设时切换到MySQL等更健壮的DBMS。但是对于差别DBMS需要使用差别的Python接口库,这让DBMS的切换变得不太容易。
ORM会自动处理参数的转义,尽可能地避免SQL注入的发生。别的还为差别的DBMS提供统一的接口,让切换工作变得简单。ORM饰演翻译的脚色,将我们的Python语言转换为DBMS能够读懂的SQL指令,让我们能够使用Python来操控数据库。
ORM把底层的SQL数据库实体转化成高层的Python对象。ORM主要实现了三层映射关系:


[*]表--Python类
[*]字段(列)--类属性
[*]记录(行)--类实例
比如,创建一个contacts表来存储留言,其中包含用户名称和电话号码两个字段。在SQL中:
 CREATE TABLE contacts(
     name varchar(100) NOT NULL,
     phone_number varchar(32),
 ) 假如使用ORM:
 from foo_orm import Model, Column, String
 ​
 class Contact(Model):
     __tablename__ = 'contacts'
     name = Column(String(100),nullable=False)
     phone_number = Column(String(32)) 要向表中插入一条记录,需要使用下面的SQL语句:
 INSERT INTO contacts(name,phone_number)
 VALUES('Grey Li','12345678') 使用ORM则只需要创建一个Contact类的实例,传入对应的参数表示各个列的数据即可。
 contact = Contact(name="Grey Li",phone_number="12345678") 除了便于使用,ORM还有下面这些优点:


[*]灵活性好。既能使用高层对象来操作数据库,又支持实行原生SQL语句。
[*]提升服从。从高层对象转换成原生SQL会牺牲一些性能,但这换取的是巨大的服从提升
[*]可移植性好。ORM通常支持多种DBMS,只需要稍微改动少量配置
使用Python实现的ORM有SQLALchemy、Peewee、PonyORM等。其中SQLALchemy是Python社区使用最广泛的ORM之一
三、使用Flask-SQLALchemy管理数据库

扩展Flask-SQLALchemy集成了SQLALchemy,它简化了连接数据库服务器、管理数据库操作会话等各类工作,让Flask中的数据处理体验变得更加轻松。
 pip install flask-sqlalchemy 实例化Flask-SQLALchemy提供的SQLALchemy类,传入 步伐实例app以完成扩展的初始化:
 from flask import Flask
 from flask_sqlalchemy import SQLAlchemy
 ​
 app = Flask(__name__)
 ​
 db = SQLAlchemy(app) 3.1、连接数据库服务器

DBMS通常会提供数据库服务器运行在操作系统中。要连接数据库服务器,起首要为我们的步伐指定数据库URI(Uniform Resource Identifier,统一资源标识符)。数据库URI是一串包含各种属性的字符串,其中包含了各种用于连接数据库的信息。
常用的数据库URI格式示例:
DBMSURIPostgreSQLpostgresql://username:password@host/databasenameMySQLmysql://username:password@host/databasenameOracleoracle://username:password@host:port/sidnameSQLite(UNIX)sqlite:absolute/path/to/foo.dbSQLite(Windows)sqlite:///absolute\\path\\to\\foo.db或r'sqlite:///absolute\path\to\foo.db'SQLite(内存型)sqlite:///或sqlite:///:memory: 在Flask-SQLALchemy中,数据库的URI通过配置变量SQLALCHEMY_DATABASE_URI设置,默认为SQLite内存型数据库(sqlite:///:memory:),SQLite是基于文件的DBMS,不需要设置数据库服务器,只需要指定数据库文件的绝对路径。我们使用app.root_path来定位数据库文件的路径,并将数据库文件定名为data.db:
 import os
 ​
 app.config['SQLALCHEMY_DATABASE_URI'] = os.getenv('DATABASE_URL','sqlite:///'+os.path.join(app.root_path,'data.db')) 在生产环境下更换到其他类型的DBMS时,数据库URL会包含敏感信息,所有这里优先从环境变量DATABSE_URL获取。
SQLite的数据库URI在Linux或macOS系统下的斜线数目是4个;在Windows系统下的URI中的斜线数目为3个。内存型数据库的斜线固定为3个。文件名不限后缀,常用的定名方式有foo.sqlite,foo.db或是注明版本的foo.sqlite3
设置好数据库URI后,在Python Shell中导入并查看db对象会得到下面的输出
>>> from app import db
>>> db
<SQLAlchemy> 3.2、界说数据库模子

用来映射到数据库表的Python类通常被称为数据库模子(model),一个数据库模子类对应数据库中的一个表。界说模子即使用Python类界说表模式,并声明映射关系。所有模子类都需要继承Flask-SQLALchemy提供的db.Model基类。下面界说一个Note模子类,用来存储笔记:
class Note(db.Model):
    id = db.Column(db.Integer,primary_key=True)
    body = db.Column(db.Text) 表的字段(列)由db.Column类的实例表示,字段的类型通过Column类构造方法的第一个参数传入。常用的SQLALchemy字段类型:
字段阐明Integer参数String字符串,可选参数length可以用来设置最大长度Text较长的Unicode文本Date日期,存储Python的datetime.date对象Time时间,存储Python的datetime.time对象DateTime时间和日期,存储Python的datetime对象Interval时间隔断,存储Python的datetime.timedelta对象Float浮点数Boolen布尔值PickleType存储Pickle列化的Python对象LargeBinary存储任意二进制数据 字段类型一般直接声明即可,假如需要传入参数,也可以添加括号。对于雷同String的字符串列,有些数据库会要求限定长度,因此最好为其指定长度。虽然使用Text类型可以存储相对灵活的变长文本,但从性能上考虑,我们仅在必须的环境下使用Text类型,比如用户发表的文章和批评等不限长度的内容。
一般环境字段的长度是由步伐设计者自定的。但也有特殊约束:比如姓名(英语)的长度一般不超过70个字符,中文名一般不超过20个字符,电子邮件所在的长度不超过254个字符。(当在数据库模子类中限定了字段的长度后,在接收对应数据的表单类字段里,也需要使用Length验证器来验证用户的输入数据)。
默认环境下,Flask-SQLALchemy会限定模子类的名称天生一个表名称,天生规则如下:
Message --> message         # 单个单词转换为小写
FooBar --> foo_bar                # 多个单词转换为小写并使用下划线分隔 Note类对应的表名称即note。若想本身指定名称,可以通过界说tablename属性来实现。字段名默认为类属性名,也可以通过字段类构造方法的第一个参数指定,或使用关键字name。根据我们界说的Note模子类,最终天生一个note表,表中包含id和body字段,
常用的SQLALchemy字段参数:
参数名阐明primary_key假如设为True,该字段为主键unique假如设为True,该字段不允许出现重复值index假如设为True,为该字段创建索引,以提高查询服从nullable确定字段值能否为空,值为True或False,默认值为Truedefault为字段设置默认值 (不需要在所有列都建立索引。一般来说,取值可能性多(比如姓名)的列,以及经常被用来作为排序参照的列(比如时间戳)更适当建立索引。)
3.3、创建数据库和表

创建模子类后,我们需要手动创建数据库和对应的表,也就是我们常说的建库和建表。这通过我们的db对象调用create_all()方法实现。
$ flask shell
>>> from app import db
>>> db.create_all() 假如将模子类界说在单独的模块中,那么必须在调用db.create_all()方法前导入相应模块,以便让SQLALchemy获取模子类被创建时天生的表信息,进而正确天生数据表。
通过下面的方式可以查看模子对应的SQL模式(建表语句):
>>> from sqlalchemy.schema import CreateTable
>>> print(CreateTable(Note.__table__))

CREATE TABLE note (
      id INTEGER NOT NULL,
      body TEXT,
      PRIMARY KEY (id)
) (我们数据库和表一旦创建后,之后对模子的改动不会自动作用到实际的表中。如要使改动生效,调用db.drop_all()方法删除数据库和表,然后再调用create_all()方法创建)。
我们也可以自界说flask命令完成这个工作:
import click

@app.cli.command()
def initdb():
    db.create_all()
    click.echo('Initialized database') 在命令行输入flask initdb即可创建数据库和表:
$ flask initdb
Initialized database. 四、数据库操作

数据库操作主要是CRUD,即Create(创建)、Read(读取/查询)、Update(更新)和Delete(删除)。
SQLALchemy使用数据库会话来管理数据库操作,这里的数据库会话也称事件(transaction)。Flask-SQLALchemy自动帮我们创建会话,可以通过db.session属性获取
数据库中的会话代表一个暂时缓存区,对数据库做出的任何改动都会存放在这里。可以调用add()方法将新创建的对象添加到数据库会话中,或是对会话中的对象举行更新。只有当你对数据库会话对象调用commit()方法时,改动才会被提交到数据库,这确保了数据提交的一致性。别的数据库会话也支持回滚操作。当你对会话调用rollback()方法时,添加到会话中且未提交的改动都将被打消。
4.1、CRUD

默认环境下,Flask-SQLALchemy会自动为模子天生一个__repr()方法。当在Python shell中调用模子的对象时,__reper()方法会返回一条雷同“<模子类名 主键值>”的字符串,比如<Note2>。为了便于操作,本示例重新界说__repr__()方法,返回一些更有用的信息。
class Note(db.Model):
        ...
    def __repr__(self):
      return '<Note %r>' % self.body 4.1.1、Create

添加一条新记录到数据库主要分为三步:


[*]创建Python对象(实例化模子类)作为一条记录。
[*]添加新创建的记录到数据库会话
[*]提交数据库会话
# 下面示例向数据库中添加了三条留言
>>> from app import db,Note
>>> note1 = Note(body='remember Sammy Jankis')
>>> note2 = Note(body='SHAVE')
>>> note3 = Note(body='DON NOT BELIEVE HIS LIES, HE IS THE ONE, KILL HIM')
>>> db.session.add(note1)
>>> db.session.add(note2)
>>> db.session.add(note3)
>>> db.session.commit() 除了依次调用add()方法添加多个记录,也可以使用add_all()方法一次添加包含所有记录对象的列表。
我们在创建模子类的时间并没有界说id字段的数据,这是由于主键由SQLALchemy管理。模子类对象创建后作为暂时对象(transient),当你提交数据库会话后,模子类对象才会转换为数据库记录写入数据库中,这时模子类对象会自动获得id值。
4.1.2、Read

使用模子类提供的query属性附加调用各种过滤方法及查询方法即可从数据库中取出数据
一般来说,一个完整的查询遵照下面的模式:
<模型类>.query.<过滤方法>.<查询方法> 从某个模子出发,通过在query属性对应的Query对象上附加的过滤方法和查询函数对模子类对应的表中的记录举行各种筛选和调解,最终返回包含对应数据库记录数据的模子类实例,对返回的实例调用属性即可获得对应的字段数据。
SQLALchemy提供了许多查询方法用来获取记录:
查询方法阐明all()返回包含所有查询记录的列表first()返回查询的第一条记录,假如未找到,则返回Noneone()返回第一条记录,且仅允许有一条记录。假如记录数目大于1或小于1,则抛堕落误get(ident)传入主键值作为参数,返回指定主键值的记录,假如未找到,则返回Nonecount()返回查询效果的数目one_or_none()雷同one(),假如效果数目不为1,返回Nonefirst_or_404()返回查询的第一条记录,假如未找到,则返回404错误响应get_or_404(ident)传入主键值作为参数,返回指定主键值记录,假如未找到,则返回404错误响应paginate()返回一个Pagination对象,可以对记录举行分页处理with_parent(instance)传入模子实例作为参数,返回和这个实例相关联的对象,背面会具体先容 示例:all()返回所有记录:
>>> Note.query.all()
[<Note 'remember Sammy Jankis'>, <Note 'SHAVE'>, <Note 'DON NOT BELIEVE HIS LIES, HE IS THE ONE, KILL HIM'>] first()返回第一条记录:
>>> note1 = Note.query.first()                                                                              
>>> note1
<Note 'remember Sammy Jankis'>
>>> note1.body
'remember Sammy Jankis' get()返回指定主键值(id字段)的记录:
>>> note2 = Note.query.get(2)
>>> note2
<Note 'SHAVE'> count()返回记录的数目:
>>> Note.query.count()
3 SQLALchemy还提供许多过滤方法,使用这些过滤方法可以获取更准确的查询,比如获取指定字段值的记录。对模子类的query属性存储的Query对象调用过滤方法将返回一个更准确的Query对象(背面简称为查询对象)。由于每个过滤方法都会返回新的查询对象,以是过滤器可以叠加使用。
在查询对象上调用前面先容的查询方法,即可获得一个包含过滤后的记录的列表。常用的过滤方法有:
查询过滤器的名称阐明filter()使用指定的规则过滤记录,返回新产生的查询对象filter_by()使用指定的规则过滤记录(以关键字表达式的形式),返回新产生的查询对象order_by()根据指定条件对记录举行排序,返回新产生的查询对象limit(limit)使用指定的值限定原查询返回的记录数目,返回新产生的查询对象group_by()根据指定条件对记录举行分组,返回新产生的查询对象offset(offset)使用指定的值偏移原查询的效果,返回新产生的查询对象 filter()方法是最基础的查询方法。它使用指定的规则来过滤记录,示例:在数据库中找出body字段值为"SHAVE"的记录:
>>> Note.query.filter(Note.body=='SHAVE').first()
<Note 'SHAVE'> 直接打印查询对象或将其转换为字符串可查看对应的SQL语句:
>>> print(Note.query.filter_by(body='SHAVE'))
SELECT note.id AS note_id, note.body AS note_body
FROM note
WHERE note.body = ? 在filter()方法中传入表达式时,除了"=="以及表示不等于的"!=",其他常用的查询操作符以及使用示例如下:
LIKE:
filter(Note.body.like('%foo%'))
IN:
filter(Note.body.in_(['foo','bar','baz']))
NOT IN:
filter(~Note.body.in_(['foo','bar','baz']))
AND:
# 使用and_()
from sqlalchemy import and
filter(and_(Note.body == 'foo',Note.title == 'FooBar'))

# 或在filter()中加入多个表达式,使用逗号隔开
filter(Note.body == 'foo',Note.title == 'FooBar')

# 或叠加调用多个filter()/filter_by()方法
filter(Note.body == 'foo').filter(Note.title == 'FooBar')

OR:
from sqlalchemy import or_
filter(or_(Note.body == 'foo',Note.body == 'bar')) 和filter方法相比,filter_by()方法更易于使用。在filter_by()方法中,可以使用关键字表达式来指定过滤规则。更方便的是,可以在这个过滤器 中直接使用字段名字。
>>> Note.query.filter_by(body='SHAVE').first()
<Note 'SHAVE'> 其他方法,后续使用时先容。
4.1.3、Update

更新一条记录非常简单,直接赋值给模子类的字段属性就可以改变字段值,然后调用commit()方法提交给会话即可。示例:改变一条记录的body字段的值:
>>> note = Note.query.get(2)
>>> note.body
'SHAVE'
>>> note.body = 'SHAVE LEFT THING'
>>> db.session.commit() 4.1.4、Delete

删除记录和添加记录很相似,不外要把add()方法换成delete()方法,末了都需要调用commit()方法提交修改。示例:删除id(主键)为2的记录:
>>> note = Note.query.get(2)
>>> db.session.delete(note)
>>> db.session.commit() 4.2、在视图函数里操作数据库

在视图函数里操作数据库的方式和我们在Python Shell中练习的大致雷同,只不外需要一些额外的工作。比如把查询效果作为参数传入模板渲染出来,或是获取表单的字段值作为提交到数据库的数据。
4.2.1、Create

为了支持输入笔记内容,我们先创建一个用于填写新笔记的表单:
from wtforms import TextAreaField
from flask_wtf import FlaskForm
from wtforms.validators import DataRequired

class NewNoteForm(FlaskForm):
    body = TextAreaField('Body',validators=)
    submit = SubmitField('Save') 我们创建一个new_note视图,这个视图负责渲染创建笔记的模板,并处理表单的提交:
@app.route('/new',methods=['GET','POST'])
def new_note():
    form = NewNoteForm()
    if form.validate_on_submit():
      body = form.body.data
      note = Note(body=body)
      db.session.add(note)
      db.session.commit()
      flash('Your note is saved.')
      return redirect(url_for('index'))
    return render_template('new_note.html',form=form) 逻辑:当form.validate_on_submit()返回True时,即表单被提交且验证通过期,获取表单body字段的数据,然后创建新的Note实例,将表单中body字段的值作为body参数传入,末了添加到数据库会话中并提交会话。这个过程接收用户通过表单提交的数据并保存到数据库中,末了我们使用flash()函数发送提示消息并重定向到index视图。
表单在new_note.html模板中渲染,这里使用前面先容的form_filed宏渲染表单字段,传入rows和cols参数来定制<textarea>输入框的大小:
{% from 'macro.html' import form_field %}
{% block content %}
<h2>New Note</h2>
    <form method="post">
      {{ form.csrf_token }}
      {{ form_field(form.body,rows=5,cols=50) }}
      {{ form.submit }}
    </form>
{% endblock %} index视图用来显示主页,如今它的所有作用就是渲染主页对应的模板:
@app.route('/')
def index():
        return render_template('index.html') 在对应的index.html模板中,我们添加一个指向创建新笔记页面的链接:
<h1>Notebook</h1>
    <a href="{{ url_for('new_note') }}">New Note</a> 4.2.2、Read

上面为步伐添加了新笔记的功能,当在创建笔记的页面单击保存后,步伐会重定向到主页,提示的消息告诉你刚刚提交的笔记已经成功保存,可却无法看到保存后的笔记。为了在主页列出所有保存的笔记,我们需要修改index视图:
@app.route('/index')
def index():
    form = DeleteForm()
    notes = Note.query.all()
    return render_template('index.html',notes=notes,form=form) 在模板中渲染数据库记录:
<h1>Notebook</h1>
    <a href="{{ url_for('new_note') }}">New Note</a>    <h4>{{ notes|length }} notes:</h4>    {% for note in notes %}      <div class="note">            <p>{{ note.body }}</p>      </div>    {% endfor %} 在模板中,我们迭代这个notes列表,调用Note对象的body属性获取body字段的值。别的,我们还通过length过滤器获取笔记的数目。https://img-blog.csdnimg.cn/direct/0c3cdadf2dd8441684ab378f4a1140a8.png
4.2.3、Update

更新一条笔记和创建一条新笔记的实现代码几乎完全雷同,起首是编辑笔记的表单:
class EditNoteForm(FlaskForm):
    body = TextAreaField('Body',validators=)
    submit = SubmitField('Update') 发现这和创建新笔记NewNoteForm唯一的差别是提交字段的标签参数差别,因此这个表单的界说也可以通过继承来简化:
class EditNoteForm(NewNoteForm):
    submit = SubmitField('Update') 用来渲染更新笔记页面和处理更新表单提交的edit_note视图:
@app.route('/edit/<int:note_id>',methods=['GET','POST'])
def edit_note(note_id):
    form = EditNoteForm()
    note = Note.query.get(note_id)
    if form.validate_on_submit():
      note.body = form.body.data
      db.session.commit()
      flash('Your note is update.')
      return redirect(url_for('index'))
    form.body.data = note.body
    return render_template('edit_note.html',form=form) 逻辑:通过URL变量note_id获取要修改的笔记的主键值(id字段),然后我们就可以使用get()方法获取对应的Note实例。当表单被提交且通过验证时,我们将表单中body字段的值赋值给note对象的body属性,然后提交数据库会话,如许就完成了更新操作。末了重定向。
注意,在GET哀求的实行流程中,我们添加了这行代码:
form.body.data = note.body 由于要添加笔记内容的功能,那么当我们打开修改某个笔记的页面时,这个页面的表单中必然要包含笔记原有的内容。
假如手动创建HTML表单,那么可以通过将note记录传入模板,然后手动为对应字段填入笔记的原有内容:
<textarea name="body">{{ note.body }}</textarea> 其他input元素则通过value属性来设置输入框中的值:
<input name="foo" type="text" value="{{ note.title }}"> 使用WTForms可以省略这些步调,当我们渲染表单字段时,假如表单字段的data属性不为空,WTForms会自动把data属性的值添加到表单字段的value属性中,作为表单的值填充进去,我们不消手动为value属性赋值。
模板的内容根本雷同,末了的工作是在主页笔记列表中的每个笔记内容下添加一个编辑按钮,用来访问编辑页面:
   {% for note in notes %}
      <div class="note">
            <p>{{ note.body }}</p>
            <a class="btn" href="{{ url_for('edit_note',note_id=note.id) }}">Edit</a>
      </div>
    {% endfor %} 4.2.4、Delete

在步伐中,删除的实现也非常简单,不外这里会有一个误区。大多数人通常会考虑在笔记内容下添加一个删除链接:
<a href="{{ url_for('delete_note',note_id=note.id) }}">Delete</a> 这个链接用来指向删除笔记的detele_note视图:
@app.route('/delete/<int:note_id>')
def delete_note(note_id):
    note = Note.query.get(note_id)
    db.session.delete(note)
    db.session.commit()
    flash('Your note is deleted.')
    return redirect(url_for('index')) 虽然这看起来很合理,但这种处理方式会使步伐处于CSRF攻击的风险之中。在前面强调过,防范CSRF攻击的根本原则是正确使用GET和POST方法。像删除这类修改数据的操作绝不能通过GET哀求实现,正确的做法是为删除操作创建一个表单:
class DeleteNoteForm(FlaskForm):
    submit = SubmitField('Delete') 这个表单类只有一个提交字段,由于我们只需要在页面上显示一个删除按钮来提交表单:
@app.route('/delete/<int:note_id>',methods=['POST'])
def delete_note(note_id):
    form = DeleteNoteForm()
    if form.validate_on_submit():
      note = Note.query.get(note_id)      # 获取对应记录
      db.session.delete(note)             # 删除记录
      db.session.commit()               # 提交修改
      flash('Your note is deleted.')
    else:
      abort(400)
    return redirect(url_for('index')) 逻辑:和编辑笔记的视图雷同,这个视图函数接收note_id(主键值)作为参数。假如提交表单且通过验证(唯一需要被验证的是CSRF令牌),就使用get()方法查询对应的记录,然后调用db.session.delete()方法删除并提交数据库会话。如验证错误则使用abort()函数返回400错误响应。
由于删除按钮要在主页的笔记内容下添加,我们需要在index视图中实例化DeleteNoteForm类,然后传入模板。在index.html中:
{% for note in notes %}
      <div class="note">
            <p>{{ note.body }}</p>
            <a class="btn" href="{{ url_for('edit_note',note_id=note.id) }}">Edit</a>
            <form method="post" action="{{ url_for('delete_note',note_id=note.id) }}">
                {{ form.csrf_token }}
                {{ form.submit(class='btn') }}
            </form>
      </div>
    {% endfor %} 我们将表单的action属性设置为删除笔记的URL,URL变量note_id的值通过note.id属性获取,当单击提交按钮时,会将哀求发送到action属性中的URL。添加删除表单的主要目的就是防止CSRF攻击,以是不要忘记渲染CSRF令牌字段form.csrf_token。
五、界说关系

在关系型数据库中,我们可以通过关系让差别表之间的字段建立接洽。一般来说,界说关系需要两步:创建外键和界说关系属性。在更复杂的多对多关系中,我们还需要界说关联表来管理关系。
5.1、配置Python Shell上下文

在上面许多操作中,每一次使用flask shell命令启动Python Shell后都要从app模块里导入db对象和相应的模子类。我们可以使用app.shell_context_processor装饰器注册一个shell上下文处理函数。和模板上下文处理函数一样,也需要返回包含变量和变量值的字典:
@app.shell_context_processor
def make_shell_context():
    return dict(db=db,Note=Note)    # 等同于('db':db,'Note':Note) 当使用flask shell启动Python Shell时,所有使用app.shell_context_processor装饰器注册的shell上下文处理函数都会被自动实行,这将db和Note对象推送到Python Shell上下文里:
$ flask shell
>>> db
<SQLAlchemy sqlite:///D:\Python Web\Pycharm-project\flask\data.db>
>>> Note
<class 'app.Note'> 5.2、一对多

以作者和文章的关系来演示一对多关系:一个作者可以写作多篇文章。
...
class Author(db.Model):
    id = db.Column(db.Integer,primary_key=True)
    name = db.Column(db.String(70),unique=True)
    phone = db.Column(db.String(20))

class Article(db.Model):
    id = db.Column(db.Integer,primary_key=True)
    title = db.Column(db.String(50),index=True)
    body = db.Column(db.Text) 我们将在这两个模子中建立一个简单的一对多关系,建立这个一对多关系的目的是在表示作者的Author类中添加一个关系属性articles,作为聚集(collection)属性,当我们对特定的Author对象调用articles属性会返回所有相关的Article对象。
5.2.1、界说外键

界说关系的第一步是创建外键。外键是(foreign key)用来在A表存储B表的主键值以便和B表建立接洽的关键字段。由于外键只能存储单一数据(标量),以是外键总是在"多"这一侧界说,多篇文章属于同一个作者,以是我们需要为每篇文章添加外键存储作者的主键值以指向对应的作者。
class Article(db.Model):
    ...
    author_id = db.Column(db.Integer,db.ForeignKey('author.id')) 使用db.ForeignKey类界说外键,传入关系另一侧的表名和主键字段名,即author.id。实际效果是将article表的author_id的值限定为author表的id列的值。它将用来存储author表中记录的主键值:
https://img-blog.csdnimg.cn/direct/f6be158a139240aa93a71b03c927f78f.png5.2.2、界说关系属性

界说关系的第二步是使用关系函数界说关系属性。关系属性在关系的出发侧界说,即一对多关系的“一”这一侧。一个作者拥有多篇文章,在Author模子中,我们界说了一个articles属性来表示对应的多篇文章:
class Author(db.Model):
    ...
    articles = db.relationship('Article') 使用db.relationship()关系函数界说为关系属性,由于这个关系属性返回多个记录,我们称之为聚集关系属性。relationship()函数的第一个参数为关系另一侧的模子名称,它会告诉SQLALchemy将Author类与Article类建立接洽。
$ flask shell
>>> foo = Author(name='foo')
>>> spam = Article(title='Spam')
>>> ham = Article(title='Ham')
>>> db.session.add(foo)
>>> db.session.add(spam)
>>> db.session.add(ham) 5.2.3、建立关系

建立关系有两种方式,第一种方式是为外键字段赋值,比如:
>>> spam.author_id = 1
>>> db.session.commit() 将spam对象的author_id字段的值设为1,这会和id值为1的Author对象建立关系。提交数据库改动后,假如我们对id为1的foo对象调用articles关系属性,会看到spam对象包罗在返回的Article对象中:
>>> foo.articles
[<Article u'spam>,<Article u'Ham>] 另一种方式是通过操作关系属性,将关系属性赋给实际的对象即可建立关系。聚集关系属性可以像列表一样操作,调用append()方法来与一个Article对象建立关系:
>>> foo.articles.append(spam)
>>> foo.articles.append(ham)
>>> db.session.commit() 和前面的第一种方式雷同,为了让改动生效,我们需要调用db.session.commit()方法提交数据库会话。建立关系后,存储外键的author_id自动获得正确的值,调用author 实例的关系属性articles时,会获得所有建立关系的Article对象。
>>> spam.author_id
1
>>> foo.articles
[<Article u'Spam'>,<Article u'Ham'>] (之后统一用第二种方式,即通过关系属性来建立关系)
和append()相对,对关系属性调用remove()方法可以与对应的Article对象清除关系:
>>> foo.articles.remove(spam)
>>> db.session.commit()
>>> foo.articles
[<Article u'Ham'>] 常用的SQLALchemy关系函数参数
参数名阐明back_populates界说反向引用,用于建立双向关系,在关系的另一侧也必须显式界说关系属性backref添加反向引用,自动在另一侧建立关系属性,是back_populates的简化版lazy指定怎样加载相关记录uselist指定是否使用列表的形式加载记录,设为False则使用标量(scalar)cascade设置级联操作order_by指定加载相关记录时的排序方式secondary在多对多关系中指定关联表primaryjoin指定多对多关系中的一级联结条件secondaryjoin指定多对多关系中的二级联结条件 当关系属性被调用时,关系函数会加载相应的记录,下面列出控制关系记录加载方式的lazy参数的常用选项:
关系加载方式阐明select在须要时一次性加载记录,返回包含记录的列表(默认值),等同于lazy=Truejoined和父查询一样加载记录,但使用联结,等同于lazy=Falseimmediate一旦父查询加载就加载subquery雷同于joined,不外使用子查询dynamic不直接加载记录,而是返回一个包含相关记录的query对象,以便再继续附加查询函数对效果直接举行过滤 (dynamic选项仅用于聚集关系属性,不可用于多对一,一对一或是在关系函数中将uselist参数设为False的环境)
(许多教程和示例使用dynamic来动态加载所有聚集关系属性对应的记录,这是应该避免的举动。使用dynamic加载方式意味着每次操作关系都会实行一次SQL查询,这会造成埋伏的性能题目。大多数环境下我们只需要使用默认值(select),只有在调用关系属性会返回大量记录,而且总是需要对关系属性返回的效果附加额外的查询才需要使用动态加载(dynamic))。
5.2.4、建立双向关系

我们在Author类中界说了聚集关系属性articles,用来获取某个作者拥有的多篇文章记录。在某些环境下,你大概希望能在Article类中界说一个雷同的author关系属性,当被调用时返回对应的作者记录,这类返回单个值的关系属性被称为标量关系属性。而这两侧都添加关系属性获取对方的关系称之为双向关系
双向关系并不是必须的,但在某些环境下会非常方便。双向关系的建立很简单,通过在关系的另一侧也创建一个relationship()函数,我们就可以在两个表之间建立双向关系。下面使用作家(Writer)和书(Book)的一对多关系举行演示:
class Writer(db.Model):
    id = db.Column(db.Integer,primary_key= True)
    name = db.Column(db.String(70),unique=True)
    books = db.relationship('Book',back_populates='writer')

class Book(db.Model):
    id = db.Column(db.Integer,primary_key=True)
    title = db.Column(db.String(50),index=True)
    writer_id = db.Column(db.Integer,db.ForeignKey('writer.id'))
    writer = db.relationship('Writer',back_populates='books') 逻辑:在“多”这一侧的Book(书)类中,我们新创建了一个writer关系属性,这是一个标量关系属性,调用它会获取对应的Writer(作者)记录;而在Writer(作者)类中的books属性则用来获取对应的多个Book(书)记录。在关系函数中,我们使用back_populates参数来连接对方,back_populates参数的值需要设为关系另一侧的关系属性名。
>>> king = Writer(name='Stephen King')
>>> carrie = Book(name='Carrie')
>>> it = Book(name='IT')
>>> db.session.add(King)
>>> db.session.add(carrie)
>>> db.session.add(it)
>>> db.session.commit() 设置双向关系后,除了通过聚集属性books来操作关系,也可使用标量属性writer来举行关系操作。比如将一个Writer对象赋值给某个Book对象的writer属性,就会和这个Book对象建立关系:
>>> carrie.writer = king
>>> carrie.writer
<Writer u'Stephen King'>
>>> king.books
[<Book u'Carrie'>]
>>> it.writer = writer
>>> king.books
[<Book u'Carrie'>,<Book u'IT'>] 相对的,将某个Book的writer属性设为None,就会清除与对应Writer对象的关系:
>>> carrie.writer = None
>>> king.books
[<Book u'IT'>]
>>> db.session.commit() 需要注意,我们只需要在关系的一侧操作关系。当为Book对象的writer属性赋值后,对应Writer对象的books属性的返回值也会自动包含这个Book对象。反之,当某个Writer对象被删除时,对应的Book对象的writer属性被调用时的返回值也会被置空(即NULL,会返回None)
5.2.5、使用backref简化关系界说

以一对多关系为例,backref参数用来自动为关系另一侧添加关系属性,作为反向引用,赋予的值会作为关系的另一侧的关系属性名称。
class Singer(db.Model):
        id = db.Column(db.Integer,primary_key=True)
        name = db.Column(db.String(70),unique=True)
        songs = db.relationship('Song',backref='singer')

class Song(db.Model):
        id = db.Column(db.Integer,primary_key=True)
        name = db.Column(db.String(50),index=True)
        singer= db.Column(db.Integer,db.ForeignKey('singer.id')) 逻辑:在界说聚集属性songs的关系函数中,我们将backref参数设为singer,这会同时在Song类中添加一个singer标量属性。这时我们仅需界说一个关系函数,虽然singer是一个“看不见的关系属性”,但在使用上和界说两个关系函数并使用back_populates参数的效果完全雷同。
注意:backref允许我们仅在关系一侧界说另一侧的关系属性,但在某些环境下,我们希望可以对在关系另一侧的关系属性举行设置,这时需要使用backref()函数。backref()函数接收第一个参数作为在关系另一侧添加的关系属性名,其他关键字参数会作为关系另一侧关系函数的参数传入。比如:在关系的另一侧“看不见的relationship()函数”中将uselist参数设为False:
class Singer(db.Model):
        ...
        songs = relationship('Song',backref=backref('singer',userlist=False)) 5.3、多对一

使用住民和都会演示:多个住民住在同一个都会。前面先容:关系属性在关系模式的触发侧界说。当出发点在“多”这一侧时,我们希望在Citizen类中添加一个关系属性city来获取对应的都会对象,由于这个关系属性返回单个值,我们称之为标量关系属性。在界说关系时,外键总是在“多”这一侧界说,以是在多对一关系中,外键和关系属性都界说在“多”这一侧:
class Citizen(db.Model):
    id = db.Column(db.Integer,primary_key=True)
    name = db.Column(db.String(70),unique=True)
    city_id = db.Column(db.Integer,db.ForeignKey('City.id'))
    city = db.relationship('City')

class City(db.Model):
    id = db.Column(db.Integer,primary_key=True)
    name = db.Column(db.String(30),unique=True) 逻辑:这时界说的city属性是一个标量属性(返回单一数据)。当Citizen.city被调用时,SQLALchemy会根据外键字段city_id对象并返回,即住民记录对应的都会记录。
当建立双向关系时,假如不使用backref,那么一对多和多对一关系模式在上完全雷同,这时可以将一对多和多对一视为同一种关系模式。在背面我们通常都会为一对多或多对一建立双向关系,这时将弱化这两种关系的区别,一律称为一对多关系
5.4、一对一

使用国家和都城演示:每个国家只有一个都城。
一对一关系实际上是通过建立双向关系的一对多的基础上转换而来。我们要确保关系两侧的关系属性都是标量关系,都只返回单个值,以是要在界说聚集属性的关系函数中将uselist参数设为False,这时一对多关系将被转换为一对一关系:
class Country(db.Model):
    id = db.Column(db.Inyeger,primary_key=True)
    name = db.Column(db.String(30),unique=True)
    capital = db.relationship('Capital',uselist=False)

class Capital(db.Model):
    id = db.Column(db.Integer,primary_key=True)
    name = db.Column(db.String(30),unique=True)
    country_id = db.Column(db.Integer,db.ForeignKey('country.id'))
    country = db.relationship('Country') 逻辑:”多“这一侧本身就是标量关系属性,不消做任何改动。而”一“这一侧的聚集关系属性,通过将uselist设为False后,将仅返回对应的单个记录,而且无法再使用列表语义操作:
>>> china = Country(name='China')
>>> beijing = Captital(name='Beijing')
>>> db.session.add(china)
>>> db.session.add(beijing)
>>> db.sessiom.commit()
>>> china.captital = beijing
>>> china.captital
<Captital 1>
>>> beijing.country
u'China'
>>> tokyo = Capital(name'Tokyo')
>>> china.capital.append(tokyo)
Traceback (most recent call last):
        File "<console>", line 1, in<module>
AttributeError: 'Captital' object has no attribute 'append' 5.5、多对多

使用门生和老师演示多对多关系:每个门生有多个老师,而每个老师有多个门生。
在多对多关系中,每一个记录都可以与关系另一侧的多个记录建立关系,关系两侧的模子都需要存储一组外键。在SQLALchemy中,我们还需要创建一个关联表。关联表不存储数据,只用来存储关系两侧模子的外键对应关系:
association_table = db.Table('association', db.Column('student_id', db.Integer, db.ForeignKey('student.id')),
                           db.Column('teacher_id', db.Integer, db.ForeignKey('teacher.id')))

class Student(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(70), unique=True)
    grade = db.Column(db.String(20))
    teachers = db.relationship('Teacher', secondary=association_table, back_populates='students')

class Teacher(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(70), unique=True)
    office = db.Column(db.String(20)) 关联表使用db.Table界说,传入的第一个参数是关联表的名称。在上述关联表中界说了两个外键字段:teacher_id字段存储Teacher类的主键,student_id存储Student类的主键。借助关联表中这个中央人存储的外键对,我们可以把多对多关系分化成两个一对多关系:
https://img-blog.csdnimg.cn/direct/f0a32c519a20465581c7629b4d1f8c84.png示例:当需要查询某个门生记录的多个老师时,先通过门生和管理表的一对多关系查询所有包含该门生的关联表记录,然后就可以从这些记录中再进一步获取每个关联表记录包含的老师记录。
在Student类中界说一个teachers关系属性用来获取老师聚集。在多对多关系中界说关系函数,除了第一个参数是关系另一侧的模子名称外,还需要添加一个secondary参数,把这个值设为管理表的名称。
为了便于实现真正的多对多关系,我们需要建立双向关系。在Student类上的teachers聚集属性会返回所有关联的老师记录,而在Teacher类上的students聚集属性会返回所有相关的门生记录:
class Student(db.Model):
        ...
        teachers = db.relationship('Teacher',
                                                                secondary=association_table,
                                                                back_populates='students')
       
class Teacher(db.Model):
    ...
    students = db.relationship('Student',
                                                            secondary=association_table,
                               back_populates='teachers') 除了在声明关系模式在操作关系时和其他关系模式根本雷同。调用关系属性student.teachers时,SQLALchemy会直接返回关系另一侧的Teacher对象,而不是关联表记录。和其他关系模式中的聚集属性一样,我们可以将关系属性teachers和students像列表一样操作。比如,当需要为某一个门生添加老师时,对关系属性使用append()方法即可。清除关系使用remove()方法。
六、更新数据库表

6.1、重新天生表

使用drop_all()方法删除表以及其中的数据,然后使用create_all()方法重新创建:
>>> db.drop_all()
>>> db.create_all() 为了便于开发,我们修改initdb命令函数的内容,为其增加一个--drop选项来支持删除表和数据库后举行重建:
@app.cli.command()
@click.option('--drop',is_flag=True,help='Create after drop.')
def initdb(drop):
    """Initialize the database."""
    if drop:
      click.confirm('This operation will delete the database,do you want to continue?',abort=True)
      db.drop_all()
      click.echo('Drop tables.')
    db.create_all()
    click.echo('Initialized database.') 如今,实行下面的命令会重建数据库和表:
$ flask initdb --drop (当使用SQLite时,直接删除data.db文件和调用drop_all()方法效果雷同,更直接不容易堕落)
6.2、使用Flask-Migrate迁移数据库

不想要数据库中的数据被删撤消就使用数据库迁移来完成。
迁移工具--Alembic来帮我们实现数据库的迁移,数据库迁移工具可以在不粉碎数据的环境下更新数据库表的结构。
扩展Flask-Migrate集成了Alembic,提供了一些flask命令来简化迁移工作:
pip install flask-migrate 在步伐中,我们实例化Flask-Migrate提供的Migrate类,举行初始化操作:
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from flask_migrate import Migrate

app = Flask(__name__)
...
db = SQLAlchemy(app)
migrate = Migrate(app,db) 6.2.1、创建迁移环境

开始迁移数据库之前,需要使用下面的命令创建一个迁移环境:
$ flask db init 迁移环境只需要创建一次。这会在项目根目次下创建一个migrations文件夹,其中包含了自动天生的配置文件和迁移版本文件夹
6.2.2、天生迁移脚本

使用migrate子命令可以自动天生迁移脚本:
$ flask db migrate -m "add note timestamp" 这条命令:在flask里对数据库(db)举行迁移。-m选项用来添加迁移备注信息。天生内容示例:
"""add note timestamp
Revision ID: b27e0318b424
"""
from alembic import op
import sqlalchemy as sa
...
def upgrade():
    # ### commands auto generated by Alembic - please adjust! ###
    op.add_column('note',sa.Column('timestamp',sa.DateTime(),nullable=True))
    # ### end Alembic commands ###

def downgrade():
    # ### commands auto generated by Alembic - please adjust! ###
    op.drop_column('note','timestamp')
    # ### end Alembic commands ### 迁移脚本主要包含了两个函数:upgrade()函数用来将改动应用到数据库,函数中包含了向表中添加timestamp字段的命令;而downgrade()函数用来打消改动,包含了删除timesptamp字段的命令。
6.2.3、更新数据库

天生了迁移脚本后,使用upgrade子命令即可更新数据库:
>>> $ flask db upgrade 若没有创建数据库和表,这个命令会自动创建;若已创建,则会在不粉碎数据的前提下实行更新。
6.3、开发时是否要迁移?

尽可能让开发环境和生产环境保持一致。考虑直接在本地使用MySQL或PostgreSQL等性能更高的DBMS,然后设置迁移环境。
七、数据库进阶实践

7.1、级联操作

Cascade意为“级联操作”,就是在操作一个对象的同时,对相关的对象也实行某些操作。示例:
class Post(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    title = db.Column(db.String(50), unique=True)
    body = db.Column(db.Text)
    comments = db.relationship('Comment', back_populates='post')

class Comment(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    body = db.Column(db.Text)
    post_id = db.Column(db.Integer, db.ForeignKey('post.id'))
    post = db.relationship('Post', back_populates='comments') 级联举动通过关系函数relationship()的cascade参数设置。我们希望在操作Post对象时,处于附属低位的Comment对象也被相应实行某些操作,这时应该在Post类的关系函数中界说级联参数。设置了cascade参数的一侧将被视为父对象,相关的对象则视为子对象。
cascade通常使用多个组合值,级联值之间使用逗号分隔:
class Post(db.Model):
        ...
        comments = relationship('Comment',cascade='save-update,merge,delete') 常用的配置组合如下所示:


[*]save-update、merge(默认值)
[*]save-update、merge、delete
[*]all
[*]all、delete-orphan
当没有设置cascade参数时,会使用默认值save-update、merge。上面的all等同于除了delete-orphan以为所有可用值的组合,即save-update、merge、refresh-expire、expunge、delete。下面我们会先容常用的几个级联值:
7.1.1、save-update

save-update是默认的级联举动,当cascade参数设为save-update时,假如使用db.session.add()方法将Post对象添加到数据库会话时,那么与Post相关联的Comment对象也将被添加到数据库会话。
>>> post1 = Post()
>>> comment1 = Comment()
>>> comment2 = Comment() 将post1添加到数据库会话后,只有post1在数据库会话中:
>>> db.session.add(post1)
>>> post1 in db.session
True
>>> comment1 in db.session
False
>>> comment2 in db.session
False 假如我们让post1与两个Comment对象建立关系,那么这两个Comment对象也会自动被添加到数据库会话中:
>>> post1.comments.append(comment1)
>>> post1.comments.append(comment2)
>>> comment1 in db.session
True
>>> comment2 in db.session
True 当调用db.session.commit()数据库会话时,这三个对象都会被提交到数据库中。
7.1.2、delete

假如某个Post对象被删除,那么按照默认的举动,该Post对象相关联的所有Comment对象都将与这个Post对象取消关联,外键字段的值会被清空。假如Post类的关系函数中cascade参数设为delete时,这些相关的Comment会在关联的Post对象删除时被一并删除。当需要设置delete级联时,我们会将级联值设为all或save-update、merge、delete,比如:
class Post(db.Model):
        ...
        comments = relationship('Comment',cascade='all') 示例:
>>> post2 = Post()
>>> comment3 = Comment()
>>> comment4 = Comment()
>>> post2.comments.append(comment3)
>>> post2.comments.append(commetn4)
>>> db.session.add(post2)
>>> db.session.commit() 如今共有两条Post记录和四条Comment记录:
>>> Post.query.all()
[<Post 1>,<Post 2>]
>>> Comment.query.all()
[<Comment 1>,<Comment 2>,<Comment 3>,<Comment 4>] 假如删除文章对象Post2,那么对应的两个批评对象也会一并被删除:
>>> post2 = Post.query2y.get(2)
>>> db.session.delete(post2)
>>> db.session.commit()
>>> Post.query.all()
[<Post 1>]
>>> Comment.query.all()
[<Comment 1>,<Comment 2>] 7.1.3、delete-orphan

这个模式是基于delete级联的,必须和delete级联一起使用,通常会设为all、delete-orphan,由于all包含delete。因此当cascade参数设为delete-orphan时,它起首包含delete级联的举动:当某个Post对象被删除,所有相 Comment 都将被删除 delete 级联) 。除此之外, 当某个Post对象(父对象)与某Comment对象(子对象)清除关系时,也会删除该 Comment对象, 这个清除关系的对象被称为孤立对象(orphan object):
class Post(db.Model):
        ...
        comments = relationship('Comment',cascade='all,delete-orphan') delete和delete-orphan通常会在一对多关系模式中,而且“多”这一侧的对象附属于“一”这一侧的对象时使用。尤其是假如“一”这一侧的“父”对象不存在了,那么“多”这一侧的“子“对象不再有意义的环境。比如,文章和批评。
虽然级联操作方便,但是容易带来安全隐患,因此要谨慎使用。默认值能够满意大部门环境,以是最好仅在需要的时间才修改它。
7.2、事件监听

SQLALchemy提供了一个listen_for()装饰器,可以用来注册事件回调函数。
listen_for()装饰器接收两个参数,target表示监听的对象,这个对象可以是模子类、类实例或类属性等。identifier参数表示被监听事件的标识符,比如,用于监听属性的事件标识符有set、append、remove、init_scalar、init_collection等。
# 创建一个Draft模型类表示草稿
class Draft(db.Model):
        id = db.Column(db.Integer,primary_key=True)
        body = db.Column(db.Text)
        edit_time = db.Column(db.Integer,default=0) 通过注册件监听函数,实如今body列修改时,自动叠加表示被修改次数的edit_ time字段。在SQLAlchemy中,每个事件都会有一个对应的事件方法,差别的事件方法支持差别的参数,被注册的监听函数需要接收对应事件方法的 所有参数,以是具体的监听函数用法因使用的事件而异。设置某个字段值将触发set事件:
@db.event.listens_for(Draft.body,'set')
def increment_edit_time(target,value,oldvalue,initiator):
    if target.edit_time is not None:
      target.edit_time += 1
      
# target参数表示触发事件的模型类实例,使用target.edit_time即可获取我们需要叠加的字段。value表示被设置的值,oldvalue表示被取代的旧值。 当set事件发生在目的对象Draft.body上时,这个监听函数就会被实行,从而叠加Draft.edit_time列的值:
>>> draft = Draft(body='init')
>>> db.session.add(draft)
>>> db.session.commit()
>>> draft.edit_time
0
>>> draft.body = 'edited'
>>> draft.edit_time
1
>>> draft.body = 'edited again'
>>> draft.edit_time
2
>>> draft.body = 'edited agian again'
>>> draft.edit_time
3
db.session.commit() 除了这种传统的参数接收方式,即接收所有事件方法接收的参数,还有一种:通过在listen_for()装饰器中将关键字参数name设为True,可以在监听函数中接收**kwargs作为参数(可变长关键字参数)。然后在函数中可以使用参数名作为键从kwargs字典中取出对应的参数值:
@db.event.listens_for(Draft.body,'set')
def increment_edit_time(**kwargs):
    if kwargs['target'].edit_time is not None:
      kwargs['target'].edit_time += 1 SQLALchemy作为SQL工具集本身包含两大主要组件:SQLALchemy ORM和SQLALchemy Core。前者实现了前面先容的ORM功能,后者实现了数据库接口等焦点功能,这两类组件都提供了大量的监听事件,几乎覆盖了整个SQLALchemy使用的生命周期。
致谢

在此,我要对所有为知识共享做出贡献的个人和机构表示最深切的感谢。同时也感谢每一位花时间阅读这篇文章的读者,假如文章中有任何错误,欢迎留言指正。 
   学习永无止境,让我们共同进步!!

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
页: [1]
查看完整版本: Flask之数据库