天天看點

Flask架構的學習——10—(Flask-SQLAlchemy插件、Flask-Script指令行傳參、Flask-Migrate資料遷移、WTForms表單驗證)

1、Flask-SQLAlchemy插件

另外一個架構,叫做Flask-SQLAlchemy,Flask-SQLAlchemy是對SQLAlchemy進行了一個簡單的封裝,使得我們在flask中使用sqlalchemy更加的簡單。可以通過pip install flask-sqlalchemy。

Flask架構的學習——10—(Flask-SQLAlchemy插件、Flask-Script指令行傳參、Flask-Migrate資料遷移、WTForms表單驗證)
  • 資料庫初始化:資料庫初始化不再是通過create_engine
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from constants import DB_URI
app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = DB_URI
db = SQLAlchemy(app)
           
  • ORM類:之前都是通過Base = declarative_base()來初始化一個基類,然後再繼承,在Flask-SQLAlchemy中更加簡單了
class User(db.Model):
  id = db.Column(db.Integer,primary_key=True)
  username = db.Column(db.String(80),unique=True)
  email = db.Column(db.String(120),unique=True)
  
  def __repr__(self):
      return '<User %s>' % self.username
           
  • 映射模型到資料庫表:使用Flask-SQLAlchemy所有的類都是繼承自db.Model,并且所有的Column和資料類型也都成為db的一個屬性。但是有個好處是不用寫表名了,Flask-SQLAlchemy會自動将類名小寫化,然後映射成表名。

    寫完類模型後,要将模型映射到資料庫的表中,使用以下代碼建立所有的表:

  • 添加資料:這時候就可以在資料庫中看到已經生成了一個user表了。
admin = User('admin','[email protected]')
guest = User('guest','[email protected]')
db.session.add(admin)
db.session.add(guest)
db.session.commit()
           
  • 添加資料和之前的沒有差別,隻是session成為了一個db的屬性。
  • 查詢資料:查詢資料不再是之前的session.query了,而是将query屬性放在了db.Model上,是以查詢就是通過Model.query的方式進行查詢了
  • 删除資料:删除資料跟添加資料類似,隻不過session是db的一個屬性而已:
db.session.delete(admin)
db.session.commit()
           

執行個體整體代碼flask_sqlalchemy_demo.py如下:

# -*- encoding: utf-8 -*-
"""
@File    : flask_sqlalchemy_demo.py
@Time    : 2020/4/28 9:38
@Author  : chen

"""
from flask import Flask
from flask_sqlalchemy import SQLAlchemy


# 127.0.0.1
HOSTNAME = "localhost"
DATABASE = "demo0425"
PORT = 3306
USERNAME = "root"
PASSWORD = "root"
DB_URL = 'mysql+mysqlconnector://{}:{}@{}:{}/{}'.format(USERNAME, PASSWORD, HOSTNAME, PORT, DATABASE)


app = Flask(__name__)

'''
# 建立引擎并生成Base類
engine = create_engine(DB_URL)
Base = declarative_base(engine)
'''
app.config['SQLALCHEMY_DATABASE_URI'] = DB_URL           # 資料庫連接配接成功


# 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 '
# 這裡是為了解決上面的警告
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False

db = SQLAlchemy(app)               # 建立SQLAlchemy


class User(db.Model):
    __tablename__ = "user2"            # 如果沒定義__tablename__,會預設以User模型的小寫user定義表名
    
    id = db.Column(db.Integer, primary_key=True, autoincrement=True)
    name = db.Column(db.String(50))
    
    # articles = db.relationship('author')             # 下面使用backref='articles',這裡就不用定義了
    
    # 輸出資訊     __str__方法列印不夠詳細
    def __repr__(self):
        return "User(name:%s)" % self.name


class Article(db.Model):
    __tablename__ = "article2"
    
    id = db.Column(db.Integer, primary_key=True, autoincrement=True)
    title = db.Column(db.String(50))
    
    uid = db.Column(db.Integer, db.ForeignKey('user2.id'))    # 定義外鍵
    
    author = db.relationship('User', backref='articles')      # 反向引用articles屬性


'''
Base.metadata.drop_all()
Base.metadata.create_all()         下面是替代簡化語句
'''
db.drop_all()
db.create_all()

# 添加資料
user = User(name='U1')
article = Article(title='A1')
article.author = user    # 反向引用

db.session.add(article)         # 資料添加到資料庫中
db.session.commit()

# articles = db.session.query(Article).order_by(Article.id).all()
# 查詢資料
user = User.query.all()                     # 注意和之前的寫法不一樣,簡化寫法
print(user)
# users = db.session.query(User).all()      # 列印資料和上面相同,書寫方式不一樣

