三十五、python學習之Flask框架(七)資料庫:Flask對資料庫的基本操作、常見關係模板、資料庫遷移、綜合案例:圖書管理
阿新 • • 發佈:2018-11-01
補充:
使用SQL_Alchemy定義一個模型類,不可以不指定primary_key=True建立表.
一、資料庫基本操作
1. 資料庫的基本操作(CRUD):
-
在Flask-SQLAlchemy中,插入、修改、刪除操作,均由資料庫會話管理。
-
- 會話用 db.session 表示。在準備把資料寫入資料庫前,要先將資料新增到會話中然後呼叫 commit() 方法提交會話。
-
在 Flask-SQLAlchemy 中,查詢操作是通過 query 物件操作資料。
-
- 最基本的查詢是返回表中所有資料,可以通過過濾器進行更精確的資料庫查詢。
2.在檢視函式中定義模型類:
# !/usr/bin python
# coding=utf-8
from flask import Flask
# 匯入flask_sqlalchemy擴充套件
from flask_sqlalchemy import SQLAlchemy
app = Flask(__name__)
# 配置資料庫連線
app.config['SQLALCHEMY_DATABASE_URI'] = "mysql://root:[email protected]:3306/python32"
# 關閉動態追蹤修改的警告
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
# 展示sql語句
app.config['SQLALCHEMY_ECHO'] = True
# 例項化sqlalchemy物件
db = SQLAlchemy(app)
# 定義使用者型別資料模型
class Role(db.Model):
__tablename__ = "roles"
id = db.Column(db.Integer, primary_key = True)
name = db.Column(db.String(32), unique=True)
# 定義關係引用,第一個引數User表示多方的類名
# 第二個backref表示的是反向引用,給User模型用,實現多對一的查詢
# 等號左邊給一方Role使用,backref給多方User使用
us = db.relationship('User',backref='role')
# 定義方法,輸出查詢結果
def __repr__(self):
return "name:%s" % self.name
# 定義使用者資料模型
class User(db.Model):
__tabelname__ = "users"
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(32))
email = db.Column(db.String(32), unique=True)
pswd = db.Column(db.String(32))
# 定義外來鍵
role_id = db.Column(db.Integer, db.ForeignKey("roles.id"))
def __repr__(self):
return "name:%s, email:%s, passwd:%s" % (self.name, self.email, self.pswd)
# 定義檢視
@app.route("/")
def index():
return "hello world!!"
if __name__ == '__main__':
# 刪除資料庫
db.drop_all()
# 建立新庫
db.create_all()
# 例項化使用者型別模型類
ro1 = Role(name = "admin")
ro2 = Role(name = "user")
# 通過db.session提交資料庫
db.session.add_all([ro1, ro2])
db.session.commit()
# 例項化使用者模型累
us0 = User(name = "張三", email = " [email protected]", pswd = "123456", role_id = ro1.id)
us1 = User(name='wang', email='[email protected]', pswd='123456', role_id=ro1.id)
us2 = User(name='zhang', email='[email protected]', pswd='201512', role_id=ro2.id)
us3 = User(name='chen', email='[email protected]', pswd='987654', role_id=ro2.id)
us4 = User(name='zhou', email=' [email protected]', pswd='456789', role_id=ro1.id)
db.session.add_all([us0,us1,us2,us3,us4])
db.session.commit()
app.run(debug=True)
3.常用的SQLAlchemy查詢過濾器:
過濾器 | 說明 |
---|---|
filter() | 把過濾器新增到原查詢上,返回一個新查詢 |
filter_by() | 把等值過濾器新增到原查詢上,返回一個新查詢 |
limit() | 使用指定的值限定原查詢返回的結果,限制查詢的條數 |
offset() | 偏移原查詢返回的結果,返回一個新的查詢 |
order_by() | 根據指定的條件對原查詢結果進行排序,返回一個新查詢 |
group_by() | 根據指定條件對原查詢結果進行分組,返回一個新查詢 |
分析:
- filter(): 可以進行邏輯判斷,(相對重要)
- filter_by():等值就是賦值語句
- order_by(): (相對重要)
-
- 引數:desc:使用降序排序; asc:使用升序排序(預設)
4.常用的SQLAlchemy查詢執行器:
方法 | 說明 |
---|---|
all() | 以列表形式返回查詢結果 |
first() | 返回查詢結果的第一個結果,如果未找查到,返回None |
first_or_404() | 返回查詢的第一個結果,如果未查到返回404 |
get() | 返回指定的主鍵對應的行,如果不存在,返回None |
get_or_404() | 返回指定主鍵對應的行,如果不存在,返回404 |
count() | 返回查詢結果的數量 |
paginate() | 返回一個paginate物件,它包含指定範圍內的結果 |
分析:
- 查詢器得到一個類,通過執行器拿到結果
- all,first,get常用
- paginate:分頁,比較難,重點掌握一下
5.資料庫的查詢操作:
5.1 查詢所有
User.query.all() # 返回一個列表,是物件列表
解決問題:在模型類中定義方法,實現查詢結果顯示成可讀字串:
def __repr__(self):
return 'name: %s' % self.name # 返回刻度物件中的屬性
# Python中這個_repr_函式,對應repr(object)這個函式,返回一個可以用來表示物件的可列印字串.
# __str__就是基於__repr__實現的.
5.2 查詢一個
User.query.first() # 直接返回一個數據
5.3 查詢過濾器:類似於sql中的where
User.query.filter(User.name == "wang") # 返回一個基本查詢物件
- 是過濾查詢;
- 接收的引數必須使用模型的類名;
- 必須使用查詢過濾器;
- filter查詢不加條件,相當於查詢所有;
加上執行器即可顯示查詢內容:
User.query.filter(User.name == "wang").first()
可以使用字串的操作語句,來約束要查詢的條件:
例如:
User.query.filter(User.name.endseith("g")).all() # 查詢name以字串"g"結尾的資料
User.query.filter(User.name.startswith("z")).all() # 查詢name以"z"開頭的資料
使用查詢執行器顯示需求的資料:
例如:
User.query.filter(User.name.startswith("z")).count # 查詢name以"z"開頭的資料個數
在filter中加多個查詢條件(條件以逗號分隔):
例如:
User.query.filter(User.name != "wang", User.email.endswith('163.com)).all()
sqlalchemy中的and_, or_, not_操作:
例如:
# 匯入sqlalchemy中的判斷語句
from sqlalchemy import or_, and_, not_
User.query.filter(or_(User.name != "wang", User.email.endswith('163.com))).all()```
5.4等值過濾器:
User.query.filter(name = "wang") # 返回一個基本查詢物件
- 只能使用的等值操作;
- 引數為具體模型的欄位名,但是不能出現具體的類名;
- 必須使用查詢執行器
5.5 限制查詢結果的數量:
User.query.filter().limit(2).all() # 返回一個列表,限制查詢結果的數量
5.6 分頁查詢:
paginate = User.query.filter().paginate(1, 2, False)
# 分頁操作的引數,可以是2個,也可以是3個
# 引數1:當前頁號(1~n);
# 引數2:每一頁的資料;
# 引數3:如果分頁異常不報錯;
# 得到一個分頁物件;
paginate.page # 獲取當前頁
paginate.pages # 獲取總頁數
paginate.items # 獲取當前頁中的資料
5.7 get查詢:
User,get(key) # get查詢接受的引數是主鍵值,不填報錯
5.8 排序查詢:
User.query.order_by().all() # 預設根據主鍵的順序排
User.query.order_by(User.id.desc()).all() # 使用id欄位降序排序
User.query.order_by(User.id.asc()).all() # 使用id欄位升序排序
5.9 練習:
- 查詢所有使用者資料:
User.query.all()
- 查詢有多少個使用者:
User.query.count()
- 查詢第1個使用者:
User.query.first()
- 查詢id為4的使用者[3種方式]:
# 方法一:
User.query.filter(User.id==4).all()
# 方法二:
User.query.filter_by(id=4).all()
# 方法三:
User.query.get(4)
- 查詢名字結尾字元為z的所有資料[開始/包含]:
User.query.filter(User.name.startswith("z")).all()
- 查詢名字不等於wang的所有資料[2種方式]:
# 方法一:
User.query.filter(User.name != "wang").all()
# 方法二:
from sqlalchemy import or_, and_,not_
User.query.filter(not_(User.name=="wang")).all()
- 查詢名字和郵箱都以 chen 開頭的所有資料[2種方式]:
# 方法一:
User.query.filter(User.name.startswith("chen"), User.email.startswith("chen")).all()
# 方法二:
User.query.filter(and_(User.name.startswith("chen"), User.email.startswith("chen"))).all()
- 查詢password是
123456
或者email
以itheima.com
結尾的所有資料:
User.query.filter(or_(User.pswd == "123456", User.email=="itcast.com")).all()
- 查詢id為 [1, 3, 5, 7, 9] 的使用者列表:
User.query.filter(User.id.in_([1,3,5,7,9])).all()
- 查詢name為zhou的角色資料:
User.query.filter(User.name == "zhou").all()
- 查詢所有使用者資料,並以郵箱排序:
User.query.order_by(User.email).all()
- 每頁3個,查詢第2頁的資料:
User.query.paginate(2,3,False).items
6.資料庫修改操作:
完成後需要由sqlalchemy的物件db通過session.commit()提交操作
6.1更新資料:
方法一:
user = User.query.first()
user.name = 'dong'
db.session.commit()
方法二:
User.query,filter(User.name == "zhang").update({"name":"li"})
db.session.commit()
方法三:
user = user = User.query.get(2)
user.name = "zhang"
User.query.all()
db.session.add(user) # 通過額外的一個物件修改的資料,需要先新增這個物件,再提交
db.session.commit()
7. 一對多和多對多查詢:
7.1 一對多:
r = Role.query.get(1) # 拿到物件
r.us # 使用關係引用返回的物件
7.2 多對一
u = User.query.get(2)
u.role
二、綜合案例:圖書管理系統:
1.開發基本過程:
- 需求: 實現圖書案例,資料的增刪改查.
- 流程:
-
- 1.web表單:wtf擴充套件,新增資料,驗證函式,csrf保護,自定義表單類,設定祕鑰,表單域中需要設定csrf_token
-
- 2.模板:使用模板語法,獲取檢視返回的資料,使用語句遍歷後端返回的資料;
-
- 3.模型類:flask_sqlalchemy,配置資料的連線和動態追蹤修改,定義模型(作者和圖書),建立表,新增測試資料
-
- 4.業務邏輯:檢視中數顯wtf表單,實現資料的查詢,刪除,呼叫模板
2. 程式碼實現:
- 實現簡單的功能,然後版本迭代
版本一: 功能實現
模板檔案:index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>圖書管理</title>
</head>
<body>
<form method="post">
{{ form.csrf_token }}
<p>作者: {{ form.wtf_author }}</p>
<p>書名: {{ form.wtf_book }}</p>
<p>{{ form.wtf_submit}}</p>
</form>
<ul>
{% for author in authors %}
<li>{{ author.name}}</li>
<a href="/delete_author/{{ author.id }}">刪除</a>
<br>
{% endfor %}
</ul>
<ul>
{% for book in books %}
<li>{{ book.info }}</li>
<a href="/delete_book/{{ book.id }}">刪除</a>
<br>
{% endfor %}
</ul>
</body>
</html>
檢視模組:demo02.py
匯入使用的模組
from flask import Flask, render_template,url_for,redirect
# 匯入wtf表單
from flask_wtf import FlaskForm
# 匯入wtf的欄位型別
from wtforms import StringField, SubmitField
# 匯入表單的驗證欄位
from wtforms.validators import DataRequired
# 匯入sqlalcjemy模組
from flask_sqlalchemy import SQLAlchemy
例項化Flask物件, 資料庫物件和設定配置檔案
app = Flask(__name__)
# 設定祕鑰
app.config['SECRET_KEY'] = "frekflnlngldnglk"
# 配置資料庫
app.config["SQLALCHEMY_DATABASE_URI"] = "mysql://root:[email protected]:3306/auth_book"
# 關閉動檢測
app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False
db = SQLAlchemy(app)
定義表單類
# 定義表單
class Form(FlaskForm):
wtf_author = StringField(validators=[DataRequired()])
wtf_book = StringField(validators=[DataRequired()])
wtf_submit = SubmitField("提交")
定義作者模型類
# 定義作者模型
class Author(db.Model):
__tablename__ = "authors"
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(32), unique=True)
# 重寫方法
def __repr__(self):
return "name: %s \n" % self.name
定義書籍模型類
# 定義書籍
class Book(db.Model):
__tablename__ = "books"
id = db.Column(db.Integer, primary_key=True)
info = db.Column(db.String(32), unique=True)
# 重寫方法
def __repr__(self):
return "book_name: %s \n" % self.info
實現主頁面檢視函式:
# 定義主頁面的檢視
@app.route('/',methods=['GET','POST'])
def index():
form = Form()
# 獲取資料
authors = Author.query.all()
books = Book.query.all()
if form.validate_on_submit():
author = form.wtf_author.data
book = form.wtf_book.data
au = Author(name = author)
bk = Book(info = book)
db.session.add_all([au, bk])
db.session.commit()
authors = Author.query.all()
books = Book.query.all()
print(form.wtf_author.data)
print(form.wtf_book.data)
print(form.validate_on_submit())
return render_template("index.html", authors = authors, books = books,form=form)
實現刪除作者的檢視函式:
# 定義刪除作者的檢視函式
@app.route("/delete_author/<int:id>")
def del_auth(id):
author = Author.query.get(id)
db.session.delete(author)
db.session.commit()
return redirect(url_for("index"))
實現刪除圖書的檢視函式:
# 定義刪除圖書的檢視函式
@app.route("/delete_book/<int:id>")
def del_book(id):
book = Book.query.get(id)
db.session.delete(book)
db.session.commit()
return redirect(url_for("index"))
入口主函式
if __name__ == '__main__':
app.run(debug=True)
版本二: 在原有基礎上做異常處理:
主頁面檢視加入異常處理:
# 定義主頁面的檢視
@app.route('/',methods=['GET','POST'])
def index():
form = Form()
authors, books = None, None
# 獲取資料
try:
authors = Author.query.all()
books = Book.query.all()
except Exception as e:
print(e)
if form.validate_on_submit():
author = form.wtf_author.data
book = form.wtf_book.data
try:
au = Author(name = author)
bk = Book(info = book)
db.session.add_all([au, bk])
db.session.commit()
except Exception as e:
print(e)
# 新增資料如果發生異常,需要進行回滾
db.session.rollback()
# 獲取資料
try:
authors = Author.query.all()
books = Book.query.all()
except Exception as e:
print(e)
return render_template("index.html", authors = authors, books = books,form=form)
刪除作者檢視加入異常處理:
# 定義刪除作者的檢視函式
@app.route("/delete_author/<int:id>")
def del_auth(id):
author = Author.query.get(id)
try:
# 執行刪除
db.session.delete(author)
db.session.commit()
except Exception as e:
print(e)
db.session.rollback()
return redirect(url_for("index"))
刪除圖書檢視加入異常處理:
# 定義刪除圖書的檢視函式
@app.route("/delete_book/<int:id>")
def del_book(id):
book = Book.query.get(id)
try:
# 執行刪除
db.session.delete(book)
db.session.commit()
except Exception as e:
print(e)
db.session.rollback()
return redirect(url_for("index"))
需要異常處理的情況:
- 對資料庫的增刪改查都需要異常處理;
- 修改資料庫時需要異常處理。如果發生異常,需要回滾資料庫:db.session.rollback()
在請求過程中自動提交資料:(但是不建議使用)
app.config["SQLALCHEMY_COMMIT_ON_TEARDOWN] = True
三、常見關係模板程式碼:
1.一對多:
示例場景:
- 使用者與其釋出的帖子(使用者表與帖子表)
- 角色與所屬於該角色的使用者(角色表與多使用者表)
例項程式碼:
class Role(db.Model):
"""角色表"""
__tablename__ = 'roles'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(64), unique=True)
users = db.relationship('User', backref='role', lazy='dynamic')
class User(db.Model):
"""使用者表"""
__tablename__ = 'users'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(64), unique=True, index=True)
2.多對多:
示例場景
- 講師與其上課的班級(講師表與班級表)
- 使用者與其收藏的新聞(使用者表與新聞表)
- 學生與其選修的課程(學生表與選修課程表)
示例程式碼:
tb_student_course = db.Table('tb_student_course',
db.Column('student_id', db.Integer, db.ForeignKey('students.id')),
db.Column('course_id', db.Integer, db.ForeignKey('courses.id'))
)
class Student(db.Model):
__tablename__ = "students"
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(64), unique=True)
courses = db.relationship('Course', secondary=tb_student_course,
backref=db.backref('students', lazy='dynamic'),
lazy='dynamic')
class Course(db.Model):
__tablename__ = "courses"
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(64), unique=True)```
3.自關聯一對多:
示例場景
- 評論與該評論的子評論(評論表)
- 參考網易新聞
- 省市區表
例項程式碼:
class Comment(db.Model):
"""評論"""
__tablename__ = "comments"
id = db.Column(db.Integer, primary_key=True)
# 評論內容
content = db.Column(db.Text, nullable=False)
# 父評論id
parent_id = db.Column(db.Integer, db.ForeignKey("comments.id"))
# 父評論(也是評論模型)
parent = db.relationship("Comment", remote_side=[id],
backref=db.backref('childs', lazy='dynamic'))
# 測試程式碼
if __name__ == '__main__':
db.drop_all()
db.create_all()
com1 = Comment(content='我是主評論1')
com2 = Comment(content='我是主評論2')
com11 = Comment(content='我是回覆主評論1的子評論1')
com11.parent = com1
com12 = Comment(content='我是回覆主評論1的子評論2')
com12.parent = com1
db.session.add_all([com1, com2, com11, com12])
db.session.commit()
app.run(debug=True)
4.自關聯多對多:
示例場景
- 使用者關注其他使用者(使用者表,中間表)
示例程式碼:
tb_user_follows = db.Table(
"tb_user_follows",
db.Column('follower_id', db.Integer, db.ForeignKey('info_user.id'), primary_key=True), # 粉絲id
db.Column('followed_id', db.Integer, db.ForeignKey('info_user.id'), primary_key=True) # 被關注人的id
)
class User(db.Model):
"""使用者表"""
__tablename__ = "info_user"
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(32), unique=True, nullable=False)
# 使用者所有的粉絲,添加了反向引用followed,代表使用者都關注了哪些人
followers = db.relationship('User',
secondary=tb_user_follows,
primaryjoin=id == tb_user_follows.c.followed_id,
secondaryjoin=id == tb_user_follows.c.follower_id,
backref=db.backref('followed', lazy='dynamic'),
lazy='dynamic')
四、資料庫的遷移:
1.為什麼要遷移:
- 在開發過程中,需要修改資料庫模型,而且還要在修改之後更新資料庫。最直接的方式就是刪除舊錶,但這樣會丟失資料。
- 更好的解決辦法是使用資料庫遷移框架,它可以追蹤資料庫模式的變化,然後把變動應用到資料庫中。
- 在Flask中可以使用Flask-Migrate擴充套件,來實現資料遷移。並且整合到Flask-Script中,所有操作通過命令就能完成。
- 為了匯出資料庫遷移命令,Flask-Migrate提供了一個MigrateCommand類,可以附加到flask-script的manager物件上。
2.遷移流程:
- 在開發過程中,需要修改資料庫模型,而且還要在修改之後更新資料庫。最直接的方式就是刪除舊錶,但這樣會丟失資料。
- 更好的解決辦法是使用資料庫遷移框架,它可以追蹤資料庫模式的變化,然後把變動應用到資料庫中。
- 在Flask中可以使用Flask-Migrate擴充套件,來實現資料遷移。並且整合到Flask-Script中,所有操作通過命令就能完成。
- 為了匯出資料庫遷移命令,Flask-Migrate提供了一個MigrateCommand類,可以附加到flask-script的manager物件上。
檔名:sql_moe.py
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from flask_migrate import Migrate,MigrateCommand
from flask_script import Shell,Manager
app = Flask(__name__)
manager = Manager(app)
app.config['SQLALCHEMY_DATABASE_URI'] = 'mysql://root:[email protected]:3306/Flask_test'
app.config['SQLALCHEMY_COMMIT_ON_TEARDOWN'] = True
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = True
db = SQLAlchemy(app)
#第一個引數是Flask的例項,第二個引數是Sqlalchemy資料庫例項
migrate = Migrate(app,db)
#manager是Flask-Script的例項,這條語句在flask-Script中新增一個db命令
manager.add_command('db',MigrateCommand)
#定義模型Role
class Role(db.Model):
# 定義表名
__tablename__ = 'roles'
# 定義列物件
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(64), unique=True)
user = db.relationship('User', backref='role')
#repr()方法顯示一個可讀字串,
def __repr__(self):
return 'Role:'.format(self.name)
#定義使用者
class User(db.Model):
__talbe__ = 'users'
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(64), unique=True, index=True)
#設定外來鍵
role_id = db.Column(db.Integer, db.ForeignKey('roles.id'))
def __repr__(self):
return 'User:'.format(self.username)
if __name__ == '__main__':
manager.run()
步驟: