第4天:在Flask應用中使用數據庫—Flask_SQLAlchemy

原文: http://www.catonlinepy.tech/
聲明:原創不易,未經許可,不得轉載

1. 你將學會什麼

接第3天表單的使用課程,今天的課程主要涉及到與數據庫相關的兩個插件的使用,一個是Flask_SQLAlchemy,另外一個是Flask_Migrate。通過今天的學習,你將學會如何對數據庫進行基本的操作,以及如何完成數據庫的遷移。教程中的代碼都會託管到github上,貓姐不厭其煩地強調,在學習本課內容時一定要自己嘗試手敲代碼,遇到問題再到github上查看代碼,如果實在不知道如何解決,可以在日誌下方留言。

2.使用Flask_SQLAlchemy管理數據庫

2.1 Flask_SQLAlchemy的安裝

對於web後臺開發工作,必須要掌握的一項技能便是對數據庫的CRUD(create, read, update, delete)操作,如果開發過程中直接使用原生的sql語句對數據庫進行操作,將是非常痛苦的事件(畢竟sql語句有很多反人類的設計)。Flask_SQLAlchemy插件將開發人員從這個泥潭中解救出來了,我們只需要使用Python的類就能輕鬆的完成對錶的增刪改查操作,並且該插件還支持多種數據庫類型,如MySQL、PostgreSQL、和SQLite等。

在進入正式學習之前,我們照舊要建立今天的項目目錄,如下:

# 進入到虛擬環境目錄,激活虛擬環境
maojie@Thinkpad:~/flask-plan/$ source miao_venv/bin/activate

# 到flask-course-primary目錄下創建第四天的課程day4目錄
(miao_venv) maojie@Thinkpad:~/flask-plan/flask-course-primary$ mkdir day4

# 進入day4目錄
(miao_venv) maojie@Thinkpad:~/flask-plan/flask-course-primary$ cd day4

# 新建database_demo目錄
(miao_venv) maojie@Thinkpad:~/flask-plan/flask-course-primary/day4$ mkdir database_demo

# 進入到database_demo目錄
(miao_venv) maojie@Thinkpad:~/flask-plan/flask-course-primary/day4$ cd database_demo/

# 在database_demo目錄中新建__init__.py文件
(miao_venv) maojie@Thinkpad:~/flask-plan/flask-course-primary/day4/database_demo$ touch __init__.py

# 在database_demo包中新建routes.py路由文件
(miao_venv) maojie@Thinkpad:~/flask-plan/flask-course-primary/day4/database_demo$ touch routes.py

# 在day4目錄下新建run.py文件
(miao_venv) maojie@Thinkpad:~/flask-plan/flask-course-primary/day4/$ touch run.py

安裝Flask_SQLAlchemy插件還是使用pip命令,如下:

# 注意一定要在虛擬環境中
(miao_venv) maojie@Thinkpad:~/flask-plan/flask-course-primary/day4$ pip install Flask_SQLAlchemy

2.2 配置Flask_SQLAlchemy

我們的教程中使用的數據庫是SQLite(Linux),主要原因是SQLite足夠簡單,不需要進行任何配置便可使用,十分適用於入門。下面在__init__.py文件中配置數據庫,如下所示:

# 在__init__.py文件中的內容
from flask import Flask
# 從flask_sqlalchemy導入SQLAlchemy類
from flask_sqlalchemy import SQLAlchemy

import os
# 通過Flask創建一個app實例
app = Flask(__name__)
basedir = os.path.abspath(os.path.dirname(__file__))
# Flask_SQLAlchemy插件從SQLALCHEMY_DATABASE_URI配置的變量中獲取應用的數據庫位置
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///' + os.path.join(basedir, 'database.db')
# 通過SQLAlchemy創建db實例,表示程序使用的數據庫,並且db能夠使用Flask_SQLAlchemy的所有功能
db = SQLAlchemy(app)

2.3 構建數據庫模型

數據模型通常用來定義數據庫中的表及表中的字段。下面代碼中的User類和Post類就代表了數據庫中的兩張表(官方叫法是數據模型)。後面我們會看到,通過這裏定義的兩個Python類,我們就可以非常容易的完成表的增刪改查,以下是routes.py文件中的內容。

# 在routes.py文件中的內容
from database_demo import db

