Python全棧(七)Flask框架之5.視圖高級--類視圖和藍圖

1、標準類視圖及使用

在前面,咱們定義視圖都是經過route裝飾器裝飾函數來定義的,通常稱之爲視圖函數。除了這種方式,還能夠基於類實現。
類視圖支持繼承,可是類視圖不能跟函數視圖同樣經過裝飾器添加路由,須要經過app.add_url_rule(url_rule,view_func)來註冊。
css

1.添加url映射規則的其餘方法嘗試

在以前的代碼中,都是經過@app.route裝飾器來實現url與視圖函數的映射的,除了這種方法外,咱們也能夠經過Flask對象的add_url_rule()方法來定義映射規則。
測試(新建一個Python文件class_view.py)以下:
html

from flask import Flask

app = Flask(__name__)


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


def profile():
    return '我的中心'

# 添加url規則
app.add_url_rule('/profile/', view_func=profile)

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

顯示:
flask class view addurlrule
顯然,add_url_rule()能夠定義路由規則。

python

add_url_rule()方法有一個參數endpoint,表示結束點,指定後url_for()方法中傳入的就再也不是視圖函數名了,而是指定的endpoint,至關於給url取了一個名字。
經過請求上下文函數能夠輸出url_for的結果,測試以下:
json

from flask import Flask,url_for

app = Flask(__name__)


@app.route('/')
def index():
    print(url_for('personal'))
    return '首頁'


def profile():
    return '我的中心'


app.add_url_rule('/profile/', endpoint='personal', view_func=profile)

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

此時訪問http://127.0.0.1:5000/在控制檯會打印/profile/
此時不能再在url_for()方法中傳入’profile’參數,不然會報錯。
flask

2.標準類視圖

標準類視圖繼承自flask.views.View,而且類視圖中必須重寫dispatch_request()方法,這個方法相似於視圖函數,會返回一個基於Response或者其子類的對象。類視圖定義後,須要經過add_url_rule()方法和url進行映射,還須要在as_view()方法中指定該url的名稱,方便url_for()函數調用。
定義類視圖以下:
服務器

from flask import Flask, url_for, views

app = Flask(__name__)


@app.route('/')
def index():
    print(url_for('personal'))
    return '首頁'


def profile():
    return '我的中心'


class ListView(views.View):
    def dispatch_request(self):
        return 'class view'

    def demo(self):
        return '測試'


app.add_url_rule('/profile/', endpoint='personal', view_func=profile)
app.add_url_rule('/list/', endpoint='list', view_func=ListView.as_view('list'))

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

訪問http://127.0.0.1:5000/list/,顯示:
flask class view standard list
此時,若是要用url_for()方法來獲取URL,須要注意:
若是add_url_rule()中指定了endpoint參數,url_for()中傳的參數就應該是endpoint參數的值;
若是沒有指定,就是as_view()方法的值。



markdown

在定義視圖類時,必需要重寫dispatch_request()方法,不然會拋出異常,以下:
flask class view NotImplementedError
能夠查看源碼views.py:

app

def dispatch_request(self):
    """Subclasses have to override this method to implement the actual view function code. This method is called with all the arguments from the URL rule. """
    raise NotImplementedError()

顯然,若是不重寫dispatch_request()方法,會直接拋出NotImplementedError錯誤。dom

再嘗試用類視圖傳遞Json數據:ide

from flask import Flask, url_for, views, jsonify

app = Flask(__name__)


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


def profile():
    return '我的中心'


class JsontView(views.View):
    def get_response(self):
        raise NotImplementedError()

    def dispatch_request(self):
        response = self.get_response()
        return jsonify(response)


class ListJsonView(JsontView):
    def get_response(self):
        return {'username': 'Corley'}


app.add_url_rule('/listjson/', view_func=ListJsonView.as_view('listjson'))

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

顯示:
flask class view standard json
顯然,此時返回頁面的是json數據。

返回公共變量測試:

from flask import Flask, url_for, views, jsonify, render_template

app = Flask(__name__)


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


