3. 数据库操作 - session储存设置 - 蓝图
数据库操作
ORM
ORM 全拼Object-Relation Mapping
,中文意为 对象-关系映射。主要实现模型对象到关系数据库数据的映射
优点 :
- 只需要面向对象编程, 不需要面向数据库编写代码.
- 对数据库的操作都转化成对类属性和方法的操作.
- 不用编写各种数据库的
sql语句
.
- 实现了数据模型与数据库的解耦, 屏蔽了不同数据库操作上的差异.
- 不再需要关注当前项目使用的是哪种数据库。
- 通过简单的配置就可以轻松更换数据库, 而不需要修改代码.
缺点 :
- 根据对象的操作转换成SQL语句,根据查询的结果转化成对象, 在映射过程中有性能损失.
Flask-SQLAlchemy
flask默认提供模型操作,但是并没有提供ORM,所以一般开发的时候我们会采用flask-SQLAlchemy模块来实现ORM操作。
SQLAlchemy是一个关系型数据库框架,它提供了高层的 ORM 和底层的原生数据库的操作。flask-sqlalchemy 是一个简化了 SQLAlchemy 操作的flask扩展。
安装flask-sqlalchemy [清华园]
pip install flask-sqlalchemy -i https://pypi.tuna.tsinghua.edu.cn/simple
如果连接的是 mysql 数据库,需要安装 flask-mysqldb扩展驱动
pip install flask-mysqldb -i https://pypi.tuna.tsinghua.edu.cn/simple
安装flask-mysqldb时,注意
安装 flask-mysqldb的时候,python底层依赖于一个底层的模块 mysql-client模块
如果没有这个模块,则会报错如下:
Command "python setup.py egg_info" failed with error code 1 in /tmp/pip-install-21hysnd4/mysqlclient/
解决方案:
sudo apt-get install libmysqlclient-dev python3-dev
运行上面的安装命令如果再次报错如下:
dpkg 被中断,您必须手工运行 ‘sudo dpkg --configure -a’ 解决此问题。
则根据提示执行命令以下命令,再次安装mysqlclient
sudo dpkg --configure -a
apt-get install libmysqlclient-dev python3-dev
解决了mysqlclient问题以后,重新安装 flask-mysqldb即可。
pip install flask-mysqldb -i https://pypi.tuna.tsinghua.edu.cn/simple
数据库连接
数据库连接配置
# 加载配置项
class Config(object):
'''设置秘钥'''
SECRET_KEY = 'jyh123'
# 数据库链接配置 = 数据库名称://登录账号:登录密码@数据库主机IP:数据库访问端口/数据库名称?charset=编码类型
SQLALCHEMY_DATABASE_URI = 'mysql://root:123@127.0.0.1:3306/students?charset=utf8mb4'
# 动态追踪修改设置,如未设置只会提示警告
SQLALCHEMY_TRACK_MODIFICATIONS = True
# 查询时会显示原始SQL语句
SQLALCHEMY_ECHO = True
app.config.from_object(Config)
配置完成需要去 MySQL 中创建项目所使用的数据库
$ mysql -uroot -p123
mysql > create database students charset=utf8mb4;
常用的SQLAlchemy字段类型
模型字段类型名 | python中数据类型 | 说明 |
---|---|---|
Integer | int | 普通整数,一般是32位 |
SmallInteger | int | 取值范围小的整数,一般是16位 |
BigInteger | int或long | 不限制精度的整数 |
Float | float | 浮点数 |
Numeric | decimal.Decimal | 普通数值,一般是32位 |
String | str | 变长字符串 |
Text | str | 变长字符串,对较长或不限长度的字符串做了优化 |
Unicode | unicode | 变长Unicode字符串 |
UnicodeText | unicode | 变长Unicode字符串,对较长或不限长度的字符串做了优化 |
Boolean | bool | 布尔值 |
Date | datetime.date | 日期 |
Time | datetime.datetime | 日期和时间 |
LargeBinary | str | 二进制文件内容 |
常用的SQLAlchemy列约束选项
选项名 | 说明 |
---|---|
primary_key | 如果为True,代表表的主键 |
unique | 如果为True,为这列创建唯一索引, 代表这列不允许出现重复的值 |
index | 如果为True,为这列创建普通索引,提高查询效率 |
nullable | 如果为True,允许有空值,如果为False,不允许有空值 |
default | 为这列定义默认值 |
视图: manage.py
from flask import Flask
from flask_script import Manager
from flask_sqlalchemy import SQLAlchemy
# 初始化
app = Flask(__name__,template_folder='templates')
"""使用脚手架[终端脚本]启动项目"""
manage = Manager(app)
# 初始化SQLAlchemy
db = SQLAlchemy() # 初始化数据库操作对象
db.init_app(app) # 初始化数据库链接
# 等同于
# db = SQLAlchemy(app=app)
# 加载配置项
class Config(object):
'''设置秘钥'''
SECRET_KEY = 'jyh123'
# 数据库链接配置 = 数据库名称://登录账号:登录密码@数据库主机IP:数据库访问端口/数据库名称?charset=编码类型
SQLALCHEMY_DATABASE_URI = 'mysql://root:123@127.0.0.1:3306/students?charset=utf8mb4'
# 动态追踪修改设置,如未设置只会提示警告
SQLALCHEMY_TRACK_MODIFICATIONS = True
# 查询时会显示原始SQL语句
SQLALCHEMY_ECHO = True
app.config.from_object(Config)
# 定义模型类
class Student(db.Model):
# 定义表名
__tablename__ = 'student'
# 声明字段
id = db.Column(db.Integer,primary_key=True,comment='主键id')
name = db.Column(db.String(64),index=True,comment='姓名')
sex = db.Column(db.Boolean,default=0,comment='性别')
age = db.Column(db.SmallInteger,nullable=True,comment='年龄')
email = db.Column(db.String(128), unique=True, comment="邮箱地址")
money = db.Column(db.Numeric(8, 2), default=0, comment="钱包")
# __repr__()方法类似于django的__str__,用于打印模型对象时显示的字符串信息
def __repr__(self):
return self.name
class Teacher(db.Model):
__tablename__='teacher'
id = db.Column(db.Integer,primary_key=True)
name = db.Column(db.String(64),unique=True)
option = db.Column(db.Enum('讲师','助教','班主任'),default='讲师')
def __repr__(self):
return self.name
class Course(db.Model):
__tablename__ = 'course'
id = db.Column(db.Integer,primary_key=True,comment='主键')
name = db.Column(db.String(66),unique=True,comment='课程')
price = db.Column(db.Numeric(6,2), default=2, comment='价格')
def __repr__(self):
return self.name
@app.route("/")
def index():
return 'ok!'
# 创建所有表,没有就创建,有就不创建了
@app.route('/create_table')
def create_table():
db.create_all()
return '创建表'
# 删除连接库下所有表
@app.route('/del_table')
def del_table():
db.drop_all()
return '删除表'
if __name__ == '__main__':
with app.app_context():
# 创建所有表
db.create_all()
manage.run()
数据基本操作
- 在Flask-SQLAlchemy中,添加、修改、删除操作,均由数据库会话管理。
- 会话用 db.session 表示。在准备把数据写入数据库前,要先将数据添加到会话中然后调用 db.commit() 方法提交会话。
- 在 Flask-SQLAlchemy 中,查询操作是通过 query 对象操作数据。
- 最基本的查询是返回表中所有数据,可以通过过滤器进行更精确的数据库查询。
1.添加、修改(更新)、删除操作
@app.route('/add')
def add():
st = Student(name='小米',sex=1,age=11,email='1234567@qq.com',money=123)
db.session.add(st)
db.session.commit()
return '添加一条数据'
@app.route('/add_all')
def add_all():
st1 = Student(name='wang', email='wang@163.com', age=22)
st2 = Student(name='zhang', email='zhang@189.com', age=22)
st3 = Student(name='chen', email='chen@126.com', age=22)
st4 = Student(name='zhou', email='zhou@163.com', age=22)
st5 = Student(name='tang', email='tang@163.com', age=22)
st6 = Student(name='wu', email='wu@gmail.com', age=22)
st7 = Student(name='qian', email='qian@gmail.com', age=22)
st8 = Student(name='liu', email='liu@163.com', age=22)
st9 = Student(name='li', email='li@163.com', age=22)
st10 = Student(name='sun', email='sun@163.com', age=22)
db.session.add_all([st1, st2, st3, st4, st5, st6, st7, st8, st9, st10])
db.session.commit()
return '添加多条数据'
@app.route('/update')
def update():
'''方法一:修改第一条数据'''
st = Student.query.first()
st.name = 'hehe'
db.session.commit()
'''方法二:修改指定数据'''
res = Student.query.filter(Student.name=='zhang').update({'money':111})
db.session.commit()
'''方法二:修改指定数据,运算'''
res = Student.query.filter(Student.name=='zhang').update({'money':Student.money + 111})
db.session.commit()
return '更新数据'
@app.route('/delete')
def delete():
'''方法一:删除第一条数据,先查询再删除'''
st = Student.query.first()
db.session.delete(st)
db.session.commit()
'''方法二:删除指定数据'''
res = Student.query.filter(Student.id==2).delete()
db.session.commit()
return '删除一条数据'
2.查询
常用的SQLAlchemy查询过滤器
过滤器 | 说明 |
---|---|
filter() | 把过滤器添加到原查询上,返回一个新查询 |
filter_by() | 把等值过滤器添加到原查询上,返回一个新查询 |
limit() | 使用指定的值限定原查询返回的结果 |
offset() | 偏移原查询返回的结果,返回一个新查询 |
order_by() | 根据指定条件对原查询结果进行排序,返回一个新查询 |
group_by() | 根据指定条件对原查询结果进行分组,返回一个新查询 |
常用的SQLAlchemy查询结果的方法
方法 | 说明 |
---|---|
all() | 以列表形式返回查询的所有结果 |
first() | 返回查询的第一个结果,如果未查到,返回None |
first_or_404() | 返回查询的第一个结果,如果未查到,返回404 |
get() | 返回指定主键对应的行,如不存在,返回None |
get_or_404() | 返回指定主键对应的行,如不存在,返回404 |
count() | 返回查询结果的数量 |
paginate() | 返回一个Paginate分页器对象,它包含指定范围内的结果 |
having | 返回结果中符合条件的数据,必须跟在group by后面,其他地方无法使用。 |
简单查询
@app.route('/select')
def select():
'''get()参数是数字,根据主键查询数据'''
st = Student.query.get(5)
print(st)
'''all()返回查询到的所有对象列表'''
st_list = Student.query.all()
print(st_list)
'''first()返回查询到的第一个对象'''
st = Student.query.first()
print(st)
return '查询数据'
过滤查询 - filter和filter_by
@app.route('/select')
def select():
'''filter()模糊查询 名字中以g开头,结尾,包含'''
ret = Student.query.filter(Student.name.startswith("g")).all()
ret = Student.query.filter(Student.name.endswith("g")).all()
ret = Student.query.filter(Student.name.contains("g")).all()
print(ret)
'''
也可以使用filter进行精确查找,
则需要指定条件格式为: 模型.字段 比较运算符 值。
运算符可以是: ==表示相等,!=不相等,> 表示大于 < 表示小于,>=大于等于,<=小于等于
'''
ret = Student.query.filter(Student.age == 22).all()
print(ret)
'''filter_by精准查询,只支持字段的值是否相等这种条件'''
res = Student.query.filter_by(name='wang').all()
print(res)
return '查询数据'
多条件查询
from sqlalchemy import not_,and_,or_
'''
逻辑与 : and_
逻辑或 : or_
逻辑非 : not_
'''
@app.route('/select')
def select():
'''1.逻辑与 - 查询姓名不为wang,邮箱以126.com结尾的数据'''
res = Student.query.filter(and_(Student.name!='wang',Student.email.endswith('126.com'))).all()
print('and_',res)
'''2.逻辑或'''
res = Student.query.filter(or_(Student.name!='wang',Student.email.endswith('126.com'))).all()
print('or_',res)
'''3.逻辑非 - 查询姓名不为wang的数据'''
res = Student.query.filter(not_(Student.name=='wang')).all()
print('not_',res)
'''4.in_范围查询 - 查询id在1,3,5的学生'''
res = Student.query.filter(Student.id.in_([1,3,5])).all()
print('in_>>',res)
'''in_范围查询 - 查询id不在1,3,5的学生'''
res = Student.query.filter(not_(Student.id.in_([1,3,5]))).all()
print('in_>>',res)
'''5.order_by排序'''
# 查询所有学生,并按年龄进行倒序排列,年龄相同,则按id进行降序排序.
res = Student.query.order_by(Student.age.desc(), Student.id.desc()).all()
print('order_by>>>',res)
'''6.count统计数量'''
# 查询age>=19的男生的数量
# res = Student.query.filter( and_(Student.age>=19,Student.sex==True) ).count()
res = Student.query.filter(Student.age >= 19, Student.sex == True).count()
print('count>>>',res)
'''7.limit数量限制'''
# 查询年龄最大的3个学生
res = Student.query.order_by(Student.age.desc()).limit(3).all()
print('limit>>>',res)
'''8.offset偏移量 - 从哪开始,默认从0开始'''
# 查询年龄排第4到第7名的学生
res = Student.query.order_by(Student.age.desc()).offset(3).limit(4).all()
print('offset>>>', res)
return '查询数据'
分组查询
一般分组都会结合聚合函数来一起使用。SQLAlchemy中所有的聚合函数都在func
模块中声明的。
from sqlalchemy import func
函数名 | 说明 | |
---|---|---|
func.count | 统计总数 | |
func.avg | 平均值 | |
func.min | 最小值 | |
func.max | 最大值 | |
func.sum | 和 |
# 语法
db.session.query(func.聚合函数(实体类.属性),).all()
练习:
from sqlalchemy import func
@app.route('/select')
def select():
# 查询当前所有男生女生的数量
res = db.session.query(Student.sex,func.count(Student.id)).group_by(Student.sex).all()
print(res)
# 查询男生和女生中,年龄最小的是几岁?
ret = db.session.query(Student.sex, func.min(Student.age)).group_by(Student.sex).all()
print(ret)
return '查询数据'
支持原生SQL语句
@app.route('/select')
def select():
'''原生SQL语句查询'''
# 读取多条数据
res = db.session.execute("select * from student").fetchall()
# 读取一条数据
res = db.session.execute("select * from student").fetchone()
print(res)
# 添加
db.session.execute('insert into student (name,age,email) values("jyh",25,"3543@qq.com") ')
db.session.commit() # 别忘了提交
# 修改
db.session.execute('update student set name="jia" where name="jyh"')
db.session.commit()
# 删除
db.session.execute('delete from student where name="jia"')
db.session.commit()
return '查询数据'
分页器
基本使用
@app.route('/')
def index():
# 分页器的使用
page = int(request.args.get('page', 1)) # 获取路径参数page,设置默认为1
per_page = int(request.args.get('per_page', 3)) ## 获取路径参数per_page,设置默认为3,每页显示条数
"""获取分页器对象"""
paginage = Student.query.paginate(page=page,per_page=per_page)
print(paginage) # 当前页的分页器对象
print(paginage.page) # 获取当前页码 - 1
print(paginage.pages) # 获取最后一页页码 - 33
item_list = paginage.items
print(item_list) # 获取当前页的数据对象列表 - [程星云<student>, 陈峰<student>, 苏礼就<student>]
print(paginage.has_prev) # 是否有上一页 - False
print(paginage.prev_num) # 上一页页码 - None
print(paginage.prev) # 上一页的分页器对象
print(paginage.has_next) # 是否有下一页 - True
print(paginage.next_num) # 下一页页码 - 2
print(paginage.next) # 下一页的分页器对象
return 'ok'
案例: 模板渲染
视图 : manage.py
from flask import Flask,request,redirect,render_template,url_for
from flask_script import Manager
from flask_sqlalchemy import SQLAlchemy
# 初始化
app = Flask(__name__,template_folder='templates')
"""使用脚手架[终端脚本]启动项目"""
manage = Manager(app)
# 初始化SQLAlchemy
# db = SQLAlchemy() # 初始化数据库操作对象
# db.init_app(app) # 初始化数据库链接
db = SQLAlchemy(app=app)
# 加载配置项
class Config(object):
'''设置秘钥'''
SECRET_KEY = 'jyh123'
# 数据库链接配置 = 数据库名称://登录账号:登录密码@数据库主机IP:数据库访问端口/数据库名称?charset=编码类型
SQLALCHEMY_DATABASE_URI = 'mysql://root:123@127.0.0.1:3306/db101?charset=utf8mb4'
# 动态追踪修改设置,如未设置只会提示警告
SQLALCHEMY_TRACK_MODIFICATIONS = True
# 查询时会显示原始SQL语句
SQLALCHEMY_ECHO = True
app.config.from_object(Config)
# 定义模型类
class Student(db.Model):
# 定义表名
__tablename__ = 'db_student'
# 声明字段
id = db.Column(db.Integer,primary_key=True,comment='主键id')
name = db.Column(db.String(64),index=True,comment='姓名')
classa = db.Column(db.String(25),comment='班级')
sex = db.Column(db.Enum("男","女"),comment='性别')
age = db.Column(db.SmallInteger,nullable=True,comment='年龄')
description = db.Column(db.Text(655), comment="个人简介")
# __repr__()方法类似于django的__str__,用于打印模型对象时显示的字符串信息
def __repr__(self):
return f"{self.name}<student>"
# 学生列表
@app.route('/student')
def student():
# stu_list = Student.query.order_by(Student.id.asc()).all()
# data = {}
# data['stu_list'] = stu_list
data = {}
page = int(request.args.get('page',1)) # 获取路径参数page,设置默认为1
per_page = int(request.args.get('per_page',10)) ## 获取路径参数per_page,设置默认为10,每页显示条数
# 按id升序排列
paginate = Student.query.order_by(Student.id.asc()).paginate(page=page,per_page=per_page)
data['paginate'] = paginate
#
return render_template('student.html',**data)
if __name__ == '__main__':
"""创建数据表"""
with app.app_context():
db.create_all()
"""运行项目"""
manage.run()
模板 : templates/student.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title></title>
<style>
a{text-decoration:none; color:blue; } #page{margin: 10px auto; text-align: center; }
</style>
</head>
<body>
<h1 align="center">学生管理</h1>
<table align="center" border="1" width="600" cellspacing="0">
<tr>
<td colspan="4"></td>
<td bgcolor="orange" align="center"><a href="/student/add">添加学生</a></td>
<td bgcolor="orange" align="center"><a href="/course">课程列表</a></td>
</tr>
<tr bgcolor="orange" align="center">
<td><a href="">学号👆<!-- 👇 --></a></td>
<td>姓名</td>
<td>年龄</td>
<td>性别</td>
<td>班级</td>
<td>编辑/删除</td>
</tr>
<!--模板渲染-->
{% for stu in paginate.items %}
<tr>
<td>{{ stu.id }}</td>
<td>{{ stu.name }}</td>
<td>{{ stu.age }}</td>
<td>{{ stu.sex }}</td>
<td>{{ stu.classa }}</td>
<td><a href="/student/edit/{{ stu.id }}">编辑</a>/<a href="/student/delete/{{ stu.id }}">删除</a></td>
</tr>
{% endfor %}
</table>
<!--分页器的使用-->
<div id="page">
<a href="?page=1">首页</a>
{% if paginate.has_prev %} <!--判断是否有上一页-->
<a href="?page={{ paginate.prev_num }}">上一页</a>
{% endif %}
{% if paginate.has_next %} <!--判断是否有下一页-->
<a href="?page={{ paginate.next_num }}">下一页</a>
{% endif %}
<a href="?page={{ paginate.pages }}">尾页</a>
</div>
</body>
</html>
联表查询
常用的SQLAlchemy关系选项
选项名 | 说明 |
---|---|
backref | 在关系的另一模型中添加反向引用,用于设置外键名称,在1查多的 |
primary join | 明确指定两个模型之间使用的连表条件,用于1对1 或者1对多连表中 |
lazy | 指定如何加载关联模型数据的方式。参数值: select(立即加载,查询所有相关数据显示,相当于lazy=True) subquery(立即加载,但使用子查询) dynamic(不立即加载,但提供加载记录的查询对象,只有查询指定类型数据才加载) |
uselist | 如果为False,不使用列表,而使用标量值。 一对一关系中,需要设置relationship中的uselist=Flase,其他数据库操作一样。 |
secondary | 指定多对多关系中关系表的名字。 多对多关系中,需建立关系表,设置 secondary=关系表 |
secondary join | 在SQLAlchemy中无法自行决定时,指定多对多关系中的二级连表条件 |
一对一
声明关联属性 - relationship - uselist=False
1. 关联属性relationship在主模型中声明[最常用]
# 定义学生模型
class Student(db.Model):
"""学生表"""
__tablename__ = 'd_student'
id = db.Column(db.Integer, primary_key=True, comment='学生id')
name = db.Column(db.String(25), nullable=False, index=True, comment='姓名')
age = db.Column(db.SmallInteger, nullable=False, comment='年龄')
sex = db.Column(db.Enum('男','女'), default='男', comment='性别')
"""
在主模型中声明关联属性:relationship
设置关联属性 - 并不会在数据表中创建字段,仅仅是为了关联查询方便
info : 可以代表与当前数据对应的外键模型对象
backref='student' :student是关联表反向引用主模型对象
"""
info = db.relationship('StudentInfo', uselist=False, backref='student')
# 学生信息附加表
class StudentInfo(db.Model):
"""学生信息附加表"""
__tablename__ = 'd_studentinfo'
id = db.Column(db.Integer, primary_key=True, comment='主键')
address = db.Column(db.String(255),nullable=True, comment='地址')
# 设置物理外键【会在数据表中创建字段】 - 与学生表关联
stu_id = db.Column(db.Integer, db.ForeignKey(Student.id), comment='学生id')
# stu_id = db.Column(db.Integer, db.ForeignKey('d_student.id'), comment='学生id')
2. 关联属性relationship在外键模型中声明
# 外键关联属性需要引用backref
from sqlalchemy.orm import backref
# 定义学生模型
class Student(db.Model):
"""学生表"""
__tablename__ = 'd_student'
id = db.Column(db.Integer, primary_key=True, comment='学生id')
name = db.Column(db.String(25), nullable=False, index=True, comment='姓名')
age = db.Column(db.SmallInteger, nullable=False, comment='年龄')
sex = db.Column(db.Enum('男','女'), default='男', comment='性别')
# 学生信息附加表
class StudentInfo(db.Model):
"""学生信息附加表"""
__tablename__ = 'd_studentinfo'
id = db.Column(db.Integer, primary_key=True, comment='主键')
address = db.Column(db.String(255),nullable=True, comment='地址')
# 设置物理外键【会在数据表中创建字段】 - 与学生表关联
stu_id = db.Column(db.Integer, db.ForeignKey(Student.id), comment='学生id')
# stu_id = db.Column(db.Integer, db.ForeignKey('d_student.id'), comment='学生id')
"""
在外键模型中声明关联属性:relationship
设置关联属性 - 并不会在数据表中创建字段,仅仅是为了关联查询方便
info : 是关联表反向引用主模型对象
student : 代表与当前数据对应
"""
student = db.relationship('Student', uselist=False, backref=backref('info',uselist=False))
数据查询/修改/添加
manage.py
from flask import Flask
from flask_script import Manager
from flask_sqlalchemy import SQLAlchemy
# 实例化flask对象
app = Flask(__name__)
# 终端启动脚手架
manage = Manager(app)
# 实例化数据库对象
db = SQLAlchemy(app=app)
# 声明加载配置项
class Config(object):
"""设置secret_key,随机值"""
SECRET_KEY = 'jyh123'
"""连接数据库"""
SQLALCHEMY_DATABASE_URI = 'mysql://root:123@127.0.0.1:3306/db102?charset=utf8mb4'
# 动态追踪修改设置,如未设置只会提示警告
SQLALCHEMY_TRACK_MODIFICATIONS = True
# 查询时会显示原始SQL语句
SQLALCHEMY_ECHO = True
app.config.from_object(Config)
# 定义学生模型
class Student(db.Model):
"""学生表"""
__tablename__ = 'd_student'
id = db.Column(db.Integer, primary_key=True, comment='学生id')
name = db.Column(db.String(25), nullable=False, index=True, comment='姓名')
age = db.Column(db.SmallInteger, nullable=False, comment='年龄')
sex = db.Column(db.Enum('男','女'), default='男', comment='性别')
"""
一对一:
在主模型中声明关联属性:relationship - uselist=False 不允许返回列表
设置关联属性 - 并不会在数据表中创建字段,仅仅是为了关联查询方便
info : 可以代表与当前数据对应的外键模型对象
backref='student' :student是关联表反向引用主模型对象
"""
info = db.relationship('StudentInfo', uselist=False, backref='student')
# 学生信息附加表
class StudentInfo(db.Model):
"""学生信息附加表"""
__tablename__ = 'd_studentinfo'
id = db.Column(db.Integer, primary_key=True, comment='主键')
address = db.Column(db.String(255),nullable=True, comment='地址')
# 设置物理外键【会在数据表中创建字段】 - 与学生表关联
'''外键可以使用类名,也可以使用表名关联字段'''
stu_id = db.Column(db.Integer, db.ForeignKey(Student.id), comment='学生id')
# stu_id = db.Column(db.Integer, db.ForeignKey('d_student.id'), comment='学生id')
@app.route('/')
def index():
return 'ok'
# 添加数据
@app.route('/add')
def add():
# 1.添加主表信息时通过关联属性info同步添加附表信息 - 关联物理外键自动添加
stu = Student(
name = 'jyh',
age = 16,
sex = '男',
info = StudentInfo(address = '盛新家园101')
)
db.session.add(stu)
db.session.commit()
# 2.添加附加表信息,通过关联属性backref的student反向引用,同步添加主表信息
stu_info = StudentInfo(
address = '工华家园101',
student = Student(
name='xiaoming',
age=24,
sex='女',
)
)
db.session.add(stu_info)
db.session.commit()
return '添加数据'
# 查询数据
@app.route('/select')
def select():
# 1.正向查询 ---> 主模型查询外键模型 - 通过关联属性info
stu = Student.query.filter(Student.id == 1).first()
print(stu.info) # 获取到附加表对象
print(stu.info.address) # 获取到该数据的地址
# 2.反向查询 ---> 外键模型查询主模型 - 通过关联属性student反向引用
stu_info = StudentInfo.query.get(2)
print(stu_info.student) # <Student 2> 主模型对象
print(stu_info.student.name) # xiaoming
return '查询数据'
# 修改更新数据
@app.route('/update')
def update():
# 1.通过主表使用关联属性可以修改附加表的数据
stu = Student.query.get(1)
stu.info.address = '你好111'
db.session.commit()
# 2.通过附加表使用关联属性修改主表的数据
stu_info = StudentInfo.query.get(2)
stu_info.student.name = '熊大'
stu_info.student.age = 33
db.session.commit()
return '修改数据'
if __name__ == '__main__':
with app.app_context():
db.create_all()
manage.run()
运行
python manage.py runserver -d
一对多
声明关联属性 - relationship - uselist=True
1. 关联属性relationship在主模型中声明[最常用]
# 定义学生模型
class Student(db.Model):
"""学生表"""
__tablename__ = 'd_student'
...
# 声明关联属性 - lazy='dynamic' 懒加载
course_list = db.relationship('Course', uselist=True, backref='student', lazy='dynamic')
# 课程模型
class Course(db.Model):
"""学生选课附加表"""
__tablename__ = 'd_course'
id = db.Column(db.Integer, primary_key=True, comment='主键')
cname = db.Column(db.String(255), nullable=True, comment='课程名称')
# 设置物理外键【会在数据表中创建字段】
# stu_id = db.Column(db.Integer, db.ForeignKey(Student.id), comment='学生id')
stu_id = db.Column(db.Integer, db.ForeignKey('d_student.id'), comment='学生id')
def __repr__(self):
return f'{self.cname}<course>'
2. 关联属性relationship在外键模型中声明
# 外键关联属性需要引用backref
from sqlalchemy.orm import backref
# 定义学生模型
class Student(db.Model):
"""学生表"""
__tablename__ = 'd_student'
id = db.Column(db.Integer, primary_key=True, comment='学生id')
name = db.Column(db.String(25), nullable=False, index=True, comment='姓名')
age = db.Column(db.SmallInteger, nullable=False, comment='年龄')
sex = db.Column(db.Enum('男','女'), default='男', comment='性别')
# 课程模型
class Course(db.Model):
"""学生选课附加表"""
__tablename__ = 'd_course'
id = db.Column(db.Integer, primary_key=True, comment='主键')
cname = db.Column(db.String(255), nullable=True, comment='课程名称')
# 设置物理外键【会在数据表中创建字段】
# stu_id = db.Column(db.Integer, db.ForeignKey(Student.id), comment='学生id')
stu_id = db.Column(db.Integer, db.ForeignKey('d_student.id'), comment='学生id') stu_id = db.Column(db.Integer, db.ForeignKey('d_student.id'), comment='学生id')
"""
在外键模型中声明关联属性:relationship - uselist=True
设置关联属性 - 并不会在数据表中创建字段,仅仅是为了关联查询方便
course_list : 是关联表反向引用主模型对象
student : 代表与当前数据对应
"""
student = db.relationship('Student', uselist=False, backref=backref('course_list',uselist=True))
数据查询/修改/添加
manage.py
from flask import Flask
from flask_script import Manager
from flask_sqlalchemy import SQLAlchemy
# 实例化flask对象
app = Flask(__name__)
# 终端启动脚手架
manage = Manager(app)
# 实例化数据库对象
db = SQLAlchemy(app=app)
# 声明加载配置项
class Config(object):
"""设置secret_key,随机值"""
SECRET_KEY = 'jyh123'
"""连接数据库"""
SQLALCHEMY_DATABASE_URI = 'mysql://root:123@127.0.0.1:3306/db102?charset=utf8mb4'
# 动态追踪修改设置,如未设置只会提示警告
SQLALCHEMY_TRACK_MODIFICATIONS = True
# 查询时会显示原始SQL语句
SQLALCHEMY_ECHO = True
app.config.from_object(Config)
# 定义学生模型
class Student(db.Model):
"""学生表"""
__tablename__ = 'd_student'
id = db.Column(db.Integer, primary_key=True, comment='学生id')
name = db.Column(db.String(25), nullable=False, index=True, comment='姓名')
age = db.Column(db.SmallInteger, nullable=False, comment='年龄')
sex = db.Column(db.Enum('男','女'), default='男', comment='性别')
"""
一对一:
在主模型中声明关联属性:relationship - uselist=False 不允许返回列表
设置关联属性 - 并不会在数据表中创建字段,仅仅是为了关联查询方便
info : 可以代表与当前数据对应的外键模型对象
backref='student' :student是关联表反向引用主模型对象
"""
info = db.relationship('StudentInfo', uselist=False, backref='student')
"""
一对多:
在主模型中声明关联属性:relationship - uselist=True 可以返回列表
设置关联属性 - 并不会在数据表中创建字段,仅仅是为了关联查询方便
course_list : 可以代表与当前数据对应的外键模型对象
backref='student' :student是关联表反向引用主模型对象
"""
course_list = db.relationship('Course', uselist=True, backref='student', lazy='dynamic')
def __repr__(self):
return f'{self.name}<student>'
# 学生信息附加表
class StudentInfo(db.Model):
"""学生信息附加表"""
__tablename__ = 'd_studentinfo'
id = db.Column(db.Integer, primary_key=True, comment='主键')
address = db.Column(db.String(255),nullable=True, comment='地址')
# 设置物理外键【会在数据表中创建字段】 - 与学生表关联
stu_id = db.Column(db.Integer, db.ForeignKey(Student.id), comment='学生id')
# stu_id = db.Column(db.Integer, db.ForeignKey('d_student.id'), comment='学生id')
# 课程模型
class Course(db.Model):
"""学生选课附加表"""
__tablename__ = 'd_course'
id = db.Column(db.Integer, primary_key=True, comment='主键')
cname = db.Column(db.String(255), nullable=True, comment='课程名称')
# 设置物理外键【会在数据表中创建字段】
stu_id = db.Column(db.Integer, db.ForeignKey('d_student.id'), comment='学生id')
def __repr__(self):
return f'{self.cname}<course>'
@app.route('/')
def index():
return 'ok'
# 添加数据
@app.route('/add')
def add():
# 1.添加主表信息时通过关联属性info同步添加附表信息 - 关联物理外键自动添加
stu = Student(
name = 'jyh',
age = 16,
sex = '男',
course_list = [
Course(cname = '物理'),
Course(cname = '生物'),
Course(cname = '化学'),
]
)
db.session.add(stu)
db.session.commit()
# 2.添加附加表信息,通过关联属性backref的student反向引用,同步添加主表信息
course = Course(
cname = '数学',
student = Student(
name='xiaoming',
age=24,
sex='女',
)
)
db.session.add(course)
db.session.commit()
return '添加数据'
# 查询数据
@app.route('/select')
def select():
# 1.正向查询 ---> 主模型查询外键模型 - 通过关联属性course_list
stu = Student.query.filter(Student.id == 1).first()
print(stu.course_list) # 获取到附加表对象
print(stu.course_list[0].cname) # 获取到该数据的课程名
# 2.反向查询 ---> 外键模型查询主模型 - 通过关联属性student反向引用
course = Course.query.get(2)
print(course.student) # jyh<student> 主模型对象
print(course.student.name) # jyh
return '查询数据'
# 修改更新数据
@app.route('/update')
def update():
# 1.通过主表使用关联属性可以修改附加表的数据
stu = Student.query.get(1)
stu.course_list[1].cname = '你好111'
db.session.commit()
# 2.通过附加表使用关联属性修改主表的数据
course = Course.query.get(2)
course.student.name = '熊大'
course.student.age = 33
db.session.commit()
return '修改数据'
if __name__ == '__main__':
"""创建表"""
with app.app_context():
db.create_all()
manage.run()
运行
python manage.py runserver -d
多对多
两种关联方式:
- 利用db.Table创建第三方关系表 - 从主模型中不能直接查到表信息
- 直接创建第三方模型关系类,以两个主模型与第三方模型 - 一对多 两两关联 方式进行关联, 可以查到第三方模型信息
1.基于第三方关系表建立多对多关系
from flask import Flask
from flask_script import Manager
from flask_sqlalchemy import SQLAlchemy
# 实例化flask对象
app = Flask(__name__)
# 终端启动脚手架
manage = Manager(app)
# 实例化数据库对象
db = SQLAlchemy(app=app)
# 声明加载配置项
class Config(object):
"""设置secret_key,随机值"""
SECRET_KEY = 'jyh123'
"""连接数据库"""
SQLALCHEMY_DATABASE_URI = 'mysql://root:123@127.0.0.1:3306/db102?charset=utf8mb4'
# 动态追踪修改设置,如未设置只会提示警告
SQLALCHEMY_TRACK_MODIFICATIONS = True
# 查询时会显示原始SQL语句
SQLALCHEMY_ECHO = True
app.config.from_object(Config)
# 1.利用db.Table创建第三方关系表
sanfang = db.Table(
'd_sanfang', # 表名
# 字段名称写在里面第一位
db.Column("id", db.Integer, primary_key=True, comment='主键'),
db.Column("stu_id", db.Integer, db.ForeignKey('d_student.id')),
db.Column("cou_id", db.Integer, db.ForeignKey('d_course.id')),
)
# 定义学生模型
class Student(db.Model):
"""学生表"""
__tablename__ = 'd_student'
id = db.Column(db.Integer, primary_key=True, comment='学生id')
name = db.Column(db.String(25), nullable=False, index=True, comment='姓名')
age = db.Column(db.SmallInteger, nullable=False, comment='年龄')
sex = db.Column(db.Enum('男','女'), default='男', comment='性别')
# 主模型中建立关联 - secondary
course_list = db.relationship('Course', secondary=sanfang, backref='student_list', lazy='dynamic')
def __repr__(self):
return f'{self.name}<student>'
# 课程模型
class Course(db.Model):
"""学生选课附加表"""
__tablename__ = 'd_course'
id = db.Column(db.Integer, primary_key=True, comment='主键')
name = db.Column(db.String(255), nullable=True, comment='课程名称')
price = db.Column(db.Numeric(6,2), default=0, comment='价格')
def __repr__(self):
return f'{self.name}<course>'
@app.route('/')
def index():
return 'ok'
# 添加数据
@app.route('/add')
def add():
# 1.添加主表信息时通过关联属性info同步添加附表信息 - 关联物理外键自动添加
stu = Student(
name = 'jyh',
age = 16,
sex = '男',
course_list = [
Course(name = '物理', price = 23.3),
Course(name = '生物', price = 25.5),
Course(name = '化学', price = 24),
]
)
db.session.add(stu)
db.session.commit()
# 2.添加附加表信息,通过关联属性backref的student反向引用,同步添加主表信息
course = Course(
name = '数学',
price = 22.2,
student_list = [
Student( name='xiaoming', age=24, sex='女'),
Student( name='xiong', age=25, sex='男'),
]
)
db.session.add(course)
db.session.commit()
# 3.学生1已经学习了课程1,2,3 还想学习课程7
stu = Student.query.get(1)
"""错误写法,会把已有的学习课程1,2,3全部清空,再添加课程7,最好使用列表追加方式"""
# stu.course_list = [Course.query.get(7)]
stu.course_list.append(Course.query.get(7))
db.session.commit()
return '添加数据'
# 查询数据
@app.route('/select')
def select():
# 1.正向查询 ---> 主模型查询外键模型 - 通过关联属性course_list
stu = Student.query.filter(Student.id == 1).first()
print(stu.course_list) # 获取到附加表对象
print(stu.course_list[0].name) # 获取到该数据的课程名
# 2.反向查询 ---> 外键模型查询主模型 - 通过关联属性student_list反向引用
course = Course.query.get(2)
print(course.student_list)
for i in course.student_list:
print(i.name)
return '查询数据'
# 修改更新数据
@app.route('/update')
def update():
# 1.通过主表使用关联属性可以修改附加表的数据
'''学生id为1的同学,学习的课程价格都下降了10元'''
stu = Student.query.get(1)
for course in stu.course_list:
course.price -=10
db.session.commit()
return '修改数据'
if __name__ == '__main__':
"""创建表"""
with app.app_context():
db.create_all()
manage.run()
2.基于第三方模型建立多对多关系
在SQLAlchemy中,基于db.Table创建的关系表,如果需要新增除了外键以外其他字段,无法操作。所以将来实现多对多的时候,除了上面db.Table方案以外,还可以把关系表声明成模型的方法,如果声明成模型,则原来课程和学生之间的多对多的关系,就会变成远程的1对多了。
from flask import Flask
from flask_script import Manager
from flask_sqlalchemy import SQLAlchemy
# 实例化flask对象
app = Flask(__name__)
# 终端启动脚手架
manage = Manager(app)
# 实例化数据库对象
db = SQLAlchemy(app=app)
# 声明加载配置项
class Config(object):
"""设置secret_key,随机值"""
SECRET_KEY = 'jyh123'
"""连接数据库"""
SQLALCHEMY_DATABASE_URI = 'mysql://root:123@127.0.0.1:3306/db102?charset=utf8mb4'
# 动态追踪修改设置,如未设置只会提示警告
SQLALCHEMY_TRACK_MODIFICATIONS = True
# 查询时会显示原始SQL语句
SQLALCHEMY_ECHO = True
app.config.from_object(Config)
"""# 1.利用db.Table创建第三方关系表
sanfang = db.Table(
'd_sanfang', # 表名
db.Column("id", db.Integer, primary_key=True, comment='主键'),
db.Column("stu_id", db.Integer, db.ForeignKey('d_student.id')),
db.Column("cou_id", db.Integer, db.ForeignKey('d_course.id')),
)"""
# 2.根据第三方模型建立多对多
# 建立成绩表
class Score(db.Model):
__tablename__ = 'd_score'
id = db.Column(db.Integer, primary_key=True, comment='主键')
student_id = db.Column(db.Integer, db.ForeignKey('d_student.id'), comment='学生id')
course_id = db.Column(db.Integer, db.ForeignKey('d_course.id'), comment='课程id')
num = db.Column(db.Numeric(4,1), comment='成绩')
# 定义学生模型
class Student(db.Model):
"""学生表"""
__tablename__ = 'd_student'
id = db.Column(db.Integer, primary_key=True, comment='学生id')
name = db.Column(db.String(25), nullable=False, index=True, comment='姓名')
age = db.Column(db.SmallInteger, nullable=False, comment='年龄')
sex = db.Column(db.Enum('男','女'), default='男', comment='性别')
# 主模型中建立关联 - 与成绩表创建一对多关系
score_list = db.relationship('Score', uselist=True, backref='student', lazy='dynamic')
def __repr__(self):
return f'{self.name}<student>'
# 课程模型
class Course(db.Model):
"""学生选课附加表"""
__tablename__ = 'd_course'
id = db.Column(db.Integer, primary_key=True, comment='主键')
name = db.Column(db.String(255), nullable=True, comment='课程名称')
price = db.Column(db.Numeric(6,2), default=0, comment='价格')
# 主模型中建立关联 - 与成绩表创建一对多关系
score_list = db.relationship('Score', uselist=True, backref='course', lazy='dynamic')
def __repr__(self):
return f'{self.name}<course>'
@app.route('/')
def index():
return 'ok'
# 添加数据
@app.route('/add')
def add():
# 1.记录xiaoming数学成绩为78
score = Score(
student = Student.query.filter(Student.name == 'xiaoming').first(),
course = Course.query.filter(Course.name == '数学').first(),
num = 78
)
db.session.add(score)
db.session.commit()
return '添加数据'
# 查询数据
@app.route('/select')
def select():
# 1.查询xiaoming学习所有课程和成绩
stu = Student.query.filter(Student.name == 'xiaoming').first()
for score in stu.score_list:
print(f'{score.course.name} : {score.num}')
# 2.查询学习语文课的所有学生姓名
course = Course.query.filter(Course.name == '语文').first()
lst = []
for score in course.score_list:
lst.append(score.student.name)
print(course.name,lst)
return '查询数据'
# 修改更新数据
@app.route('/update')
def update():
# 1.把xiaoming的数学成绩改为99
stu = Student.query.filter(Student.name == "xiaoming").first()
course = Course.query.filter(Course.name == '数学').first()
score = Score.query.filter_by(student_id = stu.id, course_id = course.id).first()
score.num = 99
db.session.commit()
return '修改数据'
if __name__ == '__main__':
"""创建表"""
with app.app_context():
db.create_all()
manage.run()
3.逻辑外键
多张表都没有外键关联,只是储存了相应的字段id
语法与MySQL中的inner join .. on.. id= id
差不多
# 语法: join是俩表关联, with_entities是显示字段 ,label是起别名(与as一样)
模型.query.join(模型类,主模型.id==外键模型.id).filter().with_entities("字段1","字段2".label("字段别名")).all()
from flask import Flask
from flask_script import Manager
from flask_sqlalchemy import SQLAlchemy
# 实例化flask对象
app = Flask(__name__)
# 终端启动脚手架
manage = Manager(app)
# 实例化数据库对象
db = SQLAlchemy(app=app)
# 声明加载配置项
class Config(object):
"""设置secret_key,随机值"""
SECRET_KEY = 'jyh123'
"""连接数据库"""
SQLALCHEMY_DATABASE_URI = 'mysql://root:123@127.0.0.1:3306/db102?charset=utf8mb4'
# 动态追踪修改设置,如未设置只会提示警告
SQLALCHEMY_TRACK_MODIFICATIONS = True
# 查询时会显示原始SQL语句
SQLALCHEMY_ECHO = True
app.config.from_object(Config)
"""# 1.利用db.Table创建第三方关系表
sanfang = db.Table(
'd_sanfang', # 表名
db.Column("id", db.Integer, primary_key=True, comment='主键'),
db.Column("stu_id", db.Integer, db.ForeignKey('d_student.id')),
db.Column("cou_id", db.Integer, db.ForeignKey('d_course.id')),
)"""
# 2.根据第三方模型建立多对多
# 建立成绩表
class Score(db.Model):
__tablename__ = 'd_score'
id = db.Column(db.Integer, primary_key=True, comment='主键')
student_id = db.Column(db.Integer, comment='学生id')
course_id = db.Column(db.Integer, comment='课程id')
num = db.Column(db.Numeric(4,1), comment='成绩')
# 定义学生模型
class Student(db.Model):
"""学生表"""
__tablename__ = 'd_student'
id = db.Column(db.Integer, primary_key=True, comment='学生id')
name = db.Column(db.String(25), nullable=False, index=True, comment='姓名')
age = db.Column(db.SmallInteger, nullable=False, comment='年龄')
sex = db.Column(db.Enum('男','女'), default='男', comment='性别')
def __repr__(self):
return f'{self.name}<student>'
# 课程模型
class Course(db.Model):
"""学生选课附加表"""
__tablename__ = 'd_course'
id = db.Column(db.Integer, primary_key=True, comment='主键')
name = db.Column(db.String(255), nullable=True, comment='课程名称')
price = db.Column(db.Numeric(6,2), default=0, comment='价格')
def __repr__(self):
return f'{self.name}<course>'
@app.route('/')
def index():
return 'ok'
# 查询数据
@app.route('/select')
def select():
# 1.查询xiaoming学习所有课程和成绩 - 三表关联
res_list = Student.query.join(
Score,
Student.id == Score.student_id
).join(
Course,
Course.id == Score.course_id
).filter(
Student.name == 'xiaoming'
).with_entities(
Student.name,
Course.name.label('course_name'),
Score.num
).all()
for res in res_list:
print(res.name, res.course_name, res.num)
return '查询数据'
if __name__ == '__main__':
"""创建表"""
with app.app_context():
db.create_all()
manage.run()
数据库迁移
- 在开发过程中,需要修改数据库模型,而且还要在修改之后更新数据库。最直接的方式就是删除旧表,但这样会丢失数据。
- 更好的解决办法是使用数据库迁移框架,它可以追踪数据库模式的变化,然后把变动应用到数据库中。
- 在Flask中可以使用Flask-Migrate扩展,来实现数据迁移。并且集成到Flask-Script中,所有操作通过命令就能完成。
安装Flask-Migrate。
pip install flask-migrate
使用 :
manage.py
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from flask_migrate import Migrate
# 实例化flask对象
app = Flask(__name__)
# 实例化数据库对象
db = SQLAlchemy(app)
# 实例化数据库迁移对象
migrate = Migrate(app,db)
# 声明加载配置项
class Config(object):
DEBUG = True
"""设置secret_key,随机值"""
SECRET_KEY = 'jyh123'
"""连接数据库"""
SQLALCHEMY_DATABASE_URI = 'mysql://root:123@127.0.0.1:3306/db102?charset=utf8mb4'
# 动态追踪修改设置,如未设置只会提示警告
SQLALCHEMY_TRACK_MODIFICATIONS = True
# 查询时会显示原始SQL语句
SQLALCHEMY_ECHO = True
app.config.from_object(Config)
# 定义学生模型
class Student(db.Model):
"""学生表"""
__tablename__ = 'd_student'
id = db.Column(db.Integer, primary_key=True, comment='学生id')
name = db.Column(db.String(25), nullable=False, index=True, comment='姓名')
age = db.Column(db.SmallInteger, nullable=False, comment='年龄')
sex = db.Column(db.Enum('男','女'), default='男', comment='性别')
qq = db.Column(db.String(55), comment='qq号')
def __repr__(self):
return f'{self.name}<student>'
@app.route('/')
def index():
return 'ok'
if __name__ == '__main__':
app.run()
创建迁移版本仓库
# 切换到项目根目录下
cd ~/flaskdemo
# 设置flask项目的启动脚本位置
# 新版本的flask1.1以后,会在系统终端下提供一个类似django-admin的终端命令工具
export FLASK_APP=manage.py
flask --help # 查看flask命令
# flask run 启动flask项目
# 数据库迁移初始化,这个命令会在当前项目根目录下创建migrations文件夹,所有数据表相关的迁移文件都放在里面。
flask db --help # 查看数据迁移命令
flask db init
创建迁移版本
- 自动创建的迁移脚本会根据模型定义和数据库当前状态的差异,生成upgrade()和downgrade()函数的内容。
# 根据flask项目的模型生成迁移文件
# 这里等同于django里面的 makemigrations,生成迁移版本文件
flask db migrade -m 'initial migration'
# 完成2件事情:
# 1. 在migrations/versions生成一个数据库迁移文件
# 2. 如果是首次生成迁移文件的项目,则迁移工具还会在数据库创建一个记录数据库版本的version表
升级版本库的版本
# 从migations目录下的versions中根据迁移文件upgrade方法把数据表的结构同步到数据库中。
flask db upgrade
降级版本库的版本
# 从migations目录下的versions中根据迁移文件downgrade把数据表的结构同步到数据库中。
flask db downgrade
版本库的历史管理
可以根据history命令找到版本号,然后传给downgrade命令:
flask db history
输出格式:<base> -> 版本号 (head), initial migration
回滚到指定版本
flask db downgrade # 默认返回上一个版本
flask db downgrade 版本号 # 回滚到指定版本号对应的版本
flask db upgrade 版本号 # 升级到指定版本号对应的版本
数据迁移的步骤:
1. 初始化数据迁移的目录
export FLASK_APP=manage.py
flask db init
2. 数据库的数据迁移版本初始化
flask db migrate -m 'initial migration'
3. 升级版本[创建表/创建字段/修改字段]
flask db upgrade
4. 降级版本[删除表/删除字段/恢复字段]
flask db downgrade
Faker - 随机生成数据
中文文档: https://faker.readthedocs.io/en/master/locales/zh_CN.html
官方文档: https://github.com/joke2k/faker
安装
pip install faker -i https://pypi.douban.com/simple
批量生成数据
- 自定义终端命令
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from flask_script import Manager,Command,Option
from faker import Faker
import random
# 实例化flask对象
app = Flask(__name__)
# 脚手架,终端命令启动
manager = Manager(app)
# 实例化数据库对象
db = SQLAlchemy(app)
# 实例化随机生产数据对象,locale指定中文,默认英文
faker = Faker(locale='ZH_CN')
# 声明加载配置项
class Config(object):
DEBUG = True
"""设置secret_key,随机值"""
SECRET_KEY = 'jyh123'
"""连接数据库"""
SQLALCHEMY_DATABASE_URI = 'mysql://root:123@127.0.0.1:3306/db102?charset=utf8mb4'
# 动态追踪修改设置,如未设置只会提示警告
SQLALCHEMY_TRACK_MODIFICATIONS = True
# 查询时会显示原始SQL语句
SQLALCHEMY_ECHO = True
app.config.from_object(Config)
# 定义学生模型
class Student(db.Model):
"""学生表"""
__tablename__ = 'd_student'
id = db.Column(db.Integer, primary_key=True, comment='学生id')
name = db.Column(db.String(25), nullable=False, index=True, comment='姓名')
age = db.Column(db.SmallInteger, nullable=False, comment='年龄')
sex = db.Column(db.Enum('男','女'), default='男', comment='性别')
email = db.Column(db.String(55), comment='邮箱')
def __repr__(self):
return f'{self.name}<student>'
# 自定义终端命令 - 批量生产学生数据
class FakerStudentCommand(Command):
# 终端使用的命令
name = 'student'
# 传递的参数 - 生成多少数据
option_list = (
Option('-n', '--num', dest='num', type=int),
)
# 运行
def run(self,num):
student_list = []
for i in range(num):
student = Student(
name = faker.name(), # 名字随机
age = random.randint(20,50),
sex = '男' if i%2==0 else '女',
email = faker.email() # 邮箱随机
)
student_list.append(student)
db.session.add_all(student_list)
db.session.commit()
manager.add_command(FakerStudentCommand.name, FakerStudentCommand)
@app.route('/')
def index():
return 'ok'
if __name__ == '__main__':
with app.app_context():
db.create_all()
manager.run()
启动命令 : 生成1000条数据
python index.py student -n1000
指定空间储存session
安装 flask-session
pip install flask-session -i https://pypi.douban.com/simple
使用session之前,必须配置session秘钥:
SECRET_KEY = "*(%#4sxcz(^(#$#8423" # session秘钥 - 随机字符串
SQLAlchemy存储session的基本配置
在项目第一次启动的时候,会自动创建session表,使用db.create_all()
来完成创建。
manage.py
from flask import Flask,session
from flask_sqlalchemy import SQLAlchemy
from flask_script import Manager
from flask_session import Session
# 实例化flask对象
app = Flask(__name__)
# 脚手架,终端命令启动
manager = Manager(app)
# 实例化数据库对象
db = SQLAlchemy()
# 实例化session对象
sesson_store = Session()
# 声明加载配置项
class Config(object):
DEBUG = True
"""设置secret_key,随机值"""
SECRET_KEY = 'jyh123'
"""连接数据库"""
SQLALCHEMY_DATABASE_URI = 'mysql://root:123@127.0.0.1:3306/db102?charset=utf8mb4'
# 动态追踪修改设置,如未设置只会提示警告
SQLALCHEMY_TRACK_MODIFICATIONS = True
# 查询时会显示原始SQL语句
SQLALCHEMY_ECHO = False
"""把session通过SQLAlchmey保存到mysql中"""
SESSION_TYPE = 'sqlalchemy' # session存储方式为sqlalchemy
SESSION_SQLALCHEMY = db # SQLAlchemy对象
SESSION_SQLALCHEMY_TABLE = 'db_session' # 保存session的表名称
SESSION_PERMANENT = True # 如果设置为True,则关闭浏览器session就失效
SESSION_USE_SIGNER = False # 是否对发送到浏览器上session的cookie值进行加密
SESSION_KEY_PREFIX = 'session:' # sessionID的前缀,默认就是 session:
app.config.from_object(Config)
# 一定加载配置后,再传入app实力参数
db.init_app(app)
sesson_store.init_app(app)
@app.route('/')
def index():
return 'ok'
@app.route('/set')
def set_session():
session['uname'] = 'jyh'
session['age'] = 20
return '设置session'
@app.route('/get')
def get_session():
print(session.get('uname'))
print(session.get('age'))
return '获取session'
@app.route(('/del'))
def del_session():
if session.get('uname'):
# print(session.pop('uname'))
del session['uname']
if session.get('age'):
# print(session.pop('age'))
del session['age']
return '删除session'
if __name__ == '__main__':
with app.app_context():
db.create_all()
manager.run()
redis保存session的基本配置
这个功能必须确保,服务器必须已经安装了redis而且当前项目虚拟环境中已经安装了redis扩展库
安装 flask-redis
pip install flask-redis -i https://pypi.douban.com/simple
在flask中要基于flask-redis进行数据库则可以完成以下3个步骤即可:
# flask-redis是第三方开发者为了方便我们在flask框架中集成redis数据库操作所封装一个redis操作库、
# 1. 引入flaskRedis并实例化
from flask_redis import FlaskRedis
redis = FlaskRedis() # 初始化
redis.init_app(app) # 加载
# 2. 在config配置中使用 REDIS_URL配置redis的url地址
REDIS_URL = "redis://:password@localhost:6379/0"
# 3. 使用实例化后的flaskRedis对象即可操作redis数据库,这个库就是我们之前在django中操作redis时使用库
# 直接通过 redis对象.命令方法(参数1, 参数2...) 就可以使用
redis.setex("sms",5 * 60 , "10010")
配置
manage.py
from flask import Flask,session
from flask_sqlalchemy import SQLAlchemy
from flask_script import Manager
from flask_session import Session
from flask_redis import FlaskRedis
# 实例化flask对象
app = Flask(__name__)
# 脚手架,终端命令启动
manager = Manager(app)
# 实例化数据库对象
# db = SQLAlchemy()
# 实例化session对象
sesson_store = Session()
# 实例化redis数据库对象
redis_session = FlaskRedis()
# 声明加载配置项
class Config(object):
DEBUG = True
"""设置secret_key,随机值"""
SECRET_KEY = 'jyh123'
# """连接数据库"""
# SQLALCHEMY_DATABASE_URI = 'mysql://root:123@127.0.0.1:3306/db102?charset=utf8mb4'
# # 动态追踪修改设置,如未设置只会提示警告
# SQLALCHEMY_TRACK_MODIFICATIONS = True
# # 查询时会显示原始SQL语句
# SQLALCHEMY_ECHO = False
"""把session保存到redis中"""
# redis连接地址 redis://用户名:密码@IP地址:端口/数据库名
REDIS_URL = 'redis://:@127.0.0.1:6379/15'
SESSION_TYPE = 'redis' # session存储方式为redis
SESSION_PERMANENT = True # 如果设置为True,则关闭浏览器session就失效
SESSION_USE_SIGNER = False # 是否对发送到浏览器上session的cookie值进行加密,False代表是
SESSION_KEY_PREFIX = 'session:' # 保存到redis的session数的名称前缀
# session保存数据到redis时启用的链接对象
SESSION_REDIS = redis_session # 用于连接redis的配置
app.config.from_object(Config)
# 另一种编写flask配置的方式:
"""
app.config["REDIS_URL"] = "redis://:@127.0.0.1:6379/15"
app.config["SESSION_TYPE"] = "redis" # session存储方式为redis
app.config["SESSION_PERMANENT"] = False # 如果设置session的生命周期是否是会话期, 为True,则关闭浏览器session就失效
app.config["SESSION_USE_SIGNER"] = False # 是否对发送到浏览器上session的cookie值进行加密
app.config["SESSION_KEY_PREFIX"] = "session:" # 保存到redis的session数的名称前缀
app.config["SESSION_REDIS"] = redis_session # 用于连接redis的配置
"""
# 一定加载配置后,再传入app实力参数
# db.init_app(app)
sesson_store.init_app(app)
redis_session.init_app(app)
@app.route('/')
def index():
return 'ok'
@app.route('/set')
def set_session():
session['uname'] = 'jyh'
session['age'] = 20
return '设置session'
@app.route('/get')
def get_session():
print(session.get('uname'))
print(session.get('age'))
return '获取session'
@app.route(('/del'))
def del_session():
if session.get('uname'):
print(session.pop('uname'))
# del session['uname']
if session.get('age'):
print(session.pop('age'))
# del session['age']
return '删除session'
if __name__ == '__main__':
# with app.app_context():
# db.create_all()
manager.run()
蓝图 Blueprint
模块化 - 相当于Django中的子应用
随着flask程序越来越复杂,我们需要对程序进行模块化的处理,之前学习过django的子应用管理,flask程序进行可以进行类似的模块化处理保存代码。
简单来说,Blueprint 是一个存储视图方法/模型代码的容器(目录),这些操作在这个Blueprint 被注册到flask的APP实例对象应用之后就可以被调用,Flask 可以通过Blueprint来组织URL以及处理客户端请求。
Flask使用Blueprint让应用实现模块化,在Flask中,Blueprint具有如下属性:
- 一个项目可以具有多个Blueprint
- 可以将一个Blueprint注册到任何一个未使用的URL下比如 “/”、“/sample”或者子域名,就是路由前缀
- 在一个flask项目中,一个BluePrint模块可以注册多次
- Blueprint可以单独具有自己的模板、静态文件或者其它的通用操作方法,它并不是必须要实现应用的视图和函数的
- 在一个flask项目初始化时,就应该要注册需要使用的Blueprint
注意:flask中的Blueprint并不是一个完整的应用,它不能独立于应用运行,而必须要注册到某一个flask项目中。
创建蓝图
在flask中,要使用蓝图Blueprint可以分为四个步骤:
- 手动创建一个蓝图的包目录,例如user,并在
__init__.py
文件中创建蓝图对象
from flask import Blueprint
user = Blueprint('user',__name__)
- 创建视图函数文件
user/views.py
# 可以不用写路由
def index():
return 'welcome to user blueprint'
- 在
user/__init__.py
中引入user/views.py
中所有的视图函数并绑定路由地址
from flask import Blueprint
# 初始化蓝图对象
user = Blueprint('user',__name__)
# 引入视图函数,并且绑定路由地址
from . import views
user.add_url_rule('/index', view_func=views.index)
- 在主应用
manage.py
文件中注册蓝图对象
from flask import Flask
from flask_script import Manager
from user import user
# 初始化
app = Flask(__name__)
# 注册蓝图,url_prefix设置蓝图路由前缀
app.register_blueprint(user, url_prefix='/user')
"""使用脚手架[终端脚本]启动项目"""
manage = Manager(app)
if __name__ == '__main__':
manage.run()
- 启动项目
# 终端启动命令
python manage.py runserver -d
$ 访问 : http://127.0.0.1:5000/user/index
蓝图运行机制
- 蓝图Blueprint是保存了一组将来可以在flask应用对象上执行的操作,注册路由/视图编写就是一种操作
- 在视图上方调用 app.route 装饰器注册路由时,这个操作本质就是将修改视图和url地址的映射关系添加到app.url_map路由表
- 蓝图对象根本没有路由表,当我们在蓝图中的视图函数上调用route装饰器(或者add_url_role)注册路由时,它只是在蓝图对象的内部的defered_functions(延迟操作记录列表)中添加了一个路由项(路由项实际上就是一个绑定了视图和url地址的匿名函数)
- 当执行app.register_blueprint() 时,应用对象app会将从蓝图对象的 defered_functions 列表中取出每一个路由项,并把app对象自己作为参数执行路由项对应的匿名函数,匿名函数执行以后就调用app.add_url_rule() 方法,这将蓝图下之前暂存的路由信息全部添加到了app.url_map路由表中了。
设置蓝图中的静态文件及的相关路由
和app应用对象不同,蓝图对象创建时不会默认注册静态目录的路由。需要我们在创建时指定 static_folder 参数。
下面的代码将蓝图所在目录下的static_user
目录设置为静态目录
users/__init__.py
,代码:
from flask import Blueprint
# 初始化蓝图对象
"""
static_folder='static_user' --指定静态文件目录
static_url_path='/st' --修改静态目录的url地址。
"""
user = Blueprint('user', __name__, static_folder='static_user', static_url_path='/st')
# 引入视图函数,并且绑定路由地址
from . import views
user.add_url_rule('/index', view_func=views.index)
主文件manage.py
from flask import Flask
from flask_script import Manager
from user import user
# 初始化
app = Flask(__name__)
# 注册蓝图,url_prefix设置蓝图路由前缀
app.register_blueprint(user, url_prefix='/user')
"""使用脚手架[终端脚本]启动项目"""
manage = Manager(app)
if __name__ == '__main__':
manage.run()
**启动项目: **
# 终端启动命令
python manage.py runserver -d
# 现在就可以使用/user/st/ 访问`user/static_user/`目录下的静态文件.
# 查看静态文件下的图片
http://127.0.0.1:5000/user/st/1.jpg
设置蓝图中模版的目录
蓝图对象默认的模板目录为系统的模版目录,也可以在创建蓝图对象时使用 template_folder 指定模板目录
新建蓝图模板文件夹templates_user
注册蓝图时指定模板目录
user/__init__.py
from flask import Blueprint
# 初始化蓝图对象
"""
template_folder='templates_user' -- 指定蓝图模板目录
"""
user = Blueprint('user', __name__, template_folder='templates_user')
# 引入视图函数,并且绑定路由地址
from . import views
user.add_url_rule('/index', view_func=views.index)
视图 user/views.py
from flask import render_template
# 可以不用写路由
def index():
return render_template('index.html')
模板 user/templates_user/index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>欢迎来的蓝图模板</h1>
</body>
</html>
主文件manage.py
from flask import Flask
from flask_script import Manager
from user import user
# 初始化
app = Flask(__name__)
# 注册蓝图,url_prefix设置蓝图路由前缀
app.register_blueprint(user, url_prefix='/user')
"""使用脚手架[终端脚本]启动项目"""
manage = Manager(app)
if __name__ == '__main__':
manage.run()
**启动项目: **
# 终端启动命令
python manage.py runserver -d
# 访问路径 -- 可以看到HTML内容
http://127.0.0.1:5000/user/index
注意:
如果在 templates 中存在和 templates_user 同名模板文件时, 则系统会优先使用 templates 中的文件
解决办法:
初始化flask时,设置template_folder 模板参数为 " "
或 None
主文件manage.py
from flask import Flask
from flask_script import Manager
from user import user
# 初始化
app = Flask(__name__, template_folder='')
# 注册蓝图,url_prefix设置蓝图路由前缀
app.register_blueprint(user, url_prefix='/user')
"""使用脚手架[终端脚本]启动项目"""
manage = Manager(app)
if __name__ == '__main__':
manage.run()