Flask 模型操作
ORM?
簡稱 ORM, O/RM, O/R Mapping
持久化模型
特征
- 數據表和編程類的映射
- 數據類型映射
- 字段屬性映射類的屬性和數據類型
- 關系映射
- 表于表之間的關系對應到 編程類的關系上
優點
數據操作和業務邏輯區分
封裝了數據庫操作, 提升效率
省略了龐大的數據訪問層
鏈接數據庫
flask中是不自帶ORM的,如果在flask里面連接數據庫有兩種方式
- pymysql
- SQLAlchemy
是python 操作數據庫的一個庫。能夠進行 orm 映射官方文檔 sqlchemy
SQLAlchemy“采用簡單的Python語言,為高效和高性能的數據庫訪問設計,實現了完整的企業級持久模型”。
SQLAlchemy的理念是,SQL數據庫的量級和性能重要于對象集合;而對象集合的抽象又重要于表和行。
SQLAlchemy 方式
鏈接數據庫
鏈接語法
app.config['SQLALCHEMY_DATABASE_URL'] = "mysql://用戶名:密碼@數據庫服務器地址:端口號/數據庫名稱"實例
from flask import Flask from flask_sqlalchemy import SQLAlchemy# 導入pymysql , 并且將其偽裝成 ,MySQLdb # import pymysql # pymysql.install_as_MySQLdb() app = Flask(__name__)# 通過 app 配置數據庫信息 # app.config['SQLALCHEMY_DATABASE_URI'] = "mysql://root:123456@127.0.0.1:3306/flask" app.config['SQLALCHEMY_DATABASE_URI'] = "mysql+pymysql://root:123456@127.0.0.1:3306/flask"# 創建數據庫實例 db = SQLAlchemy(app) print(db)@app.route('/') def hello_world():return 'Hello World!'if __name__ == '__main__':app.run()定義模型( 重點 )
模型
根據數據庫表結構而創建出來的類 ( 模型類, 實體類 )
1. 先創建實體類, 通過實體類自動生成表
2. 先創建表, 根據表借口編寫實體類
語法
class MODELNAME(db.Model):__tablename__ = "TABLENAME"COLUMN_NAME = db.Column(db.TYPE, OPTIONS)參數
"""MODELNAME : 定義的模型類名稱, 根據表名而設定 比如:表名: USERS MODELNAME : Users TABLENAME : 要映射到數據庫中表的名稱COLUMN_NAME : 屬性名, 映射到數據庫中的列名TYPE : 映射到列的數據類型類型 python類型 說明Integer int 32 位整數SmallInteger int 16 位整數BiglInteger int 不限精度整數Float float 浮點數String str 字符串Text str 字符串Boolean bool 布爾值Date datatime.date 日期類型Time datatime.time 時間類型DateTime datatime.datatime 時間和日期類型OPTIONS : 列選項, 多個列選項用逗號隔開選項名 說明autoincrement 如果設置為 True, 表示該列自增, 列類型是整數且是主鍵, 默認自增primary_key 如果設置為 Ture, 表示該列為主鍵unique 如果設置為 Ture, 表示該列的值唯一index 如果設置為 Ture, 表示該列的值要加索引nullable 如果設置為 Ture, 表示該列的值可以為空default 指定該列的默認值 """實例
""" 創建實體類 Users , 映射數據庫中的 users 表 id 自增 主鍵 username 長度為 80 的字符串, 非空, 唯一, 加索引 age 整數 允許為空 email 長度 120 字符串 唯一 """class Users(db.Model):__tablename__ = 'users' # 如果表名為 users 的話可以省略id = db.Column(db.Integer, primary_key=True)username = db.Column(db.String(80), nullable=False, unique=True, index=True)age = db.Column(db.Integer, nullable=True)email = db.Column(db.String(120), unique=True)應用模型
創建
db.create_all() # 將所有的類創建到數據庫上 # 只有類對應的表不存在才會創建, 否則無反應, 因此想更改表結構就必須刪掉重建 # 由此可見此方法及其愚蠢 # 臨時性的, 此方法一般都不會用, 不對, 是絕對不要用刪除
db.drop_all() # 將所有的數據表全部刪除數據庫遷移
實體類的改動映射回數據庫
此操作需要依賴第三方庫
如果不使用第三方庫也想重寫表結構
就只能全部刪掉在全部創建, 極其智障.
1. flask-script?
對于項目進行管理, 啟動項目, 添加 shell 命令
包 flask-script
類 Manager
# 將 app 交給 Manager 管理 manager = Manager(app)if __name__ == '__main__':# app.run(debug=True) manager.run()"""命令行啟動 python app.py runserver --port 5555python app.py runserver --host 0.0.0.0python app.py runserver --host 0.0.0.0 --port 5555"""2. flask_migrate
包 flask_migrate
類
Migrate 管理 app 和 db 中間的關系, 提供數據庫遷移
MigrateCommand 允許終端中執行遷移命令
# 創建 Migrate 對象 migrate = Migrate(app, db)# 為 manager 增加子命令, 添加做數據庫遷移的子命令manager.add_command('db', MigrateCommand)"""命令行執行
python app.py db init 初始化 一個項目中的 init 只執行一次即可, 生成 migrations 文件夾 python app.py db migrate 將編輯好的實體類生成中間文件, 只有在實體類有更改的時候才會生成 python app.py db upgrade 將中間文件映射回數據庫
其他的命令可以通過 python app.py db 會進行查閱 """
數據庫修改
# 修改操作 # 創建實體類對象并賦值 user = Users() user.username = 'yangtuo' user.age = 18 user.email = '7452@qq.com'# 將實體對象保存回數據庫 db.session.add(user)
# 針對非查詢操作, 必須手動操作提交 db.session.commit()
自動視圖提交修改
每次進行修改都需要執行 add 以及 commit 略顯繁瑣
設置??SQLALCHEMY_COMMIT_ON_TEARDOWN 即可不需要手動更新, 但是僅局限于在?視圖函數?中可以簡化操作
# 配置數據庫操作自動提交 app.config['SQLALCHEMY_COMMIT_ON_TEARDOWN'] = True@app.route('/add_user') def add_view():user = Users()user.username = 'yangtuo'user.age = 18user.email = '7452@qq.com'# 如果設置了 SQLALCHEMY_COMMIT_ON_TEARDOWN = True 則不需要進行 add 和 commit 操作# db.session.add(user)# db.session.commit()return '增加數據成功'數據庫單表查詢
語法
db.session.query(查詢域).過濾方法(過濾條件).執行方法()返回值
# 條件必須要由 實體類.屬性 構成db.session.query(Users).filter(age>18).all() # 錯誤db.session.query(Users).filter(Users.age>18).all() # 正確 # 此命令支持跨表多個查詢, 因此必須要帶實體來進行查詢 query = db.session.query(Users.id, Teacher.tanme) query = db.session.query(Users, Teacher) # 查詢 Users,Teacher 的所有的列# 無執行方法的時候 返回值為一個 query 對象, 類型為 BaseQuery print(query) # sql 語句 print(type(query)) # <class 'flask_sqlalchemy.BaseQuery'># 有執行方法的時候 返回值為 列表 query = db.session.query(Users.id, Teacher.tanme).all() print(query) # 列表執行函數
# 執行函數 all() # 列表形式返回所有數據 first() # 返回實體對象第一條數據, 沒有數據返回 None fist_or_404() # 效果同上, 沒有數據返回 404 count() # 返回查詢結果數量過濾函數
# 過濾器 filter() # 指定查詢條件 ( 所有條件都可以 ) filter_by() # 等值條件中使用( 只能做 = 操作, > < 都不行, 注意不是 == ) limit() # 限制訪問條數 order_by() # 排序 group_by() # 分組與查詢
# 多條件查詢 # 鏈式操作或者逗號隔開表示并列條件db.session.query(Users).filter(Users.age>17).filter(Users.id>10).all()db.session.query(Users).filter(Users.age>17,Users.id>10).all()或查詢
# 或關系查詢, 首選需要引入from sqlalchemy import or_# 語法db.session.query(Users).filter(or_(條件1,條件2))與, 或并列查詢
# 語法 .and_(), .or_() db.session.query(Users).filter(or_(Users.id < 2,and_(Users.name == 'eric', Users.id > 3),Users.extra != "")).all() # 可以嵌套非查詢
任何的查詢是在 類字段前加 ~ 表示非查詢
db.session.query(Users).filter(~Users.email.like('%yangtuo%')).all() # not like模糊查詢
# 模糊查詢 # 語法 .like('%xxxx%')db.session.query(Users).filter(Users.email.like('%yangtuo%')).all() # likedb.session.query(Users).filter(Users.email.like('%%%s%%' % 'yangtuo')).all() # 格式化字符串形式要 兩個 %% 來表示一個 % 因此就出現這么多% 很麻煩...區間查詢
# 區間查詢 # 語法 .between(條件1,條件2)db.session.query(Users).filter(Users.id.between(30,40)) # 30~40 包括 30 40 db.session.query(Users).filter(~Users.id.between(30,40)) # <30, >40 不包括 30 40區間判斷
# 區間判斷 # 語法 .in_(列表/元組)db.session.query(Users).filter(Users.id.in_([1,3,4])).all() # in截取查詢
# 截取查詢 # .limit(n)db.session.query(Users).limit(3).all()跳過查詢
limit 和 offset 可以配合實現分頁操作
# 跳過查詢 # .offset(n)db.session.query(Users).offset(3).all()db.session.query(Users).offset(3).limit(3).all()排序查詢
# 排序查詢# 語法 .order_by('列 規則,列 規則') # 規則 desc 降序 asc 升序(默認, 不寫就表示升序)db.session.query(Users).order_by('Users.id desc').all()# 先按xxx排序,xxx排序不出來的部分 再用 yyy 來排序 db.session.query(Users).order_by('Users.id desc, Users.name').all()聚合查詢
需要配合聚合函數
from sqlalchemy import func func.sum() # 求和 func.count() # 求數量 func.max() # 求最大值 func.min() # 求最小值 func.avg() # 求平均數 # 語法 db.session.query(func.聚合函數(實體類.屬性),func.聚合函數(實體類.屬性)..).all()# 返回值 列表里面又套了個元組, 因為聚合出來的數據是固定的不可修改,因此用元組修飾
[((),(),()...)]
分組查詢
# 分組查詢# 語法 db.session.query(查詢列, 聚合列).group_by(分組列).all() ret = db.session.query(Users.is_active, func.avg(Users.age)).group_by('is_active').all()print(ret) # [(None, Decimal('22.5000')), (True, Decimal('24.0000'))]帶條件的分組聚合
?條件必須放在 group_by 后面, 關鍵字不在使用 filter 使用 having 和 mysql 類似 (不能再使用where)
ret = db.session.query(Users.is_active, func.avg(Users.age)).group_by(Users.age).having(func.avg(Users.age) > 23).all()print(ret) # [(True, Decimal('24.0000'))]綜合查詢
# 查詢所有年齡 18 以上按找 active 分組后, 組內大于兩人的信息ret = db.session.query(Users.is_active, func.count(Users.is_active))\.filter(Users.age > 18)\.group_by(Users.is_active)\.having(func.count(Users.is_active) > 2).all()print(ret)查詢優先級
執行順序和 mysql 的 一樣 , mysql 的優先順序點擊 這里
但是 mysql 中沒有 offset , 但是 mysql 的 limit 可以提供兩個參數來獲取類似 offset 的功能
因此結合 對 mysql 的分析 SQLAlchemy 的查詢優先順序如下?
query > filter > group > having > order >limit = offset?
# 查詢所有人的年齡ret = db.session.query(func.sum(Users.age)).all()# 查詢多少人ret = db.session.query(func.count(Users.id)).all()# 查詢所有 大于 19 歲的人的平均年齡ret = db.session.query(func.avg(Users.age)).filter(Users.age > 19).all()# 查詢 按找active 分組后 組內成員大于2 的組信息ret = db.session.query(Users.is_active, func.count(Users.is_active))\.group_by(Users.is_active)\.having(func.count(Users.is_active) > 2).all()# 查詢所有年齡 18 以上按找 active 分組后, 組內大于兩人的信息ret = db.session.query(Users.is_active, func.count(Users.is_active))\.filter(Users.age > 18)\.group_by(Users.is_active)\.having(func.count(Users.is_active) > 2).all()數據庫刪除
db.session.delete(記錄對象)ps
以上都是基于 db.session 的方式查詢
還可以基于實體類進行查詢 , 和 db 方式沒有效率性能上的區別
兩種方式沒什么大區別, 貌似也沒有說法哪種更好哪種在某場合下必須要用的情況
Users.query.all() db.session.query(Users).all() db.session.query(Users.id).all() db.session.query('users.id'').all() User.query.filter(Users.is_active==True).all() # 實體類方式查詢中的等于必須是雙等于號以及 所有的 實體類.列名, 都可以用 "表名.屬性" 或者 "實體類.列名" 來表示 , 不會影響顯示
但是如果使用 實體類.列名 的方式 會存在互相引用的時候因為未加載到的問題 因此這個要稍微注意到
數據庫關聯
一對多
什么是一對多
A表中的一條數據可以關聯到B表中的多條數據
B表中的一條數據只能關聯到A表中的一條數據
示例
Teacher(老師) 與 Course(課程)之間的關系
一名老師(Teacher)只能教授一門課程(Course)
一門課程(Course)可以被多名老師(Teacher)所教授
在數據庫中體現
"主外鍵關系" 完成實現一對多
在 "一" 表中創建主鍵
在 "多" 表中創建外鍵來對 "一" 表進行關聯?
在 SQLAlchemy 中的實現
1. 在 "多" 的實體類中增加對 "一" 的實體類的引用( 外鍵 )
語法
外鍵列名 = db.Column(db.type,db.ForeignKey(主鍵表.主鍵列) )實例
class Course(db.Model):id = db.Column(db.Integer, primary_key=True)canme = db.Column(db.String(30), nullable=False, unique=True, index=True)class Teacher(db.Model):id = db.Column(db.Integer, primary_key=True)tanme = db.Column(db.String(30), nullable=False)tage = db.Column(db.Integer, nullable=False)# 增加外鍵列cid = db.Column(db.Integer, db.ForeignKey(Course.id))2. 在 "一" 的實體類中增加對 "關聯屬性" 以及 "反向引用關系屬性"
目的 在編程語言中創建類(對象) 與類(對象) 之間的關系, 不會對數據庫有所操作, 僅僅是為了代碼邏輯方便
關聯屬性
在 "一" 的實體中, 通過哪個 <<屬性>> 來獲取對應到 "多" 的實體對象們
反向引用關系屬性
在 "多" 的實體中, 通過哪個 <<屬性>> 來獲取對應到 "一" 的實體對象
語法
屬性名 = db.relationship(關聯到的實體類,backref="自定義一個反向關系屬性名",lazy="dynamic", )""" lazy 參數: 指定如何加載相關記錄1.select首次訪問源對象時加載關聯數據2.immediate源對象加載后立馬加載相關數據(使用連接)3.subquery效果同上(使用子查詢)4.noload永不加載5.dynamic不加載記錄但提供加載記錄的查詢 """實例
class Course(db.Model):id = db.Column(db.Integer, primary_key=True)canme = db.Column(db.String(30), nullable=False, unique=True, index=True)# 增加關系屬性 和 反向引用關系屬性teachers = db.relationship('Teacher',backref="course",lazy="dynamic")class Teacher(db.Model):id = db.Column(db.Integer, primary_key=True)tanme = db.Column(db.String(30), nullable=False)tage = db.Column(db.Integer, nullable=False)# 增加外鍵列cid = db.Column(db.Integer, db.ForeignKey(Course.id))關聯數據操作
方式一? 操作外鍵字段
直接操作本表中有的字段 外鍵映射字段進行賦值, 即正向操作
tea = Teacher() tea.cid = 1方式二 操作反向引用屬性
操作關聯表的 relationship 關系字段的 bcakref 屬性來進行賦值, 即反向操作
cou = Course() tea = Teacher() tea.course = cou一對一
在一對多的基礎上添加唯一約束 ( 即對 "多" 設置唯一約束 )
語法
設置外鍵字段和唯一索引
外鍵列名 = db.Column(db.TYPE,db.ForeignKey('主表.主鍵'),unique = True )增加關聯屬性和反向引用關系屬性
在另一個實體類中增加關聯屬性和反向引用關系屬性
屬性名 = db.relationship("關聯的實體類名",backref="反向引用關系屬性名",uselist = False )# userlist:設置為False,表示關聯屬性是一個標量而非一個列表實例
用戶和用戶老婆是一對一關系( 大概 )
class Wife(db.Model):id = db.Column(db.Integer,primary_key=True)name = db.Column(db.String(30))#增加外鍵約束和唯一約束,引用自users表中的id列uid = db.Column(db.Integer,db.ForeignKey('users.id'),unique = True) class Users(db.Model):id = db.Column(db.Integer,primary_key=True)username = db.Column(db.String(80),nullable=False,unique=True,index=True)age = db.Column(db.Integer,nullable=True)email = db.Column(db.String(120),unique=True)isActive = db.Column(db.Boolean,default=True)#增加關聯屬性和反向引用關系屬性(Wife表一對一)wife = db.relationship("Wife",backref="user",uselist = False)關聯字段操作 ( 和一對多完全一樣, 本質上就是一對多字段加了個唯一索引而已 )
方式一? 操作外鍵字段
直接操作本表中有的字段 外鍵映射字段進行賦值, 即正向操作
wif = Wife() wif.uid = 1方式二 操作反向引用屬性
操作關聯表的 relationship 關系字段的 bcakref 屬性來進行賦值, 即反向操作
wife = Wife()user = User() wife.Wife = user
多對多
雙向的一對多, "多" 代表著是無法直接用字段來處理, 即不會再本表中添加關系字段, 只需要做反向引用屬性即可
而且需要指定第三張表來映射( 第三張表需要手動創建 ), 設置語法在兩個多對多表中的任意一個設置即可
因為此設置中同時對兩個都設置了外向引用屬性
設置語法
屬性值 = db.relationship("關聯實體類",lazy="dynamic", backref=db.backref("關聯實體類中的外向引用屬性",lazy="dynamic" )secondary="第三張關聯表名")實例
# 編寫StudentCourse類,表示的是Student與Course之間的關聯類(表)# 此表需要設定到兩個關聯表的外鍵索引 # 表名:student_course class StudentCourse(db.Model):__tablename__ = "student_course"id = db.Column(db.Integer, primary_key=True)# 外鍵:student_id,引用自student表的主鍵idstudent_id = db.Column(db.Integer, db.ForeignKey('student.id'))# 外鍵:course_id,引用自course表的主鍵idcourse_id = db.Column(db.Integer, db.ForeignKey('course.id'))# 兩個多對多關聯表二選一進行配置即可 class Student(db.Model):id = db.Column(db.Integer, primary_key=True)sname = db.Column(db.String(30), nullable=False)sage = db.Column(db.Integer, nullable=False)isActive = db.Column(db.Boolean, default=True)# 在此表中進行設置反向引用屬性, 一次性設置兩份 class Course(db.Model):id = db.Column(db.Integer, primary_key=True)canme = db.Column(db.String(30), nullable=False, unique=True, index=True)students = db.relationship("Student",lazy="dynamic", # 針對 Course 類中的 students 屬性的延遲加載backref=db.backref("courses",lazy="dynamic" # 針對 students 類中的 courses 屬性的延遲加載 )secondary="student_course" # 指定第三張關聯表)
?關聯數據操作
# 向第三張關聯表中插入數據# 取到 數據 courses = request.form.getlist('courses') # 根據數據 查到所有對象 obj_list = Course.query.filter(Course.id.in_(courses)).all() for cou in obj_list :# 方案1:通過 stu 關聯 course 值# stu.courses.append(cou)# 方案2:通過 course 關聯 stu 值cou.students.append(stu)ps:
當然也可以直接操作第三張表, 畢竟是個實表. 但是不推薦. 就邏輯層面我們是盡力無視第三表的存在
還請無視到底
pymysql 方式
鏈接數據庫
格式
pymysqlmysql+pymysql://<username>:<password>@<host>/<dbname>[?<options>]配置文件中添加
SQLALCHEMY_DATABASE_URI = "mysql+pymysql://root:123456@127.0.0.1:3306/yangtuoDB?charset=utf8"SQLALCHEMY_POOL_SIZE = 10
SQLALCHEMY_MAX_OVERFLOW = 5
注冊初始化數據庫
函數級別
不推薦
from flask_sqlalchemy import SQLAlchemy from flask import FLask app = Flask(__name__) app.config['SQLALCHEMY_DATABASE_URI'] ="mysql://root:12345@localhost/test" db = SQLAlchemy(app)全局
from flask_sqlalchemy import SQLAlchemy from flask import FLask db = SQLAlchemy()def create_app():app = Flask(__name__)db.init_app(app)return app注意
必須在導入藍圖之前?
from flask_sqlalchemy import SQLAlchemy必須要在初始化之前導入模板,不然是沒辦法正確得到db
from .models import *創建生成表
在離線腳本中操作數數據庫創建 create_all? drop_all?
from chun import db,create_appapp = create_app() app_ctx = app.app_context() # app_ctx = app/g with app_ctx: # __enter__,通過LocalStack放入Local中db.create_all() # 調用LocalStack放入Local中獲取app,再去app中獲取配置操作表
#方式一db.session #會自動創建一個session db.session.add()db.session.query(models.User.id,models.User.name).all()db.session.commit()db.session.remove() #方式二 導入modelsmodels.User.query目錄結構
chun 項目名chun 與項目名同名的文件夾 static 靜態文件相關templates 模板文件相關view 視圖函數acctount.py 具體視圖函數user.py 具體視圖函數__init__.py 初始化文件models.py 數據庫相關create_table.py 數據庫創建離線腳本settings.py 配置文件chun.chun.__init__.py
用于初始化,創建DB對象,app對象
from flask import Flask from flask_session import Sessionfrom flask_sqlalchemy import SQLAlchemy db = SQLAlchemy() from .views.account import ac from .views.user import usfrom .models import *def create_app():app = Flask(__name__)app.config.from_object('settings.ProConfig')app.register_blueprint(ac)app.register_blueprint(us)db.init_app(app) # return appchun.settings.py
配置文件相關存放,數據庫的鏈接之類的
from redis import Redisclass BaseConfig(object):# SQLALCHEMY_DATABASE_URI = "mysql+pymysql://root:123456@127.0.0.1:3306/s9day122?charset=utf8"SQLALCHEMY_POOL_SIZE = 10SQLALCHEMY_MAX_OVERFLOW = 5SQLALCHEMY_TRACK_MODIFICATIONS = False# passchun.chun.models.py
數據庫表文件
from sqlalchemy.ext.declarative import declarative_base from sqlalchemy import Column from sqlalchemy import Integer,String,Text,Date,DateTime from sqlalchemy import create_engine from chun import dbclass Users(db.Model): __tablename__ = 'users'id = Column(Integer, primary_key=True)name = Column(String(32), index=True, nullable=False)chun.create_table.py
數據庫離線操作腳本文件,用于 操作 app,g,db 的相關腳本
from chun import db,create_appapp = create_app() app_ctx = app.app_context() with app_ctx: db.create_all() class ProConfig(BaseConfig):passchun.chun.views.user.py
視圖請求回應相關的文件
from flask import Blueprint from chun import db from chun import models us = Blueprint('us',__name__)@us.route('/index') def index():# 使用SQLAlchemy在數據庫中插入一條數據# db.session.add(models.Users(name='yangtuo',depart_id=1))# db.session.commit()# db.session.remove()result = db.session.query(models.Users).all()print(result)db.session.remove()return 'Index'?
轉載于:https://www.cnblogs.com/shijieli/p/10355861.html
總結
以上是生活随笔為你收集整理的Flask 模型操作的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Java输出数组中最长递增子序列的代码
- 下一篇: 猫头鹰的深夜翻译:API网关的重要性