可能需要引用得库:
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)]
↑:
- 这种报错信息 可以先进行
db.drop_all()再db.create_all()进行重置数据库。 - 或者可以进行
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
>>>