skip to content

In Django, what do the is_staff and is_superuser user flags each control, and can a superuser without is_staff use the admin?

level: juniorimportance: must knowfreq 62%

answer

  1. one opens the door, one grants rights
  2. the admin site's own gate
  3. has_perm short-circuits
  4. is_active overrides both

basics

~20 s

is_staff lets an active user into the Django admin; is_superuser makes has_perm() return True for every permission. They are independent: a superuser with is_staff=False cannot log in to the admin, and a staff user with no permissions sees nothing to edit.

solid answer

~40 s

The two flags answer different questions. `is_staff` is the admin's door: `AdminSite.has_permission()` returns `request.user.is_active and request.user.is_staff`, and the admin login form rejects non-staff accounts with its "staff account" error. `is_superuser` is about rights: for an active superuser, `has_perm()` and `has_module_perms()` return `True` without looking at groups or permissions. So a superuser with `is_staff=False` still cannot use the admin, while a staff user with no permissions can log in but only sees "You don't have permission to view or edit anything." `createsuperuser` sets both, and `is_active=False` disables the account for both purposes. Everything in between is granted per model through permissions and groups.

code

python · 13 lines
python
from django.contrib.auth import get_user_model

User = get_user_model()

clerk = User.objects.create_user("clerk", password="s3cret-pass", is_staff=True)
clerk.has_perm("shops.change_store")  # False: staff opens the door, grants nothing

root = User.objects.create_superuser("root", password="s3cret-pass")
root.is_staff, root.is_superuser  # (True, True)
root.has_perm("shops.change_store")  # True: active superusers pass every check

root.is_active = False
root.has_perm("shops.change_store")  # False: inactive users fail every check

go deeper

for a junior

Recall that is_staff opens the admin and is_superuser grants every permission, and that createsuperuser sets both.

for a middle

Explain where each flag is checked: AdminSite.has_permission and the admin login form for staff, has_perm and has_module_perms for superusers.

for a senior

Keep superuser accounts few and audited, give staff rights through groups, and remember overridden hooks can deny even superusers.

for a principal

Decide who in the organisation may hold staff or superuser status at all, and how that grant is reviewed and revoked.

## Three booleans on the user Django's built-in user model (`AbstractUser`, and `User` that extends it) carries three flags that together decide what someone can do in `django.contrib.admin`: - **`is_active`** — whether the account is usable at all. Inactive users fail every check below. - **`is_staff`** — documented simply as "allows this user to access the admin site". - **`is_superuser`** — "treats this user as having all permissions without assigning any permission to it in particular". It comes from `PermissionsMixin`. ## What `is_staff` does `is_staff` is checked in exactly the places that guard the admin as a whole: 1. `AdminSite.has_permission(request)` returns `request.user.is_active and request.user.is_staff`. Every admin view except the login page itself is wrapped by `AdminSite.admin_view()`, which redirects to the admin login page when this returns `False`. 2. The admin's login form, `AdminAuthenticationForm`, calls `confirm_login_allowed()` and rejects a correct password for a non-staff account with the same message as a wrong password: "Please enter the correct username and password for a staff account." It grants **no permission on any model**. A staff user with no permissions logs in and lands on an index that says "You don't have permission to view or edit anything." ## What `is_superuser` does `is_superuser` changes the answer of the permission checks rather than the door: - `PermissionsMixin.has_perm()` and `has_module_perms()` return `True` immediately for an **active** superuser, skipping groups, `user_permissions` and authentication backends. - Every default `ModelAdmin` hook — `has_view_permission()`, `has_add_permission()`, `has_change_permission()`, `has_delete_permission()`, `has_module_permission()` — delegates to those methods, so a superuser passes them all. - A hook you override to return `False` unconditionally applies to superusers too, because it never asks `has_perm()`. ## How the flags combine | `is_active` | `is_staff` | `is_superuser` | Admin outcome | |---|---|---|---| | True | True | True | Full access to every registered model | | True | True | False | Logs in; sees only models its permissions allow | | True | False | True | Cannot log in to the admin; still passes `has_perm()` in your own views | | True | False | False | Cannot log in to the admin | | False | any | any | Cannot log in; `has_perm()` returns False | The third row is the one interviewers like: superuser status does **not** open the admin by itself. ## Where the flags come from - `manage.py createsuperuser` and `UserManager.create_superuser()` set `is_staff` and `is_superuser` to `True` and refuse to create a superuser with either set to `False`. - `create_user()` sets both to `False` unless you pass them. - A custom user model built on `AbstractBaseUser` plus `PermissionsMixin` gets `is_superuser` from the mixin but must define `is_staff` itself if it is to use the admin. Designing that model belongs to the auth topics. ## Good practice - Give most staff accounts **no** superuser flag; grant rights through groups so they can be reviewed. - Treat `is_superuser` as break-glass: few accounts, audited use. - Remember that `is_staff` is only the door. What a staff user can see and change is the job of the `has_*_permission` hooks and `get_queryset()`. ## Checking it in a test The flag combinations are cheap to pin down with Django's test client: 1. Create a user with `is_superuser=True` and `is_staff=False` via `create_user(..., is_superuser=True)`, log in with `client.force_login()`, and GET the admin index: the response redirects to the admin login page. 2. Create a staff user with no permissions and GET the index: it renders with status 200 and the "You don't have permission to view or edit anything" message. 3. Add that user to a group holding `shops.view_store` and GET the Store change list: 200, with no "Add" button. Tests like these catch the most common production surprise — a support account someone "made superuser" that still cannot reach the admin, or a staff account that sees far more than intended. ## The interview angle A junior answer names the two flags. A stronger one explains that `is_staff` is checked by the admin site and its login form, `is_superuser` by the permission methods, and that the two are independent — then adds `is_active` as the switch that turns both off.

  • In the Django admin, why does a non-staff user with a correct password see the same error as a wrong password?
    `AdminAuthenticationForm.confirm_login_allowed()` raises the form's `invalid_login` error for non-staff users, the same message used for bad credentials. The login page therefore does not tell an attacker which accounts exist or which are staff; it only says to use a staff account.
  • A Django staff user has the change permission on Store but not the view permission. Can they open the Store change list?
    Yes. `ModelAdmin.has_view_permission()` returns `True` when the user has either the model's view or change permission, because being able to change implies being able to see. The view permission alone gives a read-only change form; change adds editing.

saying these in an interview costs you the question

  • is_superuser alone is enough to log in to the Django admin
  • is_staff grants permission to edit every model in the admin
  • An inactive superuser still passes has_perm() checks
  • createsuperuser sets is_superuser but leaves is_staff False
  • A ModelAdmin hook returning False is bypassed for superusers