23.

Django の管理者ユーザー|is_staff と is_superuser の違いと権限設計

編集
この記事の要点
  • Django の「管理者」は 2 つのフラグで決まる。is_staff(管理画面に入れる)is_superuser(全権限)
  • createsuperuser で作られるのは両方 True のユーザー
  • is_superuser=True は権限チェックを全部素通りさせる。運用担当には is_staff + グループを割り当てる
  • 自作画面の保護は @user_passes_test / UserPassesTestMixin、管理画面まわりは @staff_member_required
  • テンプレート側の出し分けは {{ user.is_staff }}。ただし表示を隠すだけでは保護にならない

2 つのフラグの違い

フラグ意味付けると起きること
is_active有効なアカウントかFalse にするとログインできない(削除の代わりに使う)
is_staff/admin/ にログインできるか入れるが、見えるのは権限を与えたモデルだけ
is_superuserすべての権限を持つ個別の権限チェックをすべて無視して通す

is_superuser=True のユーザーは user.has_perm() が常に True を返します。日常業務のアカウントに付けると、権限設計そのものが意味を失います。

管理者ユーザーを作る

python manage.py createsuperuser
# Username: admin
# Email address: admin@example.com
# Password: (入力しても表示されない)
# シェルから作る / 既存ユーザーを昇格させる
from django.contrib.auth import get_user_model

User = get_user_model()          # settings.AUTH_USER_MODEL を尊重する書き方

User.objects.create_superuser(username="admin", email="a@example.com",
                              password="秘密")

# 管理画面に入れるだけの運用担当を作る
staff = User.objects.create_user(username="operator", password="秘密")
staff.is_staff = True
staff.save()

# 既存ユーザーを昇格
u = User.objects.get(username="taro")
u.is_staff = True
u.save(update_fields=["is_staff"])

パスワードは create_user() / create_superuser() が自動でハッシュ化します。User.objects.create(password="...") と書くと平文がそのまま入り、ログインできなくなります。個別に設定するなら u.set_password("...") を使ってください。

ユーザーの作成そのものは ユーザーの作成 でも扱っています。

グループと権限で分ける

Django はマイグレーション時に、モデルごとの権限(add / change / delete / view)を自動生成します。これをグループにまとめて割り当てます。

from django.contrib.auth.models import Group, Permission
from django.contrib.contenttypes.models import ContentType
from myapp.models import Article

group, _ = Group.objects.get_or_create(name="記事編集者")

ct = ContentType.objects.get_for_model(Article)
perms = Permission.objects.filter(content_type=ct,
                                  codename__in=["view_article", "change_article"])
group.permissions.set(perms)

user.groups.add(group)
user.is_staff = True             # 管理画面に入れるようにする
user.save()

print(user.has_perm("myapp.change_article"))   # True
print(user.has_perm("myapp.delete_article"))   # False

独自の権限を足したいときはモデルの Meta に書きます。

class Article(models.Model):
    title = models.CharField(max_length=200)

    class Meta:
        permissions = [("publish_article", "記事を公開できる")]

# 使う側
if request.user.has_perm("myapp.publish_article"):
    ...

ビューを保護する

from django.contrib.admin.views.decorators import staff_member_required
from django.contrib.auth.decorators import login_required, permission_required, user_passes_test

@login_required
def mypage(request):
    ...

@staff_member_required          # is_staff が False なら管理画面のログインへ
def dashboard(request):
    ...

@permission_required("myapp.publish_article", raise_exception=True)
def publish(request, pk):
    ...

@user_passes_test(lambda u: u.is_superuser)   # 条件は自由に書ける
def danger(request):
    ...

raise_exception=True を付けると、権限が無いときにログイン画面へ飛ばさず 403 を返します。ログイン済みのユーザーがログイン画面に飛ばされて混乱するのを防げます。

クラスベースビューの場合

from django.contrib.auth.mixins import LoginRequiredMixin, UserPassesTestMixin
from django.views.generic import ListView

class AdminOnlyListView(LoginRequiredMixin, UserPassesTestMixin, ListView):
    model = Article
    raise_exception = True          # 403 を返す(ログイン画面へ飛ばさない)

    def test_func(self):
        return self.request.user.is_staff

Mixin は継承の左側に置きます。右側に書くと ListView の処理が先に走り、判定前にデータを返してしまいます。

テンプレートでの出し分け

{% if user.is_authenticated %}
  {% if user.is_staff %}
    <a href="/admin/">管理画面</a>
  {% endif %}

  {% if perms.myapp.publish_article %}
    <button>公開する</button>
  {% endif %}
{% endif %}

テンプレートで隠すのは見た目だけです。URL を直接叩けばビューは実行されるので、必ずビュー側でも同じ条件を判定してください。テンプレート側の判定は テンプレート側のログイン判定、ビュー側は ビュー側のログイン判定 を参照してください。

管理画面の見え方を役割で変える

from django.contrib import admin
from myapp.models import Article

@admin.register(Article)
class ArticleAdmin(admin.ModelAdmin):
    list_display = ("title", "author", "published")

    def get_queryset(self, request):
        qs = super().get_queryset(request)
        if request.user.is_superuser:
            return qs
        return qs.filter(author=request.user)     # 自分の記事だけ見せる

    def has_delete_permission(self, request, obj=None):
        return request.user.is_superuser           # 削除は管理者のみ

運用上の注意

  • is_superuser は最小限の人数に。日常操作は is_staff + グループで足りる
  • 退職者は削除ではなく is_active = False。削除すると関連レコードが道連れになる
  • 本番の /admin/ は URL を変える・IP を制限するなど、露出を減らす
  • createsuperuser のパスワードを .env やスクリプトに直書きしない
  • 権限を変えたら実際にそのアカウントでログインして確認する。付け忘れは画面を見ないと分からない

関連

編集
Post Share
子ページ

子ページはありません

同階層のページ
  1. 環境構築とプロジェクト/アプリの作成
  2. MVC(MVT)のそれぞれの使い方と説明
  3. データベースへの接続と操作
  4. Django Administration
  5. git管理
  6. エラー一覧
  7. バージョンの確認方法
  8. ログ出力方法
  9. SQLのログ出力方法
  10. ログのローテート設定
  11. settings.pyの定数にアクセスする方法
  12. 本番環境へのインストールとアプリのデプロイ(apache編)
  13. 本番環境へのインストールとアプリのデプロイ(nginx編)
  14. djangoアプリの本番の開始URLを変更する
  15. 静的(static)ファイルの置き場所と読み込み(画像、css、js )
  16. CSRFトークンをAjaxで使用する方法
  17. ajaxの使用例(POST編)
  18. ファイルのアップロードとファイルの名前
  19. クイックスタート/チュートリアル
  20. ログイン機能
  21. テンプレート側のログイン判定
  22. ビュー側のログイン判定
  23. 管理者ユーザーの作成/判定と管理画面
  24. モデルのjson化とレスポンス
  25. runserverでポートを指定する方法
  26. cronによるバッチ実行
  27. テンプレートで利用する共通のcontextを定義する方法
  28. プログラムが本番サーバーで反映されない場合の対処法
  29. APIの作成
  30. cron用コマンド・ファイルの作成

最近更新/作成されたページ