Flask 数据库操作

249 阅读11分钟

可能需要引用得库:

SQLAlchemy(1.2.7)
·主页:www.sqlalchemy.org/ ·源码:github.com/zzzeek/sqla…
·文档:docs.sqlalchemy.org/en/latest/

Flask-SQLAlchemy(2.3.2)
·主页:github.com/mitsuhiko/f… ·文档:flask-sqlalchemy.pocoo.org/2.3/

Alembic(0.9.9)
·主页:bitbucket.org/zzzeek/alem… ·文档:alembic.zzzcomputing.com/en/latest/2

Flask-Migrate(2.1.1)
·主页:github.com/miguelgrinb… ·文档:flask-migrate.readthedocs.io/en/latest/


ORM
  • ORM主要实现了三层映射关系:
    • 表 → Python类
    • 字段(列) → 类属性
    • 记录(行) → 类实例

例子:

sql 中创建表

CREATE TABLE contacts (
  name varchar(100) not null,
  phone_number varchar(32)
)

ORM实现

from foo_orm import Medel, Column, String

class Contact(Model): 
  __tablename__ = 'contacts'
  name = Column(String(100), nullable = False)
  phone_number = Column(String(32))

插入记录

INSERT INTO contacts (name, phone_number) VALUES ('Wuhan', '123123123')
contact = Contact(name="Wuhan", phone_number="121312312")

使用Flask-SQLAlchemy管理数据库

pip install flask-sqlalchemy
from flask import Flask
from flask_sqlalchemy import SQLAlchemy

app = Flask(__name__)

db = SQLAlchemy(app)

链接数据库

import os
app.config['SQLALCHEMY_DATABASE_URI'] = os.getenv('DATABASE_URL', 'sqlite:///' + os.path.join(app.root_path, 'data.db'))
# SQLite URI compatible
WIN = sys.platform.startswith('win')
if WIN:
    prefix = 'sqlite:///'
else:
    prefix = 'sqlite:////'


dev_db = prefix + os.path.join(os.path.dirname(app.root_path), 'data.db')

SQLAlchemy常用得字段类型

字段说明
Integer整数
String字符串,可选参数length可以用来设置最大长度
Text较长得Unicode文本
Date日期,存储Python得datetime.date 对象
Time时间,存储Python得datetime.time对象
DateTime时间和日期,存储Python得datetime对象
Interval时间间隔,存储Python得datetime.timedelta对象
Float浮点数
Boolean布尔值
PickleType存储Piekle列化得Python对象
LargeBinary存储任意二进制数据
  • 定义模型例子
class Note(db.Model):
  id = db.Column(db.Integer, primart_key = True)
  body = db.Column(db.Text)

class Note(db.Model):
  __tablename__ = "note"  # 指定表名
  id = db.Column(db.Integet, primart_key = True)
  body = db.Column(db.Text)
> Note类对应的表名称即note。如果你想自己指定表名称,可以通过 定义__tablename__属性来实现。字段名默认为类属性名,你也可以通过 字段类构造方法的第一个参数指定,或使用关键字name。根据我们定义 的Note模型类,最终将生成一个note表,表中包含id和body字段。

常用得SQLAlchemy字段参数

参数名说明
primary_key如果设为True,该字段为主键
unique如果设为True,该字段不允许出现重复值
index如果设为True,为该字段创建索引,以提高查询效率
nullable确定字段值可否为空,值为True或False,默认值为True
default为字符设置默认值

创建数据库和表

  • 创建数据库
flask shell
from app import db

db.create_all()
  • 查看模型对应得SQL模式(建表语句)
from sqlalchemy.schema import CreateTable
print(CreateTable(Note.__table__))

CREATE TABLE not(
    id Integet NOT NULL,
    body TEXT,
    PRIMARY_KEY (id)
 )
> 数据库和表一旦创建之后,如果想要更改他得话,那么就需要先调用 `db.drop_all() `方法删除数据库和表 再调用 `db.create_all()`方法创建才可以。
  • 自定义创建数据库和表的flask命令
import click
@app.cli.command() # 自定义flask命令
def initdb():
    db.create_all()
    click.echo("initialzed database.")

flask initdb
>> initialzed database.

数据库操作

数据库操作模板

from flask_sqlalchemy import SQLAlchemy
import click,sys
# 数据库处理


# SQLite URI compatible
WIN = sys.platform.startswith('win') 
if WIN:
    prefix = 'sqlite:///'
else:
    prefix = 'sqlite:////'