# 定義類User,繼承自基類db.Model
class User(db.Model):
    # 定義數據庫表的名稱,爲user表
    __tablename__ = 'user'
    # db.Column類構造函數中的第一個參數表中該字段的類型和該字段的其它屬性
    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String(20), unique=True, nullable=False)
    # print User對象時,會打印return的字符串,方便調試
    def __repr__(self):
        return f"User('{self.username}')"


class Post(db.Model):
    __tablename__ = 'post'
    id = db.Column(db.Integer, primary_key=True)
    title = db.Column(db.String(100), unique=True, nullable=False)

    def __repr__(self):
        return f"Post('{self.title}')"

2.4 建立表之間的關係

關係型數據庫是通過關係把數據庫中不同表進行關聯。下面,將介紹表之間的一種關係(一對多的關係),通常一個用戶可以發表多篇日誌,這也是與實際情況相符的。在routes.py文件中添加兩行代碼便可以建立這種“一對多”的關係了。貓姐提醒:”一對多“的關係在實際開發中十分常見,但是下面的兩行建立關係的代碼卻不太容易看懂,因此大家不用太關心代碼的細節,後面只要大家會照着葫蘆畫瓢,建立好這種關係就足夠了。

# 修改routes.py文件的內容

from database_demo import db

class User(db.Model):
    __tablename__ = 'user'
    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String(20), unique=True, nullable=False)
    # 添加反向引用關係
    posts = db.relationship('Post', backref='author', lazy=True)

    def __repr__(self):
        return f"User('{self.username}')"


class Post(db.Model):
    __tablename__ = 'post'
    id = db.Column(db.Integer, primary_key=True)
    title = db.Column(db.String(100), unique=True, nullable=False)
    # 添加表user的外鍵user_id
    user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)

    def __repr__(self):
        return f"Post('{self.title}')"

添加到Post模型中的user_id被稱爲外鍵,兩個表的關係主要通過外鍵建立起表之間的聯繫。db.ForeignKey的參數'user.id'表示這列的值是user表中的id。添加到User模型的posts屬性表示這個關係的面向對象視圖,它不是實際數據庫的字段,它是用戶和其發表日誌關係之間的視圖。如果有一個User類的實例u,u.posts則返回用戶發表過的所有日誌。

3. 數據庫的基本操作

3.1 數據庫中表的創建

首先在run.py文件中輸入如下代碼:

# run.py文件中的內容
from database_demo import app

if __name__ == "__main__":
     app.run(debug=True)

然後在終端中輸入flask shell命令,前提條件是必須在終端中配置環境變量,在第一課中貓姐已經講過如何配置,這裏不再重複。輸入flask shell後進入python交互環境,如下所示:

 在終端中輸入命令flask shell

(miao_venv) maojie@Thinkpad:~/flask-plan/flask-course-primary/day4$ flask shell
/home/meyou/flask-plan/miao_venv/lib/python3.6/site-packages/flask_sqlalchemy/__init__.py:835: FSADeprecationWarning: SQLALCHEMY_TRACK_MODIFICATIONS adds significant overhead and will be disabled by default in the future.  Set it to True or False to suppress this warning.
  'SQLALCHEMY_TRACK_MODIFICATIONS adds significant overhead and '
Python 3.6.7 (default, Oct 22 2018, 11:32:17) 
[GCC 8.2.0] on linux
App: database_demo [production]
Instance: /home/meyou/flask-plan/flask-course-primary/day4/instance
>>> 

# 注意:出現三個大於號,表示已經進入python交互環境

開始在數據庫中創建user表和post表,如下所示:

>>> from database_demo import db       # 從database_demo包中導入db
>>> db.create_all()                    # 用db.create_all()創建數據庫表                 
>>>

這時查看程序目錄,發現已經存在database.db庫了,如下所示:

# 數據庫存在於database_demo包裏
(miao_venv) maojie@Thinkpad:~/flask-plan/flask-course-primary/day4/database_demo$ ls
database.db  __init__.py  __init__.pyc  __pycache__  routes.py

# database.db文件名是我們在__init__.py中配置的

要想查看dabase.db中是否存在新建的表,可以用下面的方式,如下所示:

# 在database_demo包目錄中用sqlite3 加上數據庫名字進入到數據庫中

(miao_venv) maojie@Thinkpad:~/flask-plan/flask-course-primary/day4/database_demo$ sqlite3 database.db 
SQLite version 3.22.0 2018-01-22 18:45:57
Enter ".help" for usage hints.

