【Flask/跟着學習】Flask大型教程項目#03:數據庫

跟着學習:http://www.pythondoc.com/flask-mega-tutorial/database.html
回顧上一章:https://blog.csdn.net/weixin_41263513/article/details/85007362

本章參考到的各種內容,感謝大大們的收集總結

常用的SQLalchemy 字段類型
SQLAlchemy查詢過濾器以及查詢執行方法

本章內容

  • Flask 中的數據庫
  • 遷移
  • 配置
  • 數據庫模型
  • 創建數據庫
  • 遷移數據庫
  • 數據庫升級和回退
  • 數據庫關係

這一章節值得注意的是,創建數據庫,遷移數據庫,數據庫升級和回退小節所涉及的代碼不需要特別弄懂,相當於我們自己寫的api,在需要的時候複製去用就可以了(我看的也不是很懂hh)

Flask 中的數據庫

我們將使用 Flask-SQLAlchemy 擴展來管理我們應用程序的數據。這個擴展封裝了 SQLAlchemy 項目,這是一個 對象關係映射器 或者 ORM。

ORMs 允許數據庫應用程序與對象一起工作,而不是表以及 SQL。執行在對象的操作會被 ORM 翻譯成數據庫命令。這就意味着我們將不需要學習 SQL,我們將讓 Flask-SQLAlchemy 代替 SQL。

遷移

我見過的大多數數據庫教程會涉及到創建和使用一個數據庫,但沒有充分講述隨着應用程序擴大更新數據庫的問題。通常情況下,每次你需要進行更新,你最終不得不刪除舊的數據庫和創建一個新的數據庫,並且失去了所有的數據。如果數據不能容易地被重新創建,你可能會被迫自己編寫導出和導入腳本。

幸運地,我們還有一個更好的選擇。

我們將使用 SQLAlchemy-migrate 來跟蹤數據庫的更新。它只是在開始建立數據庫的時候多花費些工作,這只是很小的代價,以後就再不用擔心人工數據遷移了。

配置

針對我們小型的應用,我們將採用 sqlite 數據庫。sqlite 數據庫是小型應用的最方便的選擇,每一個數據庫都是存儲在單個文件裏。

應用使用的數據庫URL必須保存到Flask配置對象的SQLALCHEMY_DATABASE_URI鍵中。Flask-SQLAlchemy文檔還建議把SQLALCHEMY_TRACK_MODIFICATIONS鍵設爲False,以便在不需要跟蹤對象變化時降低內存消耗,其他配置選項的作用參閱Flask-SQLAlchemy的文檔

我們有許多新的配置項需要添加到配置文件中
文件:/config.py

import os
basedir = os.path.abspath(os.path.dirname(__file__))

class Config(object):
    SECRET_KEY = os.environ.get('SECRET_KEY') or 'you-will-never-guess'
    SQLALCHEMY_DATABASE_URI = 'sqlite:///' + os.path.join(basedir, 'app.db')
    SQLALCHEMY_TRACK_MODIFICATIONS = False

最後,當我們初始化應用程序的時候,我們也必須初始化數據庫。這是我們更新後的初始化文件
文件:/app/–init–.py

from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from config import Config

app = Flask(__name__)
app.config.from_object(Config)
db = SQLAlchemy(app)

from app import routes, models

注意我們在初始化腳本中的兩個改變。創建了一個 db 對象,這是我們的數據庫,通過db可以獲得Flask-SQLAlchemy提供的所有功能,接着導入一個新的模塊,叫做 models。接下來我們將編寫這個模塊

數據庫模型

我們存儲在數據庫中數據將會以類的集合來表示,我們稱之爲數據庫模型。ORM 層需要做的翻譯就是將從這些類創建的對象映射到適合的數據庫表的行。

讓我們創建一個表示用戶的模型。使用 WWW SQL Designer 工具,我製作如下的圖來表示我們用戶的表
在這裏插入圖片描述
id 字段通常會在所有模型中,並且用於作爲主鍵。在數據庫的每一個用戶會被賦予一個不同的 id 值,存儲在這個字段中。幸好這是自動完成的,我們僅僅需要的是提供 id 這個字段。

username 以及 email 字段是被定義成字符串,並且指定了最大的長度以便數據庫可以優化空間佔用。

字段是被作爲 db.Column 類的實例創建的,db.Column 把字段的類型作爲參數,並且還有一些其它可選的參數,比如表明字段是否唯一。

–repr-- 方法告訴 Python 如何打印這個類的對象。我們將用它來調試。

常用的SQLalchemy 字段類型

文件:/app/models.py

from app import db

