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

- 資料庫初始化:資料庫初始化不再是通過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()
我們把腳本指令代碼放在一個叫做manage.py檔案中,然後在終端運作python manage.py hello指令,就可以看到輸出hello了。
用指令行建立超級使用者,并向資料庫中添加資料,執行個體如下:
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()
定義指令的三種方法
- 使用@command裝飾器
- 使用類繼承自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方法添加指令。
- 使用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需要注意的是:
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:])
注意上面這個bug原因是資料庫的版本問題,我在MySQL版本8.0時候會出現這個問題,把MySQL版本換成5.5的時候BUG不會出現。
4、WTForms表單驗證
Flask-WTF
Flask-WTF是簡化了WTForms操作的一個第三方庫。WTForms表單的兩個主要功能是驗證使用者送出資料的合法性以及渲染模闆。當然還包括一些其他的功能:CSRF保護,檔案上傳等。安裝Flask-WTF預設也會安裝WTForms,是以使用以下指令來安裝Flask-WTF。
pip install flask-wtf
表單驗證
安裝完Flask-WTF後。來看下第一個功能,就是用表單來做資料驗證,現在有一個forms.py檔案,然後在裡面建立一個RegistForm的注冊驗證表單。
程式主檔案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="兩次密碼不一緻")])