1. 程式人生 > >Flask修煉——資料庫!

Flask修煉——資料庫!



內容概述
ORM,
Flak-SQLAlchemy 安裝及配置,
資料庫的基本操作,
綜合案例-圖書管理,
多對多演練


ORM

Object-Relation Mapping 物件-關係對映

主要實現模型物件到關係資料庫資料的對映.

優點 :
  • 只需要面向物件程式設計, 不需要面向資料庫編寫程式碼.
    • 對資料庫的操作都轉化成對類屬性和方法的操作.
    • 不用編寫各種資料庫的sql語句.
  • 實現了資料模型與資料庫的解耦, 遮蔽了不同資料庫操作上的差異.
    • 不在關注用的是mysql
      oracle…等.
    • 通過簡單的配置就可以輕鬆更換資料庫, 而不需要修改程式碼.
缺點 :
  • 相比較直接使用SQL語句操作資料庫,有效能損失.
  • 根據物件的操作轉換成SQL語句,根據查詢的結果轉化成物件, 在對映過程中有效能損失.



Flask-SQLAlchemy 安裝及配置

windows 中安裝 SQLAlchemy:

pip install mysqlclient==1.3.12

pip install flask-mysqldb

pip install flask-sqlalchemy

pip install mysql-connector


資料庫連線設定
  • 在 Flask-SQLAlchemy 中,資料庫使用URL指定,而且程式使用的資料庫必須儲存到Flask配置物件的 SQLALCHEMY_DATABASE_URI鍵中
app.config['SQLALCHEMY_DATABASE_URI'] = "mysql://root:[email protected]:3306/booktest"
  • 其他設定:
# 動態追蹤修改設定,如未設定只會提示警告
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'
] = True #查詢時會顯示原始SQL語句 app.config['SQLALCHEMY_ECHO'] = True

常用的 SQLAlchemy 列選項
選項名 說明
primary_key 如果為True,代表表的主鍵
unique 如果為True,代表這列不允許出現重複的值
index 如果為True,為這列建立索引,提高查詢效率
nullable 如果為True,允許有空值,如果為False,不允許有空值
default 為這列定義預設值

常用的 SQLAlchemy 關係選項
選項名 說明
backref 在關係的另一模型中新增反向引用
primary join 明確指定兩個模型之間使用的聯結條件
uselist 如果為False,不使用列表,而使用標量值
order_by 指定關係中記錄的排序方式
secondary 指定多對多關係中關係表的名字
secondary join 在SQLAlchemy中無法自行決定時,指定多對多關係中的二級聯結條件



資料庫的基本操作

  • 在Flask-SQLAlchemy中,插入、修改、刪除操作,均由資料庫會話管理。
    • 會話用 db.session 表示。在準備把資料寫入資料庫前,要先將資料新增到會話中然後呼叫 commit() 方法提交會話。
  • 在 Flask-SQLAlchemy 中,查詢操作是通過 query 物件操作資料。
    • 最基本的查詢是返回表中所有資料,可以通過過濾器進行更精確的資料庫查詢。

常用的SQLAlchemy查詢過濾器
過濾器 說明
filter() 把過濾器新增到原查詢上,返回一個新查詢
filter_by() 把等值過濾器新增到原查詢上,返回一個新查詢
limit 使用指定的值限定原查詢返回的結果
offset() 偏移原查詢返回的結果,返回一個新查詢
order_by() 根據指定條件對原查詢結果進行排序,返回一個新查詢
group_by() 根據指定條件對原查詢結果進行分組,返回一個新查詢

常用的SQLAlchemy查詢執行器
方法 說明
all() 以列表形式返回查詢的所有結果
first() 返回查詢的第一個結果,如果未查到,返回None
first_or_404() 返回查詢的第一個結果,如果未查到,返回404
get() 返回指定主鍵對應的行,如不存在,返回None
get_or_404() 返回指定主鍵對應的行,如不存在,返回404
count() 返回查詢結果的數量
paginate() 返回一個Paginate物件,它包含指定範圍內的結果

from flask import Flask
from flask_sqlalchemy import SQLAlchemy

app = Flask(__name__)

