基於類別的視圖¶
本頁介紹如何使用 View
和 MethodView
類別來編寫基於類別的視圖。
基於類別的視圖是一個充當視圖函數的類別。由於它是一個類別,因此可以使用不同的參數創建該類別的不同實例,以更改視圖的行為。這也稱為通用、可重用或可插拔的視圖。
一個有用的例子是定義一個類別,該類別基於它初始化的資料庫模型創建一個 API。
對於更複雜的 API 行為和自訂,請研究 Flask 的各種 API 擴展。
基本可重用視圖¶
讓我們逐步瀏覽一個將視圖函數轉換為視圖類別的例子。我們從一個查詢使用者列表,然後渲染範本以顯示列表的視圖函數開始。
@app.route("/users/")
def user_list():
users = User.query.all()
return render_template("users.html", users=users)
這適用於使用者模型,但是假設您還有更多模型需要列表頁面。您需要為每個模型編寫另一個視圖函數,即使唯一會改變的是模型和範本名稱。
相反,您可以編寫一個 View
子類別,它將查詢模型並渲染範本。作為第一步,我們將把視圖轉換為一個沒有任何自訂的類別。
from flask.views import View
class UserList(View):
def dispatch_request(self):
users = User.query.all()
return render_template("users.html", objects=users)
app.add_url_rule("/users/", view_func=UserList.as_view("user_list"))
View.dispatch_request()
方法相當於視圖函數。調用 View.as_view()
方法將創建一個視圖函數,可以使用 add_url_rule()
方法在應用程式上註冊。as_view
的第一個參數是用於使用 url_for()
引用視圖的名稱。
注意
您不能像使用基本視圖函數那樣使用 @app.route()
修飾類別。
接下來,我們需要能夠為不同的模型和範本註冊相同的視圖類別,使其比原始函數更有用。該類別將接受兩個參數,模型和範本,並將它們存儲在 self
上。然後 dispatch_request
可以引用這些,而不是硬編碼的值。
class ListView(View):
def __init__(self, model, template):
self.model = model
self.template = template
def dispatch_request(self):
items = self.model.query.all()
return render_template(self.template, items=items)
請記住,我們使用 View.as_view()
創建視圖函數,而不是直接創建類別。傳遞給 as_view
的任何額外參數都會在創建類別時傳遞。現在我們可以註冊相同的視圖來處理多個模型。
app.add_url_rule(
"/users/",
view_func=ListView.as_view("user_list", User, "users.html"),
)
app.add_url_rule(
"/stories/",
view_func=ListView.as_view("story_list", Story, "stories.html"),
)
URL 變數¶
URL 捕獲的任何變數都會作為關鍵字參數傳遞給 dispatch_request
方法,就像它們對於常規視圖函數一樣。
class DetailView(View):
def __init__(self, model):
self.model = model
self.template = f"{model.__name__.lower()}/detail.html"
def dispatch_request(self, id)
item = self.model.query.get_or_404(id)
return render_template(self.template, item=item)
app.add_url_rule(
"/users/<int:id>",
view_func=DetailView.as_view("user_detail", User)
)
視圖生命週期和 self
¶
預設情況下,每次處理請求時都會創建視圖類別的新實例。這意味著在請求期間將其他資料寫入 self
是安全的,因為下一個請求將看不到它,這與其他形式的全局狀態不同。
但是,如果您的視圖類別需要執行大量複雜的初始化,則為每個請求執行它是沒有必要的,並且可能效率低下。為了避免這種情況,請將 View.init_every_request
設置為 False
,這將只創建一個類別實例並將其用於每個請求。在這種情況下,寫入 self
是不安全的。如果您需要在請求期間存儲資料,請改用 g
。
在 ListView
示例中,在請求期間沒有任何東西寫入 self
,因此創建單個實例更有效。
class ListView(View):
init_every_request = False
def __init__(self, model, template):
self.model = model
self.template = template
def dispatch_request(self):
items = self.model.query.all()
return render_template(self.template, items=items)
對於每個 as_view
調用,仍然會創建不同的實例,但不會為對這些視圖的每個請求創建。
視圖裝飾器¶
視圖類別本身不是視圖函數。視圖裝飾器需要應用於 as_view
返回的視圖函數,而不是類別本身。將 View.decorators
設置為要應用的裝飾器列表。
class UserList(View):
decorators = [cache(minutes=2), login_required]
app.add_url_rule('/users/', view_func=UserList.as_view())
如果您沒有設置 decorators
,您可以改為手動應用它們。這相當於
view = UserList.as_view("users_list")
view = cache(minutes=2)(view)
view = login_required(view)
app.add_url_rule('/users/', view_func=view)
請記住,順序很重要。如果您習慣了 @decorator
風格,這相當於
@app.route("/users/")
@login_required
@cache(minutes=2)
def user_list():
...
方法提示¶
常見的模式是使用 methods=["GET", "POST"]
註冊視圖,然後檢查 request.method == "POST"
以決定要做什麼。設置 View.methods
相當於將方法列表傳遞給 add_url_rule
或 route
。
class MyView(View):
methods = ["GET", "POST"]
def dispatch_request(self):
if request.method == "POST":
...
...
app.add_url_rule('/my-view', view_func=MyView.as_view('my-view'))
這相當於以下內容,除非進一步的子類別可以繼承或更改方法。
app.add_url_rule(
"/my-view",
view_func=MyView.as_view("my-view"),
methods=["GET", "POST"],
)
方法分派和 API¶
對於 API,為每個 HTTP 方法使用不同的函數可能很有幫助。MethodView
擴展了基本的 View
,以便根據請求方法分派到類別的不同方法。每個 HTTP 方法都映射到類別中具有相同(小寫)名稱的方法。
MethodView
會根據類別定義的方法自動設置 View.methods
。它甚至知道如何處理覆蓋或定義其他方法的子類別。
我們可以創建一個通用的 ItemAPI
類別,它為給定的模型提供 get(詳細資訊)、patch(編輯)和 delete 方法。GroupAPI
可以提供 get(列表)和 post(創建)方法。
from flask.views import MethodView
class ItemAPI(MethodView):
init_every_request = False
def __init__(self, model):
self.model = model
self.validator = generate_validator(model)
def _get_item(self, id):
return self.model.query.get_or_404(id)
def get(self, id):
item = self._get_item(id)
return jsonify(item.to_json())
def patch(self, id):
item = self._get_item(id)
errors = self.validator.validate(item, request.json)
if errors:
return jsonify(errors), 400
item.update_from_json(request.json)
db.session.commit()
return jsonify(item.to_json())
def delete(self, id):
item = self._get_item(id)
db.session.delete(item)
db.session.commit()
return "", 204
class GroupAPI(MethodView):
init_every_request = False
def __init__(self, model):
self.model = model
self.validator = generate_validator(model, create=True)
def get(self):
items = self.model.query.all()
return jsonify([item.to_json() for item in items])
def post(self):
errors = self.validator.validate(request.json)
if errors:
return jsonify(errors), 400
db.session.add(self.model.from_json(request.json))
db.session.commit()
return jsonify(item.to_json())
def register_api(app, model, name):
item = ItemAPI.as_view(f"{name}-item", model)
group = GroupAPI.as_view(f"{name}-group", model)
app.add_url_rule(f"/{name}/<int:id>", view_func=item)
app.add_url_rule(f"/{name}/", view_func=group)
register_api(app, User, "users")
register_api(app, Story, "stories")
這會產生以下視圖,一個標準的 REST API!
網址 |
方法 |
描述 |
|
|
列出所有使用者 |
|
|
創建新使用者 |
|
|
顯示單個使用者 |
|
|
更新使用者 |
|
|
刪除使用者 |
|
|
列出所有故事 |
|
|
創建新故事 |
|
|
顯示單個故事 |
|
|
更新故事 |
|
|
刪除故事 |