class LoginView(views.View):
    def dispatch_request(self):
        self.context = {
            'name': 'Corley'
        }
        return render_template('login.html', **self.context)


class RegisterView(views.View):
    def dispatch_request(self):
        self.context = {
            'name': 'Corley'
        }
        return render_template('register.html', **self.context)


app.add_url_rule('/login/', view_func=LoginView.as_view('login'))
app.add_url_rule('/register/', view_func=RegisterView.as_view('register'))

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

在templates目錄下建立login.htmlregister.html,login.html以下:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>登陸頁面</title>
</head>
<body>
    <h1>這是登陸頁面</h1>
    <h2>{{ name }}</h2>
</body>
</html>

register.html以下:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>註冊頁面</title>
</head>
<body>
    <h1>這是註冊頁面</h1>
    <h2>{{ name }}</h2>
</body>
</html>

顯示:
flask class view standard public variables
顯然,能夠正常訪問登陸和註冊頁面,可是兩個視圖類中含有相同的變量,能夠進行優化:

from flask import Flask, url_for, views, jsonify, render_template

app = Flask(__name__)


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


class BaseView(views.View):
    """ 保存公共變量 """

    def __init__(self):
        super().__init__()
        self.context = {
            'name': 'Corley'
        }


class LoginView(BaseView):
    def dispatch_request(self):
        return render_template('login.html', **self.context)


class RegisterView(BaseView):
    def dispatch_request(self):
        return render_template('register.html', **self.context)


app.add_url_rule('/login/', view_func=LoginView.as_view('login'))
app.add_url_rule('/register/', view_func=RegisterView.as_view('register'))

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

將公有變量存儲到BaseView,其餘類視圖再繼承自BaseView。

2、基於調度方法的視圖

Flask中除了標準類視圖,還有一種類視圖flask.views.MethodView,對每一個HTTP方法執行不一樣的函數,映射到對應方法的同名方法上。

測試以下:

from flask import Flask, url_for, views, jsonify, render_template, request

app = Flask(__name__)


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


class BaseView(views.View):
    """ 保存公共變量 """

    def __init__(self):
        super().__init__()
        self.context = {
            'name': 'Corley'
        }


class LoginView(views.MethodView):
	# 當客戶端經過get方法訪問的時候執行
    def get(self):
        return render_template('login.html')

	# 當客戶端經過post方法訪問的時候執行
    def post(self):
        name = request.form.get('name')
        password = request.form.get('password')
        if name == 'Corley' and password == "123":
            return '登陸成功!!!'
        else:
            error =  '帳號或密碼錯誤⚠'
            return render_template('login.html', error=error)


class RegisterView(BaseView):
    def dispatch_request(self):
        return render_template('register.html')


app.add_url_rule('/login/', view_func=LoginView.as_view('login'))
app.add_url_rule('/register/', view_func=RegisterView.as_view('register'))

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

顯示:
flask class view method login
顯然,經過get和post請求都能實現登陸的效果,代碼還能夠進行必定的優化:

from flask import Flask, url_for, views, jsonify, render_template, request

app = Flask(__name__)


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


class BaseView(views.View):
    """ 保存公共變量 """

    def __init__(self):
        super().__init__()
        self.context = {
            'name': 'Corley'
        }


class LoginView(views.MethodView):
    def get(self, error=None):
        return render_template('login.html', error=error)

    def post(self):
        name = request.form.get('name')
        password = request.form.get('password')
        if name == 'Corley' and password == "123":
            return '登陸成功!!!'
        else:
            error =  '帳號或密碼錯誤⚠'
            return self.get(error)


class RegisterView(BaseView):
    def dispatch_request(self):
        return render_template('register.html')


app.add_url_rule('/login/', view_func=LoginView.as_view('login'))
app.add_url_rule('/register/', view_func=RegisterView.as_view('register'))

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

運行效果與前者相同。

類視圖的一個缺陷就是比較難用裝飾器來裝飾,好比須要作權限驗證的時候。此時能夠在類視圖中定義一個默認屬性decorators,用於存儲裝飾器。之後每次調用這個類視圖的時候,就會執行這個裝飾器。
在類外添加裝飾器,限制只有登陸以後才能訪問我的中心,以下:

from flask import Flask, url_for, views, jsonify, render_template, request

app = Flask(__name__)


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


def login_required(func):
    '''裝飾器,登陸以後才能訪問我的中心'''
    def wrapper(*args, **kwargs):
        username = request.args.get('username')
        if username:
            return func(*args, **kwargs)
        else:
            return '請先登陸再訪問'
    return wrapper


@app.route('/profile/')
@login_required
def profile():
    return '我的中心'


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

顯示:
flask class view method decorator outside
很明顯,只有登陸以後才能夠正常訪問到我的中心。

經過在decorators列表中添加裝飾器實如今類中使用裝飾器,測試以下:

from flask import Flask, url_for, views, jsonify, render_template, request

app = Flask(__name__)


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


def login_required(func):
    '''裝飾器,登陸以後才能訪問我的中心'''
    def wrapper(*args, **kwargs):
        username = request.args.get('username')
        if username:
            return func(*args, **kwargs)
        else:
            return '請先登陸再訪問'
    return wrapper


class ProfileView(views.View):
    # 在類中用裝飾器
    decorators = [login_required]
    def dispatch_request(self):
        return '我的中心頁面'


app.add_url_rule('/profile/', view_func=ProfileView.as_view('profile'))

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

3、Flask藍圖的基本使用

以前,咱們的代碼都是放在一個Python文件裏的,不一樣的功能模塊都在一個文件中實現,代碼量較少時尚可,隨着業務需求的複雜化和代碼的增多,項目逐漸變大,會顯得很臃腫,也沒有條理層次感,這顯然不是一個合理的結構。此時就須要定義多個文件(夾),不一樣的文件(夾)放不一樣的功能模塊,此時藍圖能夠優雅地實現這種需求。
定義了藍圖文件以後,須要在主程序中經過app.register_blueprint()方法將這個藍圖註冊進url映射中。
簡單使用示例以下:
先建立項目根目錄,如blue_demo,在下面建立Python文件blue_flask.py以下


from flask import Flask
from blueprints.news import news_bp
from blueprints.book import book_bp

app = Flask(__name__)
app.register_blueprint(news_bp)
app.register_blueprint(book_bp)

@app.route('/')
def index():
    return '這是首頁'


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

再建立blueprints目錄,下邊建立news.py和book.py,news.py以下:

from flask import Blueprint

news_bp = Blueprint('news', __name__)   # 第一個參數是藍圖名稱,通常是當前的Python文件名,第二個參數是藍圖所在的包或模塊,通常用__name__變量


@news_bp.route('/news/')
def news():
    return '新聞頁面'

book.py以下:

from flask import Blueprint

book_bp = Blueprint('book', __name__)   # 第一個參數是藍圖名稱,通常是當前的Python文件名,第二個參數是藍圖所在的包或模塊,通常用__name__變量


@book_bp.route('/book/')
def book():
    return '圖書列表'


@app.route('/book/detail/<bid>')
def book_detail(bid):
    return '當前圖書ID:%s' % bid

運行blue_flask.py以後,顯示:
flask blueprint simple use
顯然,基本功能已經實現。
心啊在訪問/news//book/,都是在執行news.py、book.py中的視圖函數,從而實現了項目的模塊化。
若是在blue_flask.py中不能導入blueprints中的文件或者出現警告信息,能夠將當前目錄設爲根目錄,以下:
flask blueprint source root
此時再導入就會出現提示,導入後也不會報錯。
Blueprint對象在實例化時,還能夠傳入一個參數url_prefix,即url前綴,會給當前文件下全部的路由加上指定的前綴,如上面的book.py能夠改爲






from flask import Blueprint

book_bp = Blueprint('book', __name__, url_prefix='/book')   # 第一個參數是藍圖名稱,通常是當前的Python文件名,第二個參數是藍圖所在的包或模塊,通常用__name__變量


@book_bp.route('/')
def news():
    return '圖書列表'