# 配置連線資料庫
app.config['SQLALCHEMY_DATABASE_URI'] = "mysql://root:[email protected]:3306/test_04"
# 是否追蹤資料庫的修改
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False

# 初始化 SQLAlchemy 物件
db = SQLAlchemy(app)


class Role(db.Model):
    # 定義表名
    __tablename__ = 'roles'
    # 定義列物件
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(64), unique=True)
    us = db.relationship('User', backref='role')

    # 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(64), unique=True, index=True)
    email = db.Column(db.String(64), unique=True)
    password = db.Column(db.String(64))
    role_id = db.Column(db.Integer, db.ForeignKey('roles.id'))

    def __repr__(self):
        return 'User:%d:%s' % (self.id, self.name)


@app.route('/')
def index():
    return 'Hello World!'


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()

    """
    查詢操作
    
    查詢所有使用者資料
        User.query.all()
    查詢有多少個使用者
        User.query.count()
    查詢第1個使用者
        User.query.get(1)
        User.query.first()
    查詢id為4的使用者[3種方式]
        User.query.get(4)
        User.query.filter(User.id == 4).first()
        User.query.filter_by(id=4).first()
    查詢名字結尾字元為g的所有資料[開始/包含]
        User.query.filter(User.name.endswith('g')).all()
    查詢名字不等於wang的所有資料[2種方式]
        User.query.filter(User.name != 'wang').all()
        User.query.filter(not_(User.name == 'wang')).all()
    查詢名字和郵箱都以 li 開頭的所有資料[2種方式]
        User.query.filter(User.name.startswith('li'), User.email.startswith('li')).all()
        User.query.filter(and_(User.name.startswith('li'), User.email.startswith('li'))).all()
    查詢password是 `123456` 或者 `email` 以 `itheima.com` 結尾的所有資料
        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.query.filter(User.name == 'liu').first().role
    查詢所有使用者資料,並以郵箱排序
        User.query.order_by(User.email.desc()).all()
    每頁3個,查詢第2頁的資料
        paginate = User.query.paginate(2,3)
        paginate.items
        paginate.page
        paginate.pages
    """
    app.run(debug=True)



綜合案例—圖書管理

MVC 簡介:

MVC 的全名是 Model View Controller,是模型 (model) -檢視 (view) -控制器 (controller) 的縮寫,是一種軟體設計典範。它是用一種業務邏輯、資料與介面顯示分離的方法來組織程式碼,將眾多的業務邏輯聚集到一個部件裡面,在需要改進和個性化定製介面及使用者互動的同時,不需要重新編寫業務邏輯,達到減少編碼的時間。

MVC 開始是存在於桌面程式中的,M是指業務模型,V是指使用者介面,C則是控制器

V即 View 檢視是指使用者看到並與之互動的介面;

M即 model 模型是指模型表示業務規則。

C即 controller 控制器是指控制器接受使用者的輸入並呼叫模型和檢視去完成使用者的需求,控制器本身不輸出任何東西和做任何處理。

在這裡 M 就是指建立的幾個模型類,資料表;

V 就是指 index 這個函式裡的業務邏輯加模板渲染,一塊構成了檢視;

C 就是客戶端傳送過來的請求,進行判斷來決定去呼叫哪個模型


  • 邏輯程式碼
from flask import Flask, render_template, flash, request, redirect, url_for
from flask_sqlalchemy import SQLAlchemy
from flask_wtf import FlaskForm
from wtforms import StringField, SubmitField
from wtforms.validators import InputRequired

app = Flask(__name__)
app.secret_key = 'asdfasdf'

# 配置資料庫
app.config['SQLALCHEMY_DATABASE_URI'] = "mysql://root:[email protected]:3306/booktest"
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False

db = SQLAlchemy(app)


class AddBookForm(FlaskForm):
    """自定義新增書籍的表單"""
    author = StringField('作者: ', validators=[InputRequired('請輸入作者')])
    book = StringField('書名: ', validators=[InputRequired('請輸入書名')])
    submit = SubmitField('新增')


class Author(db.Model):
    """ 作者模型,一 的一方"""
    __tablename__ = 'authors'
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(64), unique=True)
    # 定義屬性,以便作者模型可以直接通過該屬性訪問其多的一方的資料(書的資料)
    # backref 給 Book 也添加了一個 author 的屬性,可以通過 book.author 獲取 book 所對應的作者資訊
    books = db.relationship('Book', backref='author')