class User(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String(64), index=True, unique=True)
    email = db.Column(db.String(120), index=True, unique=True)
    password = db.Column(db.String(128))

    def __repr__(self):
        return '<User {}>'.format(self.username)

創建數據庫

配置以及模型都已經到位了,是時候準備創建數據庫文件。SQLAlchemy-migrate 包自帶命令行和 APIs,這些 APIs 以一種將來允許容易升級的方式來創建數據庫。我發現命令行使用起來比較彆扭,因此我們自己編寫一些 Python 腳本來調用遷移的 APIs。

SQLALCHEMY_DATABASE_URI 是 Flask-SQLAlchemy 擴展需要的。這是我們數據庫文件的路徑。
SQLALCHEMY_MIGRATE_REPO 是文件夾,我們將會把 SQLAlchemy-migrate 數據文件存儲在這裏。
文件:/db_create.py

from migrate.versioning import api
from app import db
import os.path

basedir = os.path.abspath(os.path.dirname(__file__))
SQLALCHEMY_DATABASE_URI = 'sqlite:///' + os.path.join(basedir, 'app.db')
SQLALCHEMY_MIGRATE_REPO = os.path.join(basedir, 'migrations')

db.create_all()
if not os.path.exists(SQLALCHEMY_MIGRATE_REPO):
    api.create(SQLALCHEMY_MIGRATE_REPO, 'database repository')
    api.version_control(SQLALCHEMY_DATABASE_URI, SQLALCHEMY_MIGRATE_REPO)
else:
    api.version_control(SQLALCHEMY_DATABASE_URI, SQLALCHEMY_MIGRATE_REPO, api.version(SQLALCHEMY_MIGRATE_REPO))

在運行上述命令之後你會發現一個新的 app.db 文件。這是一個空的 sqlite 數據庫,創建一開始就支持遷移。同樣你還將有一個 db_repository 文件夾,裏面還有一些文件,這是 SQLAlchemy-migrate 存儲它的數據文件的地方。請注意,我們不會再生的存儲庫,如果它已經存在。這將使我們重新創建數據庫,同時保留現有的存儲庫,如果我們需要。
運行前,後:
在這裏插入圖片描述
在這裏插入圖片描述

遷移數據庫

現在,我們已經定義了我們的模型,我們可以將其合併到我們的數據庫中。我們會把應用程序數據庫的結構任何的改變看做成一次遷移,因此這是我們第一次遷移,我們將從一個空數據庫遷移到一個能存儲用戶的數據庫上。

爲了實現遷移,我們需要編寫一小段 Python 代碼
文件:/db_migrate.py

import imp
from migrate.versioning import api
from app import db
import os

basedir = os.path.abspath(os.path.dirname(__file__))
SQLALCHEMY_DATABASE_URI = 'sqlite:///' + os.path.join(basedir, 'app.db')
SQLALCHEMY_MIGRATE_REPO = os.path.join(basedir, 'migrations')
v = api.db_version(SQLALCHEMY_DATABASE_URI, SQLALCHEMY_MIGRATE_REPO)

migration = SQLALCHEMY_MIGRATE_REPO + ('/versions/%03d_migration.py' % (v+1))
tmp_module = imp.new_module('old_model')
old_model = api.create_model(SQLALCHEMY_DATABASE_URI, SQLALCHEMY_MIGRATE_REPO)
exec(old_model, tmp_module.__dict__)
script = api.make_update_script_for_model(SQLALCHEMY_DATABASE_URI, SQLALCHEMY_MIGRATE_REPO, tmp_module.meta, db.metadata)
open(migration, "wt").write(script)
api.upgrade(SQLALCHEMY_DATABASE_URI, SQLALCHEMY_MIGRATE_REPO)
v = api.db_version(SQLALCHEMY_DATABASE_URI, SQLALCHEMY_MIGRATE_REPO)
print('New migration saved as ' + migration)
print('Current database version: ' + str(v))

SQLAlchemy-migrate 遷移的方式就是比較數據庫(在本例中從 app.db 中獲取)與我們模型的結構(從文件 app/models.py 獲取)。兩者間的不同將會被記錄成一個遷移腳本存放在遷移倉庫中。遷移腳本知道如何去遷移或撤銷它,所以它始終是可能用於升級或降級一個數據庫。

然而在使用上面的腳本自動地完成遷移的時候也不是沒有問題的,我見過有時候它很難識別新老格式的變化。爲了讓 SQLAlchemy-migrate 容易地識別出變化,我絕不會重命名存在的字段,我僅限於增加或者刪除模型或者字段,或者改變已存在字段的類型。當然我一直會檢查生成的遷移腳本,確保它是正確。