app.config['SQLALCHEMY_DATABASE_URI'] = prefix + os.path.join(app.root_path, 'data.db')
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False  # 关闭对模型修改的监控
**db = SQLAlchemy(app)**  # 初始化数据库
 
class Note(db.Model):
  id = db.Column(db.Integer, primary_key=True)
  body = db.Column(db.Text)

@app.cli.command()
def initdb():
    db.create_all()
    click.echo("initialzed database.")
    
    

增删改查

  • __repr__ (): 在Python Shell 中调用模型的对象时返回的信息。
class Note(db.Model):
  ...
  
  def __repr__(self):
    return '<Note %r>' % self.body

Create

from app import db,Note
note1 = Note(body='remember Sammy Jankis')
note2 = Note(body='SHAVE')
note3 = Note(body='DON\'t BELIEVE LIES,HEIS THE ONE, KILL HIM')
-----------------
方法1:
db.session.add(note1)
db.session.add(note2)
db.session.add(note3)
db.session.commit()
方法2:
db.session.add_all([
  Note(body='remember Sammy Jankis'),
  Note(body='SHAVE'),
  Note(body='DON\'t BELIEVE LIES,HEIS THE ONE, KILL HIM')
])

Read

<模型类>.query.<过滤方法>.<查询方法>
  • 常用的SQLAlchemy查询方法
查询方法说明
all()返回包含所有查询记录的列表
first返回查询的第一条记录,如果未找到,则返回None
one()返回第一条记录,且仅仅允许有一条记录。如果记录数量大于1或小于1,则抛出错误
get(id)传入主键值作为参数,返回指定主键值的记录,如果微软找到,则返回None
count()返回查询结果的数量
one_or_none()类似one(),如果结果数量不为1,返回None
first_or_404()返回查询的第一条记录,如果未找到,则返回404错误响应
get_or_404()传入主键值作为参数,返回指定主键值的记录,如果为找到,则返回404错误响应
paginate()返回一个Pagination对象,可以对记录进行分页处理
with_parent(instance)传入模型类实例作为参数,返回和这个实例相关联的对象
Note.query.all()
[<Note u'remember Sammy Jankis'>, <Note u'SHAVE'>, <Note u'DON'T BELIEVE HIS LIES, HE IS THE ONE, KILL HIM'>]

  • 常用SQLAlchemy过滤方法
过滤方法说明
filter()使用指定的规则过滤记录,返回新产生的查询对象
filter_by()使用指定规则过滤记录(以关键字表达式的形式),返回新产生的查询对象
order_by()根据指定条件对记录进行排序,返回新产生的查询对象
limit(limit)使用指定的值限制元查询返回的记录数量,返回新产生的查询对象
group_by()根据指定条件对记录进行分组,返回新产生的查询对象
offset(offset)使用指定的值偏移原查询的结果,返回新产生的查询对象

filter()

Note.query.filter(Note.body=='SHAVE').first()
<Note u'SHAVE'>

filter_by()

Note.query.filter_by(body=="SHAVE").first()
<Note u'SHAVE'>

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'))

Update

note = Note.query.get(2)

note.body = "SHAVE LEFT THIGH"
db.session.commit()

Delete

note = Note.query.get(2)
db.session.delete(note)
db.session.commit()

在视图函数中操作数据库

Create

# 视图函数中操作数据库
from flask_wtf import FlaskForm
from wtforms import TextAreaField, SubmitField
from wtforms.validators import DataRequired

class NewNoteForm(FlaskForm):
  body = TextAreaField('Body', validators=[DataRequired()])  
  submit = SubmitField("Save")

@app.route("/")
def index():
    return render_template('index.html')

@app.route("/new", methods=['GET', 'POST'])
def new_note():
   ** ****form = NewNoteForm()**
    if form.validate_on_submit():
        **body = form.body.data   # 获取页面上传入的body**
       ** note = Note(body = body) # 存入数据库
        db.session.add(note)
        db.session.commit() # 必须进行commit方法来进行提交**
        flash('Your notes is saved.')
        return redirect(url_for('index'))
    return render_template('new_note.html', form = form)
============================
{% extends 'base.html' %}

{% block content %}
<h2>New Note</h2>
<form method="post">
  {% from 'macro.html' import get_field %}
  {{ form.csrf_token }}
  {{ get_field(form.body, rows = 5, cols = 50) }}
  {{ form.submit }}
</form>
{% endblock %}

Read

 notes = Note.query.all() # 进行数据库的查询所有操作
    return render_template('index.html',notes = notes) # 吧所有数据传入到模板中
 ===========================
 <h4>留言数量:{{ notes | length }}</h4>   # 使用lenght 来获取数据库的内容长度
{% for note in notes %}  # 循环遍历数据
  <p>{{ note.body }}</p>  # 使用 .属性 来获取具体的属性值
{% endfor %}

Update

  • 因为编辑表单是在提交的基础上来操作的,所以可以使用继承来操作
class EditNoteForm(FlaskForm):
    body = TextAreaField('Body', validators=[DataRequired()])  
    submit = SubmitField("Update")
class EditNoteForm(**NewNoteForm**):
    **"""使用继承的方式"""**
    submit = SubmitField("Update")
    
@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 updated.")
        return redirect(url_for('index'))
    form.body.data = note.body
    return render_template("edit_note.html", form = form)
=======================================================
**edit_note.html
**{% extends 'base.html' %}

{% block content %}
<h2>Edit Note</h2>
<form method="post">
  {% from 'macro.html' import get_field %}
  {{ form.csrf_token }}
  {{ get_field(form.body) }}
  {{ form.submit }}
</form>
{% endblock %}

**index.html
**<h1>Notebook</h1>
<a href="{{ url_for('new_note') }}">New Note</a>

<h4>留言数量:{{ notes | length }}</h4>
{% for note in notes %}
<div class="note">
  <p>{{ note.body }}</p>
**  </div><a class="btn"  href="{{ url_for('edit_note', note_id=note.id) }}">Edit</a>
**</div>
{% endfor %}

{% for message  in get_flashed_messages() %}
  {{ message }}
{% endfor %}


Delete(Post请求)

  • 修改数据的操作,一定不要使用GET请求,否则会有CSRF攻击风险
class DeleteNoteForm(FlaskForm):
    submit = SubmitField("Delete")

@app.route("/delete/<int:note_id>", methods=['GET', '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 note.")
    else:
        abort(404)
    return redirect(url_for('index'))
========================
更新index方法
@app.route("/")
def index():
    form = DeleteNoteForm()
    notes = Note.query.all()
    return render_template('index.html',notes = notes**,form = form**) # 不传form的话 会报错没有form
============================
更新 index.html
<h1>Notebook</h1>
<a href="{{ url_for('new_note') }}">New Note</a>

<h4>留言数量:{{ notes | length }}</h4>
{% for note in notes %}
<div class="note">
  <p>{{ note.body }}</p>
  </div><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 %}

{% for message  in get_flashed_messages() %}
  {{ message }}
{% endfor %}

定义关系

配置Python Shell 上下文

# 配置Python Shell上下文
@app.shell_context_processor
def make_shell_context():
    return dict(db = db, Note = Note)

![[Pasted image 20211220084429.png]]


一对多

一个作者可以写多个文章。

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))
    
   articles = db.relationship('Article')
    
    def __repr__(self):
        return "<Author %r>" % self.name

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_id = db.Column(db.Integer, db.ForeignKey('author.id'))
    
    def __repr__(self):
        return "<Article %r>" % self.title
