DjangoでViewのリクエスト前後に任意の処理を差し込む

デコレータを使用し、DjangoのViewでGETやPOSTリクエストの処理前後に任意の処理を差し込む方法をまとめます。

やりたいこと

例えば以下のようなViewにおいて、 MyView.get の処理前に @pre_request で指定した program1 関数を実行し、 処理後に @post_response で指定した program2 関数を実行できるようにします。

class MyView(View):
    @pre_request(program1)
    @post_response(program2)
    def get(self, request, *args, **kwargs):
        ...

デコレータの作成

decorators モジュールに事前処理を実行するデコレータ pre_request と事後処理を実行するデコレータ post_response を実装します。
いずれも実行する関数( program )を引数にとります。
実のところ特にDjangoの要素はなく、単なるPythonのデコレータです。

from functools import wraps


def pre_request(program):
    """事前処理"""
    def _decorator(function):
        @wraps(function)
        def wrap(self, request, *args, **kwargs):
            # 引数の関数を実行
            program(self, request, *args, **kwargs)
            # デコレートした関数(getなど)を実行
            return function(self, request, *args, **kwargs)
        return wrap
    return _decorator


def post_response(program):
    """事後処理"""
    def _decorator(function):
        @wraps(function)
        def wrap(self, request, *args, **kwargs):
            # デコレートした関数(getなど)を実行
            response = function(self, request, *args, **kwargs)
            # 引数の関数を実行
            # HTTPレスポンスも引数に追加
            return program(self, request, response, *args, **kwargs)
        return wrap
    return _decorator

Viewの実装

あとは decorators から pre_requestpost_response をインポートして使うだけです。

from django.views.generic.base import View

from .decorators import post_response, pre_request

def program1(self, request, *args, **kwargs):
    """事前処理"""
    ...

def program2(self, request, response, *args, **kwargs):
    """事後処理"""
    ...
    # レスポンスを返すこと
    return response


class MyView(View):
    @pre_request(program1)
    @post_response(program2)
    def get(self, request, *args, **kwargs):
        ...

利用例

(実用性は無視して)簡単な利用例を挙げておきます。
get()post() の引数を扱えるので、工夫次第で色々できます。

事前処理でビューへのアクセス権限を判定

以下の関数は一般ユーザーのアクセスを拒否します。
この例はあまり実用性がないですが、@permisson_requiredでは対応しきれないような決めの細かい権限管理が必要な場合などに使用できるかもしれません。

from django.core.exceptions import PermissionDenied


def do_not_accept_general_user(self, request, *args, **kwargs):
    """一般ユーザのアクセス拒否"""
    if not request.user.is_superuser:
        raise PermissionDenied()

事後処理でレスポンスを更新

事後処理は HttpResponse を受け取るため、コンテキストの追加変更やレスポンスそのものの上書き(遷移先の変更やURLパラメタの追加など)が可能です。

def update_response(self, request, response, *args, **kwargs):
    """コンテキストの書き換え"""
    if something:
        response.context_data['foo'] = '..'
    return response

まとめ

デコレータでViewに任意の事前処理、事後処理を差し込む方法をご紹介しました。
Viewに共通する処理をどのように切り出すかは状況に依りますが(CBVの継承やバックエンド、シグナルの実装など)、任意の機能を付け外しできるデコレータは使い方次第で大きな効果を発揮すると思います。