skip to content

In Django, what breaks when a manager that hides draft episodes is declared first on the model, and how do default_manager_name and base_manager_name help?

level: seniorimportance: should knowfreq 40%

answer

  1. first declared manager is special
  2. admin and dumpdata use the default
  3. related managers inherit its filter
  4. forward relations use the base manager
  5. never filter the base manager

basics

~20 s

The first declared manager becomes the default manager, used by the admin, dumpdata, form choices, unique checks and reverse related managers, so drafts vanish from all of them. Declare a plain manager first or set Meta.default_manager_name.

solid answer

~40 s

Django treats the first manager declared on a model as its **default manager** (`Model._default_manager`) unless `Meta.default_manager_name` names another. If that manager's `get_queryset()` hides drafts, drafts disappear from the admin changelist, from `dumpdata` (unless `--all`), from `ModelChoiceField` querysets for foreign keys pointing at the model, from reverse related managers like `podcast.episodes`, and from `validate_unique()`, so a duplicate slug slips past form validation and fails with `IntegrityError` at save time. The **base manager** (`Model._base_manager`) is separate: a plain `Manager` unless `Meta.base_manager_name` is set, used for forward relation access such as `listen.episode`, `refresh_from_db()` and the `UPDATE` in `save()`. Fix it by declaring a plain `objects` first and the filtered manager second, or by setting `default_manager_name = "objects"`, and never filter rows in a base manager.

code

python · 22 lines
python
from django.db import models
from django.utils import timezone


class PublishedManager(models.Manager):
    def get_queryset(self):
        return super().get_queryset().filter(
            status="published", published_at__lte=timezone.now()
        )


class Episode(models.Model):
    podcast = models.ForeignKey("Podcast", on_delete=models.CASCADE, related_name="episodes")
    slug = models.SlugField(unique=True)
    status = models.CharField(max_length=20, default="draft")
    published_at = models.DateTimeField(null=True, blank=True)

    objects = models.Manager()   # default manager: every row
    live = PublishedManager()    # opt-in filtered view

    class Meta:
        default_manager_name = "objects"  # survives reordering

go deeper

for a junior

Recall that the first manager declared on a model is its default manager and that the admin uses it.

for a middle

Explain default versus base manager: which Django features use each, and how Meta.default_manager_name and base_manager_name change them.

for a senior

Diagnose data that exists but is invisible in the admin, fixtures or podcast.episodes as a filtering default manager, and fix it without filtering the base manager.

for a principal

Set a policy for soft-delete and visibility rules: explicit QuerySet methods versus filtered default managers, and how exports and admin access must see all rows.

## Two special managers Every Django model has two managers with special jobs, whatever names you gave your managers: - **`Model._default_manager`**: the manager Django itself uses when it needs "the" way to query this model. It is the manager named by `Meta.default_manager_name`, otherwise the **first manager declared** on the model, otherwise the default manager of the first parent model. - **`Model._base_manager`**: the manager Django uses when it must reach a row **no matter what**. It is a plain `django.db.models.Manager` unless `Meta.base_manager_name` names one of your managers (or a parent sets one). ## The trap: a filtering manager declared first ```python class Episode(models.Model): ... live = PublishedManager() # get_queryset() hides drafts objects = models.Manager() ``` Because `live` is declared first, it is the default manager, and drafts silently disappear from every place that uses the default manager: | Consumer | What goes wrong | |---|---| | Admin changelist (`ModelAdmin.get_queryset()`) | editors cannot see or edit drafts | | `manage.py dumpdata` | drafts are missing from the fixture unless you pass `--all` (`-a`) | | `ModelChoiceField` for a `ForeignKey` to `Episode` | drafts cannot be chosen in forms | | Reverse related manager `podcast.episodes` | drafts are hidden, since it subclasses the default manager's class | | `validate_unique()` in `ModelForm` / `full_clean()` | a draft with the same slug is not seen, so the duplicate reaches the database and raises `IntegrityError` | | Generic code using `model._default_manager` | behaves as if drafts did not exist | Nothing raises an error at startup; the symptom is data that "is in the database but not in the app". ## What the base manager covers The base manager exists so that Django can always follow a reference that already exists. It is used for: - **forward relation access**: `listen.episode` or a reverse one-to-one, so a listen of a now-hidden episode still resolves; - **`refresh_from_db()`**; - the **`UPDATE`** issued by `save()` on an existing row; - **`ForeignKey.validate()`**, which checks that the referenced row exists. The documentation is explicit: **do not filter away rows in a base manager**. If you point `base_manager_name` at a filtering manager, `listen.episode` raises `DoesNotExist` for hidden episodes and saves can misbehave. `base_manager_name` is for adding behaviour that must apply to relation access, not for hiding rows. The base manager is **not** used for reverse and many-to-many managers (`podcast.episodes`), nor for filters across relations such as `Listen.objects.filter(episode__title=...)`; those follow the default manager's class or plain joins. ## The fixes 1. **Order the declarations.** Declare a plain or custom-QuerySet manager first, filtered ones after: ```python class Episode(models.Model): objects = EpisodeQuerySet.as_manager() # default manager: all rows live = PublishedManager() # opt-in filtered view ``` 2. **Name the default explicitly** with `Meta.default_manager_name = "objects"`, which survives someone reordering the class body. 3. **Prefer a chainable `published()`** method over a filtered manager for most cases: `Episode.objects.published()` is explicit at each call site, so nothing is hidden by accident. 4. **Leave `_base_manager` alone** unless you have a concrete reason, and never make it filter. ## How to spot it in a real project 1. Editors report that drafts "disappeared" from the admin, but `Episode.objects.count()` in a shell shows them. 2. Check `Episode._default_manager` in the shell: if it is the filtering manager, you have found the cause. 3. Look at declaration order in the model, including managers inherited from abstract bases. 4. Check fixtures and exports: a `dumpdata` without `--all` that is short by the number of drafts confirms it. 5. Check forms: a unique slug that passes `ModelForm` validation and then fails with `IntegrityError` is the `validate_unique()` symptom. After the fix, add a test asserting that `Episode._default_manager` returns drafts, so a future reordering of the class body cannot reintroduce the bug. ## Inheritance and third-party code - Managers declared on an **abstract base** are inherited, and the child's default manager is the first one it declares, else the parent's default. - Reusable apps should use `model._default_manager` rather than assuming `objects`; that is precisely why its choice matters.

  • In Django, how do you export drafts with dumpdata when the default manager hides them?
    Run `manage.py dumpdata --all` (short form `-a`). It makes `dumpdata` use each model's base manager instead of the default manager, so rows a custom default manager filters or modifies are included.
  • In Django, a model sets base_manager_name to a manager that hides soft-deleted rows. What breaks?
    Forward relation access goes through the base manager, so `listen.episode` raises `DoesNotExist` for a soft-deleted episode even though the foreign key still points at it. `refresh_from_db()` and foreign-key validation are affected the same way, which is why the docs say never to filter rows in a base manager.

The base manager is the building's master key: staff can always open the room a record points to. The default manager is the public entrance map; if it leaves rooms off, every visitor who relies on the map, admins and exports included, never finds them.

saying these in an interview costs you the question

  • The manager named objects is always the default manager
  • The admin uses the base manager, so drafts stay visible
  • listen.episode uses the default manager and hides drafts
  • Setting base_manager_name to a filtering manager is a safe soft-delete
  • dumpdata always exports every row regardless of managers