$ 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)

# 建立关系
spam.author_id = 1
ham.author_id = 1
db.session.commit()

报错信息

sqlalchemy.exc.IntegrityError: (sqlite3.IntegrityError) UNIQUE constraint failed: author.name
[SQL: INSERT INTO author (name, phone) VALUES (?, ?)]
[parameters: ('Foo', None)]

↑:

  1. 这种报错信息 可以先进行 db.drop_all()db.create_all()进行重置数据库。
  2. 或者可以进行 db.session.rollback()

建立关系 1.

spam.author_id = 1

foo.articles.append(spam)
  • 删除关联关系
foo.articles.remove(spam) # pop()方法也可以,但是会删除对应关系属性列表的最后一个对象

常用的SQLAlchemy关系函数参数

参数名说明
back_populates定义反向引用,用于建立双向关系,在关系的另一侧也必须显式定义关系属性
backref添加反向引用,自动在另一个侧建立关系属性,是back_populates得简化版
lazy指定如何加载相关记录
uselist指定是否使用列表得形式加载记录,设为False则使用标量(scalar)
cascade设置级联操作
order_by指定加载相关记录时得排序方式
secondary在多堆垛关系中指定关联表
primaryjoin指定多对多关系中的以及联结条件
secondaryjoin指定多对多关系中的二级联结条件

常用的SQLAlchemy关系记录加载方式(lazy参数可选值)

