はじめに
私は Django Rest Frameworkを利用しており、プロジェクト全体でログインユーザーのモデルを利用する方法を模索していました。そして django-currentuser というライブラリを知ることができました。
from rest_framework import status
from rest_framework.response import Response
from django_currentuser.middleware import (
get_current_user,
get_current_authenticated_user
)
class View(APIView):
def get(self, request):
user = get_current_user()
return Response(status=status.HTTP_200_OK)
しかし、私の場合、利用するには少し問題がありました。
user = get_current_user()
で取得するモデルは、request.user
に入っているものです。私の場合、Djangoに標準で備わっている AbstractUser
の継承を行わず、セッションを手動で作成しているようなオリジナルのユーザーモデルを利用しているので、このままでは利用できませんでした。
user には
AnonymousUser
(非ログインユーザー)が入っていました。
よって、このライブラリを継承してカスタマイズすることを試みました。
私独自のユーザーモデルを original_user
とし、ライブラリ内で request.user
を取得している箇所をoriginal_user
にできないか試します。
ライブラリのカスタマイズ
ソースコードはこちらに書いてあります。
この中でユーザーモデルを取得している箇所は29行目です。
def __enter__(this):
_do_set_current_user(lambda self: getattr(this.request, 'user', None))
よって、これが入っている SetCurrentUser
を継承して以下のようにオーバーライドしました。
from django_currentuser.middleware import (
SetCurrentUser,
_do_set_current_user
)
# SetCurrentUser を継承しています
class CustomSetCurrentUser(SetCurrentUser):
# このメソッドをオーバーライドしています
def __enter__(this):
id = this.request.session['id']
user = original_user.objects.get(id = id)
_do_set_current_user(lambda self: user)
request の中の session にユーザーのIDを格納しています。
これで、current_user にセットするのは original_user
モデルになります。
次は CustomSetCurrentUser
を利用するために、元の ThreadLocalUserMiddleware
を継承しました。
from django_currentuser.middleware import (
ThreadLocalUserMiddleware,
SetCurrentUser,
_do_set_current_user
)
# ThreadLocalUserMiddleware を継承しています
class CustomThreadLocalUserMiddleware(ThreadLocalUserMiddleware):
def __call__(self, request):
with CustomSetCurrentUser(request): # CustomSetCurrentUser を利用しています
response = self.get_response(request)
return response
# SetCurrentUser を継承しています
class CustomSetCurrentUser(SetCurrentUser):
def __enter__(this):
id = this.request.session['id']
user = original_user.objects.get(id = id)
_do_set_current_user(lambda self: user)
最後にこのクラスを settings.py
に書きます。
MIDDLEWARE = [
...
'path_to_middleware.CustomThreadLocalUserMiddleware',
...
]
これで get_current_user()
で私独自のユーザーモデルを取得できるようになりました。
自力で似たモジュールを作成する
自分のプロジェクトに合わせるため、ライブラリをどんどんカスタマイズしていくと、次第に継承の必要性を感じなくなりました。よって、django-currentuser
で使われている理屈だけお借りしてモジュールも自作しようと思いました。
理屈としては、
- 各Viewに入る前に middleware でユーザー情報を外部クラスに保存し、
- 各View で import して使う
ようなイメージです。
1.
まず、ユーザー情報を取り扱う専用のクラスを用意します。
django-currentuser ライブラリと区別するために、LoginUser という命名にします。
import threading
class LoginUserManager():
_thread_local = threading.local()
# ユーザー情報を取得します
@classmethod
def get_login_user(cls):
return cls._thread_local.user
# ユーザー情報を保存します
@classmethod
def set_login_user(cls, user):
cls._thread_local.user = user
# ユーザー情報を破棄します
@classmethod
def delete_login_user(cls):
cls._thread_local.user = None
threading.local()
というスレッドローカルストレージを使うことで複数のリクエスト毎にスレッドを分け、ユーザー情報を独立して保存できます。
次に middleware で LoginUserManager
を用いてユーザーモデルを保存します。
class LoginUserMiddleware:
def __init__(self, get_response=None):
self.get_response = get_response
def __call__(self, request, *args, **kwds):
id = request.session['id']
user = original_user.objects.get(id = id)
LoginUserManager.set_login_user(user) # ユーザーモデルをセットします
response = self.get_response(request) # 各Viewに入ります
LoginUserManager.delete_login_user() # ユーザーモデルを破棄します
return response
2.
各Viewで以下のように LoginUserManager
をインポートして利用することができます。
from path_to_module import LoginUserManager
class View(APIView):
def get(self, request):
user = LoginUserManager.get_login_user() # ここでメソッドを使っています
return Response(status=status.HTTP_200_OK)
もちろん、View 以外のところでも使えます。
Django プロジェクト全体でログインユーザーモデルを簡易的に利用できるのは大きなメリットだと思います。
また、外部クラスに保存するのはログインユーザー以外でもできます。
プロジェクト全体で使い回す変数においてこの理屈を再利用できれば、開発の効率化が見込めると思います。