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

# はじめに

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

https://pypi.org/project/django-currentuser/

https://qiita.com/startours777/items/2a4e70ec85cc3e93a1ab

https://www.hshshsk.com/django/django-model-auto-user-id/

```python: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)

```

:::note warn
しかし、私の場合、利用するには少し問題がありました。
:::

`user = get_current_user()`で取得するモデルは、`request.user` に入っているものです。私の場合、Djangoに標準で備わっている `AbstractUser`の継承を行わず、セッションを手動で作成しているようなオリジナルのユーザーモデルを利用しているので、このままでは利用できませんでした。
> user には`AnonymousUser`（非ログインユーザー）が入っていました。

よって、このライブラリを継承してカスタマイズすることを試みました。
私独自のユーザーモデルを `original_user`とし、ライブラリ内で `request.user` を取得している箇所を`original_user`にできないか試します。

# ライブラリのカスタマイズ

https://github.com/zsoldosp/django-currentuser/blob/master/django_currentuser/middleware.py

ソースコードはこちらに書いてあります。

この中でユーザーモデルを取得している箇所は29行目です。
```python
def __enter__(this):
    _do_set_current_user(lambda self: getattr(this.request, 'user', None))
```

よって、これが入っている `SetCurrentUser` を継承して以下のようにオーバーライドしました。

```python
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` を継承しました。

```python
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` に書きます。

```python:settings.py
MIDDLEWARE = [
    ...
    'path_to_middleware.CustomThreadLocalUserMiddleware',
    ...
]
```

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


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

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

理屈としては、
1. 各Viewに入る前に middleware でユーザー情報を外部クラスに保存し、
2. 各View で import して使う

ようなイメージです。

**1.**
まず、ユーザー情報を取り扱う専用のクラスを用意します。
> django-currentuser ライブラリと区別するために、LoginUser という命名にします。

```python
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
```

:::note
`threading.local()` というスレッドローカルストレージを使うことで複数のリクエスト毎にスレッドを分け、ユーザー情報を独立して保存できます。
:::

次に middleware で `LoginUserManager` を用いてユーザーモデルを保存します。

```python
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` をインポートして利用することができます。


```python: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)
```

:::note
もちろん、View 以外のところでも使えます。
Django プロジェクト全体でログインユーザーモデルを簡易的に利用できるのは大きなメリットだと思います。
:::

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