skip to content

In Django REST Framework, what exactly do IsAdminUser, IsAuthenticatedOrReadOnly and DjangoModelPermissions check before letting a request through?

level: middleimportance: should knowfreq 44%

answer

  1. staff flag, not superuser
  2. the three safe methods
  3. HTTP method mapped to model codenames
  4. reads need no model permission by default

basics

~10 s

IsAdminUser requires user.is_staff; IsAuthenticatedOrReadOnly lets anyone use GET, HEAD and OPTIONS but requires login for writes; DjangoModelPermissions requires login plus the add, change or delete model permission matching the HTTP method.

solid answer

~40 s

`IsAdminUser` passes when `request.user.is_staff` is true — it does not look at `is_superuser`. `IsAuthenticatedOrReadOnly` passes when the method is in `SAFE_METHODS` (`GET`, `HEAD`, `OPTIONS`) or the user is authenticated. `DjangoModelPermissions` requires an authenticated user and then maps the method to Django model permissions through `perms_map`: `POST` needs `add`, `PUT` and `PATCH` need `change`, `DELETE` needs `delete`, and `GET`, `HEAD` and `OPTIONS` need nothing. It finds the model from the view's `get_queryset()` or `queryset`, so it only works on views that have one, and it calls `user.has_perms()`, which an active superuser always passes. To require the `view` permission for reads, subclass it and extend `perms_map`; `DjangoModelPermissionsOrAnonReadOnly` also lets anonymous users read.

code

python · 9 lines
python
from rest_framework.permissions import DjangoModelPermissions


class DjangoModelPermissionsWithView(DjangoModelPermissions):
    perms_map = {
        **DjangoModelPermissions.perms_map,
        "GET": ["%(app_label)s.view_%(model_name)s"],
        "HEAD": ["%(app_label)s.view_%(model_name)s"],
    }

go deeper

for a junior

Recall which flag IsAdminUser checks and which methods count as safe for IsAuthenticatedOrReadOnly.

for a middle

Explain DjangoModelPermissions end to end: the queryset requirement, the method-to-permission map, the empty mapping for reads, and superuser behaviour.

for a senior

Spot the production gaps — staff mistaken for superuser, reads that ignore the view permission, anonymous metadata — and fix them with small subclasses.

for a principal

Decide whether API authorisation should reuse Django's model permissions managed in the admin or a domain-specific policy, and what that costs in coupling.

## The built-ins at a glance Django REST Framework (DRF) ships its permission classes in `rest_framework.permissions`. All of them implement only the view-level hook, `has_permission()`, except `DjangoObjectPermissions`, which adds an object-level hook. | Class | Passes when | Typical use | |---|---|---| | `AllowAny` | always | explicit public endpoints; the shipped default | | `IsAuthenticated` | `request.user.is_authenticated` | any signed-in user | | `IsAdminUser` | `request.user.is_staff` | back-office endpoints | | `IsAuthenticatedOrReadOnly` | method in `SAFE_METHODS`, or authenticated | public reads, signed-in writes | | `DjangoModelPermissions` | authenticated and has the mapped model permission | APIs driven by Django's permission system | | `DjangoModelPermissionsOrAnonReadOnly` | as above, but anonymous reads allowed | public catalogue, permissioned writes | | `DjangoObjectPermissions` | model and per-object permissions | needs an object-permission backend | ## `IsAdminUser` means staff `IsAdminUser.has_permission()` returns `bool(request.user and request.user.is_staff)`. The name suggests superusers, but the check is the **staff flag** — the same flag that lets a user into the Django admin site. A superuser who is not staff fails it; a staff member with no other rights passes it. If an endpoint really needs superusers, write a small class that checks `is_superuser`. ## `IsAuthenticatedOrReadOnly` and `SAFE_METHODS` `SAFE_METHODS` is the tuple `('GET', 'HEAD', 'OPTIONS')`. `IsAuthenticatedOrReadOnly` passes any request whose method is in it, and otherwise requires `request.user.is_authenticated`. Two consequences: - anonymous clients can read everything the view exposes, including the `OPTIONS` metadata response; - it says nothing about **which** signed-in user may write; pair it with an object-level class for ownership rules. ## How `DjangoModelPermissions` decides 1. It rejects the request if there is no user, or if the user is anonymous and `authenticated_users_only` is `True` (the default). 2. It finds the model by calling the view's `get_queryset()` (or reading `queryset`); a view with neither fails an assertion. 3. It looks the method up in `perms_map` and formats codenames such as `documents.change_document` from the model's `app_label` and `model_name`. A method missing from the map raises `MethodNotAllowed`. 4. It returns `request.user.has_perms(perms)`. The default `perms_map` is: | Method | Required permission | |---|---| | `GET`, `HEAD`, `OPTIONS` | none | | `POST` | `add` | | `PUT`, `PATCH` | `change` | | `DELETE` | `delete` | The row that surprises people is the first: **any authenticated user can read** under the default map, even though Django creates a `view` permission for every model. To enforce it, subclass and set `perms_map` with `'%(app_label)s.view_%(model_name)s'` for the safe methods. Because the check ends in Django's `User.has_perms()`, an **active superuser passes every permission** without holding any explicitly; an inactive user holds none. ## Variants - `DjangoModelPermissionsOrAnonReadOnly` sets `authenticated_users_only = False`, so anonymous users pass the safe methods (which map to no permissions) and fail the rest. - `DjangoObjectPermissions` extends the model check with `user.has_perms(perms, obj)` in `has_object_permission()`. Django's default `ModelBackend` returns no object permissions, so it only works with an object-permission backend installed. When a user lacks the object permission it raises `Http404` if the user cannot even read the object, and returns `False` (a 403) if they can. ## Choosing among them - For "only signed-in users", use `IsAuthenticated`. - For "only staff", use `IsAdminUser`, and know it means `is_staff`. - For "permissions managed by an administrator in the admin", use `DjangoModelPermissions`, and decide explicitly whether reads should require `view`. - For "only the owner", none of the built-ins fits; write an object-level class.

  • What happens if you put DjangoModelPermissions on a DRF APIView with no queryset?
    Its `has_permission()` needs the model, so it calls `get_queryset()` or reads `queryset`. A plain `APIView` has neither, and the class fails an assertion saying it cannot be applied to a view without `.queryset` or `.get_queryset()`. Use it on generic views and viewsets, or give the view a `queryset`.
  • Why does a DRF request with an unusual method such as PROPFIND fail under DjangoModelPermissions?
    `get_required_permissions()` looks the method up in `perms_map`; a method with no entry raises `MethodNotAllowed`, so the client gets 405. Supporting an extra method means adding it to `perms_map` in a subclass.

saying these in an interview costs you the question

  • IsAdminUser admits only superusers.
  • DjangoModelPermissions requires the view permission for GET by default.
  • IsAuthenticatedOrReadOnly lets anonymous users send PATCH if they have a token.
  • DjangoModelPermissions works on any APIView, with or without a queryset.
  • A superuser still needs explicit model permissions to pass DjangoModelPermissions.