應用、藍圖與視圖函數 1. 結構,如圖: 1. Flask最上層是 ,在這個核心對象上可以插入很多藍圖,這個藍圖是不能單獨存在的,必須將app作為插板插入app ,在每一個藍圖上,可以註冊很多靜態文件,視圖函數,模板 ,一個業務模塊可以做為一個藍圖,比如book,之前的book.py 放到了app/ ...
應用、藍圖與視圖函數
結構,如圖:
Flask最上層是
app核心對象
,在這個核心對象上可以插入很多藍圖,這個藍圖是不能單獨存在的,必須將app作為插板插入app ,在每一個藍圖上,可以註冊很多靜態文件,視圖函數,模板 ,一個業務模塊可以做為一個藍圖,比如book,之前的book.py 放到了app/web/路徑下,就是考慮到了藍圖,app屬於是整個Flask應用層,web屬於是藍圖一些初始化操作應該放入到
__init__
文件中,比如Flask的核心應用app初始化對象,應該放入到在應用層級app包的__init__.py
中 ,而藍圖的初始化應該放入到藍圖層的web包__init__.py
中,如圖:Flask的核心應用app初始化對象文件
app/__init__.py
# -*- coding: utf-8 -*-
from flask import Flask
def create_app():
app = Flask(__name__)
app.config.from_object('config')
# 要返回回去
return app
- 此時在主文件中
# -*- coding: utf-8 -*-
from app import create_app
app = create_app()
if __name__ == '__main__':
app.run(debug=app.config['DEBUG'])
用藍圖註冊視圖函數
- 在藍圖中註冊試圖函數,在
app/web/book.py
中,記得導入Blueprint
# -*- coding: utf-8 -*-
from flask import jsonify, Blueprint
from helper import is_isbn_key
from ShanqiuBook import ShanqiuBook
# 藍圖 blueprint,進行初始化,藍圖的名字和參數為藍圖所在的模塊名一般用__name__
web = Blueprint ('web',__name__)
# 此時這裡用的就是web了
@web.route('/book/search/<q>/<page>')
def hello(q,page):
is_or_key = is_isbn_key(q)
if is_or_key == 'isbn':
result = ShanqiuBook.search_by_isbn(q)
else:
result = ShanqiuBook.search_by_keyword(q)
return jsonify(result)
- 在藍圖中註冊了試圖函數,還需要把藍圖插入到app中,
app/__init__.py
# -*- coding: utf-8 -*-
from flask import Flask
def create_app():
app = Flask(__name__)
app.config.from_object('config')
# 調用一下就可以
register_blueprint(app)
return app
# 通過這個方法插入到app中
def register_blueprint(app):
from app.web.book import web
# 註冊這個藍圖對象
app.register_blueprint(web)
單藍圖多模塊拆分視圖函數
- 藍圖,就是為了分模塊的,比如一個web系統就是屬於一個web模塊,一個移動端使用的api就是一個api模塊,而我們這裡的book,user等不同類別的py文件,要是每一個都註冊一個藍圖的話就有點小題大作了,所以要進行單藍圖
- 在一個模塊(web)的初始文件中定義藍圖對象,然後這個模塊中的其他的py文件引用的就是這一個藍圖對象來註冊路由函數,
- 在app/web/book.py文件中
# -*- coding: utf-8 -*-
from flask import jsonify, Blueprint
from helper import is_isbn_key
from ShanqiuBook import ShanqiuBook
# 導入web模塊
from . import web
@web.route('/book/search/<q>/<page>')
def hello(q,page):
# 調用方法判斷用戶是根據什麼查的
is_or_key = is_isbn_key(q)
if is_or_key == 'isbn':
result = ShanqiuBook.search_by_isbn(q)
else:
result = ShanqiuBook.search_by_keyword(q)
return jsonify(result)
- 這裡先建立一個偽代碼user.py,為了多一個模塊進行演示
# -*- coding: utf-8 -*-
# 導入web模塊
from . import web
@web.route("/user/login")
def login():
return "success"
- 此時在
app/web/__init__.py
文件中,定義這個藍圖對象
# -*- coding: utf-8 -*-
# 藍圖 blueprint,進行初始化
from flask import Blueprint
web = Blueprint ('web',__name__)
# 這兩個導入之後就可以成功的運行對應模塊中相關的代碼,註意這個位置,這藍圖實例化之後
from app.web import book
from app.web import user
Request對象
- 在app/web/book.py文件中,定義的url請求是
/book/search/<q>/<page>
這種格式的,Flask會將<>里的值自動映射成視圖函數方法的參數,但是這種格式用著不爽,要把用戶輸入的參數作為請求參數傳入,這個時候就要使用這種格式了http://127.0.0.1:5000/book/search/?q=金庸&page=1
- 這個該怎麼獲取值呢,這個時候就用到Flask內置的Request了,通過request對象就可以獲取HTTP請求中包含的詳細信息了,具體的用法看下麵的代碼
# -*- coding: utf-8 -*-
# 導入這個request模塊,
from flask import jsonify, Blueprint,request
from helper import is_isbn_key
from ShanqiuBook import ShanqiuBook
from . import web
# http://127.0.0.1:5000/book/search/?q=金庸&page=1
@web.route('/book/search/')
def hello():
# 通過Request對象拿到對應值的信息,但是這個並不是py中原始的字典,而是dict的子類immutableDict
q = request.args['q']
page = request.args['page']
# ip = request.remote_addr
# 通過這個方法把它轉換為普通的dict
# a = request.args.to_dict()
# print(a)
is_or_key = is_isbn_key(q)
if is_or_key == 'isbn':
result = ShanqiuBook.search_by_isbn(q)
else:
result = ShanqiuBook.search_by_keyword(q)
return jsonify(result)
- Flask的request是基於代理模式實現的,想讓request正常使用,必須確保是http請求觸發的函數或視圖函數中使用
WTForms參數驗證
- 上面我們把url改了,但是如果用戶輸入了一些特殊的符號該怎麼辦?這個時候就要使用到參數驗證,而WTForms框架就是一個優秀的參數驗證框架,首先在對應的環境中進行安裝
(flask--yQglGu4) E:\py\qiyue\flask>pipenv install wtforms
- 這個參數驗證寫在哪裡好呢,直接寫在book.py中,這樣是最不妥的,為了方便調用,應該寫成一個類,所以寫在app/forms/book.py文件中
# -*- coding: utf-8 -*-
# 導入需要使用的模塊
from wtforms import Form,StringField,IntegerField
from wtforms.validators import Length,NumberRange
class SearchForm(Form):
# 直接調用內置對象
# 參數校驗規則:
# 1.定義的屬性名q,page要與要校驗的參數同名
# 2.根據要傳入的參數類型選擇不同的Field類進行實例化
# 3.傳入一個數組,作為校驗規則validators
# 4.可以設置預設值
q = StringField(validators=[DataRequired(),Length(min=1,max=30)])
page = IntegerField(validators=[NumberRange(min=1,max=10)],default=1)
- 此時在app/web/book.py文件中就可以直接調用就行了
# -*- coding: utf-8 -*-
from flask import jsonify, Blueprint,request
from helper import is_isbn_key
from ShanqiuBook import ShanqiuBook
from . import web
# 導入參數校驗
from app.forms.book import SearchForm
# http://127.0.0.1:5000/book/search/?q=金庸&page=1
@web.route('/book/search/')
def hello():
# 驗證層
# 實例化我們自定義的SearchForm,需要傳入一個字典作為要校驗的參數
form = SearchForm(request.args)
# validate()方法返回True/False來標示是否校驗通過
if form.validate():
# 從form中取出校驗後的q與page,並且清除空格
q = form.q.data.strip()
page = form.page.data
is_or_key = is_isbn_key(q)
if is_or_key == 'isbn':
result = ShanqiuBook.search_by_isbn(q)
else:
result = ShanqiuBook.search_by_keyword(q)
return jsonify(result)
else:
return jsonify({'msg':'參數校驗失敗'})
拆分配置文件
- 之前訪問數據的時候,count和start都是寫死的,現在來進行重構,之前的代碼
@classmethod
def search_by_key(cls, q, count=15, start=0):
# count:每頁顯示的數量
# start:每頁的第一條數據的下標
url = cls.search_by_key_url.format(q, count, start)
return HTTP.get(url)
這樣寫非常的不妥
在視圖函數中接收到的參數是page,代碼的封裝性,我們應該把count和start的計算過程放到ShanqiuBook.py的 search_by_key方法中來寫
count的值為了方便日後的管理,這個應該放入到配置文件中,之前的配置文件是config.py,在根目錄下,而這個應該放入到app目錄下,而關於一些比較隱私的配置信息要妥善處理,所以在app目錄下建立兩個文件,secure.py用來存放私密的配置信息,setting.py用於存放一些不重要的配置信息,如下
app/secure.py
# -*- coding: utf-8 -*- # 存放比較機密的配置文件,在上傳git的時候不應該上傳此文件 DEBUG = True
app/setting.py
# -*- coding: utf-8 -*- # 生產環境和開發環境幾乎一樣的,不怎麼機密的配置文件 # 每頁顯示的數據量 PER_PAGE = 15
start的計算是一個單獨的邏輯,應該用封裝成一個方法,使用的時候直接調用
# 獲取每一頁的起始下標 @staticmethod def calculate_start(page): # 獲取配置信息中的每頁顯示的數量 return (page -1 ) * current_app.config['PER_PAGE']
重構後的
ShanqiuBook.py
-*- coding: utf-8 -*-
from httper import httper
# 通過這種方式來導入當前的app對象,方便調用配置而文件
from flask import current_app
class ShanqiuBook:
isbn_url = 'http://t.yushu.im/v2/book/search/isbn/{}'
keyword_url = 'http://t.yushu.im/v2/book/search?q={}&count={}&start={}'
@classmethod
def search_by_isbn(cls,isbn):
url = cls.isbn_url.format(isbn)
result = httper.get(url)
return result
@classmethod
def search_by_keyword(cls,keyword,page=1):
# 每頁顯示的數據(通過這種方式從配置文件中獲取到),每一頁的起始下標
url = cls.keyword_url.format(keyword,current_app.config['PER_PAGE'],cls.calculate_start(page))
result = httper.get(url)
return result
# 獲取每一頁的起始下標
@staticmethod
def calculate_start(page):
return (page -1 ) * current_app.config['PER_PAGE']
- 這個時候在
app/__init__.py
文件中把配置文件添加到app中
#-- coding: utf-8 --
from flask import Flask
def create_app():
app = Flask(name)
# app.config.from_object('config')
# 把配置文件裝載進來
app.config.from_object('app.secure')
app.config.from_object('app.setting')
register_blueprint(app)
return app
def register_blueprint(app):
from app.web.book import web
app.register_blueprint(web)
定義第一個模型類
- 我們現在把文件進行整理,如下:
- 首先在本地創建一個資料庫,如下:
在app/models/book.py文件中建立模型,這裡使用到sqlalchemy來實現自動化映射,在Flask框架中對這個進行了改良Flask_SQLAlchemy,這個更加人性化,安裝
(flask--yQglGu4) E:\py\qiyue\flask>pipenv install flask-sqlalchemy
建立模型,這樣就建立好了模型
# -*- coding: utf-8 -*-
# 首先導入
from sqlalchemy import Column,Integer,String
# sqlalchemy,自動化映射
# Flask_SQLAlchemy,這個是Flask封裝後的api,更加人性化
class Book():
# 需要把這些屬性的預設值寫成sqlalchemy提供的固定的類型
# Column()傳入參數:數據類型,主鍵,自增
id = Column(Integer,primary_key=True,autoincrement=True)
# 數據類型,不為空
title = Column(String(50),nullable=False)
author = Column(String(30),default='未名')
binding = Column(String(20))
publisher = Column(String(50))
price = Column(String(20))
pages = Column(Integer)
pubdate = Column(String(20))
# 唯一:unique=True
isbn = Column(String(15),nullable=False,unique=True)
summary = Column(String(1000))
image = Column(String(50))
# 定義一些方法
def sample(self):
pass
將模型映射到資料庫中
- 在模型類app/models/book.py中引入導入核心對象,並實例化,繼承
# -*- coding: utf-8 -*-
from sqlalchemy import Column,Integer,String
# 將模型映射到資料庫中
# 首先導入核心的對象
from flask_sqlalchemy import SQLAlchemy
# 初始化
db = SQLAlchemy()
# 繼承db.Model
class Book(db.Model):
id = Column(Integer,primary_key=True,autoincrement=True)
title = Column(String(50),nullable=False)
author = Column(String(30),default='未名')
binding = Column(String(20))
publisher = Column(String(50))
price = Column(String(20))
pages = Column(Integer)
pubdate = Column(String(20))
isbn = Column(String(15),nullable=False,unique=True)
summary = Column(String(1000))
image = Column(String(50))
- 在
app/__init__.py
中進行模型與flask關聯
# -*- coding: utf-8 -*-
from flask import Flask
# 導入這個db
from app.models.book import db
def create_app():
app = Flask(__name__)
app.config.from_object('app.secure')
app.config.from_object('app.setting')
register_blueprint(app)
# 把這個db和核心對象關聯起來了
db.init_app(app)
# 註意這裡,這樣寫的話會報錯
db.create_all() # 把所有的數據模型映射到資料庫中
return app
def register_blueprint(app):
from app.web.book import web
app.register_blueprint(web)
- 配置資料庫連接的配置文件在app/secure.py文件中
# -*- coding: utf-8 -*-
# 存放比較機密的配置文件
DEBUG = True
# 資料庫連接url,固定格式
# 要連接的資料庫類型,資料庫驅動(這裡還要進行安裝:pipenv install cymysql)
SQLALCHEMY_DATABASE_URI = 'mysql+cymysql://root:123456@localhost:3306/book'
- 之後運行項目,就會創建在指定的資料庫中創建一個數據表了,但是運行項目會出現下麵的這種錯誤
'No application found. Either work inside a view function or push'
這個是因為在Flask中,不是實例化了app核心對象,其他代碼就可以直接使用,要在上面的第二步的註意事項中` db.create_all()`方法中,把app核心對象傳入即可
db.create_all(app=app)
,這樣就可以了,在資料庫中就可以看到表了