class Book(db.Model):
    """書的模型,多的一方"""
    __tablename__ = 'books'
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(64), unique=True)
    # 記錄 一 的一方的id作為外來鍵
    author_id = db.Column(db.Integer, db.ForeignKey(Author.id))


@app.route('/delete_author/<author_id>')
def delete_author(author_id):
    """刪除作者以及作者所有的書籍"""
    try:
        author = Author.query.get(author_id)
    except Exception as e:
        print(e)
        return "查詢錯誤"

    if not author:
        return '作者不存在'

    try:
        # 刪除作者及其所有書籍
        Book.query.filter(Book.author_id == author_id).delete()
        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/<book_id>')
def delete_book(book_id):
    """ 刪除書籍"""
    book = None
    try:
        book = Book.query.get(book_id)
    except Exception as e:
        print(e)
        return "查詢錯誤"

    if not book:
        return '書籍不存在'

    try:
        db.session.delete(book)
        db.session.commit()
    except Exception as e:
        print(e)
        db.session.rollback()
        return '刪除失敗'

    return redirect(url_for('index'))


@app.route('/', methods=['GET', 'POST'])
def index():
    """返回首頁"""

    book_form = AddBookForm()

    # 如果 book_form 可以被提交
    if book_form.validate_on_submit():
        # 1.取出表單中資料
        author_name = book_form.author.data
        book_name = book_form.book.data

        # 2.做具體業務邏輯程式碼實現
        # 2.1.查詢指定名字的作者
        author = Author.query.filter(Author.name == author_name).first()
        # if 指定名字的作者不存在:
        if not author:
            try:
                # 新增作者資訊到資料庫
                # 初始化作者的模型物件
                author = Author(name=author_name)
                db.session.add(author)
                db.session.commit()
                # 新增書籍資訊到資料庫(指定其作者)
                book = Book(name=book_name, author_id=author.id)
                # book.author = author
                db.session.add(book)
                db.session.commit()
            except Exception as e:
                db.session.rollback()
                print(e)
                flash('新增失敗')
        else:
            book = Book.query.filter(Book.name == book_name).first()
            if not book:
                try:
                    # 新增書籍資訊到資料庫(指定其作者)
                    book = Book(name=book_name, author_id=author.id)
                    # book.author = author
                    db.session.add(book)
                    db.session.commit()
                except Exception as e:
                    print(e)
                    flash('新增失敗')
            else:
                flash('當前資料已存在')
    else:
        if request.method == 'POST':
            flash('引數錯誤')

    # 1.查詢資料
    authors = Author.query.all()

    # 2.將資料傳入到模板中進行渲染返回
    return render_template('demo1_bookDemo.html', authors=authors, form=book_form)


if __name__ == '__main__':
    db.drop_all()
    db.create_all()

    # 生成資料
    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(debug=True)


  • 模板程式碼
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h1>圖書管理</h1>
<form method="post">
    {{ form.csrf_token() }}<br/>
    {{ form.author.label }}{{ form.author }}<br/>
    {{ form.book.label }}{{ form.book }}<br/>
    {{ form.submit }}<br/>

    {% for message in get_flashed_messages() %}
        {{ message }}
    {% endfor %}
</form>
<hr/>
<ur>
    {% for author in authors %}
        <li>{{ author.name }}<a href="/delete_author/{{ author.id }}">刪除</a></li>
        <ul>
            {% for book in author.books %}
                <li>{{ book.name }}<a href="/delete_book/{{ book.id }}">刪除</a></li>
            {% endfor %}
        </ul>
    {% endfor %}
</ur>
</body>
</html>



多對多演練

這塊不需要記,用到了回來複製即可

在專案開發過程中,會遇到很多資料之間多對多關係的情況

在開發過程中需要使用 ORM 模型將表與表的多對多關聯關係使用程式碼描述出來。多對多關係描述有一個唯一的點就是:需要新增一張單獨的表去記錄兩張表之間的對應關係

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='student',
                              lazy='dynamic')


class Course(db.Model):
    __tablename__ = "courses"
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(64