目录
一、什么是ORM
-
ORM 全拼
Object-Relation Mapping
. 称为对象-关系映射 - 主要实现模型对象到关系数据库数据的映射.
- 比如:把数据库表中每条记录映射为一个模型对象
关系映射
二、Flask-SQLAlchemy安装及设置
1. 安装
- 安装 flask-sqlalchemy
<code>pip install flask-sqlalchemy </code>
www#gaodaima.com来源gao($daima.com搞@代@#码(网搞代码
- 如果连接的是 mysql 数据库,需要安装 mysqldb
<code>pip install flask-mysqldb </code>
提示: 如果flask-mysqldb安装不上,推荐安装, pip install pymysql
2. 数据库连接设置
- 设置数据库的链接地址,追踪信息
- 格式:mysql://<用户名>:<密码>@:<端口>/数据库名称
<code># 数据库链接地址 app.config["SQLALCHEMY_DATABASE_URI"] = "mysql://root:[email protected]:3306/test" # 动态追踪修改设置,如未设置只会提示警告 app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = True </code>
查看映射的sql语句,设置: app.config[“SQLALCHEMY_ECHO”] = True
- 配置完成需要去 MySQL 中创建项目所使用的数据库
<code>$ mysql -uroot -pmysql $ create database test charset utf8; </code>
<code class="language-python">from flask import Flask from flask_sqlalchemy import SQLAlchemy app = Flask(__name__) #2.设置数据库的配置信息 #设置数据库的链接信息, app.config["SQLALCHEMY_DATABASE_URI"] = "mysql+pymysql://root:[email protected]: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) </code>
三、数据库基本操作
- 在Flask-SQLAlchemy中,插入、修改、删除操作,均由数据库会话管理。
- 会话用 db.session 表示。在准备把数据写入数据库前,要先将数据添加到会话中然后调用 db.session.commit() 方法提交会话。
- 在 Flask-SQLAlchemy 中,查询操作是通过 query 对象操作数据。
- 最基本的查询是返回表中所有数据,可以通过过滤器进行更精确的数据库查询。
1. 增删改
<code class="language-python">""" 增删改 - 全部都是使用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:[email protected]: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) </code>
使用ipython进行测试,前提是先进行安装
增
改
删
2. 查询
<code class="language-python"># -*- 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:[email protected]: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,%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="[email protected]", password="123456", role_id=ro1.id) us2 = User(name="zhang", email="[email protected]", password="201512", role_id=ro2.id) us3 = User(name="chen", email="[email protected]", password="987654", role_id=ro2.id) us4 = User(name="zhou", email="[email protected]", password="456789", role_id=ro1.id) us5 = User(name="tang", email="[email protected]", password="158104", role_id=ro2.id) us6 = User(name="wu", email="[email protected]", password="5623514", role_id=ro2.id) us7 = User(name="qian", email="[email protected]", password="1543567", role_id=ro1.id) us8 = User(name="liu", email="[email protected]", password="867322", role_id=ro1.id) us9 = User(name="li", email="[email protected]", password="4526342", role_id=ro2.id) us10 = User(name="sun", email="[email protected]", 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) </code>
- 其中realtionship描述了Role和User的关系。
- 第一个参数为对应参照的类”User”
- 第二个参数backref为类User,反向引用属性
第三个参数lazy决定了什么时候SQLALchemy从数据库中加载数据
- 如果设置为子查询方式(subquery),则会在加载完Role对象后,就立即加载与其关联的对象,这样会让总查询数量减少,但如果返回的条目数量很多,就会比较慢
-
- 设置为 subquery 的话,role.users 返回所有数据列表
- 另外,也可以设置为动态方式(dynamic),这样关联对象会在被使用的时候再进行加载,并且在返回前进行过滤,如果返回的对象数很多,或者未来会变得很多,那最好采用这种方式
User.query.filter().all()
3. 查询练习
<code class="language-mysql">查询所有用户数据 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,3,5,7,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() </code>
lazy=”dynamic”
四、综合案例-图书管理
目的:
- 表单创建
- 数据库操作
- 一对多关系演练
实现步骤:
- 1.创建数据库配置信息,定义模型类
- 2.创建数据库表,添加测试数据
- 3.编写html页面,展示数据
- 4.添加数据
- 5.删除书籍,删除作者
1. 图书馆测试数据显示
- 步骤
- 1.查询所有作者信息
- 2.携带作者信息,渲染页面
2. 图书馆添加数据
- 添加的逻辑分析:
- 1.如果作者存在,书籍存在, 不能添加
- 2.如果作者存在,书籍不存在,可以添加
- 3.如果作者不存在,可以添加
3. 图书馆删除书籍
- 步骤
- 1.根据书籍编号获取书籍对象
- 2.删除书籍对象
- 3.重定向到页面展示
4. 图书馆删除作者
- 步骤
- 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. 表单的创建
<code class="language-python"># -*- 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:[email protected]: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,%s>" % (self.id, 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() </code>
7. library.html
<code class="language-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> </code>
五、多对多
在项目开发过程中,会遇到很多数据之间多对多关系的情况,比如:
- 学生网上选课(学生和课程)
- 老师与其授课的班级(老师和班级)
- 用户与其收藏的新闻(用户和新闻)
- 等等…
所以在开发过程中需要使用 ORM 模型将表与表的多对多关联关系使用代码描述出来。多对多关系描述有一个唯一的点就是:需要添加一张单独的表去记录两张表之间的对应关系
1. 需求分析
- 学生可以网上选课,学生有多个,课程也有多个
- 学生有:张三、李四、王五
- 课程有:物理、化学、生物
- 选修关系有:
-
- 张三选修了化学和生物
- 李四选修了化学
- 王五选修了物理、化学和生物
需求:
- 查询某个学生选修了哪些课程
- 查询某个课程都有哪些学生选择
2. 代码
<code class="language-python"># -*- 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:[email protected]: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, primary_key=True) name = db.Column(db.String(32)) # 关系数据,使用在多对多中的时候注意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.Integer, 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, cou2, cou3]) db.session.commit() if __name__ == "__main__": app.run() </code>
六、数据库迁移
- 目的: 当数据库的表结构发生变化之后,如果直接删除原有的数据,再添加新的数据,有可能导致数据丢失
- 注意点:
- 1.是为了备份表结构,而不是数据
- 2.如果想要备份数据,需要使用工具,navicat,mysqlworkbench,等等
- 3.更新的过程数据一般不会丢失,做降级的时候需要谨慎操作
- 操作流程:
- 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)
- 相关迁移命令:
- 生成迁移文件夹[一次就好]
- python xxx.py db init
- 将模型类生成迁移脚本[重复执行]
- python xxx.py db migrate -m “注释”
- 将迁移脚本更新到数据库中[重复执行]
- python xxx.py db upgrade/downgrade [version]
- 其他命令
- 查看最新版本的命令
- python xxx.py db show
- 查看当前版本
- python xxx.py db current
- 查看所有的历史版本
- python xxx.py db history
- 查看最新版本的命令
- 生成迁移文件夹[一次就好]
- 1.安装扩展
<code class="language-python"># -*- 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,关联db,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() </code>
生成迁移文件夹
将模型类生成迁移脚本
将迁移脚本更新到数据库中
此时就可以在数据库中看到对应的表了
此时在代码中多写一行,添加一列
然后继续执行这两条命令,就会看到数据表结构中就多了一列
还可以进行降级
然后再升级到指定的版本