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?
answer
- first declared manager is special
- admin and dumpdata use the default
- related managers inherit its filter
- forward relations use the base manager
- never filter the base manager
basics
~20 sThe 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 sDjango 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 linesfrom 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 reorderinggo deeper
Recall that the first manager declared on a model is its default manager and that the admin uses it.
Explain default versus base manager: which Django features use each, and how Meta.default_manager_name and base_manager_name change them.
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.
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