# 排序
users = User.query.order_by(User.id.desc()).all()
print(users)

# 删除資料
user = User.query.filter(User.name == 'U1').first()
db.session.delete(user)
db.session.commit()


@app.route("/")
def index():
    return "首頁"


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

2、Flask-Script 指令行傳參

Flask-Script的作用是可以通過指令行的形式來操作Flask。例如通過指令跑一個開發版本的伺服器、設定資料庫,定時任務等。要使用Flask-Script,可以通過pip install flask-script安裝最新版本。

from flask_script import Manager
from flask_sqlalchemy_demo import app     # 需要将目前檔案夾設定為目前根目錄,才不會報錯

manage = Manager(app)


@manage.command
def index():
    print("hello world")

 
'''
@option('-n', '--name', dest='name')
@option('-u', '--url', dest='url')
def hello(name, url):
    print("hello", name, url)
'''


# 指令行傳參
@manage.option('-n', '--name', dest='name')
@manage.option('-u', '--url', dest='url')
def hello(name, url):
    print("hello", name, url)


if __name__ == '__main__':
    manage.run()
           
Flask架構的學習——10—(Flask-SQLAlchemy插件、Flask-Script指令行傳參、Flask-Migrate資料遷移、WTForms表單驗證)
Flask架構的學習——10—(Flask-SQLAlchemy插件、Flask-Script指令行傳參、Flask-Migrate資料遷移、WTForms表單驗證)

我們把腳本指令代碼放在一個叫做manage.py檔案中,然後在終端運作python manage.py hello指令,就可以看到輸出hello了。

用指令行建立超級使用者,并向資料庫中添加資料,執行個體如下:

Flask架構的學習——10—(Flask-SQLAlchemy插件、Flask-Script指令行傳參、Flask-Migrate資料遷移、WTForms表單驗證)

flask_script_demo.py

# -*- encoding: utf-8 -*-
"""
@File    : flask_script_demo.py
@Time    : 2020/4/28 15:35
@Author  : chen

"""
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
import config     # 導入config.py檔案

app = Flask(__name__)
app.config.from_object(config)       # 加載配置檔案中的選項

db = SQLAlchemy(app)


class AdminUser(db.Model):
    __tablename__ = "admin_users"  # 如果沒定義__tablename__,會預設以User模型的小寫adminuser定義表名
    
    id = db.Column(db.Integer, primary_key=True, autoincrement=True)
    name = db.Column(db.String(50))
    email = db.Column(db.String(50))
    
    # 輸出資訊     __str__方法列印不夠詳細
    def __repr__(self):
        return "User(name:%s)" % self.name


db.create_all()
db.session.commit()
           

manage.py

# -*- encoding: utf-8 -*-
"""
@File    : manage.py
@Time    : 2020/4/28 15:45
@Author  : chen

"""
from flask_script_demo import app, AdminUser, db      # 引用flask_script_demo.py中的app,AdminUser,db子產品
from flask_script import Manager

manage = Manager(app)


'''
@option('-n', '--name', dest='name')
@option('-u', '--url', dest='url')
def hello(name, url):
    print("hello", name, url)
'''


# 指令行傳參
@manage.option('-n', '--name', dest='name')
@manage.option('-e', '--email', dest='email')
def add_user(name, email):
    user = AdminUser(name=name, email=email)         # 前面的name=是字段,後面的name是參數值
    
    db.session.add(user)
    db.session.commit()
    

if __name__ == '__main__':
    manage.run()

           

定義指令的三種方法

  1. 使用@command裝飾器
  2. 使用類繼承自Command類
from flask_script import Command,Manager
from your_app import app

manager = Manager(app)

class Hello(Command):
    "prints hello world"

    def run(self):
        print("hello world")

manager.add_command('hello',Hello())
           

使用類的方式,有三點需要注意:

  • 必須繼承自Command基類。
  • 必須實作run方法。
  • 必須通過add_command方法添加指令。
  1. 使用option裝飾器:如果想要在使用指令的時候還傳遞參數進去,那麼使用@option裝飾器更加的友善
@manager.option('-n','--name',dest='name')
def hello(name):
    print('hello ',name)
           

調用hello指令

python manage.py -n xxx
python manage.py --name xxx
           

添加參數到指令中

  • option裝飾器:以上三種建立指令的方式都可以添加參數
@manager.option('-n', '--name', dest='name', default='joe')
@manager.option('-u', '--url', dest='url', default=None) 
def hello(name, url): 
  if url is None: 
     print("hello", name)
  else: 
      print("hello", name, "from", url)
           
  • command裝飾器:command裝飾器也可以添加參數,但是不能那麼的靈活