关系加载方式说明
select在必要时一次性加载记录,返回包含记录得列表(默认值),等同于lazy=True
joined和父查询一样加载记录,但使用联结,等同于lazy=Flase
immediate一旦父查询加载就加载
subquery类似于joined,不过将使用子查询
dynamic不直接加载记录,而是返回一个包含相关记录得query对象,以便再继续附加查询函数对结果进行过滤

建立双向关系back_populates=''字段)

文章和作者得关系:

调用文章时显示作者

调用作者时显示文章
  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)
      
      write_id = db.Column(db.Integer, **db.ForeignKey('write.id'))**
**      writer = db.relationship('Writer', back_populates = 'bookes') # 标量关系属性**
>>> from app import Writer
>>> from app import Book
>>> 
>>> 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()
>>> 
>>> carrie.writer = king  
>>> carrie.writer
<Writer 1>
>>> king.books
[<Book 1>]
>>>

使用backref简化关系定义

class Singer(db.Model):
  id = db.Column(db.Integet, primary_key = True)
  name = db.Column(db.String(70), unique = True)
  
  songs = db.relationship('Song', backref = 'singer')

class Song(db.Model):
  id = db. Coulmn(db.Integer, primary_key = True)
  name = db.Column(db.String(50), index = True)
  
  singer_id = db.Column(db.Integet, db.ForeignKey("singer.id"))

多对一

多个居民居住在同一个城市

![[Pasted image 20211220084502.png]]

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")
    
    def __repr__(self):
        return "<Citizen %r>" % self.name

class City(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(30), unique = True)
    
    def __repr__(self):
        return "<City %r>" % self.name
# 在flask shell 中操作

# 先清除数库信息,再重新创建
flask shell
from app import db, app
from app impirt City, Citizen

db.drop_all()
db.create_all()

city1 = City(name = "大连")

citizen1 = Citizen(name = "wuhan")
citizen1 = Citizen(name = "wuyanzu")
# 提交到数据库session中
db.session.add(city1)
db.session.add(citizen1 )
db.session.add(citizen1 )
# 建立关系
citizen1.city_id = 1
# citizen2.city_id = 1   # 先不建,等会好对照

# 进行数据库的提交
db.session.commit()

citizen1.city
==> <City 'Dalian'>
citizen2.city
==>  # 啥也没有


一对一

一个国家只有一个首都

![[Pasted image 20211220084512.png]]

# 一对一 关系
class Country(db.Model):
    id = db.Column(db.Integer, primary_key = True)
    name = db.Column(db.String(70), unique = True)
    
    **capital = db.relationship("Capital", back_populates="country", uselist = False)**
    
    def __repr__(self):
        return "<Country %r>" % self.name

class Capital(db.Model):
    id = db.Column(db.Integer, primary_key = True)
    name = db.Column(db.String(70), unique = True)
    
   ** country_id = db.Column(db.Integer, db.ForeignKey("country.id"))
    country = db.relationship("Country", back_populates = "capital")**
    
    def __repr__(self):
        return "<Capital %r ==> %r>" % (self.name, self.country)
>>> from app import Country, Capital
>>> 
>>> 
>>> china = Country(name="China")
>>> beijing = Capital(name="Beijing") 
>>> china.capital = beijing
>>> 
>>> db.session.add(china) 
>>> db.session.add(beijing) 
>>> db.session.commit()
>>> 
>>> china.capital 
<Capital 'Beijing' ==> <Country 'China'>>
>>> beijing.country
<Country 'China'>
>>>
>>> china.capital = shanghai 
>>> 
>>> china.capital  
<Capital 'Shanghai' ==> <Country 'China'>>   # 会自动替换掉china的首都城市
>>>
>>> shanghai.country
<Country 'China'>  # shanghai会自动与Country绑定
>>> beijing.country  # 而北京的绑定会消失
>>> 

多对多

每个学生有多个老师,每个老师有多个学生

![[Pasted image 20211220084522.png]]

![[Pasted image 20211220084531.png]]

# 多对多 关系

associtaion_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.relatioship("Teacher", 
                            secondary = associtaion_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))
    students = db.relatioship("Student", 
                            secondary = associtaion_table,
                            back_populates = "teachers"
                            )

数据库更新表

定义数据库结构的方法修改过后,就需要重新加载表

db.drop_all()
db.create_all()
# 但这种方法有种弊端,就是会清除数据库里面的所有数据。

注册 flaks方法

@app.cli.command()
@click.option("--drop", is_flag=True, help="Create after drop.")
def initdb(drop):
  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.")

![[Pasted image 20211220084543.png]]


数据库迁移

  • 安装
pip install flask-migrate
  • 引入
from flask_migrate import Migrate
migrate = Migrate(app, db)

  • 创建迁移环境
