全然サンプル見つからなかったので誰かに役に立つかも?と、妥協点はあるもののとりあえず実装としては良さそうなのでメモ
やりたかったこと
- 条件によって特定のAPI処理全体を切り替えたい
- 複数発生した場合を考えると
if hogeflag:
みたいな事はやりたくない
- 複数発生した場合を考えると
- この条件というのがログインユーザ毎、特定のデータの場合という事ではなく、環境変数や設定ファイルなど動作する環境毎に切り替えたい。
- 切り替わったら、その環境では選択されなかった処理は一切使用される事はない
具体的な例とか
- アプリケーションがパッケージ販売されていて、特定の環境では処理を変更したい
- 拡張機能などがあり、特定の処理を任意で上書きたい
対応方法
概要
DjangoのMiddlewareを使用する。
Middlewareとは、実際の処理の前後に行わせることができる処理です。hookとか言ったりします。
settings.py
に MIDDLEWARE = []
と配列として記載する事で設定しますので、実際のアプリケーションで使われている事が確認できるかと思います。認証処理などもここで行っています。
詳細
# extensions.test_middleware from django.http import JsonResponse from rest_framework.request import Request from extensions.hoge import HogeView class SimpleMiddleware: def __init__(self, get_response): self.get_response = get_response def __call__(self, request): response = self.get_response(request) return response def process_view(self, request, view_func, view_args, view_kwargs): # if view_func.__name__ == 'HogeView': # Classで比較する場合 if request.path == '/api/v1/hogehoge/': # URLPATHで比較する場合 # DjangoのRequestとDRFのRequestが異なるため変換 # django.core.handlers.wsgi.WSGIRequest -> rest_framework.request.Request req = Request(request) res = HogeView.get(HogeView, req) # process_viewではdjango.http.Responseを返す必要があり、 # rest_framework.response.Responseをそのまま返す事ができないため、値を取得して変換している return JsonResponse(res.data, status=res.status_code) else: # Noneを返す事で既存の処理が行われる return None
# extensions.hoge.HogeView from rest_framework.response import Response from rest_framework.views import APIView class HogeView(APIView): def get(self, request): return Response({'aaa': 'bbb'})
# settings.py MIDDLEWARE = [ 'django.middleware.security.SecurityMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.common.CommonMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', 'extensions.test_middleware.SimpleMiddleware' ]
コードの実物に近い形
直接クラス名とか書いていたら、各処理にifと書くのと何にも変わらない。 ので、設定系は全部DBに突っ込んで、DBの内容と合致したら設定に従ってリフレクションで処理を呼ぶようにした。
class SimpleMiddleware: def __init__(self, get_response): self.get_response = get_response def __call__(self, request): return self.get_response(request) def process_view(self, request, view_func, view_args, view_kwargs): extension = Extension.objects.filter(class_name=view_func.__name__).first() if extension: _cls = getattr(importlib.import_module(extension.module_name), extension.class_name) method = getattr(_cls, request.method.lower()) return JsonResponse(method(_cls, Request(request)).data, status=res.status_code)
没とした案
urls.pyで対応
実装して動作するところまでは確認
詳細
urlpatterns
は先勝ち(ドキュメントは調べてない!)なので、既存処理の設定をする前に独自処理のパターンをどこからか読み込んで設定すれば既存処理を上書きする事ができる。
没とした理由
ユニットテストが大変。
実際にはDBに設定が入っている事を想定していたため、urls.py のurlpatternsの設定する所でDBアクセスする必要がある。
- エラー処理の対応が必要
- url.pyはDjangoの基本機能らしく、
python manage.py migration
などでも処理が行われるので、migration前にテーブルがあるわけないのでエラー(ProgramingError)となる。 - exceptして対応する事も可能は可能。(1度は実装した)
- url.pyはDjangoの基本機能らしく、
- ユニットテストでエラーとなる