In Django, what do the is_staff and is_superuser user flags each control, and can a superuser without is_staff use the admin?
answer
- one opens the door, one grants rights
- the admin site's own gate
- has_perm short-circuits
- is_active overrides both
basics
~20 sis_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 sThe 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 linesfrom 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 checkgo deeper
Recall that is_staff opens the admin and is_superuser grants every permission, and that createsuperuser sets both.
Explain where each flag is checked: AdminSite.has_permission and the admin login form for staff, has_perm and has_module_perms for superusers.
Keep superuser accounts few and audited, give staff rights through groups, and remember overridden hooks can deny even superusers.
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