In Django, how do is_superuser and is_active change what has_perm() and get_all_permissions() return for a user?
answer
- a shortcut before the backends
- both flags must be true
- empty set when disabled
- rows exist versus answer yes
basics
~20 sAn active superuser passes every has_perm() check without holding any grants, and ModelBackend reports every Permission row for them. An inactive user gets an empty set and False from ModelBackend, even if they are a superuser or belong to groups.
solid answer
~30 s`PermissionsMixin.has_perm()` returns `True` at once when the user is both active and a superuser — no backend, no query, not even a check that the permission exists. `get_all_permissions()` still asks the backends, and for a superuser `ModelBackend` returns every `Permission` row. For an inactive user, `ModelBackend` returns an empty set and `False`, ignoring groups, direct grants and `is_superuser`, which is why `is_active = False` is the standard way to disable an account without deleting it. The traps: testing as a superuser hides missing grants, making an office manager a superuser grants everything, and a custom backend must check `is_active` itself.
go deeper
Remember: active superusers get yes to everything, inactive users get no to everything from the default backend.
Explain where the bypass sits in PermissionsMixin, why get_all_permissions() still consults backends, and how is_active disables an account.
Reserve is_superuser for a few operators, model admins as a group, test with realistic accounts, and make custom backends honour is_active.
Set a policy for who may hold superuser in production and how that access is granted, audited and removed.
## Two flags that sit above every grant `PermissionsMixin` and the default user model carry two booleans that change permission checks before any `Permission` row matters: - **`is_superuser`** — "designates that this user has all permissions without explicitly assigning them"; - **`is_active`** — whether the account is currently usable. `AbstractBaseUser` treats users as active by default, and the built-in `User` has a real `is_active` field. ## The superuser bypass `PermissionsMixin.has_perm()`, `has_perms()` and `has_module_perms()` all begin the same way: if the user is **active and a superuser**, return `True` immediately. No backend is asked, no query runs and no string is validated — even a misspelled permission passes. `get_all_permissions()` is different: it still goes through the backends. For a superuser, `ModelBackend` returns **every `Permission` row in the database** as `"app_label.codename"` strings. So the set is "all rows that exist", while `has_perm()` answers "yes" even for strings that match no row. ## Inactive users get nothing `ModelBackend` returns an **empty set** for inactive users, and its `has_perm()` is literally `user_obj.is_active and super().has_perm(...)`. That holds even when the inactive user: - is a member of groups that grant permissions; - has direct `user_permissions`; - is a superuser — the bypass requires `is_active` too, so an inactive superuser falls through to the backends, which refuse. This makes `is_active = False` the standard way to **disable an account without deleting it**: rows that reference the user (appointments, notes, audit entries) survive, and every permission check fails. Deactivating also stops `ModelBackend` from authenticating the user at all. ## The traps | Situation | What happens | |---|---| | Testing features while logged in as a superuser | Missing grants and typos are invisible, because every check passes | | Giving the clinic's office manager `is_superuser` to "manage appointments" | They can also read and change every clinical record — the bypass is all or nothing | | Using `AllowAllUsersModelBackend` so inactive users can log in | They can authenticate, but it subclasses `ModelBackend`, so their permission checks still fail | | Writing a custom backend with its own `has_perm()` | The docs warn to test `is_active` yourself; the mixin only skips the superuser shortcut for inactive users, it does not stop your backend from answering `True` | | An anonymous visitor | `ModelBackend` returns an empty set for anonymous users | ## Choosing between a superuser and a group In the clinic app, the tempting shortcut is to make administrators superusers. The better default is a **Clinic admins** group holding exactly the permissions the role needs, with `is_superuser` reserved for a few engineers who maintain the system. That keeps the principle of least privilege checkable in data: you can list what the group grants, whereas a superuser's rights are "everything, including permissions added next year". `is_staff` is a third, separate flag: it controls entry to the Django admin site, not what `has_perm()` returns. A staff user with no permissions can log in to the admin and see nothing to edit. ## Where the checks live in the code Reading the source makes the order unambiguous. Simplified, the two layers are: ```python # django.contrib.auth.models.PermissionsMixin def has_perm(self, perm, obj=None): if self.is_active and self.is_superuser: return True return _user_has_perm(self, perm, obj) # asks each backend in turn # django.contrib.auth.backends.ModelBackend def has_perm(self, user_obj, perm, obj=None): return user_obj.is_active and super().has_perm(user_obj, perm, obj=obj) ``` The first layer is the superuser shortcut; the second is the backend's own refusal of inactive users. Any other backend listed in `AUTHENTICATION_BACKENDS` is consulted by the same loop, and the first `True` wins. ## A quick mental model 1. Active superuser? Yes to everything from `has_perm()`. 2. Inactive? `ModelBackend` says no to everything, even for a superuser. 3. Otherwise, the answer is the union of group and direct grants — plus whatever other backends add. ## Interview angle Interviewers use this question to see whether a candidate knows the **order** of the checks, not just that superusers are powerful. The strong answer separates the mixin's shortcut from the backend's refusal, explains why an inactive superuser is locked out, and treats `is_superuser` as an operational risk to be rationed rather than a convenient way to give a colleague access.
- Does AllowAllUsersModelBackend give inactive users their permissions back?No. It only overrides `user_can_authenticate()` so inactive users can log in. It subclasses `ModelBackend`, whose permission methods still return an empty set and `False` for inactive users, so their grants remain unusable.
- Why deactivate a departing nurse instead of deleting the user row?Deleting cascades or orphans the rows that reference the user — charted vitals, appointments, audit entries — while `is_active = False` keeps them intact. The account can no longer authenticate through `ModelBackend`, and every `ModelBackend` permission check fails, so access ends without losing history.
saying these in an interview costs you the question
- An inactive superuser still passes every permission check
- A superuser's get_all_permissions() is empty because nothing was granted
- is_staff gives a user every model permission
- Group membership keeps working after the account is deactivated
- Testing as a superuser proves the grants are configured