目录
一、什么是ORM
关系映射
二、Flask-sqlAlchemy安装及设置
1. 安装
- 安装 flask-sqlalchemy
pip install flask-sqlalchemy
pip install flask-MysqLdb
2. 数据库连接设置
# 数据库链接地址
app.config['sqlALCHEMY_DATABASE_URI'] = 'MysqL://root:MysqL@127.0.0.1:3306/test'
# 动态追踪修改设置,如未设置只会提示警告
app.config['sqlALCHEMY_TRACK_MODIFICATIONS'] = True
$ MysqL -uroot -pMysqL
$ create database test charset utf8;
from flask import Flask
from flask_sqlalchemy import sqlAlchemy
app = Flask(__name__)
#2.设置数据库的配置信息
#设置数据库的链接信息,app.config["sqlALCHEMY_DATABASE_URI"] = "MysqL+pyMysqL://root:123456@127.0.0.1:3306/test"
#该字段增加了大量的开销,会被禁用,建议设置为False
app.config["sqlALCHEMY_TRACK_MODIFICATIONS"] = False
#3.创建sqlalchemy对象db,关联app
db = sqlAlchemy(app)
# 4.编写模型类,字段,继承自db.Model
class Student(db.Model):
__tablename__ = "students"
#主键,参数1: 表示id的类型,参数2: 表示id的约束类型
id = db.Column(db.Integer,primary_key=True)
name = db.Column(db.String(32))
@app.route('/')
def hello_world():
return "helloworld"
if __name__ == '__main__':
#删除继承自db.Model的表
db.drop_all()
#5.创建数据库的表,创建的是继承自db.Model的表
db.create_all()
app.run(debug=True)
三、数据库基本操作
1. 增删改
"""
增删改
- 全部都是使用db.session操作
- 常见方法:
- db.session.add(obj) 添加单个对象
- db.session.add_all([obj1,obj2]) 添加多个对象
- db.session.delete(obj) 删除单个对象
- db.session.commit() 提交会话
- db.drop_all() 删除继承自db.Model所有表
- db.create_all() :创建继承自db.Model的所有表
- 其他:
- db.session.rollback() 回滚
- db.session.remove() 移除会话
- 案例: 编写两个模型类,一个角色模型类,还有一个用户模型类
- 关系: 一对多
"""
from flask import Flask
from flask_sqlalchemy import sqlAlchemy
app = Flask(__name__)
#1.设置数据库的配置信息
app.config["sqlALCHEMY_DATABASE_URI"] = "MysqL+pyMysqL://root:123456@127.0.0.1:3306/test"
app.config["sqlALCHEMY_TRACK_MODIFICATIONS"] = False
#2.创建sqlalchemy对象,关联app
db = sqlAlchemy(app)
#3.编写模型类
#角色(一方)
class Role(db.Model):
__tablename__ = "roles"
id = db.Column(db.Integer,primary_key=True)
name = db.Column(db.String(32))
#如果一个类继承自object那么重写__str__方法即可,如果是继承自db.Model那么需要重写__repr__方法
def __repr__(self):
return "<Role:%s>"%self.name
#用户(多方)
class User(db.Model):
__tablename__ = "users"
id = db.Column(db.Integer,primary_key=True)
name = db.Column(db.String(32))
#建立外键
role_id = db.Column(db.Integer,db.ForeignKey(Role.id))
#如果一个类继承自object那么重写__str__方法即可,如果是继承自db.Model那么需要重写__repr__方法
def __repr__(self):
return "<User:%s>"%self.name
@app.route('/')
def hello_world():
return "helloworld"
if __name__ == '__main__':
#为了演示方便,先删除表,后创建
db.drop_all()
db.create_all()
app.run(debug=True)
使用ipython进行测试,前提是先进行安装
增
改
删
2. 查询
# -*- coding = utf-8 -*-
# @Time : 2020/10/2 10:15
# @Author : md
'''
查询练习
'''
from flask import Flask
from flask_sqlalchemy import sqlAlchemy
app = Flask(__name__)
# 1.设置数据库的配置信息
app.config["sqlALCHEMY_DATABASE_URI"] = "MysqL+pyMysqL://root:123456@127.0.0.1:3306/test"
app.config["sqlALCHEMY_TRACK_MODIFICATIONS"] = False
# app.config["sqlALCHEMY_ECHO"] = True
# 2.创建sqlalchemy对象,关联app
db = sqlAlchemy(app)
# 3.编写模型类
# 角色(一方)
class Role(db.Model):
__tablename__ = "roles"
id = db.Column(db.Integer,primary_key=True)
name = db.Column(db.String(32))
# 关系数据写在这个表中,也就是写在一方这个表中
# 为了查询方便,不会产生实体字段
# 给role添加了一个users属性,那么查询的方式是,role.users
# 给user添加了一个role属性,user.role
# 重点 建立关系,这里的名字为想要建立关系的模型名字
# 解释:前半句话是给Role(本模型)模型添加一个users属性,因为这两个数据库通外键连接
# 所以,可以通过这个表的对象,就可以访问User表中的数据,例如:查看角色是admin的所有用户role.users
# 后半句是给本(自己)模型添加一个role属性,这样User表中的对象,就可以访问本表中的数据,可以知道某个用户是什么身份
# 例如查看用户的身份,user.role
# lazy="dynamic" 是懒加载
# backref反向引用
users = db.relationship("User",backref="role",lazy="dynamic")
# 如果一个类继承自object那么重写__str__方法即可,如果是继承自db.Model那么需要重写__repr__方法
def __repr__(self):
return "<Role:%s>" % self.name
# 用户(多方)
class User(db.Model):
__tablename__ = "users"
id = db.Column(db.Integer,primary_key=True)
name = db.Column(db.String(32))
email = db.Column(db.String(32))
password = db.Column(db.String(32))
# 建立外键
role_id = db.Column(db.Integer,db.ForeignKey(Role.id))
# 如果一个类继承自object那么重写__str__方法即可,如果是继承自db.Model那么需要重写__repr__方法
def __repr__(self):
return "<User:%s,%s,%s>" % (self.id,self.name,self.email,self.password)
@app.route('/')
def hello_world():
return "helloworld"
if __name__ == '__main__':
# 为了演示方便,后创建
db.drop_all()
db.create_all()
# 创建测试数据
ro1 = Role(name='admin')
db.session.add(ro1)
db.session.commit()
# 再次插入一条数据
ro2 = Role(name='user')
db.session.add(ro2)
db.session.commit()
# 多条用户数据
us1 = User(name='wang',email='wang@163.com',password='123456',role_id=ro1.id)
us2 = User(name='zhang',email='zhang@189.com',password='201512',role_id=ro2.id)
us3 = User(name='chen',email='chen@126.com',password='987654',role_id=ro2.id)
us4 = User(name='zhou',email='zhou@163.com',password='456789',role_id=ro1.id)
us5 = User(name='tang',email='tang@itheima.com',password='158104',role_id=ro2.id)
us6 = User(name='wu',email='wu@gmail.com',password='5623514',role_id=ro2.id)
us7 = User(name='qian',email='qian@gmail.com',password='1543567',role_id=ro1.id)
us8 = User(name='liu',email='liu@itheima.com',password='867322',role_id=ro1.id)
us9 = User(name='li',email='li@163.com',password='4526342',role_id=ro2.id)
us10 = User(name='sun',email='sun@163.com',password='235523',role_id=ro2.id)
db.session.add_all([us1,us2,us3,us4,us5,us6,us7,us8,us9,us10])
db.session.commit()
app.run(debug=True)
- 其中realtionship描述了Role和User的关系。
- 第一个参数为对应参照的类"User"
- 第二个参数backref为类User,反向引用属性
第三个参数lazy决定了什么时候sqlALchemy从数据库中加载数据
- 如果设置为子查询方式(subquery),则会在加载完Role对象后,就立即加载与其关联的对象,这样会让总查询数量减少,但如果返回的条目数量很多,就会比较慢
-
- 设置为 subquery 的话,role.users 返回所有数据列表
- 另外,也可以设置为动态方式(dynamic),这样关联对象会在被使用的时候再进行加载,并且在返回前进行过滤,如果返回的对象数很多,或者未来会变得很多,那最好采用这种方式
User.query.filter().all()
3. 查询练习
查询所有用户数据
User.query.filter().all()
此时过滤器可以不写
User.query.all()
查询有多少个用户
User.query.count()
查询第1个用户
User.query.first()
查询id为4的用户[3种方式]
User.query.get(4) 此时get里面是主键
User.query.filter(User.id == 4).all() 返回的是列表,但满足的只有一个
User.query.filter(User.id == 4).first()
User.query.filter_by(id=4).first()
查询名字结尾字符为g的所有数据[开始/结尾/包含]
User.query.filter(User.name.startswith('g')).all()
User.query.filter(User.name.endswith('g')).all()
User.query.filter(User.name.contains('g')).all()
查询名字不等于wang的所有数据
User.query.filter(User.name != 'wang').all()
查询名字和邮箱都以 li 开头的所有数据
User.query.filter(User.name.startswith('li'),User.email.startswith('li')).all()
查询password是 123456 或者 email 以 itheima.com 结尾的所有数据
from sqlalchemy import or_
User.query.filter(or_(User.password == '123456',User.email.endswith('itheima.com'))).all()
查询id为 [1,3,5,7,9] 的用户列表
User.query.filter(User.id.in_([1,9])).all()
查询name为liu,的角色数据
user = User.name.filter(User.name == 'liu').first()
role = Role.query.filter(Role.id == user.role_id).first()
查询所有用户数据,并以邮箱排序
User.query.order_by(User.email).all()
User.query.order_by(User.email.desc()).all()
每页3个,查询第2页的数据
#page: 表示要查询的页数
#per_page: 表示每页有多少条数据
#Error_out: 建议写成False,查不到不会报错
paginate = User.query.paginate(page,per_page,Error_out)
paginate.pages #总页数
paginate.page #当前页
paginate.items #当前的对象列表
查询前两条数据
User.query.limit(2).all()
lazy="dynamic"
四、综合案例-图书管理
目的:
- 表单创建
- 数据库操作
- 一对多关系演练
实现步骤:
1. 图书馆测试数据显示
2. 图书馆添加数据
3. 图书馆删除书籍
4. 图书馆删除作者
5. 图书馆CSRFProtect应用
- 作用: 防止csrf攻击的
- 使用步骤:
- 1.导入类CSRFProtect
- 2.使用CSRFProtect保护app
- 一旦使用POST,PUT,DELTE,PATCH方式提交的时候就需要校验csrf_token
- 3.需要设置SECRET_KEY,用来加密csrf_token
- 4.设置csrf_token到表单中
6. 表单的创建
# -*- coding = utf-8 -*-
# @Time : 2020/10/2 16:07
# @Author : md
from flask import Flask,render_template,request,redirect,flash
from flask_sqlalchemy import sqlAlchemy
from flask_wtf.csrf import CSRFProtect
app = Flask(__name__)
# 由于使用了flash,所以得设置
app.config["SECRET_KEY"] = "wepricsjf"
# 使用 CSRFProtect保护app
CSRFProtect(app)
# 1.设置数据库的配置信息
app.config["sqlALCHEMY_DATABASE_URI"] = "MysqL+pyMysqL://root:123456@127.0.0.1:3306/test"
app.config["sqlALCHEMY_TRACK_MODIFICATIONS"] = False
# 生成对应的sql语句在控制台
# app.config["sqlALCHEMY_ECHO"] = True
# 2.创建sqlalchemy对象,关联app
db = sqlAlchemy(app)
# 3.编写模型类
# 作者(一方)
class Author(db.Model):
__tablename__ = "authors"
id = db.Column(db.Integer,primary_key=True)
name = db.Column(db.String(32))
# 关系数据,必须有外键为基础
books = db.relationship("Book",backref="author")
# 如果一个类继承自object那么重写__str__方法即可,如果是继承自db.Model那么需要重写__repr__方法
def __repr__(self):
return "<Author:%s>" % self.name
# 书籍(多方)
class Book(db.Model):
__tablename__ = "books"
id = db.Column(db.Integer,primary_key=True)
name = db.Column(db.String(32))
# 建立外键
author_id = db.Column(db.Integer,db.ForeignKey(Author.id))
# 如果一个类继承自object那么重写__str__方法即可,如果是继承自db.Model那么需要重写__repr__方法
def __repr__(self):
return "<Book:%s,self.name)
# 5. 展示数据
@app.route('/')
def hello_world():
# 1. 查询所有的作者信息,因为作者有外键,可以通过关系数据方便的查出该作者对应的书籍
authors = Author.query.all()
return render_template("library.html",authors=authors)
# 6. 添加数据
@app.route("/add_data",methods=["POST"])
def add_data():
# 1. 获取提交的数据
author_name = request.form.get("author")
book_name = request.form.get("book")
# 先判断输入的内容是否为空
if not all([author_name,book_name]):
flash("作者或书籍不能为空")
return redirect("/")
# 2. 根据作者信息查询作者对象
author = Author.query.filter(Author.name == author_name).first()
# 3. 判断作者是否存在
if author:
# 4. 通过书籍名称查询书籍对象,并且这本书的作者的id和查询出来的作者的id一样
# 也就是查看要添加的这本书是不是该作者写的
book = Book.query.filter(Book.name == book_name,Book.author_id == Author.id).first()
# 5. 判断书籍是否存在
if book:
# return "该作者已经写了这本书了"
flash("已经有该作者写的这本书了")
else:
# 创建书籍对象,添加到数据库
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()
# 6. 重定向到首页
return redirect("/")
# 7. 删除书籍
@app.route("/delete_book/<int:book_id>")
def delete_book(book_id):
# 1. 根据id获取到书籍对象
book = Book.query.get(book_id)
# 2. 删除这个书籍
db.session.delete(book)
db.session.commit()
# 3. 重定向到页面
return redirect("/")
# 7. 删除作者
@app.route("/delete_author/<int:author_id>")
def delete_author(author_id):
# 1. 根据id获取到作者对象
author = Author.query.get(author_id)
# 2. 这个作者的全部书籍,由于使用了关系数据,直接这样写就可以
books = author.books
# 3. 遍历这个作者的全部书籍
for book in books:
db.session.delete(book)
# 4. 删除作者
db.session.delete(author)
db.session.commit()
# 5. 重定向到页面
return redirect("/")
if __name__ == '__main__':
# 为了演示方便,先删除后创建
db.drop_all()
db.create_all()
# 4. 添加测试数据库
# 生成数据
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()
app.run()
7. library.html
<!DOCTYPE html>
<html lang="en">
<head>
<Meta charset="UTF-8">
<title>Title</title>
</head>
<body>
{# action: 提交到的地址,method: 表示提交的方式 #}
<form action="/add_data" method="post">
{# 设置隐藏字段csrf_token,只要使用了CSRFProtect,然后使用模板渲染的时候就可以直接使用csrf_token()方法#}
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
作者: <input type="text" name="author"><br>
书籍: <input type="text" name="book"><br>
<input type="submit" value="添加"><br>
{% for message in get_flashed_messages() %}
<span style="color: red;">{{ message }}</span>
{% endfor %}
</form>
<hr>
{# 数据展示 #}
<ul>
{# 遍历作者 #}
{% for author in authors %}
{# <li>作者: {{ author.name }}</li>#}
{# <li>作者: {{ author.name }} <a href="/delete_author/{{ author.id }}">删除</a></li>#}
<li>作者: {{ author.name }} <a href="{{ url_for("delete_author",author_id=author.id) }}">删除</a></li>
{# 遍历作者的书籍 #}
<ul>
{% for book in author.books %}
{# <li>书籍: {{ book.name }} </li>#}
<li>书籍: {{ book.name }} <a href="/delete_book/{{ book.id }}">删除</a></li>
{% endfor %}
</ul>
{% endfor %}
</ul>
</body>
</html>
五、多对多
在项目开发过程中,会遇到很多数据之间多对多关系的情况,比如:
所以在开发过程中需要使用 ORM 模型将表与表的多对多关联关系使用代码描述出来。多对多关系描述有一个唯一的点就是:需要添加一张单独的表去记录两张表之间的对应关系
1. 需求分析
- 学生可以网上选课,学生有多个,课程也有多个
- 学生有:张三、李四、王五
- 课程有:物理、化学、生物
- 选修关系有:
-
- 张三选修了化学和生物
- 李四选修了化学
- 王五选修了物理、化学和生物
需求:
2. 代码
# -*- coding = utf-8 -*-
# @Time : 2020/10/2 19:30
# @Author : md
'''
多对多,学生和课程
'''
from flask import Flask
from flask_sqlalchemy import sqlAlchemy
app = Flask(__name__)
# 1.设置数据库的配置信息
app.config["sqlALCHEMY_DATABASE_URI"] = "MysqL+pyMysqL://root:123456@127.0.0.1:3306/test"
app.config["sqlALCHEMY_TRACK_MODIFICATIONS"] = False
# app.config["sqlALCHEMY_ECHO"] = True
# 2.创建sqlalchemy对象,关联app
db = sqlAlchemy(app)
# 3.编写模型类
# 学生
class Student(db.Model):
__tablename__ = "students"
id = db.Column(db.Integer,使用在多对多中的时候注意secondary这个属性,是中间表的表名,用来二次查询
courses = db.relationship("Course",backref="students",secondary="td_student_course")
# 如果一个类继承自object那么重写__str__方法即可,如果是继承自db.Model那么需要重写__repr__方法
def __repr__(self):
return "<Role:%s>" % self.name
# 课程
class Course(db.Model):
__tablename__ = "courses"
id = db.Column(db.Integer,primary_key=True)
name = db.Column(db.String(32))
# 如果一个类继承自object那么重写__str__方法即可,如果是继承自db.Model那么需要重写__repr__方法
def __repr__(self):
return "<User:%s>" % self.name
# 中间表
db.Table(
"td_student_course",db.Column("student_id",db.Integer,db.ForeignKey(Student.id)),db.Column("course_id",db.ForeignKey(Course.id))
)
@app.route('/')
def hello_world():
return "helloworld"
# 为了演示,先删除后创建
db.drop_all()
db.create_all()
stu1 = Student(name='张三')
stu2 = Student(name='李四')
stu3 = Student(name='王五')
cou1 = Course(name='物理')
cou2 = Course(name='化学')
cou3 = Course(name='生物')
stu1.courses = [cou2,cou3]
stu2.courses = [cou2]
stu3.courses = [cou1,cou2,cou3]
db.session.add_all([stu1,stu2,stu2])
db.session.add_all([cou1,cou3])
db.session.commit()
if __name__ == '__main__':
app.run()
六、数据库迁移
- 目的: 当数据库的表结构发生变化之后,如果直接删除原有的数据,再添加新的数据,有可能导致数据丢失
- 注意点:
- 操作流程:
- 1.安装扩展
- pip install flask_script
- pip install flask_migrate
- 2.导入三个类
- from flask_script import Manager
- from flask_migrate import Migrate,MigrateCommand
- 3.通过Manager类创建对象manager,管理app
- manager = Manager(app)
- 4.使用Migrate,关联db,app
- Migrate(app,db)
- 5.给manager添加一条操作命令
- manager.add_command("db",MigrateCommand)
- 相关迁移命令:
- 1.安装扩展
# -*- coding = utf-8 -*-
# @Time : 2020/10/2 20:38
# @Author : md
from flask import Flask
from flask_script import Manager
from flask_migrate import Migrate,MigrateCommand
from flask_sqlalchemy import sqlAlchemy
app = Flask(__name__)
# 设置数据库配置信息
app.config["sqlALCHEMY_DATABASE_URI"] = "MysqL+pyMysqL://root:123456@localhost:3306/test1"
app.config["sqlALCHEMY_TRACK_MODIFICATIONS"] = False
# 创建sqlAlchemy对象,关联app
db = sqlAlchemy(app)
# 3. 通过Manager类创建对象manager,管理app
manager = Manager(app)
# 4.使用Migrate,app
Migrate(app,db)
# 5.给manager添加一条操作命令
manager.add_command("db",MigrateCommand)
# 6.编写模型类
class Student(db.Model):
id = db.Column(db.Integer,primary_key=True)
name = db.Column(db.String(32))
age = db.Column(db.Integer)
# email = db.Column(db.String(32))
@app.route('/')
def hello_world():
return "helloworld"
if __name__ == '__main__':
manager.run()
将模型类生成迁移脚本
将迁移脚本更新到数据库中
此时就可以在数据库中看到对应的表了
然后继续执行这两条命令,就会看到数据表结构中就多了一列
还可以进行降级
然后再升级到指定的版本
原文链接:/flask/993906.html