1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

django-currentuserでオリジナルのユーザーモデルを取得できるようにしてみました

Last updated at Posted at 2025-02-14

はじめに

私は Django Rest Frameworkを利用しており、プロジェクト全体でログインユーザーのモデルを利用する方法を模索していました。そして django-currentuser というライブラリを知ることができました。

views.py
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 に書きます。

settings.py
MIDDLEWARE = [
    ...
    'path_to_middleware.CustomThreadLocalUserMiddleware',
    ...
]

これで get_current_user() で私独自のユーザーモデルを取得できるようになりました。

自力で似たモジュールを作成する

自分のプロジェクトに合わせるため、ライブラリをどんどんカスタマイズしていくと、次第に継承の必要性を感じなくなりました。よって、django-currentuser で使われている理屈だけお借りしてモジュールも自作しようと思いました。

理屈としては、

  1. 各Viewに入る前に middleware でユーザー情報を外部クラスに保存し、
  2. 各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 をインポートして利用することができます。

views.py
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 プロジェクト全体でログインユーザーモデルを簡易的に利用できるのは大きなメリットだと思います。

また、外部クラスに保存するのはログインユーザー以外でもできます。
プロジェクト全体で使い回す変数においてこの理屈を再利用できれば、開発の効率化が見込めると思います。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?