# 用.tables查看錶
sqlite> .tables
post  user
sqlite> 

# post,user是我們在建立數據模型時定義的兩張表

雖然通過db.create_all()可以創建表,但是如果數據庫中已經存在這兩張表,再用db.create_all()時是不會重新創建表或是更新表,只有先通過db.drop_all()刪除數據庫中所有的表,然後再用db.create_all()創建表,這樣原有的數據庫中表的數據都被銷燬了,這是我們不想看到的。所以,在本文末會用一種更有效的方式來管理數據庫。

3.2 表的數據插入

繼上面python shell環境,在user表中插入兩行數據,如下所示:

>>> from database_demo.routes import User,Post    # 需要導入模型User,Post所在的位置
>>> user1=User(username='miaojie')                # 創建用戶1
>>> user2=User(username='miaoge')                 # 創建用戶2
>>> print(user1.id)                               # 打印用戶1的id
None
>>> print(user2.id)                               # 打印用戶2的id
None
>>> 

# 因爲這些數據還未被真正寫到數據庫中,所以用戶的id並不會顯示出來

# 將數據對象添加到會話中
>>> db.session.add(user1)
>>> db.session.add(user2)
>>>

# 只有將會話提交後,數據纔會寫到database.db文件中
>>> db.session.commit()
>>> 

# 再次查看
>>> print(user1.id)
1
>>> print(user2.id)
2

3.3 表的行數據修改

下面,繼續在python shell會話中進行操作,現在把user1重新命名,如下所示:

>>> user1.username='miaomiao'
>>> db.session.commit()
>>> print(user1)
User('miaomiao')
>>> 

3.4 表的行數據刪除

在數據庫中還可以使用delete()進行數據行的刪除,如下所示:

>>> db.session.delete(user1)
>>> db.session.commit()
>>> user1 = User.query.filter_by(username="miaomiao").first()
>>> print(user1)
None
>>>

3.5 表的查詢

通過query可以查詢模型中的所有的內容,接上個過程,如下:

>>> User.query.all()
[User('miaoge')]
>>> 

# 現在爲用戶2 ‘maoge’創建一篇日誌
>>> u2=User.query.get(2)
>>> p2=Post(title='這是一篇關於數據庫的日誌',author=u2)
>>> db.session.add(p2)
>>> db.session.commit()
>>> User.query.all()
[User('miaoge')]
>>> Post.query.filter_by(author=u2).all()
[Post('這是一篇關於數據庫的日誌')]
>>> 

4. python shell上下文

剛開始在終端中輸入flask shell,進入shell後,輸入數據庫模型等時它並沒有定義,如下所示:

# 在終端中輸入flask shell後,進入shell
# 輸入模型User時,發現它並沒有被定義
>>> User
Traceback (most recent call last):
  File "<console>", line 1, in <module>
NameError: name 'User' is not defined
>>> 
# 這就是因爲剛開始進入shell時,它沒有一個上下文,需要導入模型及其實例

這就需要每次進入flash shell環境時導入數據庫的實例以及模型,這是非常浪費時間的。爲了避免每次進入時導入,可以使用app.shell_context_processor裝飾器,將定義的函數註冊爲一個shell上下文函數,當在終端中輸入flask shell時,該函數會在shell中返回被註冊的內容。在routes.py文件中添加如下代碼:

# routes.py文件中的內容

#!coding:utf8
# 在這裏需要將app導入,以供@app裝飾器使用
from database_demo import db, app

...
...

# 添加shell上下文函數
# app.shell_context_processor裝飾器將generate_shell_context函數註冊爲一個shell上下文函數
@app.shell_context_processor
def generate_shell_context():
    return {'db': db, 'User': User, 'Post': Post}

在終端中重新輸入flask shell,進入shell後查看效果:

(miao_venv) maojie@Thinkpad:~/flask-plan/flask-course-primary/day4$ flask shell
/home/meyou/flask-plan/miao_venv/lib/python3.6/site-packages/flask_sqlalchemy/__init__.py:835: FSADeprecationWarning: SQLALCHEMY_TRACK_MODIFICATIONS adds significant overhead and will be disabled by default in the future.  Set it to True or False to suppress this warning.
  'SQLALCHEMY_TRACK_MODIFICATIONS adds significant overhead and '
