Posted at

DjangoのUserPassesTestMixinを使って柔軟にアクセス制御する


やりたいこと

Djangoでログインしたユーザーの情報を、自分自身のみが編集できるようにしたい。

Django組み込みのPermissionモデルを活用しようとしても、ログインしたユーザー自身に動的に制限をかけるのは相当難しそう。

そこで活用できるのが UserPassesTestMixin です。


UserPassesTestMixinとは?

Django 1.9でリリースされた Permission mixins for class-based views に内包されている機能のこと。

公式ドキュメントでも丁寧に解説されています。

Djangoの認証システムを使用する | Django ドキュメント | Django


UserPassesTestMixinを使ってみる

百聞は一見にしかずということで例として、UserPassesTestMixinを使って gmail.comドメインのユーザーのみがアクセスできるビュー を定義してみます。


UserPassesTestMixinの拡張

まずは下記のようにカスタムミックスインを定義します。

# mixins.py

from django.contrib.auth.mixins import UserPassesTestMixin

class OnlyGmailUserMixin(UserPassesTestMixin):
raise_exception = True

def test_func(self):
return self.request.user.email.endswith('@gmail.com')

test_func というメソッドをオーバーライドして、そのメソッド内で条件分岐式を記述しておきます。

test_func の返り値で制限をかけるかかけないかが決まります。


ビューの定義

そして、先ほど作成したカスタムミックスインを継承したクラスビューを定義します

# views.py

from django.contrib.auth.models import User
from django.views.generic import TemplateView

from .mixins import OnlyGmailUserMixin

class OnlyGmailUserView(OnlyGmailUserMixin, TemplateView):
model = User
template_name = "home.html"

以上です。めちゃ簡単!!!


自分自身のみが編集できるビューを定義する

当初の「Djangoでログインしたユーザーの情報を、自分自身のみが編集できるようにする」は UserPassesTestMixin を使うことで簡単に実装ができます。

# mixins.py

from django.contrib.auth.mixins import UserPassesTestMixin

class OnlyYouMixin(UserPassesTestMixin):
raise_exception = True

def test_func(self):
user = self.request.user
return user.pk == self.kwargs['pk']

# views.py

from django.contrib.auth.models import User
from django.views.generic import UpdateView

from .forms import UserForm # 別途フォームを定義しておく
from .mixins import OnlyYouMixin

class UserUpdateView(OnlyYouMixin, UpdateView):
model = User
template_name = "users/update.html"
form_class = UserForm

def get_success_url(self):
return resolve_url('users:update', pk=self.kwargs['pk'])


まとめ

最近になって初めてUserPassesTestMixinを使ってみましたが、簡単で応用の効くMixinで面白いですね。

ただ、複数のUserPassesTestMixinを継承できないようなので、その点は注意が必要です。

また、最後のOnlyYouMixinですが、これをUserモデル以外のモデルで使おうとするとPKが違ってうまくいかないので、もう少し処理を抽象化する必要がありそうです。