@book_bp.route('/detail/<bid>')
def book_detail(bid):
    return '當前圖書ID:%s' % bid

這與以前的效果是同樣的。

4、Flask藍圖尋找文件和url_for()尋找路由

1.Flask藍圖尋找模板文件

Flask藍圖默認不設置任何模板文件的路徑,將會在項目的templates中尋找模板文件。
也能夠設置其餘的路徑,在構造函數Blueprint中有一個template_folder參數能夠設置模板的路徑。
修改news.py以下:

from flask import Blueprint,render_template

news_bp = Blueprint('news', __name__, template_folder='blue_templates')


@news_bp.route('/news/')
def news():
    return render_template('news.hmtl')

修改templates中的news.html以下

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>新聞</title>
</head>
<body>
    <h1>這是模板中的新聞首頁</h1>
</body>
</html>

在blueprints中建立目錄blue_templates,在下面建立news.html以下:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>新聞</title>
</head>
<body>
    <h1>這是藍圖中的新聞首頁</h1>
</body>
</html>

運行後,顯示:
flask blueprint template
顯然,顯示的是項目中的templates目錄中的模板,進一步能夠總結:
在藍圖中定義的視圖函數在尋找模板時,首先會在項目的默認模板目錄templates中尋找是否有指定的模板,若是存在則進行渲染,不存在則繼續看藍圖文件中初始化Blueprint對象時是否傳入了template_folder參數來指定模板目錄,若是傳了則在參數指定的目錄中尋找,找到則進行渲染,不然報錯,若是沒有指定template_folder參數參數也會直接報錯。
在實際項目中,通常直接將模板文件放在默認的模板目錄templates下。



2.Flask藍圖尋找靜態文件

默認不設置任何靜態文件路徑,Jinja2會在項目的static文件夾中尋找靜態文件。
也能夠設置其餘的路徑,在初始化藍圖的時候,Blueprint構造函數有一個參數static_folder能夠指定靜態文件的路徑。
static_folder能夠是相對路徑(相對藍圖文件所在的目錄),也能夠是絕對路徑。配置完藍圖後,在模板中引用靜態文件應該使用藍圖名.static
測試以下:
先在blue_demo目錄下建立static目錄,並在static目錄下建立news.css以下:



body{
    background: pink;
}

templates目錄下的news.html導入css文件:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <link rel="stylesheet" href="{{ url_for('static', filename='news.css') }}">
    <title>新聞</title>
</head>
<body>
    <h1>這是模板中的新聞首頁</h1>
</body>
</html>

同時在blueprints目錄下建立blue_static目錄,下面建立news.css以下:

body{
    background: blue;
}

運行後能夠看到:
flask blueprint static root
顯然,此時加載的模板文件是templates目錄中的news.html,使用的樣式文件是static目錄下的news.css。

要想使用blue_static目錄下的樣式文件,須要改動,首先須要在Blueprint初始化時指定靜態文件的文件夾:

from flask import Blueprint,render_template

news_bp = Blueprint('news', __name__, static_folder='blue_static')


@news_bp.route('/news/')
def news():
    return render_template('news.html')

templates目錄下的news.html導入css文件的路徑修改以下:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <link rel="stylesheet" href="{{ url_for('news.static', filename='news.css') }}">
    <title>新聞</title>
</head>
<body>
    <h1>這是模板中的新聞首頁</h1>
</body>
</html>

顯示:
flask blueprint static blue
此時加載的樣式文件便是blueprints目錄下的靜態文件夾中的樣式文件。

3.藍圖中使用url_for()方法獲取路由

在存在藍圖的狀況下使用使用url_for()方法,不能直接給路由對應的函數名,須要在前面加上定義該路由所在的藍圖名字,即初始化藍圖時的第一個參數,通常即爲該函數所在的Python文件的文件名,使用的格式是藍圖名稱.視圖函數名稱

blue_flask.py以下:

from flask import Flask, url_for
from blueprints.news import news_bp
from blueprints.book import book_bp

app = Flask(__name__)
app.register_blueprint(news_bp)
app.register_blueprint(book_bp)