Python 3.6.7 (default, Oct 22 2018, 11:32:17) 
[GCC 8.2.0] on linux
App: database_demo [production]
Instance: /home/meyou/flask-plan/flask-course-primary/day4/instance
>>> db
<SQLAlchemy engine=sqlite:////home/meyou/flask-plan/flask-course-primary/day4/database_demo/database.db>
>>> User
<class 'database_demo.routes.User'>
>>> 

這時,所需要的模型都存在python shell裏,而不需要每次進入shell時導入db及其數據模型。

5. 使用 Flask-Migrate 實現數據庫遷移

在前面講過創建數據庫的方法,如果數據庫中存在表或是需要修改數據庫中模型,使用db.create_all()是不會更新數據庫表的,需要先使用db.drop_all()刪除舊錶,再創建表。但是,原來表中的數據就都被刪掉了,這時就出現了數據庫遷移的概念。開發過程中,隨着需求的變化,需要對錶加入一些新的字段,但是舊錶中又保存了很多數據,此時就需要創建新表,並將舊錶中的數據遷移至新表中,Flask_Migrate這個插件就能幫我們快速完成數據從舊錶到新表的遷移過程。

5.1 Flask_Migrate的安裝

還是在虛擬環境中安裝Flask_Migrate插件,如下:

(miao_venv) maojie@Thinkpad:~/flask-plan/flask-course-primary/day4$ pip install Flask_Migrate

5.2 Flask_Migrate的配置

在__init__.py文件中進行如下配置:

# __init__.py文件中的內容

from flask import Flask
from flask_sqlalchemy import SQLAlchemy

# 從flask_migrate中導入Migrate類
from flask_migrate import Migrate

import os

app = Flask(__name__)
basedir = os.path.abspath(os.path.dirname(__file__))
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///' + os.path.join(basedir, 'database.db')
db = SQLAlchemy(app)

# 通過類Migrate創建migrate實例
migrate = Migrate(app, db)

from database_demo.routes import *

5.3 數據庫的遷移過程

Flask_Migrate插件中添加了flask db 命令來管理數據庫遷移的所有事情,首先是遷移前的準備工作:

# 使用flask db init創建migration目錄
(miao_venv) maojie@Thinkpad:~/flask-plan/flask-course-primary/day4$ flask db init
/home/meyou/flask-plan/miao_venv/lib/python3.6/site-packages/flask_sqlalchemy/__init__.py:835: FSADeprecationWarning: SQLALCHEMY_TRACK_MODIFICATIONS adds significant overhead and will be disabled by default in the future.  Set it to True or False to suppress this warning.
  'SQLALCHEMY_TRACK_MODIFICATIONS adds significant overhead and '
  Creating directory /home/meyou/flask-plan/flask-course-primary/day4/migrations
  ... done
  Creating directory /home/meyou/flask-plan/flask-course-
  primary/day4/migrations/versions ... done
  Generating /home/meyou/flask-plan/flask-course-primary/day4/migrations/README
  ... done
  Generating /home/meyou/flask-plan/flask-course-primary/day4/migrations/env.py
  ... done
  Generating /home/meyou/flask-plan/flask-course-
  primary/day4/migrations/script.py.mako ... done
  Generating /home/meyou/flask-plan/flask-course-
  primary/day4/migrations/alembic.ini ... done
  Please edit configuration/connection/logging settings in '/home/meyou/flask-
  plan/flask-course-primary/day4/migrations/alembic.ini' before proceeding.

# 注意,如果在虛擬環境中沒有設置環境變量FLASK_APP=run.py,則在使用命令flask db init時,會提醒需要先設置環境變量。在第一課最後講到如何設置環境變量

# 在day4目錄下已經生成migrations目錄
(miao_venv) maojie@Thinkpad:~/flask-plan/flask-course-primary/day4$ ls
database_demo  migrations  __pycache__  run.py  run.pyc

使用flask db migrate命令檢測Post,User類中是否添加了新的字段:

# 使用flask db migrate命令創建遷移腳本
(miao_venv) maojie@Thinkpad:~/flask-plan/flask-course-primary/day4$ flask db migrate
/home/meyou/flask-plan/miao_venv/lib/python3.6/site-packages/flask_sqlalchemy/__init__.py:835: FSADeprecationWarning: SQLALCHEMY_TRACK_MODIFICATIONS adds significant overhead and will be disabled by default in the future.  Set it to True or False to suppress this warning.
  'SQLALCHEMY_TRACK_MODIFICATIONS adds significant overhead and '