@manager.command 
def hello(name="Fred") 
  print("hello", name)
           
  • 類繼承:類繼承也可以添加參數
from flask_Flask import Comman,Manager,Option

class Hello(Command):
  option_list = (
      Option('--name','-n',dest='name'),
  )

  def run(self,name):
      print("hello %s" % name)
           

如果要在指定參數的時候,動态的做一些事情,可以使用get_options方法:

class Hello(Command):
  def __init__(self,default_name='Joe'):
self.default_name = default_name

  def get_options(self):
      return [
          Option('-n','--name',dest='name',default=self.default_name),
      ]

  def run(self,name):
      print('hello',name)
           

3、Flask-Migrate

在實際的開發環境中,經常會發生資料庫修改的行為。一般我們修改資料庫不會直接手動的去修改,而是去修改ORM對應的模型,然後再把模型映射到資料庫中。這時候如果有一個工具能專門做這種事情,就顯得非常有用了,而flask-migrate就是做這個事情的。flask-migrate是基于Alembic進行的一個封裝,并內建到Flask中,而所有的遷移操作其實都是Alembic做的,他能跟蹤模型的變化,并将變化映射到資料庫中。

使用Flask-Migrate需要安裝

pip install flask-migrate
           

Win10系統安裝使用Flask-Migrate需要注意的是:

Flask架構的學習——10—(Flask-SQLAlchemy插件、Flask-Script指令行傳參、Flask-Migrate資料遷移、WTForms表單驗證)

alembic 1.4版本下載下傳

Flask-Migrate資料遷移步驟:

要讓Flask-Migrate能夠管理app中的資料庫,需要使用Migrate(app,db)來綁定app和資料庫

from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from constants import DB_URI
from flask_migrate import Migrate

app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = DB_URI
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = True
db = SQLAlchemy(app)
# 綁定app和資料庫
migrate = Migrate(app,db)

class User(db.Model):
    id = db.Column(db.Integer,primary_key=True)
    username = db.Column(db.String(20))

    addresses = db.relationship('Address',backref='user')

class Address(db.Model):
    id = db.Column(db.Integer,primary_key=True)
    email_address = db.Column(db.String(50))
    user_id = db.Column(db.Integer,db.ForeignKey('user.id'))

db.create_all()

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

if __name__ == '__main__':
    app.run()
           

執行個體代碼檔案如下:

正常我們使用flask-migrate時,步驟是:1、主檔案flask_app.py

# -*- encoding: utf-8 -*-
"""
@File    : flask_app.py
@Time    : 2020/4/28 21:10
@Author  : chen

"""
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
import config                             # 引用config檔案
from model import User                    # 引用model中的User類

from exts import db                      # 解決互相引用的問題,用exts.py當做第三方檔案來引用

'''
ImportError: cannot import name 'User'     互相引用報錯

需要用第三個檔案來間接導入
'''
app = Flask(__name__)

app.config.from_object(config)

# 需要解決互相引用的問題 傳app參數到db中
db.init_app(app)

# db = SQLAlchemy(app)   # 互相引用的問題導緻不能直接傳參到db


'''
name = db.Column(db.String(50))
email = db.Column(db.String(50))
password = db.Column(db.String(50))
'''
# user = User(name='x1', email='[email protected]', password='123')
# db.session.add(user)
# db.session.commit()


@app.route("/")
def index():
    return "首頁"


if __name__ == '__main__':
    app.run()
           

2、模型檔案model.py

# -*- encoding: utf-8 -*-
"""
@File    : model.py
@Time    : 2020/4/28 22:03
@Author  : chen

"""
# 這個model.py檔案放app的模型

# from flask_app import db

# 解決互相引用的問題,用exts.py當做第三方檔案來引用
from exts import db


class User(db.Model):
    __tablename__ = "user_migrate"
    
    id = db.Column(db.Integer, primary_key=True, autoincrement=True)
    name = db.Column(db.String(50))
    email = db.Column(db.String(50))
    password = db.Column(db.String(50))
    
    # migrate測試添加字段
    age = db.Column(db.Integer)
    gender = db.Column(db.Enum('1', '2'))
    
    # 輸出資訊     __str__方法列印不夠詳細
    def __repr__(self):
        return "User(name:%s)" % self.name
           

3、映射資料庫檔案manage.py

manage.py檔案

這個檔案用來存放映射資料庫的指令,MigrateCommand是flask-migrate內建的一個指令,是以想要添加到腳本指令中,需要采用manager.add_command(‘db’,MigrateCommand)的方式,以後運作python manage.py db xxx的指令,其實就是執行MigrateCommand。

