原文: http://www.catonlinepy.tech/
聲明:原創不易,未經許可,不得轉載python
接第3天表單的使用課程,今天的課程主要涉及到與數據庫相關的兩個插件的使用,一個是Flask_SQLAlchemy,另一個是Flask_Migrate。經過今天的學習,你將學會如何對數據庫進行基本的操做,以及如何完成數據庫的遷移。教程中的代碼都會託管到github上,貓姐不厭其煩地強調,在學習本課內容時必定要本身嘗試手敲代碼,遇到問題再到github上查看代碼,若是實在不知道如何解決,能夠在日誌下方留言。linux
對於web後臺開發工做,必需要掌握的一項技能即是對數據庫的CRUD(create, read, update, delete)操做,若是開發過程當中直接使用原生的sql語句對數據庫進行操做,將是很是痛苦的事件(畢竟sql語句有不少反人類的設計)。Flask_SQLAlchemy插件將開發人員從這個泥潭中解救出來了,咱們只須要使用Python的類就能輕鬆的完成對錶的增刪改查操做,而且該插件還支持多種數據庫類型,如MySQL、PostgreSQL、和SQLite等。git
在進入正式學習以前,咱們照舊要創建今天的項目目錄,以下:github
# 進入到虛擬環境目錄,激活虛擬環境 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
咱們的教程中使用的數據庫是SQLite(Linux),主要緣由是SQLite足夠簡單,不須要進行任何配置即可使用,十分適用於入門。下面在__init__.py文件中配置數據庫,以下所示:web
# 在__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)
數據模型一般用來定義數據庫中的表及表中的字段。下面代碼中的User類和Post類就表明了數據庫中的兩張表(官方叫法是數據模型)。後面咱們會看到,經過這裏定義的兩個Python類,咱們就能夠很是容易的完成表的增刪改查,如下是routes.py文件中的內容。sql
# 在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}')"
關係型數據庫是經過關係把數據庫中不一樣表進行關聯。下面,將介紹表之間的一種關係(一對多的關係),一般一個用戶能夠發表多篇日誌,這也是與實際狀況相符的。在routes.py文件中添加兩行代碼即可以創建這種「一對多」的關係了。貓姐提醒:」一對多「的關係在實際開發中十分常見,可是下面的兩行創建關係的代碼卻不太容易看懂,所以你們不用太關心代碼的細節,後面只要你們會照着葫蘆畫瓢,創建好這種關係就足夠了。shell
# 修改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則返回用戶發表過的全部日誌。
首先在run.py文件中輸入以下代碼:數據庫
# run.py文件中的內容 from database_demo import app if __name__ == "__main__": app.run(debug=True)
而後在終端中輸入flask shell命令,前提條件是必須在終端中配置環境變量,在第一課中貓姐已經講過如何配置,這裏再也不重複。輸入flask shell後進入python交互環境,以下所示:flask
在終端中輸入命令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表,以下所示:session
>>> 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()建立表,這樣原有的數據庫中表的數據都被銷燬了,這是咱們不想看到的。因此,在本文末會用一種更有效的方式來管理數據庫。
繼上面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
下面,繼續在python shell會話中進行操做,如今把user1從新命名,以下所示:
>>> user1.username='miaomiao' >>> db.session.commit() >>> print(user1) User('miaomiao') >>>
在數據庫中還可使用delete()進行數據行的刪除,以下所示:
>>> db.session.delete(user1) >>> db.session.commit() >>> user1 = User.query.filter_by(username="miaomiao").first() >>> print(user1) None >>>
經過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('這是一篇關於數據庫的日誌')] >>>
剛開始在終端中輸入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及其數據模型。
在前面講過建立數據庫的方法,若是數據庫中存在表或是須要修改數據庫中模型,使用db.create_all()是不會更新數據庫表的,須要先使用db.drop_all()刪除舊錶,再建立表。可是,原來表中的數據就都被刪掉了,這時就出現了數據庫遷移的概念。開發過程當中,隨着需求的變化,須要對錶加入一些新的字段,可是舊錶中又保存了不少數據,此時就須要建立新表,並將舊錶中的數據遷移至新表中,Flask_Migrate這個插件就能幫咱們快速完成數據從舊錶到新表的遷移過程。
仍是在虛擬環境中安裝Flask_Migrate插件,以下:
(miao_venv) maojie@Thinkpad:~/flask-plan/flask-course-primary/day4$ pip install 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 *
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): __tablename__ = '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): __tablename__ = '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
學習完今天的內容,咱們掌握了以下技能:
1.學習了Flask_SQLAlchemy的基本使用方法
2.學習了數據庫模型及表之間關係的創建
3.學習了數據庫的增刪改查基本操做
4.學習了python shell上下文的使用
5.學習了使用Flask_Migrate完成數據庫的遷移
下一課的教程,貓姐將帶領你們一塊兒學習用戶登陸功能的實現。今天的內容就到這裏,喜歡的同窗們能夠在下面點贊留言,或是訪問個人博客地址:http://www.catonlinepy.tech/ 加入咱們的QQ羣進一步交流學習!
你們能夠到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