INFO  [alembic.runtime.migration] Context impl SQLiteImpl.
INFO  [alembic.runtime.migration] Will assume non-transactional DDL.
INFO  [alembic.env] No changes in schema detected.

# 提示info消息沒有任何改變

我們在routes.py文件中的User,Post模型中添加字段,這裏,User表中添加了email字段,Post表中添加了content字段。然後再使用flask db migrate命令來執行,如下:

# routes.py文件中的內容
#!coding:utf8
from database_demo import db, app


class User(db.Model):
    __table__name = 'user'
    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String(20), unique=True, nullable=False)
    email = db.Column(db.String(120), nullable=True)
    posts = db.relationship('Post', backref='author', lazy=True)

    def __repr__(self):
        return f"User('{self.username}')"


class Post(db.Model):
    __table__name = 'post'
    id = db.Column(db.Integer, primary_key=True)
    title = db.Column(db.String(100), unique=True, nullable=False)
    content = db.Column(db.Text, nullable=True)
    user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)

    def __repr__(self):
        return f"Post('{self.title}')"


# 解釋,添加email和content字段,unique必須設置爲空或是不寫unique,否則在用flask db migrate創建遷移腳本時會報錯

再次使用flask db migrate命令檢測Post,User類中是否添加了新的字段:

(miao_venv) maojie@Thinkpad:~/flask-plan/flask-course-primary/day4$ flask db migrate
/home/meyou/flask-plan/miao_venv/lib/python3.6/site-packages/flask_sqlalchemy/__init__.py:835: FSADeprecationWarning: SQLALCHEMY_TRACK_MODIFICATIONS adds significant overhead and will be disabled by default in the future.  Set it to True or False to suppress this warning.
  'SQLALCHEMY_TRACK_MODIFICATIONS adds significant overhead and '
INFO  [alembic.runtime.migration] Context impl SQLiteImpl.
INFO  [alembic.runtime.migration] Will assume non-transactional DDL.
INFO  [alembic.autogenerate.compare] Detected added column 'post.content'
INFO  [alembic.autogenerate.compare] Detected added unique constraint 'None' on '['content']'
INFO  [alembic.autogenerate.compare] Detected added column 'user.email'
INFO  [alembic.autogenerate.compare] Detected added unique constraint 'None' on '['email']'
  Generating /home/meyou/flask-plan/flask-course-
  primary/day4/migrations/versions/f49f801bddb6_.py ... done

# 提示檢測到有內容改變,添加了列content和列email字段

最後使用flask db upgrade命令完成數據庫的遷移過程,如下所示:

# flask db upgrade可以把改變應用到數據庫中,而不改變其中保存的數據
(miao_venv) maojie@Thinkpad:~/flask-plan/flask-course-primary/day4$ flask db upgrade
/home/meyou/flask-plan/miao_venv/lib/python3.6/site-packages/flask_sqlalchemy/__init__.py:835: FSADeprecationWarning: SQLALCHEMY_TRACK_MODIFICATIONS adds significant overhead and will be disabled by default in the future.  Set it to True or False to suppress this warning.
  'SQLALCHEMY_TRACK_MODIFICATIONS adds significant overhead and '
INFO  [alembic.runtime.migration] Context impl SQLiteImpl.
INFO  [alembic.runtime.migration] Will assume non-transactional DDL.
INFO  [alembic.runtime.migration] Running upgrade  -> 8436eb139f65, empty message

6. 總結

學習完今天的內容,我們掌握瞭如下技能:

1.學習了Flask_SQLAlchemy的基本使用方法
2.學習了數據庫模型及表之間關係的建立
3.學習了數據庫的增刪改查基本操作
4.學習了python shell上下文的使用
5.學習了使用Flask_Migrate完成數據庫的遷移

下一課的教程,貓姐將帶領大家一起學習用戶登錄功能的實現。今天的內容就到這裏,喜歡的同學們可以在下面點贊留言,或是訪問我的博客地址:http://www.catonlinepy.tech/ 加入我們的QQ羣進一步交流學習!

7. 代碼的獲取

大家可以到github上獲取今天教程的所有代碼:https://github.com/miaojie19/...

具體下載代碼的命令如下:

# 使用git命令下載flask-course-primary倉庫所有的代碼
git clone https://github.com/miaojie19/flask-course-primary.git

# 下載完成後,進入day4目錄下面,即可看到今天的代碼
cd flask-course-primary
cd day4

圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章