What does Django's User.objects.with_perm() return, and why does it return no users when you pass obj=listing?
answer
- the reverse question
- a queryset, not a boolean
- superusers included by default
- one backend or name it
basics
~10 sUser.objects.with_perm('listings.approve_listing') returns a queryset of active users holding the permission directly or through a group, plus superusers by default. With obj set, ModelBackend returns an empty queryset because core has no object permissions.
solid answer
~30 s`UserManager.with_perm(perm, is_active=True, include_superusers=True, backend=None, obj=None)` answers "which users may do this?" as a queryset. It delegates to a backend's `with_perm()`; `ModelBackend` builds one query for users holding the permission directly or through groups, adds superusers unless `include_superusers=False`, and filters on `is_active` unless you pass `None`. With several `AUTHENTICATION_BACKENDS` you must name one by dotted path or get `ValueError`. Passing `obj` makes `ModelBackend` return an empty queryset — the same no-object-permissions rule as `has_perm()` — so object-level answers need a backend that implements `with_perm()` itself. It is defined on `UserManager`, not `BaseUserManager`.
go deeper
Know that with_perm() lists the users who hold a permission, as a queryset you can filter further.
Explain the defaults for is_active and include_superusers, the backend argument, and why obj yields nothing from ModelBackend.
Use it for notifications and audits without spamming superusers, and implement with_perm() in custom backends that own object rules.
Decide whether 'who can access this' must be answerable for audits, and require every authorization rule to support that reverse query.
## What `with_perm()` answers Most permission methods answer "may **this** user do X?". `with_perm()` answers the reverse: "**which** users may do X?". It lives on `UserManager` — the manager of the built-in `User` and of `AbstractUser` subclasses — and returns a **queryset of users**. ```python from django.contrib.auth import get_user_model User = get_user_model() reviewers = User.objects.with_perm("listings.approve_listing") ``` A typical use in a listings site: when an owner submits a listing for approval, email everyone who can approve it. ## Signature and defaults `UserManager.with_perm(perm, is_active=True, include_superusers=True, backend=None, obj=None)` | Argument | Meaning | |---|---| | `perm` | `"app_label.codename"` string or a `Permission` instance | | `is_active` | `True` (default) returns only active users, `False` only inactive, `None` both | | `include_superusers` | `True` (default) adds every superuser, whether or not they hold the permission | | `backend` | dotted path of the backend to ask; required when several are configured | | `obj` | passed on to the backend's own `with_perm()` | ## How the default backend computes it The manager does not loop over users. It delegates to the backend's `with_perm()`, and `ModelBackend` builds one query: - users holding the permission **directly** (`user_permissions`) or **through a group**, expressed as an `Exists()` subquery on `Permission`; - `OR is_superuser = True` when `include_superusers` is on; - `AND is_active = ...` unless `is_active` is `None`. That makes it cheap enough to use for notifications or reports even with many users. The manager is only a dispatcher: it picks one backend, checks that it has a `with_perm()` method and forwards the arguments. Everything interesting — how grants are stored, whether superusers count, what an object means — is decided by that backend. That is why the same call can answer differently after a project swaps or adds a backend, and why reading the chosen backend's implementation is the quickest way to understand a surprising result. ## The traps 1. **Several backends means you must choose.** If `AUTHENTICATION_BACKENDS` has more than one entry and `backend` is omitted, `with_perm()` raises `ValueError`. Passing a backend class instead of a dotted path string raises `TypeError`. 2. **Objects return nothing from `ModelBackend`.** With `obj` set, `ModelBackend.with_perm()` returns an **empty queryset** — the same "no object permissions in core" rule as `has_perm()`. The docs call this out: unlike the other methods, it returns an empty queryset rather than an empty set. 3. **A backend without `with_perm()` contributes nothing.** The manager returns `none()` when the chosen backend has no such method, so a custom rule backend does not appear in the answer unless it implements `with_perm()` itself. 4. **A malformed string fails loudly.** A `perm` without a dot raises `ValueError` asking for the `app_label.permission_codename` form, unlike `has_perm()`, which silently returns `False`. 5. **Custom managers may not have it.** A custom user model whose manager subclasses `BaseUserManager` rather than `UserManager` does not get `with_perm()`; the method is defined on `UserManager`. ## Answering "who can edit this listing?" Because core returns nothing for objects, the object question needs your own rule. With an ownership rule, the answer is usually the owner plus whoever holds a moderation permission: ```python def editors_of(listing): moderators = User.objects.with_perm( "listings.moderate_listing", backend="django.contrib.auth.backends.ModelBackend", ) return User.objects.filter(pk=listing.owner_id) | moderators ``` A custom backend can also implement `with_perm(perm, is_active, include_superusers, obj)` so that `User.objects.with_perm(..., backend="listings.backends.ListingOwnerBackend", obj=listing)` returns the owner. That keeps "who may" and "may this user" answered by the same rule. ## Composing the result Because the return value is an ordinary queryset, it chains like any other: - `.exclude(pk=listing.owner_id)` — do not notify the person who submitted; - `.filter(email__isnull=False)` or other filters on your user model's fields; - `.values_list("email", flat=True)` — fetch only what the notification needs; - `.count()` or `.exists()` — cheap health checks such as "does anyone hold the approval right?". A useful operational check is a startup or monitoring query that asserts `User.objects.with_perm("listings.approve_listing", include_superusers=False).exists()`: if nobody outside the engineering superusers can approve listings, submissions will pile up unseen. ## Superusers in the result Leaving `include_superusers=True` for notifications often emails engineers who hold superuser for maintenance but never review listings. Pass `include_superusers=False` when the list is meant for people who were explicitly granted the right.
- Why might with_perm() raise ValueError in a project that added a second authentication backend?With one backend configured, `with_perm()` uses it automatically. With more than one and no `backend` argument, it cannot know which to ask and raises `ValueError`; pass the dotted path, such as `"django.contrib.auth.backends.ModelBackend"`.
saying these in an interview costs you the question
- with_perm() returns a boolean like has_perm()
- Superusers are excluded unless they hold the permission
- ModelBackend.with_perm() returns the object's owner when obj is passed
- with_perm() is available on every custom user manager