iT邦幫忙

2021 iThome 鐵人賽

DAY 30
1
Modern Web

Flask系列 第 30

Day 30 最後的收尾

前言

今天是這個系列的最後一篇,我們會把之前沒有做的東西補起來,他們都是蠻麻煩有點 tricky 的東西。

admin_required

之前我們說過,在 admin_bp 裡面的路徑沒有辦法用 login_required,因為他只會驗證使用者是否有登入,但不會判斷該使用者是否為管理員。所以我們需要自己寫一個 admin_required

我們要回到 app/user_helper.py,然後對 login_manager 做一點點的修改。


from functools import wraps
from flask import current_app, request, abort, flash, redirect, session
from flask_login import LoginManager, UserMixin, current_user
from flask_login.config import USE_SESSION_FOR_NEXT
from flask_login.signals import user_unauthorized
from flask_login.utils import (
    expand_login_view,
    login_url as make_login_url,
    make_next_param,
)

class LoginManager_(LoginManager):
    def __init__(self):
        super().__init__()

    def forbidden(self):
        user_unauthorized.send(current_app._get_current_object())

        if self.unauthorized_callback:
            return self.unauthorized_callback()

        if request.blueprint in self.blueprint_login_views:
            login_view = self.blueprint_login_views[request.blueprint]
        else:
            login_view = self.login_view

        if not login_view:
            abort(403)

        if self.login_message:
            if self.localize_callback is not None:
                flash(
                    self.localize_callback(self.login_message),
                    category=self.login_message_category,
                )
            else:
                flash(self.login_message, category=self.login_message_category)

        config = current_app.config
        if config.get("USE_SESSION_FOR_NEXT", USE_SESSION_FOR_NEXT):
            login_url = expand_login_view(login_view)
            session["_id"] = self._session_identifier_generator()
            session["next"] = make_next_param(login_url, request.url)
            redirect_url = make_login_url(login_view)
        else:
            redirect_url = make_login_url(login_view, next_url=request.url)

        return redirect(redirect_url)

我們重新定義了一個 LoginManager_,並在裡面新增了 forbidden 這個函式,他是 unauthorized 的變形,基本上都是直接抄過來的,我們只把裡面的 abort 的 status code 改成 403 而已。這個 unauthorized 會在 login_required 裡面用到,所以我們在這邊加入一個 forbidden 之後,就可以在 admin_required 裡面使用了,所以我們馬上就來寫他。

def admin_required(func):
    @wraps(func)
    def decorated_view(*args, **kwargs):
        if current_user.is_active:
            if current_user.is_admin:
                return func(*args, **kwargs)
            else:
                return login_manager.forbidden()
        else:
            return login_manager.unauthorized()

    return decorated_view

基本上它的結構也跟 login_required 很像,但我們有修改裡面的邏輯判斷,讓他可以確定他有沒有登入和是否為管理員。最後我們再把這個裝飾器加到每個 admin_bp 裡面,就可以做出 admin_required 的效果了。

這邊我們沒有多做解釋,如果有興趣的話可以去看 Flask-Login 的原始碼。

AddUserForm

加入一個表單並不是一件很難的事,在之前我們都做過了好多次,但這次情況有點不太一樣,因為我們要在同一個頁面 (管理使用者的頁面) 放兩個表單,這會讓 form.validate_on_submit 變得有點奇怪。

我們要先稍微修改一下 AddUserFormUserFilterForm 的欄位,他們要各多加一個小欄位。

forms.py

from wtforms import HiddenField

class AddUserForm(FlaskForm):
    form_name = HiddenField(render_kw={"value": "add_one"})
    
class UserFilterForm(FlaskForm):
    form_name = HiddenField(render_kw={"value": "filter"})

我們各加了一個 HiddenField,我們等等會用這個欄位來判斷現在使用的是哪一個表單。以下的程式碼只有在 POST 底下的部分,form 還是原來在用的 UserFilterForm,而 form_add_user 則是 AddUserForm。接下來我們抓 request.form["form_name"] 也就是剛剛我們放進去的 HiddenField,然後在下面判斷要用哪一個表單。

結語

這系列的文章就到這邊告一段落,希望不管是跟著鐵人賽期間一篇一篇看的人,或是在查資料偶然翻到這個系列的人都可以有一些收穫,也謝謝你們願意閱讀這篇文章。


上一篇
Day 29 實作 admin_bp (2)
系列文
Flask30

尚未有邦友留言

立即登入留言