skip to content

When a custom Django user model extends AbstractBaseUser, what does adding PermissionsMixin provide, and what breaks without it?

level: middleimportance: should knowfreq 38%

answer

  1. not part of the base class
  2. one boolean, two many-to-manys
  3. the admin needs two methods
  4. ModelBackend expects the tables

basics

~20 s

PermissionsMixin adds is_superuser, the groups and user_permissions many-to-many fields, and has_perm(), has_perms(), has_module_perms() and the get_*_permissions() methods. Without it a user built on AbstractBaseUser has no permission API, and the admin and ModelBackend cannot authorize them.

solid answer

~30 s

`AbstractBaseUser` covers passwords and login only; `AbstractUser` is `AbstractBaseUser` plus `PermissionsMixin` plus the usual profile fields. The mixin is an abstract model that adds `is_superuser`, `groups` and `user_permissions`, and the permission methods `get_user_permissions()`, `get_group_permissions()`, `get_all_permissions()`, `has_perm()`, `has_perms()` and `has_module_perms()`, all routed through the authentication backends with the active-superuser shortcut. Leave it out and `user.has_perm()` does not exist, the admin — which needs `has_perm()` and `has_module_perms()` — cannot authorize, and `ModelBackend` hits missing tables if called. You can instead write those methods yourself, but you lose groups and per-model rights.

go deeper

for a junior

Know that AbstractUser already has permissions, while a model on AbstractBaseUser needs PermissionsMixin added.

for a middle

Name the three fields and the permission methods the mixin adds, and explain that the methods delegate to the authentication backends.

for a senior

Decide at user-model creation whether to include the mixin, and explain what the admin and ModelBackend require if you replace it.

for a principal

Weigh the built-in permission tables against an external authorization service before the user model is fixed in place.

## Where the permission fields come from Django ships three base classes for user models: | Class | Provides | |---|---| | `AbstractBaseUser` | password hashing, `last_login`, `USERNAME_FIELD` plumbing, `is_active` treated as `True` by default | | `PermissionsMixin` | `is_superuser`, `groups`, `user_permissions` and the permission methods | | `AbstractUser` | `AbstractBaseUser` **plus** `PermissionsMixin` plus username, names, email, `is_staff`, `is_active`, `date_joined` | The built-in `User` subclasses `AbstractUser`. So a custom user model built on `AbstractUser` already has permissions, while one built directly on `AbstractBaseUser` — the usual choice when the clinic logs staff in by email and wants a lean table — has **none of them** unless it adds `PermissionsMixin`. ## What `PermissionsMixin` adds It is an **abstract model**, so it adds fields and tables to your user model: - `is_superuser` — a `BooleanField`, default `False`; - `groups` — a many-to-many to `Group` (reverse accessor `user_set`, reverse query name `user`); - `user_permissions` — a many-to-many to `Permission`, with the same reverse names. And it adds methods that consult the configured authentication backends: 1. `get_user_permissions()`, `get_group_permissions()`, `get_all_permissions()`; 2. `has_perm()`, `has_perms()`, `has_module_perms()`, each with the active-superuser shortcut; 3. `async` variants of each (`ahas_perm()`, `aget_all_permissions()` and so on). ## What breaks without it - **`user.has_perm(...)` does not exist.** Code, decorators and mixins that call it raise `AttributeError`. - **The admin cannot authorize.** Django's docs list what a custom user model needs to work with the admin: `is_staff`, `is_active`, `has_perm()` and `has_module_perms()`. Without them the admin cannot decide what the user may see. - **`ModelBackend` assumes the fields exist.** If you implement `has_perm()` yourself but still route calls to `ModelBackend`'s permission methods, it queries `user_permissions` and `groups`; the docs warn you will get database errors when those fields are missing. ## The two legitimate options 1. **Include the mixin** — `class StaffMember(AbstractBaseUser, PermissionsMixin)`. You get groups, direct grants and the whole permission API for three columns and two join tables. This is the normal choice. 2. **Implement the methods yourself** — for a tiny internal tool where, say, every active staff member may do everything, write `has_perm()` and `has_module_perms()` that return `self.is_active and self.is_staff`. You then give up groups, per-model rights and every tool built on them. ```python from django.contrib.auth.base_user import AbstractBaseUser, BaseUserManager from django.contrib.auth.models import PermissionsMixin from django.db import models class StaffMember(AbstractBaseUser, PermissionsMixin): email = models.EmailField(unique=True) is_active = models.BooleanField(default=True) is_staff = models.BooleanField(default=False) USERNAME_FIELD = "email" objects = BaseUserManager() ``` (A real project also writes a manager with `create_user()` and `create_superuser()`; `BaseUserManager` alone does not define them.) ## Implementing the methods yourself The alternative to the mixin looks like this for a tool where every active staff member may do everything: ```python class KioskOperator(AbstractBaseUser): email = models.EmailField(unique=True) is_active = models.BooleanField(default=True) is_staff = models.BooleanField(default=False) USERNAME_FIELD = "email" def has_perm(self, perm, obj=None): return self.is_active and self.is_staff def has_module_perms(self, app_label): return self.is_active and self.is_staff ``` It satisfies the admin's contract, but every check now answers the same way for every model: there are no groups, no per-model rights and no `get_all_permissions()`. Code or packages that call `user.has_perms()` or `user.get_all_permissions()` also break, because only the two methods you wrote exist. ## Timing matters The permission fields are part of the user table's schema and migrations. Adding `PermissionsMixin` later is an ordinary migration (a boolean column and two join tables), but it is still a schema change on the most-referenced table in the project, and groups and grants created by hand in the meantime do not exist. Deciding at the start — when the custom user model is created — is cheaper than retrofitting. ## Interview angle The question tests whether the candidate knows that permissions are **not** part of `AbstractBaseUser`, can name the three fields the mixin adds, and understands that the admin and `ModelBackend` depend on them. A strong answer also says when skipping the mixin is reasonable and what is lost: groups, direct grants, the `get_*_permissions()` family and compatibility with third-party code that assumes the standard permission API. The weakest answers assume every user model has permissions because the built-in `User` does, and only discover otherwise when the admin refuses to show anything to a staff member.

  • Is AbstractUser the same as AbstractBaseUser plus PermissionsMixin?
    It is those two plus concrete fields: `username`, `first_name`, `last_name`, `email`, `is_staff`, `is_active` and `date_joined`, with `UserManager` as its manager. Choosing `AbstractBaseUser` plus `PermissionsMixin` gives the permission machinery without those fields.
  • Where do the groups and user_permissions join tables for a custom user model live?
    In your app, not in `auth`. Because `PermissionsMixin` is abstract, its many-to-many fields are created on your concrete user model, so the join tables are named after it and created by your app's migrations.

saying these in an interview costs you the question

  • AbstractBaseUser already includes groups and is_superuser
  • PermissionsMixin creates the Permission and Group tables
  • The admin works without has_perm() as long as is_staff exists
  • PermissionsMixin checks permissions itself without any backend