flask db init
  • 生成迁移脚本
flask db migrate -m "test test"  # -m 是备注
PS D:\TypeScriptDemo> flask db migrate -m "test test"
INFO  [alembic.runtime.migration] Context impl SQLiteImpl.
INFO  [alembic.runtime.migration] Will assume non-transactional DDL.
INFO  [alembic.autogenerate.compare] Detected added column 'teacher.son'
Generating D:\TypeScriptDemo\migrations\versions\3c7dbe3f60d8_test_test.py ...  done
PS D:\TypeScriptDemo> 

![[Pasted image 20211220084553.png]]

  • 更新数据库
PS D:\TypeScriptDemo> flask db upgrade
INFO  [alembic.runtime.migration] Context impl SQLiteImpl.
INFO  [alembic.runtime.migration] Will assume non-transactional DDL.
INFO  [alembic.runtime.migration] Running upgrade  -> 3c7dbe3f60d8, test test

每次更新数据库后都要进行 操作 生成迁移脚本 → 更新数据库,不然的话会更新不成功。

  • 降级命令
flask db downgrade # 这个脚本会在version中生成的文件会自动生成   upgrade 和 downgrade脚本,直接执行就可以,就会删了对应的迁移脚本 

级联操作

操作一个对象的同时,对相关的对象也执行某些操作。

# 级联操作 : 操作一个对象的同时,对相关的对象也执行某些操作。
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", \
        **cascade = "save-update, merge, delete")**
    # save-update、 merge ; save-update、merge、delete; all; all、delete-orphan

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")
>>> from app import db, app
>>> from app import Post, Comment
>>>
>>> db.drop_all()
>>> db.create_all()
>>> 
>>> post1 = Post()
>>> comment1 = Comment()
>>> comment2 = Comment()
>>>
>>> db.session.add(post1)
>>> post1 in db.session
True
>>> comment1 in db.session
False
>>> comment2 in db.session
False
>>>
>>> post1.comments.append(comment1)
>>> post1.comments.append(comment2)
>>>
>>> comment1 in db.session
True
>>> comment2 in db.session
True
>>>

删除操作:关联的对象会在对象删除时一并被

>>> post2 = Post()
>>> comment3 = Comment()
>>> comment4 = Comment()
>>> post2.comments.append(comment3) 
>>> post2.comments.append(comment4) 
>>> 
>>> db.session.add(post2) 
>>> db.session.commit() 
>>> 
>>> 
>>> Post.query.all()
[<Post 1>, <Post 2>]
>>> Comment.query.all()
[<Comment 1>, <Comment 2>, <Comment 3>, <Comment 4>]
>>>
>>> post2 = Post.query.get(2) 
>>> post2
<Post 2>
>>> db.session.delete(post2) 
>>> db.session.commit()
>>> Post.query.all()
[<Post 1>]
>>> Comment.query.all()
[<Comment 1>, <Comment 2>]
>>>

delete-orphan :与delete级联一起使用,通常设置为 all、delete-orphan。在删除对象时,被关联的对象也将删除,并且如果解除关系时(称为孤立对象),被关联对象也会被删除。

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", \
        cascade = "all,delete-orphan")
    # save-update、 merge ; save-update、merge、delete; all; all、delete-orphan

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")

>>> post3 = Post()           
>>> comment5 = Comment()
>>> comment6 = Comment()
**>>> post3.comments.append(comment5)  # 绑定关系
>>> post3.comments.append(comment6) **
>>> db.session.add(post3) 
>>> db.session.commit()
>>> 
>>> Post.query.all()
[<Post 1>, <Post 2>, <Post 3>]
>>> Comment.query.all()
[<Comment 1>, <Comment 2>, <Comment 3>, <Comment 4>, <Comment 5>, <Comment 6>]
>>>
**>>> ****post3.comments.remove(comment5) ****  # 一处comment 与post的关系,此时如果提交的话,那么这条记录会立即删除
>>> ****post3.comments.remove(comment6) **
>>> db.session.commit()
>>> 
>>> Comment.query.all()
[<Comment 1>, <Comment 2>, <Comment 3>, <Comment 4>]
>>>

事件监听

@db.event.listens_for()

# 事件监听 listens_for()
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)
# 方式1
**@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
# 方式2
@db.event.listens_for(Draft.body, "set", named = True)
def increment_edit_time(**kwargs):
    if kwargs['target'].edit_time is not None:
      kwargs['target'].edit_time += 1

>>> 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"
>>> draft.edit_time
2
>>>