毋庸置疑你不應該在沒有備份下去嘗試遷移數據庫。當然也不能在生產環境下直接運行遷移腳本,必須在開發環境下確保遷移運轉正常。

因此讓我們繼續進行,記錄下遷移,我運行了兩次,以下是我第二次運行之後的結果:
在這裏插入圖片描述

數據庫升級和回退

到現在你可能想知道爲什麼完成記錄數據庫遷移的這項令人麻煩的事情是這麼重要。

假設你有一個應用程序在開發機器上,同時有一個拷貝部署在到線上的生產機器上。在下一個版本中,你的數據模型有一個變化,比如新增了一個表。如果沒有遷移腳本,你可能必須要琢磨着如何修改數據庫格式在開發和生產機器上,這會花費很大的工作。

如果有數據庫遷移的支持,當你準備發佈新版的時候,你只需要錄製一個新的遷移,拷貝遷移腳本到生產服務器上接着運行腳本,所有事情就完成了。數據庫升級也只需要一點 Python 腳本
文件:/db_upgrade.py

from migrate.versioning import api
import os

basedir = os.path.abspath(os.path.dirname(__file__))
SQLALCHEMY_DATABASE_URI = 'sqlite:///' + os.path.join(basedir, 'app.db')
SQLALCHEMY_MIGRATE_REPO = os.path.join(basedir, 'migrations')
api.upgrade(SQLALCHEMY_DATABASE_URI, SQLALCHEMY_MIGRATE_REPO)
v = api.db_version(SQLALCHEMY_DATABASE_URI, SQLALCHEMY_MIGRATE_REPO)
print('Current database version: ' + str(v))

當你運行上述腳本的時候,數據庫將會升級到最新版本。
通常情況下,沒有必要把數據庫降低到舊版本,所以略過

數據庫關係

關係型數據可以很好的存儲數據項之間的關係。考慮一個用戶寫了一篇 blog 的例子。在 users 表中有一條用戶的數據,在 posts 表中有一條 blog 數據。記錄是誰寫了這篇 blog 的最有效的方式就是連接這兩條相關的數據項。

一旦在用戶和文章(post)的聯繫被建立,有兩種類型的查詢是我們可能需要使用的。最常用的查詢就是查詢 blog 的作者。複雜一點的查詢就是一個用戶的所有的 blog。Flask-SQLAlchemy 將會幫助我們完成這兩種查詢。

讓我們擴展數據庫以便存儲 blog。爲此我們回到數據庫設計工具並且創建一個 posts 表。
在這裏插入圖片描述
我們的 posts 表中有必須得 id 字段,以及 blog 的 body 以及一個 timestamp。這裏沒有多少新東西。只是對 user_id 字段需要解釋下。

我們說過想要連接用戶和他們寫的 blog。方式就是通過在 posts 增加一個字段,這個字段包含了編寫 blog 的用戶的 id。這個 id 稱爲一個外鍵。我們的數據庫設計工具把外鍵顯示成一個連線,這根連線連接於 users 表中的 id 與 posts 表中的 user_id。這種關係稱爲一對多,一個用戶編寫多篇 blog。

我們添加了一個 Post 類,這是用來表示用戶編寫的 blog。在 Post 類中的 user_id 字段初始化成外鍵,因此 Flask-SQLAlchemy 知道這個字段是連接到用戶上。

值得注意的是我們已經在 User 類中添加一個新的字段稱爲 posts,它是被構建成一個 db.relationship 字段。這並不是一個實際的數據庫字段,因此是不會出現在上面的圖中。對於一個一對多的關係,db.relationship 字段通常是定義在“一”這一邊。在這種關係下,我們得到一個 user.posts 成員,它給出一個用戶所有的 blog。不用擔心很多細節不知道什麼意思,以後我們會不斷地看到例子。

from datetime import datetime
from app import db

class User(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String(64), index=True, unique=True)
    email = db.Column(db.String(120), index=True, unique=True)
    password = db.Column(db.String(128))
    posts = db.relationship('Post', backref='author', lazy='dynamic')

    def __repr__(self):
        return '<User {}>'.format(self.username)

class Post(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    body = db.Column(db.String(140))
    timestamp = db.Column(db.DateTime, index=True, default=datetime.utcnow)
    user_id = db.Column(db.Integer, db.ForeignKey('user.id'))

    def __repr__(self):
        return '<Post {}>'.format(self.body)

這章節花了很大的筆墨在數據庫上,給我們的反饋並不是很多,但卻是必要的知識,最後貼以下常用的SQLAlchemy查詢過濾器以及查詢執行方法,雖然以上的創建數據庫,遷移數據庫,數據庫升級和回退內容不需要我們弄懂,但也必須要會用!

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