Python
Django

Django Admin: インスタンス権限設定

はじめに

django.contrib.admin.ModelAdminを作る時、
- has_change_permission(request, obj=None)
- has_delete_permission(request, obj=None)
上記2つのフックメソッドをオーバーライドすることで、ログインユーザやインスタンスに対して権限を設定することが可能です。
ですが、上記のフックはviewのfunctionとは違い、admin上で呼び出される個所が複数あるため、少し注意が必要です。

Environments

  • Python 3.6
  • Django 1.11

has_change_permission(request, obj=None)

このフックでobjにインスタンスが入ってるのは、インスタンスの変更フォームである、
/admin/{{ app_label }}/{{ model_name }}/{{ object_id }}/change/
のみです。
これ以外
- /admin/: 管理ページトップ
- /admin/{{ app_label }}/: appトップ
- /admin/{{ app_label }}/{{ model_name }}/: Modelのchangelist
上記3ヶ所で、obj=Noneとして呼び出されますので、

admin.py
def has_change_permission(self, request, obj=None):
    has_perm = super().has_change_permission(request, obj)
    if isinstance(obj, self.model):
        return obj.user == request.user and has_perm
    return has_perm

こういう風に分岐が必要になります。

has_delete_permission(request, obj=None)

objに関してはchangeと同じですが、もう一つ。
Admin actionsとしてビルトインされてるdelete_selectedからこのフックが呼ばれる際、削除しようとする個々のインスタンスに対する権限チェックをしないため、常にobj=Noneになるので

admin.py
has_perm = super().has_delete_permission(request, obj)
if request.POST.get('action') == 'delete_selected':
    selected_qs = self.model.objects.filter(pk__in=request.POST.getlist('_selected_action'))
    return all([o.user == request.user for o in selected_qs]) and has_perm

のような処理が必要です。

最後に

そもそも権限がないインスタンスはget_queryset(request)でリストから出さないようにしましょう。

References

Django Admin: has_delete_permission Ignored for “Delete” Action