@app.route('/')
def index():
    print(url_for('news.news'))
    print(url_for('book.book_detail', bid=2))
    return '這是首頁'


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

book.py以下:

from flask import Blueprint, url_for

book_bp = Blueprint('book', __name__, url_prefix='/book')


@book_bp.route('/')
def book():
    print(url_for('book.book_detail', bid=3))
    return '圖書列表'


@book_bp.route('/detail/<bid>')
def book_detail(bid):
    return '當前圖書ID:%s' % bid

顯示:
flask blueprint urlfor
此時查看控制檯打印以下:

127.0.0.1 - - [17/Apr/2020 17:41:17] "GET / HTTP/1.1" 200 -
/news/
/book/detail/2
/book/detail/3
127.0.0.1 - - [17/Apr/2020 17:41:27] "GET /book/ HTTP/1.1" 200 -

顯然,此時獲得了視圖函數對應的路由。

5、Flask實現子域名

子域名(subdomain)在域名系統等級中,屬於更高一層域的域,好比,mail.example.comcalendar.example.comexample.com的兩個子域名,而example.com則是頂級域.com的子域名。
子域名在不少網站中都會用到,好比對於一個網站xxx.com,咱們能夠定義一個子域名cms.xxx.com來做爲該網站內容管理系統的網址。
在Flask中,子域名通常也是經過藍圖來實現的,在以前建立藍圖的時候添加了一個參數url_prefix來指定url前綴,好比url_prefix='/user',咱們能夠經過/user/來訪問user下的地址。但使用子域名則不一樣,須要在主程序中配置SERVER_NAME,除此以外,還須要在電腦的系統文件hosts中進行配置
示例以下:
在blueprints目錄下建立cms.py文件以下:



from flask import Blueprint

cms_bp = Blueprint('cms', __name__, subdomain='cms')


@app.route('/')
def cms():
    return 'CMS頁面'

在主程序中註冊和配置服務器名稱以下:

from flask import Flask, url_for
from blueprints.news import news_bp
from blueprints.book import book_bp
from blueprints.cms import cms_bp

app = Flask(__name__)
app.register_blueprint(news_bp)
app.register_blueprint(book_bp)
app.register_blueprint(cms_bp)
# 配置服務器名
app.config["SERVER_NAME"] = 'corley.com:5000'

@app.route('/')
def index():
    print(url_for('news.news'))
    print(url_for('book.book_detail', bid=2))
    return '這是首頁'


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

此時還不能直接訪問子域名,由於子域名不支持在IP前直接添加,即cms.127.0.0.1的形式是無效的,因此須要在電腦系統文件hosts中添加域名解析,即將127.0.0.1映射到一個域名,才能訪問子域名,Windows下操做以下:
在C盤中依次打開目錄:C:→Windows→System32→drivers→etc,找到hosts文件並編輯,在該文件後面添加域名映射以下:

# localhost name resolution is handled within DNS itself.
# 127.0.0.1 localhost
# ::1 localhost


127.0.0.1 corley.com
127.0.0.1 cms.corley.com

最後兩行即爲要添加的內容,將127.0.0.1映射到corley.com,此時訪問corley.com即訪問本地迴環地址127.0.0.1

此時開啓服務,能夠在控制檯看到:

* Serving Flask app "blue_flask" (lazy loading)
 * Environment: production
   WARNING: Do not use the development server in a production environment.
   Use a production WSGI server instead.
 * Debug mode: on
 * Restarting with stat
 * Debugger is active!
 * Debugger PIN: 260-774-670
 * Running on http://corley.com:5000/ (Press CTRL+C to quit)

即此時訪問站點須要訪問http://corley.com:5000/,此時也不能再訪問http://127.0.0.1:5000/了(除非你其餘應用開啓了此服務),此時訪問示例以下:
flask blueprint subdomain
顯然,能夠訪問到子域名http://cms.corley.com:5000/

注意:
子域名不只不能在127.0.0.1上出現,也不能在localhost上出現。

發佈了117 篇原創文章 · 獲贊 1223 · 訪問量 30萬+
相關文章
相關標籤/搜索