flask_CBV_数据库操作


CBV和FBV
Python是一个面向对象的编程语言,如果只用函数来开发,有很多面向对象的优点就丢失了(继承、封装、多态)。所以Flask中可以加入Class-Based-View。可以让我们用类写View。这样做的优点主要下面两种:
- 提高了代码的复用性,可以使用面向对象的技术,比如Mixin(多继承)
- 可以用不同的方法针对不同的HTTP方法处理,而不是通过很多if判断,提高代码可读性
- 在FBV中是通过判断method是get或者post返回不同的响应内容
从视图函数装饰器开始引出,探讨FBV中的装饰器到底干了些什么事情?
from flask import Flask,render_template,redirect,views
app = Flask(__name__)
#该装饰器和视图函数是一种FBV模式
@app.route('/index') #进入route查看其实现原理,原理是基于add_url_rule实现的
def index():
return 'i am index......'
#查看视图装饰器(route)的源码可得如下结论
#app.add_url_rule('/index',None, index) == @app.route('/index')
什么时候使用FBV, 什么时候使用CBV?
FBV简单, 小巧, 当不涉及到复杂的逻辑时可以使用FBV
当设计到复杂逻辑的时候使用CBV
CBV的实现
#基础实现
from flask import Flask,render_template
from flask import views
app = Flask(__name__)
#FBV
#装饰器的本质就是在执行add_url_rule
# @app.route('/')
def index():
return 'i am index'
app.add_url_rule('/',endpoint=None,view_func=index)
#CBV
class Login(views.MethodView):
methods = ['GET']#允许请求设定
#get请求来了执行
def get(self):
return render_template('login.html')
#post请求来了执行
def post(self):
return '登录成功!'
#as_view将类转成视图函数,name的值就是视图函数的名称
app.add_url_rule('/login',endpoint=None,view_func=Login.as_view(name='login'))
if __name__ == '__main__':
#0.0.0.0表示该服务监听我本机所有的ip地址
#127.0.0.1只监听回环地址
#通常在编码阶段不需要指定监听的地址只需要启动服务即可,监听可以由运维的ngix做
app.run('0.0.0.0',9527)
数据库操作
ORM
ORM 全拼Object-Relation Mapping,中文意为 对象-关系映射。主要实现模型对象到关系数据库数据的映射
优点 :
- 只需要面向对象编程, 不需要面向数据库编写代码.
- 对数据库的操作都转化成对类属性和方法的操作.
- 不用编写各种数据库的
sql语句.
- 实现了数据模型与数据库的解耦, 屏蔽了不同数据库操作上的差异.
- 不再需要关注当前项目使用的是哪种数据库。
- 通过简单的配置就可以轻松更换数据库, 而不需要修改代码.
缺点 :
- 相比较直接使用SQL语句操作数据库,有性能损失.
- 根据对象的操作转换成SQL语句,根据查询的结果转化成对象, 在映射过程中有性能损失.
Flask-SQLAlchemy
flask默认提供模型操作,但是并没有提供ORM,所以一般开发的时候我们会采用flask-SQLAlchemy模块来实现ORM操作。
SQLAlchemy是一个关系型数据库框架,它提供了高层的 ORM 和底层的原生数据库的操作。flask-sqlalchemy 是一个简化了 SQLAlchemy 操作的flask扩展。
SQLAlchemy: https://www.sqlalchemy.org/
中文文档: https://www.osgeo.cn/sqlalchemy/index.html
安装 flask-sqlalchemy【清华源】
pip install flask-sqlalchemy -i https://pypi.tuna.tsinghua.edu.cn/simple
安装pysqldb的依赖包:
pip install mysqlclient
pip install pymysql
如果连接的是 mysql 数据库,需要安装 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
数据库连接设置
-
在 Flask-SQLAlchemy 中,数据库使用URL指定,而且程序使用的数据库必须保存到Flask配置对象的 SQLALCHEMY_DATABASE_URI 键中
config.py,配置文件代码:
class Config(object):
DEBUG = True
SECRET_KEY = "*(%#4sxcz(^(#$#8423"
# 数据库链接配置 = 数据库名称://登录账号:登录密码@数据库主机IP:数据库访问端口/数据库名称?charset=编码类型
SQLALCHEMY_DATABASE_URI = "mysql://root:123@127.0.0.1:3306/students?charset=utf8mb4"
- 其他设置:
# 动态追踪修改设置,如未设置只会提示警告
SQLALCHEMY_TRACK_MODIFICATIONS = True
#查询时会显示原始SQL语句
SQLALCHEMY_ECHO = True
- 配置完成需要去 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.time | 时间 |
| DateTime | datetime.datetime | 日期和时间 |
| LargeBinary | str | 二进制文件内容 |
常用的SQLAlchemy列约束选项
| 选项名 | 说明 |
|---|---|
| primary_key | 如果为True,代表表的主键 |
| unique | 如果为True,代表这列不允许出现重复的值 |
| index | 如果为True,为这列创建索引,提高查询效率 |
| nullable | 如果为True,允许有空值,如果为False,不允许有空值 |
| default | 为这列定义默认值 |
定义模型类
我们后面会把模型创建到单独的文件中,但是现在我们先把模型类写在main.py文件中。
from flask import Flask
# 初始化
app = Flask(import_name=__name__)
# 声明和加载配置
class Config():
DEBUG = True
# 数据库链接配置 = 数据库名称://登录账号:登录密码@数据库主机IP:数据库访问端口/数据库名称?charset=编码类型
SQLALCHEMY_DATABASE_URI = "mysql://root:123@127.0.0.1:3306/students?charset=utf8"
# 动态追踪修改设置,如未设置只会提示警告
app.config.from_object(Config)
# 初始化SQLAlchemy
from flask_sqlalchemy import SQLAlchemy
db = SQLAlchemy() # 初始化数据库操作对象
db.init_app(app) # 初始化数据库链接
class Student(db.Model):
# 表结构声明
__tablename__ = "tb_student"
# 字段声明,comment字段注释
id = db.Column(db.Integer, primary_key=True, comment="主键")
name = db.Column(db.String(64), index=True, comment="姓名")
sex = db.Column(db.Boolean, default=True, 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="钱包")
# 自定义方法
def __repr__(self):
return 'Student:%s' % self.name
class Teacher(db.Model):
# 表结构声明
__tablename__ = 'tb_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 'Teacher:%s' % self.name
class Course(db.Model):
# 定义表名
__tablename__ = 'tb_course'
# 定义字段对象
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(64), unique=True)
price = db.Column(db.Numeric(6,2))
# repr()方法类似于django的__str__,用于打印模型对象时显示的字符串信息
def __repr__(self):
return 'Course:%s'% self.name
@app.route(rule='/')
def index():
return "ok"
if __name__ == '__main__':
# 运行flask
app.run(debug=True)
数据库基本操作
- 在Flask-SQLAlchemy中,添加、修改、删除操作,均由数据库会话管理。
- 会话用 db.session 表示。在准备把数据写入数据库前,要先将数据添加到会话中然后调用 db.commit() 方法提交会话。
- 在 Flask-SQLAlchemy 中,查询操作是通过 query 对象操作数据。
- 最基本的查询是返回表中所有数据,可以通过过滤器进行更精确的数据库查询。
数据表操作
创建和删除表
创建表
db.create_all()
删除表
db.drop_all()
代码:
if __name__ == '__main__':
with app.app_context():
# db.drop_all() # 删除所有的数据表
db.create_all() # 创建所有的数据表
# 运行flask
app.run(debug=True)
注意:如果执行报错NameError: name '_mysql' is not defined,则在代码中添加
import pymysql
pymysql.install_as_MySQLdb()
数据基本操作
添加一条数据
student1 = Student(name="小明", sex=True, age=17, email="123456@qq.com", money=100)
db.session.add(student1)
db.session.commit()
#再次插入 一条数据
student2 = Student(name='小红', sex=False, age=13, email="16565666@qq.com", money=600)
db.session.add(student2)
db.session.commit()
一次插入多条数据
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()
删除数据
# 方法1
student = Student.query.first()
db.session.delete(student)
db.session.commit()
# 方法2【事务中使用,就是乐观锁】
ret = Student.query.filter(Student.name=='sun').delete()
db.session.commit()
更新数据
# 方法1
student = Student.query.first()
student.name = 'dong'
db.session.commit()
# 方法2【事务中使用,就是乐观锁】
ret = Student.query.filter(Student.name == 'liu').update({'money': 1000})
db.session.commit()
# 方法3【批量操作, 实现类似django里面F函数的效果】
ret = Student.query.filter(Student.age == 22).update({Student.money: Student.money+'200'})
db.session.commit()
数据基本查询
常用的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后面,其他地方无法使用。 |
get():参数为数字,表示根据主键查询数据,如果主键不存在返回None
Student.query.get()
all()返回查询到的所有对象
Student.query.all()
first()返回查询到的第一个对象【first获取一条数据,all获取多条数据】
Student.query.first()
filter模糊查询,支持各种运算符和查询方法
返回名字结尾字符为g的所有数据。
# name姓名中以"g"结尾的学生
ret = Student.query.filter(Student.name.endswith("g")).all()
# name姓名中包含"u"的学生
ret = Student.query.filter(Student.name.contains("u")).all()
# name姓名中以"w"开头的学生
ret = Student.query.filter(Student.name.startswith("w")).all()
# 也可以使用filter进行精确查找,
# 则需要指定条件格式为: 模型.字段 比较运算符 值。
# 运算符可以是: ==表示相等,!=不相等,> 表示大于 < 表示小于,>=大于等于,<=小于等于
# ret = Student.query.filter(Student.age==22).all()
# 另一种写法的查询方式
# db.session.query(Student) 相当于 Student.query
# ret = db.session.query(Student).filter(Student.age==22).all()
filter_by精确查询,只支持字段的值是否相等这种条件.只支持一个等号作为判断条件,而且字段左边不需要声明模型类名
例如:返回名字等于wang的学生学生
# name=wang的学生
ret = Student.query.filter_by(name="wang").first()
# age = 22的所有学生
ret = Student.query.filter_by(age=22).all()
练习
查询所有男生数据
查询所有女生数据
查询id为4的学生
查询年龄等于22的所有学生数据
查询name为lu的学生数据
多条件查询
逻辑非,返回名字不等于wang的所有数据
Student.query.filter(Student.name!='wang').all()
not_ 相当于取反
from sqlalchemy import not_
Student.query.filter(not_(Student.name=='wang')).all()
逻辑与,需要导入and,返回and()条件满足的所有数据
from sqlalchemy import and_
Student.query.filter(and_(Student.name!='wang',Student.email.endswith('163.com'))).all()
逻辑或,需要导入or_
from sqlalchemy import or_
Student.query.filter(or_(Student.name!='wang',Student.email.endswith('163.com'))).all()
in_范围查询
"""查询id为2, 3, 5, 7, 8这几个学生信息"""
student_list = Student.query.filter(Student.id.in_([2, 3, 5, 7, 8])).all()
print(student_list)
order_by 排序
# 查询所有学生,并按年龄进行倒序排列
ret = Student.query.order_by(Student.age.desc()).all()
# 查询所有学生,并按年龄进行倒序排列,年龄相同,则按id进行降序排序.
ret = Student.query.order_by(Student.age.desc(),Student.id.desc()).all()
count统计
# 查询age>=19的男生的数量
from sqlalchemy import and_
# ret = Student.query.filter( and_(Student.age>=19,Student.sex==True) ).count()
ret = Student.query.filter( Student.age>=19, Student.sex==True ).count()
对结果进行偏移量和数量的限制
# 查询年龄最大的3个学生
ret1 = Student.query.order_by(Student.age.desc()).limit(3).all()
# 查询年龄排第4到第7名的学生
ret2 = Student.query.order_by(Student.age.desc(),Student.id.desc()).offset(4).limit(4).all()
print(ret1,ret2)
分页器Pagination对象的使用:
Pagination对象是模型类调用paginate方法后返回的对象:
paginate(page=None, per_page=None, error_out=True, max_per_page=None)
page:指定页码,从1开始,page的值一般是从前台页面传过来的
per_page:每一页有几个项
error_out(默认为True):是否抛出错误.当其为True时,在以下情况会抛出404.没有匹配项或者page不等于1.page比1小或者per_page是负数.page和per_page不是整数.当其为False时,page和per_page的默认值分别为20和1
max_per_page当指定了max_per_page时,per_page会受到这个值的限制
对象的属性和方法
has_next:如果在目前页后至少还有一页的话,返回 True
has_prev:如果在目前页前至少还有一页的话,返回 True
items:当前页的元素集合
next(error_out=False):返回下一页的Pagination对象
next_num:下一页的页码
page:当前页的页码
pages:匹配的元素在当前配置一共有多少页
per_page:每一页显示的元素个数
prev(error_out=False):上一页的Pagination对象
prev_num:上一页的页码
query:创建Pagination对象对应的query对象
total:匹配的元素总数
run.py,代码:
# 初始化
app = Flask(import_name=__name__,template_folder='templates')
app.config.from_object(Config)
db.init_app(app) # 初始化数据库链接
"""分页器使用"""
@app.route(rule="/list")
def list():
pagination = Student.query.paginate(per_page=3)
return render_template("list.html",pagination=pagination)
if __name__ == '__main__':
# 运行flask
app.run(debug=True)
list.html,代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<style>
.page a,.page span{
padding: 2px 6px;
color: #fff;
background: #6666ff;
text-decoration: none;
}
.page span{
color: #fff;
background: orange;
}
</style>
</head>
<body>
<table border="1" align="center" width="600">
<tr>
<th>ID</th>
<th>age</th>
<th>name</th>
<th>sex</th>
<th>money</th>
</tr>
{% for student in pagination.items %}
<tr>
<td>{{ student.id }}</td>
<td>{{ student.age }}</td>
<td>{{ student.name }}</td>
<td>{{ "男" if student.sex else "女" }}</td>
<td>{{ student.money }}</td>
</tr>
{% endfor %}
<tr align="center">
<td colspan="5" class="page">
{% if pagination.has_prev %}
<a href="?page=1">首 页</a>
<a href="?page={{ pagination.page-1 }}">上一页</a>
<a href="?page={{ pagination.page-1 }}">{{ pagination.page-1 }}</a>
{% endif %}
<span>{{ pagination.page }}</span>
{% if pagination.has_next %}
<a href="?page={{ pagination.page+1 }}">{{ pagination.page+1 }}</a>
<a href="?page={{ pagination.page+1 }}">下一页</a>
<a href="?page={{ pagination.pages }}">尾 页</a>
{% endif %}
</td>
</tr>
</table>
</body>
</html>
分组查询group_by和分组查询的结果过滤session.query()
一般分组都会结合聚合函数来一起使用。SQLAlchemy中所有的聚合函数都在func模块中声明的。
from sqlalchemy import func
| 函数名 | 说明 | |
|---|---|---|
| func.count | 统计总数 | |
| func.avg | 平均值 | |
| func.min | 最小值 | |
| func.max | 最大值 | |
| func.sum | 和 |
代码:
from sqlalchemy import func
# 查询当前所有男生女生的数量
# ret = db.session.query(Student.sex,func.count(Student.id)).group_by(Student.sex).all()
# 查询当前不同年龄的学生数量
ret = db.session.query(Student.age,func.count(Student.id)).group_by(Student.age).all()
# 查询当前不同年龄的学生数量,找出大于19岁的分组
ret = db.session.query(Student.age,func.count(Student.id)).group_by(Student.age).having(Student.age>19).all()
执行原生SQL语句
# 读取多条数据
ret = db.session.execute("select * from tb_student").fetchall()
# 读取一条数据
ret = db.session.execute("select * from tb_student").fetchone()
# 添加/修改/删除
db.session.execute("UPDATE tb_student SET money=(tb_student.money + %s) WHERE tb_student.age = %s" % (200, 22))
db.session.commit()
关联查询
常用的SQLAlchemy关系选项
| 选项名 | 说明 |
|---|---|
| backref | 在关系的另一模型中添加反向引用,用于设置外键名称,在1查多的 |
| primary join | 明确指定两个模型之间使用的连表条件 |
| lazy | 指定如何加载关联模型数据的方式。参数值: select(立即加载,查询所有相关数据显示,相当于lazy=True) subquery(立即加载,但使用子查询) dynamic(不加载记录,但提供加载记录的查询对象) |
| uselist | 如果为False,不使用列表,而使用标量值。 一对一关系中,需要设置relationship中的uselist=Flase,其他数据库操作一样。 |
| secondary | 指定多对多关系中关系表的名字。 多对多关系中,需建立关系表,设置 secondary=关系表 |
| secondary join | 在SQLAlchemy中无法自行决定时,指定多对多关系中的二级连表条件 |
模型之间的关联
一对一:分为主表和附加表
1.主表中写relationship,附加表中写Foreignkey
2.relationship:关联属性,是SQLAlchemy提供给开发者快速引用外键模型的一个对象属性,不存在于mySQL中
3.relationship的参数backref: 反向引用,类似django的related,通过外键模型查询主模型数据时的关联属性,因为是一对一,所以值为own
class Student(db.Model):
__tablename__ = "tb_student"
id = db.Column(db.Integer, primary_key=True,comment="主键ID")
name = db.Column(db.String(250), comment="姓名")
age = db.Column(db.Integer, comment="年龄")
sex = db.Column(db.Boolean, default=False, comment="性别")
money = db.Column(db.DECIMAL(8,2), nullable=True, comment="钱包")
# 关联属性,是SQLAlchemy提供给开发者快速引用外键模型的一个对象属性,不存在于mySQL中!!!
# backref 反向引用,类似django的related,通过外键模型查询主模型数据时的关联属性
info = db.relationship("StudentInfo", backref="own", uselist=False)
def __repr__(self):
return self.name
class StudentInfo(db.Model):
__tablename__ = "tb_student_info"
#外键
sid= db.Column(db.Integer,db.ForeignKey(Student.id), comment="学生")
id = db.Column(db.Integer, primary_key=True, comment="主键ID")
address = db.Column(db.String(255), nullable=True, comment="家庭住址")
mobile = db.Column(db.String(15), unique=True, comment="紧急联系电话")
def __repr__(self):
return self.own.name
一对一模型操作:
from flask import Flask,request,jsonify,render_template
from config import Config
from models import db,Student,Course,Teacher,StudentInfo
# 初始化
app = Flask(import_name=__name__,template_folder='templates')
app.config.from_object(Config)
db.init_app(app) # 初始化数据库链接
@app.route(rule='/')
def index():
"""1对1模型操作"""
# 获取数据[从主表读取数据,获取附加表数据]
# student = Student.query.get(3)
# print( student.info.address )
# print( student.info.edu )
# 获取数据[从附加表读取数据,获取主表数据]
# student_info = StudentInfo.query.filter(StudentInfo.address=="北京市昌平区沙河地铁站对面").first()
# print(student_info.own.name)
# 添加数据[添加数据,把关联模型的数据也一并添加]
# student = Student(name="liu", sex=True, age=22, email="33523@qq.com", money=100)
# student.info = StudentInfo(address="深圳市宝安区创业2路103号", edu="本科")
# db.session.add(student)
# db.session.commit()
# 修改数据[通过主表可以修改附加表的数据,也可以通过附加表模型直接修改主表的数据]
# student = Student.query.get(4)
# student.info.address = "广州市天河区天河东路103号"
# db.session.commit()
return "ok"
if __name__ == '__main__':
# 运行flask
app.run(debug=True)
测试数据:
INSERT INTO students.tb_student (id, name, sex, age, email, money) VALUES (3, 'li', 1, 17, '333@qq.com', 300.00);
INSERT INTO students.tb_student (id, name, sex, age, email, money) VALUES (4, 'wang', 0, 15, '123@qq.com', 300.00);
INSERT INTO students.tb_student (id, name, sex, age, email, money) VALUES (5, 'zhao', 0, 17, '333@baidu.com', 300.00);
INSERT INTO students.tb_student (id, name, sex, age, email, money) VALUES (6, 'long', 0, 18, '333@163.com', 300.00);
INSERT INTO students.tb_student (id, name, sex, age, email, money) VALUES (7, 'zhang', 1, 21, '333@sina.com.cn', 300.00);
INSERT INTO students.tb_student (id, name, sex, age, email, money) VALUES (8, 'liu', 1, 22, '33523@qq.com', 100.00);
INSERT INTO students.tb_student_info (id, address, edu, uid) VALUES (1, '天津市静海区静海路2号', '本科', 3);
INSERT INTO students.tb_student_info (id, address, edu, uid) VALUES (2, '广州市天河区天河东路103号', '高中以下', 4);
INSERT INTO students.tb_student_info (id, address, edu, uid) VALUES (3, '天津市静海区静海路2号', '本科', 5);
INSERT INTO students.tb_student_info (id, address, edu, uid) VALUES (4, '北京市昌平区沙河地铁站对面', '本科', 6);
INSERT INTO students.tb_student_info (id, address, edu, uid) VALUES (5, '天津市静海区静海路2号', '本科', 7);
INSERT INTO students.tb_student_info (id, address, edu, uid) VALUES (6, '深圳市宝安区创业2路103号', '本科', 8);
一对多
class Teacher(db.Model):
__tablename__ = "tb_teacher"
id = db.Column(db.Integer, primary_key=True, comment="主键ID")
name = db.Column(db.String(250), comment="姓名")
option = db.Column(db.Enum("讲师","助教","班主任"), default="讲师", comment="教职")
course_list = db.relationship("Course",uselist=True, backref="teacher",lazy="subquery")
def __repr__(self):
return self.name
class Course(db.Model):
__tablename__ = "tb_course"
id = db.Column(db.Integer, primary_key=True, comment="主键ID")
name = db.Column(db.String(250), unique=True, comment="课程名称")
price = db.Column(db.Numeric(6, 2))
teacher_id = db.Column(db.Integer, db.ForeignKey(Teacher.id),comment="老师")
def __repr__(self):
return self.name
- 其中realtionship描述了Course和Teacher的关系。第一个参数为对应参照的类"Course"
- 第二个参数backref为类Teacher申明新属性的方法
- 第三个参数lazy决定了什么时候SQLALchemy从数据库中加载关联模型的数据
- lazy='subquery',查询当前数据模型时,采用子查询(subquery),把外键模型的属性也瞬间查询出来了。
- lazy=True或lazy='select',查询当前数据模型时,不会把外键模型的数据查询出来,只有操作到外键关联属性时,才进行连表查询数据[执行SQL]
- lazy='dynamic',查询当前数据模型时,不会把外键模型的数据查询出来,只有操作到外键关联属性并操作外键模型具体属性时,才进行连表查询数据[执行SQL]
一对多模型操作
@app.route("/")
def index():
"""1对多,多对1"""
"""添加数据"""
# 添加主模型数据,同时也添加外键模型
# teacher = Teacher(
# name="灰太狼",
# option="班主任",
# course_list=[
# Course(name="抓羊",price="9.90"),
# Course(name="挨打",price="19.90"),
# Course(name="炸房子",price="29.90"),
# ]
# )
# db.session.add(teacher)
# db.session.commit()
#添加外键模型数据,同时也添加主键模型
# course = Course(
# name="平底锅108种用法",
# price="99.99",
# teacher=Teacher(name="红太狼",option="班主任")
# )
# db.session.add(course)
# db.session.commit()
"""查询数据"""
# teacher = Teacher.query.filter(Teacher.name=="灰太狼").first()
# print(teacher.name, teacher.option)
# print("---------------------------------------------------")
# print(teacher.course_list)
# for course in teacher.course_list:
# print(course.name)
# course = Course.query.filter(Course.name=="炸房子").first()
# print(course)
# print("%s在教%s" % (course.teacher.name,course.name))
"""更新数据"""
# teacher = Teacher.query.filter(Teacher.name == "灰太狼").first()
# teacher.course_list[0].name="抓懒洋洋"
# db.session.commit()
"""删除数据"""
# teacher = Teacher.query.filter(Teacher.name=="灰太狼").first()
# for course in teacher.course_list:
# db.session.delete(course)
# db.session.delete(teacher)
# db.session.commit()
return "Ok"
测试数据:
INSERT INTO students.tb_teacher (id, name, `option`) VALUES (1, '王老师', '讲师');
INSERT INTO students.tb_teacher (id, name, `option`) VALUES (2, '许老师', '讲师');
INSERT INTO students.tb_teacher (id, name, `option`) VALUES (3, '徐老师', '讲师');
INSERT INTO students.tb_teacher (id, name, `option`) VALUES (4, '张老师', '讲师');
INSERT INTO students.tb_teacher (id, name, `option`) VALUES (5, '留老师', '讲师');
INSERT INTO students.tb_course (id, name, price, teacher_id) VALUES (1, 'JAVA入门', 299.00, 1);
INSERT INTO students.tb_course (id, name, price, teacher_id) VALUES (2, 'Python入门', 399.00, 1);
INSERT INTO students.tb_course (id, name, price, teacher_id) VALUES (3, 'linux入门', 199.00, 2);
INSERT INTO students.tb_course (id, name, price, teacher_id) VALUES (4, 'django入门', 399.00, 3);
INSERT INTO students.tb_course (id, name, price, teacher_id) VALUES (5, 'flask入门', 299.00, 3);
INSERT INTO students.tb_course (id, name, price, teacher_id) VALUES (6, '数据分析入门', 199.00, 3);
INSERT INTO students.tb_course (id, name, price, teacher_id) VALUES (7, '爬虫入门', 299.00, 4);
INSERT INTO students.tb_course (id, name, price, teacher_id) VALUES (8, '前端入门', 199.00, 5);
INSERT INTO students.tb_course (id, name, price, teacher_id) VALUES (9, 'python项目', 199.00, 5);
多对多
两种实现:
1.以一个第三方表(db.Table)的存在,该表是不允许操作的(不能加其他字段)。表中存放的是多对多两张表中的外键。
2.拆解成两个1对多
"""学生和课程之间存在成绩关系"""
# db.Table 用于设置多对多模型之间关联的关系表,这种表,不能在代码中被操作,只是进行关系的记录!!!!
achievement = db.Table(
'tb_achievement', # 第一个参数就是表明,后续所有参数都是字段名
db.Column("student_id", db.Integer, db.ForeignKey("tb_student.id"),comment="学生"),
db.Column("course_id", db.Integer, db.ForeignKey("tb_course.id"),comment="课程"),
)
class Course(db.Model):
...
#这个关联关系写在关联的哪张表中都行
#secondary:指定多对多关系中关系表的名字。多对多关系中,需建立关系表,设置 secondary=关系表
student_list = db.relationship('Student', secondary=achievement, backref='course_list',lazy=True)
class Student(db.Model):
...
多对多操作
"""
多对多
"""
"""1. 添加主键模型, 同时把外键模型也添加"""
# student = Student(name="xiaoming", age=17,sex=True,money=10000,email="1231@qq.com")
# student.course_list = [
# Course(name="3天python入门",price=39.99),
# Course(name="6天python入门",price=89.99),
# ]
# db.session.add(student)
# db.session.commit()
"""2. 查询其中一方模型数据,把多的另一方也查询出来"""
student = Student.query.filter(Student.name=="xiaoming").first()
print(student.course_list) # [3天python入门, 6天python入门]
course = Course.query.filter(Course.name=="6天python入门").first()
print(course.student_list) # [xiaoming]
"""3. 更新操作"""
# student = Student.query.filter(Student.name == "xiaoming").first()
# student.course_list[0].price=666.99
# db.session.commit()
"""4. 删除操作"""
# student = Student.query.filter(Student.name == "xiaoming").first()
# db.session.delete(student) # 删除主模型信息,会自动同步删除关联数据表中的信息,但是外键模型不会被删除
# db.session.commit()
多对多,也可以拆解成两个1对多,其中tb_achievement作为单独模型存在。
class Achievement(db.Model):
"""学生和课程之间的成绩关系模型"""
__tablename__ = 'tb_achievement'
id = db.Column(db.Integer, primary_key=True)
student_id = db.Column(db.Integer,db.ForeignKey(Student.id), comment="学生")
course_id = db.Column(db.Integer,db.ForeignKey(Course.id), comment="课程")
def __repr__(self):
return '%s-%s' % (self.student,self.course)
class Student(db.Model):
...
course_list = db.relationship("Achievement", backref="student", lazy=True)
class Course(db.Model):
...
student_list = db.relationship("Achievement", backref="course",lazy=True)
对对多操作
"""
多对多
"""
"""1. 添加主键模型,给外键模型添加数据"""
# course1 = Course(name="3天Python入门",price=99.99)
# course2 = Course(name="7天Python入门",price=399.99)
# db.session.add_all([course1,course2])
#
# student = Student(name="xiaoming",age=17,sex=True, email="64123@qq.com",money=10000)
# student.course_list = [
# Achievement(course=course1),
# Achievement(course=course2),
# ]
# db.session.add(student)
# db.session.commit()
#
"""2. 查询操作"""
student = Student.query.filter(Student.name=="xiaoming").first()
for achievement in student.course_list:
print(achievement.course.name)
数据库迁移
就是一种记录数据库修改版本的一种技术
- 在开发中经常会遇到需要修改原来的数据库模型,修改之后更新数据库,最简单粗暴的方式就是删除旧表,然后在增加新表,这样做的缺点是会造成数据丢失。
- 更好的解决办法是使用数据库迁移框架,它可以追踪数据库模式的变化,然后把变动应用到数据库中。
- 在Flask中可以使用Flask-Migrate扩展,来实现数据迁移。并且集成到Flask-Script中,所有操作通过命令就能完成。
- 为了导出数据库迁移命令,Flask-Migrate提供了一个MigrateCommand类,可以附加到flask-script的manager对象上。
首先要在虚拟环境中安装Flask-Migrate。
pip install flask-migrate
代码文件内容:在此之前可以先手动删除所有库表
#!/usr/bin/python
#coding:utf-8
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from flask_script import Manager
from flask_migrate import Migrate,MigrateCommand
"""
flask_migrate作用:
1、通过命令的方式创建表
2、修改表的结构
"""
class Config(object):
SQLALCHEMY_DATABASE_URI = "mysql://root:@127.0.0.1:3306/book"
# 这个值可以设置,也可以不设置,如果不设置,那么会一直报警告
SQLALCHEMY_TRACK_MODIFICATIONS = False
# 这里有个坑,下次再讲。在lask-sqlalchemy 2.0之后就不在需要设置这一项了。
SQLALCHEMY_COMMIT_ON_TEARDOWN = True
app = Flask(__name__)
app.config.from_object(Config)
db = SQLAlchemy(app)
manager = Manager(app)
# 第一个参数是flask实例,第二个参数SQLAlchemy实例
Migrate(app, db)
#manager是Flask-Script的实例,这条语句在flask-Script中添加一个db命令
manager.add_command("db", MigrateCommand)
class Role(db.Model):
__tablename__ = "roles"
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(128))
title = db.Column(db.String(128))
us = db.relationship("User",backref="role")
class User(db.Model):
__tablename__="users"
id = db.Column(db.Integer,primary_key=True)
name = db.Column(db.String(128))
email = db.Column(db.String(128))
password = db.Column(db.String(128))
role_id = db.Column(db.Integer,db.ForeignKey("roles.id"))
@app.route("/")
def index():
return "index"
if __name__ == '__main__':
manager.run()
迁移数据库三步走:
第一步:创建迁移版本仓库
#这个命令会创建migrations文件夹,所有迁移文件都放在里面。
python main.py db init
这个命令会创建migrations文件夹,所有迁移文件都放在里面。这里只是创建了迁移仓库,表还没创建。
第二步:创建迁移版本
- 自动创建迁移版本有两个函数
- upgrade():函数把迁移中的改动应用到数据库中。
- downgrade():函数则将改动删除。
- 自动创建的迁移脚本会根据模型定义和数据库当前状态的差异,生成upgrade()和downgrade()函数的内容。
- 对比不一定完全正确,有可能会遗漏一些细节,需要进行检查
python main.py db migrate -m 'xxoo'
# 这里等同于django里面的 makemigrations,生成迁移版本文件
运行命令之后,只是在migrations文件夹中新增了数据库迁移的版本文件并在数据库中创建了版本号,并没有生成对应的表。
第三步:更新数据库
python main.py db upgrade
指令执行后就已经创建表成功了。
当然可以将roles表中的title字段删除:(修改表结构,然后使用migrate跟踪记录)
- 将代码中的title删除,然后再次执行迁移脚本命令:python mian.py db migrate -m 'del col title'
- 执行更新数据库操作:python migrate.py db upgrade
降级版本库的版本
python main.py db downgrade
版本库的历史管理
可以根据history命令找到版本号,然后传给downgrade命令:
python manage.py db history
输出格式:<base> -> 版本号 (head), initial migration
回滚到指定版本
python manage.py db downgrade # 默认返回上一个版本
python manage.py db downgrade 版本号 # 返回到指定版本号对应的版本
数据迁移的步骤:
1. 初始化数据迁移的目录
python manage.py db init
2. 数据库的数据迁移版本初始化
python manage.py db migrate -m 'initial migration'
3. 升级版本[创建表/创建字段/修改字段]
python manage.py db upgrade
4. 降级版本[删除表/删除字段/恢复字段]
python manage.py db downgrade
模块推荐
文档: https://faker.readthedocs.io/en/master/locales/zh_CN.html
批量生成测试数据: https://github.com/joke2k/faker
flask-session
前言:
当flask服务客户的体量比较大的时候,则需要增加服务器的数量,如果现在有多台服务器,然后用户每次请求时随机分配服务器。那么在使用session进行用户校验就会出现问题。
原生session是相对安全的,如果screct_key暴露了,则session就会被破解。
因此:
可以使用flask-session组件,允许设置session到指定存储的空间(redis数据库)中。即可以让多台服务器共享使用同一个session,又可以保证session的安全。
之所以存储在redis是因为redis是一个基于缓存的数据库,读写速度极快。
安装命令:
pip install flask-Session
使用session之前,必须配置一下配置项:
SECRET_KEY = "*(%#4sxcz(^(#$#8423" # session秘钥
redis保存session的基本配置
配置文件信息:
import redis
class Config(object):
DEBUG = True
SECRET_KEY = "*(%#4sxcz(^(#$#8423"
# 把session保存到redis中
# session存储方式为redis
SESSION_TYPE="redis"
# 如果设置session的生命周期是否是会话期, 为True,则关闭浏览器session就失效
SESSION_PERMANENT = True
# 是否对发送到浏览器上session的cookie值进行加密
SESSION_USE_SIGNER = True
# 保存到redis的session的名称前缀
SESSION_KEY_PREFIX = "session:"
# session保存数据到redis时启用的链接对象
SESSION_REDIS = redis.Redis(host='127.0.0.1', port='6379') # 用于连接redis的配置
主文件信息main.py,代码:
from flask import Flask
from config import Config
from flask_session import Session
from flask import session
app = Flask(__name__,template_folder='templates')
app.config.from_object(Config)
Session(app)
@app.route("/")
def index():
return "ok"
@app.route("/set_session")
def set_session():
"""设置session"""
session["username"] = "小明"
return "ok"
if __name__ == '__main__':
app.run()
蓝图 Blueprint
模块化
由于项目开发是一个非常耗时间和精力的工程,如果我们将所有的Flask请求方法都写在同一个文件下的话,非常不便于我们代码管理和后期功能代码的添加。
随着flask程序越来越复杂,我们需要对程序进行模块化的处理,如果学过django可能了解过django中的app,django中app的主要作用就是将django项目分成一个个单独的app模块,然后将所有app分配不同的处理功能,通过路由分配将它们连接成一个大django项目,其实Flask中的蓝图和django中的app功能大同小异。
简单来说,Blueprint 是一个存储视图方法的容器,这些操作在这个Blueprint 被注册到一个应用之后就可以被调用,Flask 可以通过Blueprint来组织URL以及处理请求。
Blueprint对象用起来和一个应用/Flask对象差不多,最大的区别在于一个 蓝图对象没有办法独立运行,必须将它注册到一个应用对象上才能生效。
蓝图实践
创建一个应用文件夹student,在内添加一个s_view.py源文件
# 导入Flask中的蓝图Blueprint模块
from flask import Flask, Blueprint
# 实例化一个蓝图(Blueprint)对象
stu_blue = Blueprint("stu", __name__)
# 这里添加路由和视图函数的时候与在Flask对象中添加是一样的
@stu_blue.route("/stu")
def view_list():
return "this is stu_blue"
创建一个manager.py主文件
from flask import Flask
# 导入此前写好的蓝图模块
from student import s_view
app = Flask(__name__)
# 在Flask对象中注册蓝图模块中的蓝图对象s_view中的sbp
app.register_blueprint(s_view.stu_blue)
if __name__ == '__main__':
app.run("0.0.0.0", 5000, debug=True)
在主文件中启动服务,通过蓝图文件中的路由地址进行网络请求。
根据上述操作其实我们把蓝图可以理解成一个没有run方法的Flask对象,这个理论虽然有很多的漏洞,但是对于刚接触蓝图的你来说,就这么样理解,没有错。
当然,也可以多创建几个应用,操作同上。
下面来看一下,在实例化蓝图的时候可以传递的参数都有什么:
from flask import Blueprint, render_template
# template_folder:每个蓝图都可以为自己独立出一套template模板文件夹,如果不写则共享项目目录中的templates
# static_folder:静态文件目录也是可以独立出来的
stu = Blueprint("stu", __name__,
template_folder="stu_template",
static_folder="stu_static",
) # 实例化一个蓝图Blueprint对象
@sbp.route("/stu")
def view_list():
return render_template("stu.html")
本文来自博客园,作者:长情不羁的五年,转载请注明原文链接:https://www.cnblogs.com/grlend/articles/14408776.html

浙公网安备 33010602011771号