# -*- encoding: utf-8 -*-
"""
@File    : manage.py
@Time    : 2020/4/29 9:21
@Author  : chen

"""
# 通過這個檔案去映射資料庫
# 寫法固定
from flask_script import Manager
from flask_migrate import Migrate, MigrateCommand
from exts import db                    # exts是為了解決循環引用,根據自己建立的py檔案名稱修改
from flask_app import app

# 映射哪個模型,就導入哪個模型
from model import User                # 這個部分會在不同項目中不一樣

manage = Manager(app)

# init  migrate upgrade
# 模型 -> 遷移檔案 -> 表
# 1.要使用flask_migrate,必須綁定app和DB
Migrate(app, db)
# 2.把migrateCommand指令添加到manager中。
manage.add_command('db', MigrateCommand)

if __name__ == '__main__':
    manage.run()

           

4、配置檔案config.py

# -*- encoding: utf-8 -*-
"""
@File    : config.py
@Time    : 2020/4/28 22:01
@Author  : chen

"""
# 這裡寫的是flask_app.py配置選項,固定寫法,友善導入其他的子產品

# 127.0.0.1
HOSTNAME = "localhost"
DATABASE = "demo0429"
PORT = 3306
USERNAME = "root"
PASSWORD = "root"
DB_URL = 'mysql+mysqlconnector://{}:{}@{}:{}/{}'.format(USERNAME, PASSWORD, HOSTNAME, PORT, DATABASE)

'''
# 建立引擎并生成Base類
engine = create_engine(DB_URL)
Base = declarative_base(engine)
'''
SQLALCHEMY_DATABASE_URI = DB_URL           # 資料庫連接配接成功

# 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 '
# 這裡是為了解決上面的警告
SQLALCHEMY_TRACK_MODIFICATIONS = False

           

5、exts是為了解決循環引用問題

# -*- encoding: utf-8 -*-
"""
@File    : exts.py
@Time    : 2020/4/28 22:13
@Author  : chen

"""
# 這個檔案是充當第三方引用檔案的作用
# exts是為了解決循環引用
from flask_sqlalchemy import SQLAlchemy

'''
# 需要解決互相引用的問題 傳app參數db中
db.init_app(app)
'''
# 這裡沒有導入app參數 ,在flask_app.py中傳app參數到db中
db = SQLAlchemy()

           

6、初始化一個遷移檔案夾

python manage.py db init     每個項目執行一次 他負責生成遷移包  #初始化,工程目錄下生成一個migrations檔案夾

python manage.py db migrate  生成遷移檔案
python manage.py db upgrade  執行上升操作(執行遷移資訊)  相當于之前的creat_all()操作
           

Win10系統這裡出現一個問題:

E:\ENV\flask項目-cBMOsSmb\項目代碼10\project_demo
(flask項目-cBMOsSmb) λ python manage.py db migrate
INFO  [alembic.runtime.migration] Context impl MySQLImpl.
INFO  [alembic.runtime.migration] Will assume non-transactional DDL.
INFO  [alembic.autogenerate.compare] Detected added table 'user_migrate'
Generating E:\ENV\flask項目-cBMOsSmb\項目代碼10\project_demo\migrations\version
s\e96f8a3e6220_.py ...  done

E:\ENV\flask項目-cBMOsSmb\項目代碼10\project_demo
(flask項目-cBMOsSmb) λ python manage.py db upgrade
INFO  [alembic.runtime.migration] Context impl MySQLImpl.
INFO  [alembic.runtime.migration] Will assume non-transactional DDL.
INFO  [alembic.runtime.migration] Running upgrade 424277a9e799 -> e96f8a3e6220, empty message
Traceback (most recent call last):
  File "E:\ENV\flask項目-cBMOsSmb\lib\site-packages\sqlalchemy\engine\base.py",
 line 1248, in _execute_context
    cursor, statement, parameters, context
  File "E:\ENV\flask項目-cBMOsSmb\lib\site-packages\sqlalchemy\engine\default.p
y", line 590, in do_execute
    cursor.execute(statement, parameters)
  File "E:\ENV\flask項目-cBMOsSmb\lib\site-packages\mysql\connector\cursor.py",
 line 551, in execute
    self._handle_result(self._connection.cmd_query(stmt))
  File "E:\ENV\flask項目-cBMOsSmb\lib\site-packages\mysql\connector\connection.
py", line 490, in cmd_query
    result = self._handle_result(self._send_cmd(ServerCmd.QUERY, query))
  File "E:\ENV\flask項目-cBMOsSmb\lib\site-packages\mysql\connector\connection.
