Djangoには標準で認可(Permission)の仕組みがあります。
ただ、Django標準の認可はモデルレベルです。オブジェクトレベルは
Django のパーミッションフレームワークはオブジェクトパーミッション基盤を持っていますが、コアには実装されていません。
https://docs.djangoproject.com/ja/3.0/topics/auth/customizing/#handling-object-permissions
と公式ドキュメントにあります。
そのため、オブジェクトレベルで認可を扱いたい時はライブラリを使います。その選択肢の一つとして django-rules
があります。
dfunckt/django-rules: Awesome Django authorization, without the database
そんな中、Django + Django-rules + 自作UserモデルのDjangoアプリにて、django-rulesの has_perm
テンプレートタグを使おうとした時にハマったため、メモを残します。
目次
環境
問題
PermissionsMixinを継承せずに独自Userモデルを作成したところ、django-rulesによる認可が動作しませんでした。
再現方法
独自Userモデルの作成
Django標準のUserモデルではなく、 AbstractBaseUser
を継承して独自Userモデルを作成しました。
独自Userモデルを作成する場合、 PermissionsMixin
も継承しておくことで、パーミッションまわりの機能を独自ユーザーへ簡単に組み込めます。
https://docs.djangoproject.com/ja/3.0/topics/auth/customizing/#custom-users-and-permissions
ただ、この時は
より、 PermissionsMixin
を継承しない独自Userモデルを作成しました。
class User(AbstractBaseUser): pass
INSTALLED_APPS
ドキュメントには
- rules
- rules.apps.AutodiscoverRulesConfig
のどちらかを指定するよう記載されています。
ただ、 rules.apps.AutodiscoverRulesConfig
を指定しておくと、django-rulesが自動的に rules.py
という名前のファイルを探してくれるため、 rules.apps.AutodiscoverRulesConfig
を指定します。
rules may optionally be configured to autodiscover rules.py modules in your apps and import them at startup. To have rules do so, just edit your INSTALLED_APPS setting:
AUTHENTICATION_BACKENDS
認証バックエンドとしてdjango-rulesのものが必要なため、settings.pyに追加しておきます。
AUTHENTICATION_BACKENDS = ( 'rules.permissions.ObjectPermissionBackend', 'django.contrib.auth.backends.ModelBackend', )
rules.py
django-rules用に、 rules.py
にルールを実装しました。
import rules @rules.predicate def is_admin(user): return user.is_admin rules.add_perm('accounts.admin', is_admin)
テンプレートでdjango-rulesを利用
rules
をloadした後、 has_perm
で権限により表示/非表示を切り替えます。
{% load rules %} <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>お知らせ</title> </head> <body> <h3>お知らせ一覧</h3> <ul> {% has_perm 'accounts.admin' request.user as is_admin %} {% if is_admin %} <li>システム管理者だけが見える</li> {% endif %} <ul>
ここまででdjango-rulesの設定が終わっているものの、runserverしても has_perm
テンプレートタグが動作しませんでした。
原因
django-rulesのテンプレートタグ has_perm
のソースコードを見たところ、
@register.simple_tag def has_perm(perm, user, obj=None): if not hasattr(user, 'has_perm'): # pragma: no cover return False # swapped user model that doesn't support permissions else: return user.has_perm(perm, obj) # https://github.com/dfunckt/django-rules/blob/v2.2.0/rules/templatetags/rules.py#L15
と書かれていました。
テンプレートタグ has_perm
は、Userモデルに has_perm()
メソッドがある前提で動作するようです。
そのため、 PermissionsMixin
を継承しないなどでUserモデルに has_perm()
メソッドが無い場合は、常に False
を返します。
その結果、rules.pyに設定したコードは動作しなかったようです。
対応
簡単な対応としては、を継承した独自Userモデルでは PermissionsMixin
を継承するようにします。
class User(AbstractBaseUser, PermissionsMixin): pass
もし、 PermissionsMixin
を継承できない場合にテンプレートタグ has_perm
を使いたい場合は、何らかの形で独自ユーザークラスに has_perm
メソッドを実装しておきます。
なお、AbstractBaseUser
ではなく AbstractUser
を継承した独自ユーザーの場合は PermissionsMixin
を継承済のため、このようなことは起きません。
https://github.com/django/django/blob/3.0.5/django/contrib/auth/models.py#L316