I、关联查询
在关联查询内,存在一对一、一对多、多对多等关系我们以一对多的关系作为一种引例俩再次观察一下backref
字段的内容,以下例子为例:
class Role(db.Model):
# 定义表名
__tablename__ = 'roles'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(64), unique=True)
user_id = db.Column(db.Integer, db.ForeignKey('users.id'))
def __repr__(self):
return 'Role:{}'.format(self.name)
class User(db.Model):
__tablename__ = 'users'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(64), unique=True)
email = db.Column(db.String(64), unique=True)
password = db.Column(db.String(64), unique=True)
ur = db.relationship('Role', backref='role')
def __repr__(self):
return 'User:{}'.format(self.name)
- 其中realtionship描述了Role和User的关系。在此文中,第一个参数为对应参照的类"User"
- 第二个参数backref为类User申明新属性的方法
- 第三个参数lazy决定了什么时候SQLALchemy从数据库中加载数据
- 如果设置为子查询方式(subquery),则会在加载完Role对象后,就立即加载与其关联的对象,这样会让总查询数量减少,但如果返回的条目数量很多,就会比较慢
* 设置为 subquery 的话,role.users 返回所有数据列表
* 另外,也可以设置为动态方式(dynamic),这样关联对象会在被使用的时候再进行加载,并且在返回前进行过滤,如果返回的对象数很多,或者未来会变得很多,那最好采用这种方式
* 设置为 dynamic 的话,role.users 返回查询对象,并没有做真正的查询,可以利用查询对象做其他逻辑,比如:先排序再返回结果
在Role类内需要强调的问题
1、Role的外键,db.ForeignKey('users.id')里面必须是('引用的表表名.引用表的主键')
在User类内需要注意的问题
1、ur
是关系的声明,User的实例对象可以通过ur
来查询其对应的有那些角色
2、'Role'
是该关系对应的模型类类名,名字是类名,不能随意起
3、backref='role'
是回调的引用,是自定义的名字,
4、User的实例对习惯可以通过'role'
来查询其对应的作者
II、一个具体的结合案例: 图书作者小案例
功能描述
- 可以添加书籍
如果作者存在,书籍存在,不能添加
如果作者存在,书籍不存在,可以添加
如果作者存在,可以添加- 删除书籍
- 删除作者,同时删除作者所有的书籍
- 使用wtf表单完成
分析
对于该项目,一定与数据库连接,因此必要的是数据表的创建
其需要添加和删除操作,根据提示用wtf表单完成因此需要有关csrf的内容
1、创建项目
首先创建一个基础app文件demo.py并导入常见配置和包并关联数据库
from flask_wtf import FlaskForm
from sqlalchemy import and_, or_, not_
from flask import Flask, render_template, flash
from flask_sqlalchemy import SQLAlchemy
import pymysql
from wtforms import *
from wtforms.validators import *
app = Flask(__name__)
host = 'localhost'
port = 3306
db_username = 'root'
db_password = '123456'
db = 'test12'
connect_str = 'mysql+pymysql://{}:{}@{}:{}/{}'.format(db_username, db_password, host, port, db)
# 设置数据库连接
app.config['SQLALCHEMY_DATABASE_URI'] = connect_str
# 设置每次请求结束后自动提交数据库的改动
app.config['SQLALCHEMY_COMMIT_ON_TEARDOWN'] = True
# 动态追踪设置
app.config['SQLALCHEMY_TRACK_MODUFICATIONS'] = True
# 显示原始sql
app.config['SQLALCHEMY_ECHO'] = False
# 数据库要和 app关联
db = SQLAlchemy(app)
app.secret_key = 'dasdasdsadas'
if __name__ == '__main__':
app.run(port=5006, debug=True)
2、根据设计作者和图书的属性
# 编写模型类
class Author(db.Model):
# 定义表名
__tablename__ = 'authors'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(64), unique=True)
# 指定关系属性和反向引用
books = db.relationship('Book', backref='author')
# 编写模型类
class Book(db.Model):
# 定义表名
__tablename__ = 'books'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(64), unique=True)
# 指定外键
author_id = db.Column(db.Integer, db.ForeignKey('authors.id')) # 或者Author.id
设计填充的数据,为了方便起见我们先在__main__
内添加
db.drop_all()
db.create_all()
au1 = Author(name='老王')
au2 = Author(name='老尹')
au3 = Author(name='老刘')
# 把数据提交给用户会话
db.session.add_all([au1, au2, au3])
# 提交会话
db.session.commit()
bk1 = Book(name='老王回忆录', author_id=au1.id)
bk2 = Book(name='我读书少,你别骗我', author_id=au1.id)
bk3 = Book(name='如何才能让自己更骚', author_id=au2.id)
bk4 = Book(name='怎样征服美丽少女', author_id=au3.id)
bk5 = Book(name='如何征服英俊少男', author_id=au3.id)
# 把数据提交给用户会话
db.session.add_all([bk1, bk2, bk3, bk4, bk5])
# 提交会话
db.session.commit()
3、WTF表单设计
然后我们需要思考如何应用wtf表单,这里添加的内容仅与书籍有关,添加操作可作为一个按钮出现,因此对于WTF的表单我们可以确定如下形式
# 自定表单, 继承 FlaskForm
class BookForm(FlaskForm):
authorName = StringField(label='作者', validators=[DataRequired("作者不能为空")])
bookName = StringField(label='书籍', validators=[DataRequired("书籍不能为空")])
此时确定了这些表单,我们即可即刻编写html页面,创建library.html,在其中添加表单
<form action="">
{{ bookForm.csrf_token }}
{{ bookForm.authorName.label }}{{ bookForm.authorName }}
{{ bookForm.bookName.label }}{{ bookForm.bookName }}
{{ bookForm.submit }}
{% for message in get_flashed_messages() %}
{{ message }}
{% endfor %}
</form>
4、视图函数(显示数据)
此时我们仅仅完成了一个添加框的显示,并没有显示出作者以及其相关的书籍,因此我们需要在前端进行编写一个视图函数用于显示作者和书籍。此时我们需要思考的是,在查询过程中,是作者和书籍全都需要查询,还是仅仅查询作者即可?
那么根据本章的第一个标题的内容可知,由于book和author的关联关系,通过外键绑定,我们只需查询作者即可通过反向引用的属性立即得到作者旗下的所有书籍数据。
那么根据以上的结论,我们编写一个视图函数shouw_page
用于在html内渲染数据
@app.route('/')
def show_page():
bookForm = BookForm()
# 查询所有作者
authors = Author.query.all()
return render_template('library.html', authors=authors, bookForm=bookForm)
并在library.html内,利用列表循环展示内容
<ul>
{% for author in authors %}
<li>作者:{{ author.name }} </li>
<ul>
{% for book in author.books %}
<li>书籍: {{ book.name }}</li>
{% endfor %}
</ul>
{% endfor %}
</ul>
5、删除操作
此时我们发现,所有展示的大体结构已经完成,而对于添加和删除的操作我们并没有任何的设计,因此接下来我们先思考删除作者和删除书籍的操作
删除作者的同时需要书籍全部消失,而书籍则不需要考虑这些,且如果需要删除数据,需要前端返还一个用于查询所需删除内容的一个关键数据例如:名字或者id,因此逻辑如下:(我们通过让前端返还id来进行删除)
@app.route('/delete_author/<int:author_id>')
def delete_author(author_id):
author = Author.query.get(author_id)
for book in author.books:
db.session.delete(book)
db.session.delete(author)
db.session.commit()
return redirect(url_for('show_page'))
@app.route('/delete_book/<int:book_id>')
def delete_book(book_id):
book = Book.query.get(book_id)
db.session.delete(book)
db.session.commit()
return redirect(url_for('show_page'))
然后需要在前端添加进行一步调用这两个函数的接口,我们用<a>
标签来实现即修改<li>
的内容,由于这里采用了链接的方式调用函数,属于跳转而并非发送或接收了请求,因此视图函路由内的method=[]
数和<a method="">
是不需要的
<li>作者:{{ author.name }} <a href="{{ url_for('delete_author',author_id=author.id ) }}">删除</a></li>
<li>书籍: {{ book.name }}<a href="{{ url_for('delete_book',book_id=book.id ) }}">删除</a></li>
6、增添操作
对于增添操作我们发现两个问题,一种是添加作者已存在的书籍,另一种数增加作者不存在的书籍。
在添加作者已存在的书籍时,我们只需要添加书名以及其关联的作者id即可;而在添加作者不存在的书籍时,需要先添加作者,再添按照作者已存在的情形添加书籍即可。
而在添加操作中,我们需要应用先前所定义的WTF表单,这里将采用一种新的方式获取表单内容(WTF表单在先前的内容内有涉及,这里不做赘述)
@app.route('/add_book', methods=['POST'])
def add_book():
# 1、 创建表单
bookForm = BookForm()
# 验证参数功能
if bookForm.validate_on_submit():
# 获取参数
author_name = bookForm.authorName.data
book_name = bookForm.bookName.data
# 通过作者名称,查询作者对象
author = Author.query.filter(Author.name == author_name).first()
# 如果作者存在,书籍存在,不能添加
# 如果作者存在,书籍不存在,可以添加
# 如果作者存在,可以添加
# 进行判断
if author:
# # 通过书籍名称,查询书籍对象
book = Book.query.filter(Book.name == book_name, Book.author_id == author.id).first()
if book:
flash("该作者有该书籍")
else:
# 将书籍添加到该作者里面去
# 创建book对象
book = Book(name=book_name, author_id=author.id)
db.session.add(book)
db.session.commit()
else:
# 没作者,创建一个作者
author = Author(name=author_name)
db.session.add(author)
db.session.commit()
# 同时添加书籍
book = Book(name=book_name, author_id=author.id)
db.session.add(book)
db.session.commit()
# 重定向到原来的页面
return redirect(url_for('show_page'))
然后我们修改<form action="" method="">
以能调用上述的视图函数add_book()
,由于表单的提交为一个请求,因此不仅要添加csrf_token
,表单的action=""
,也要添加发送请求的方法:视图函数内method=[]
、表单内method=""
<form action="/add_book" method="post">
通过该例子我们熟悉了一下查询数据库操作,复习了WTF表单以及如何分析需求,至此对于Flask的基础内容就大致这么多,还有值得注意的是,我们要通过学会与Django的对比以及查看源代码去了解更多操作和内容,Flask作为一个自由度很高的微框架,还有许多内容值得我们探讨,但是作为基础篇,至此以及足以支撑flask的基础操作。