py", line 395, in _handle_result
    raise errors.get_exception(packet)
mysql.connector.errors.ProgrammingError: 1050 (42S01): Table 'user_migrate' already exists

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "manage.py", line 25, in <module>
    manage.run()
  File "E:\ENV\flask項目-cBMOsSmb\lib\site-packages\flask_script\__init__.py",
line 417, in run
    result = self.handle(argv[0], argv[1:])
           
Flask架構的學習——10—(Flask-SQLAlchemy插件、Flask-Script指令行傳參、Flask-Migrate資料遷移、WTForms表單驗證)

注意上面這個bug原因是資料庫的版本問題,我在MySQL版本8.0時候會出現這個問題,把MySQL版本換成5.5的時候BUG不會出現。

Flask架構的學習——10—(Flask-SQLAlchemy插件、Flask-Script指令行傳參、Flask-Migrate資料遷移、WTForms表單驗證)

4、WTForms表單驗證

Flask-WTF

Flask-WTF是簡化了WTForms操作的一個第三方庫。WTForms表單的兩個主要功能是驗證使用者送出資料的合法性以及渲染模闆。當然還包括一些其他的功能:CSRF保護,檔案上傳等。安裝Flask-WTF預設也會安裝WTForms,是以使用以下指令來安裝Flask-WTF。

pip install flask-wtf
           

表單驗證

安裝完Flask-WTF後。來看下第一個功能,就是用表單來做資料驗證,現在有一個forms.py檔案,然後在裡面建立一個RegistForm的注冊驗證表單。

Flask架構的學習——10—(Flask-SQLAlchemy插件、Flask-Script指令行傳參、Flask-Migrate資料遷移、WTForms表單驗證)

程式主檔案demo.py

# -*- encoding: utf-8 -*-
"""
@File    : demo.py
@Time    : 2020/4/29 15:17
@Author  : chen

"""
from flask import Flask, request, render_template
from forms import RegistForm   # 設定為根目錄之後,導入forms.py中的RegistForm進行表單驗證


app = Flask(__name__)


@app.route("/")
def index():
    return "123"


# 接受GET請求
@app.route("/regist/", methods=['GET', 'POST'])
def regist():
    if request.method == 'GET':
        return render_template('regist.html')
    else:
    	# 采用wtforms子產品進行表單驗證
        form = RegistForm(request.form)   # 将forms.py中的驗證資訊都傳入form中
        if form.validate():
            return "success"
        else:
            # 驗證錯誤資訊
            print(form.errors)
            
            return "fail"
        
        # 表單驗證
        # username = request.form.get('username')
        # password = request.form.get('password')
        # password_repate = request.form.get('password_repate')
        #
        # if len(username) > 3 or len(username) < 10:
        #     return "使用者名長度不正确"
        #
        # if password != password_repate:
        #     return "密碼不一緻"
        #
        # if len(password) > 3 or len(password) < 10:
        #     return "密碼長度不正确"


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

網頁界面:regist.html檔案

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<!--   form驗證表單     -->
<form action="" method="post">
<!--表單送出到Flask的名字    <br>換行-->
    使用者名:<input type="text" name="username"><br>
    密碼: <input type="text" name="password"><br>
    确認密碼: <input type="text" name="password_repate"><br>
    <input type="submit" value="注冊">
</form>

</body>
</html>
           

forms.py表單驗證

# -*- encoding: utf-8 -*-
"""
@File    : forms.py
@Time    : 2020/4/29 17:07
@Author  : chen

"""
from wtforms import Form, StringField, validators
from wtforms.validators import Length, Regexp, EqualTo
# Regexp 正規表達式子產品
# wtforms.validators包含很多内置驗證器
# EqualTo 驗證器:密碼不一緻
'''
表單驗證

username = request.form.get('username')
password = request.form.get('password')
password_repate = request.form.get('password_repate')

if len(username) > 3 or len(username) < 10:
    return "使用者名長度不正确"

if password != password_repate:
    return "密碼不一緻"

if len(password) > 3 or len(password) < 10:
    return "密碼長度不正确"

'''

# 表單驗證采用wtforms子產品


class RegistForm(Form):
    # 實作判斷username的長度驗證3-10
    username = StringField(validators=[Length(min=3, max=10, message="使用者名長度不正确")])
    
    # 判斷password的長度驗證3-10
    password = StringField(validators=[Length(min=3, max=10)])

    # 判斷 password_repate 的長度驗證3-10   EqualTo("password")驗證password_repate是否一緻
    password_repate = StringField(validators=[Length(min=3, max=10), EqualTo("password", message="兩次密碼不一緻")])
           

繼續閱讀