1. 程式人生 > >三十五、python學習之Flask框架(七)資料庫:Flask對資料庫的基本操作、常見關係模板、資料庫遷移、綜合案例:圖書管理

三十五、python學習之Flask框架(七)資料庫:Flask對資料庫的基本操作、常見關係模板、資料庫遷移、綜合案例:圖書管理

補充:
  使用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 或者 emailitheima.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()

步驟:

  • python 檔名.py db init

  • python 檔名 db migrate -m “版本描述”

  • python 檔名.py db upgrade

  • 觀察mysql表結構,完成第一次遷移

  • 根據要求修改模型

  • python 檔名.py db migrate -m “版本描述”

  • python 檔名.py db upgrade

  • 觀察表結構,完成修改

  • 若返回版本,則利用python 檔名 db history 檢視版本號

  • python 檔名 db downgrade(或 upgrade) 版本號