In Django, what is a model Manager such as Episode.objects, and what changes when you declare a manager under another name?
answer
- the class-level entry point for queries
- returns QuerySets, is not one
- objects is added only if none declared
- not reachable from an instance
basics
~20 sA Manager is the class-level interface Django attaches to a model for database queries; its methods return QuerySets. Django adds objects only when a model declares no manager, so naming one catalog removes objects entirely.
solid answer
~40 sIn Django, every model has at least one `Manager`, the object behind `Episode.objects`. It is the entry point for table-level queries: `all()`, `filter()`, `get()`, `create()` and the other QuerySet methods are proxied through it, each call building a new `QuerySet` from the manager's `get_queryset()`. A manager is not a QuerySet itself: you cannot iterate it, and queryset-only methods such as `delete()` are deliberately not copied onto it, so you write `Episode.objects.all().delete()`. Managers live on the class; `episode.objects` raises `AttributeError: Manager isn't accessible via Episode instances`. Django creates the `objects` manager automatically only if the model (and its parents) declare none, so `catalog = models.Manager()` means `Episode.objects` no longer exists.
code
python · 17 linesfrom django.db import models
class Episode(models.Model):
title = models.CharField(max_length=200)
status = models.CharField(max_length=20, default="draft")
catalog = models.Manager() # no automatic 'objects' now
Episode.catalog.filter(status="published") # a QuerySet
# Episode.objects -> AttributeError
# Episode.catalog.delete() -> AttributeError (queryset_only)
Episode.catalog.all().delete() # delete must go through a QuerySet
ep = Episode.catalog.first()
# ep.catalog -> AttributeError: Manager isn't
# accessible via Episode instancesgo deeper
Recall that objects is a Manager on the model class that hands out QuerySets, and that it disappears if you declare a manager under another name.
Explain get_queryset() as the source of every query, why managers are class-only, and why delete() is not on the manager.
Show how manager naming and declaration order affect third-party code, the admin and dumpdata, and recommend keeping objects as a plain manager.
Set a convention for where table-level logic lives in the codebase: managers, custom QuerySets or service functions, and how to keep it discoverable.
## What a manager is In Django's ORM a **`Manager`** is the interface through which database queries are made for a model. Every model has at least one. When you write: ```python Episode.objects.filter(status="published") ``` `objects` is a `django.db.models.Manager` instance attached to the `Episode` class. The manager does not hold rows or a query; each call asks its **`get_queryset()`** method for a fresh `QuerySet` and forwards the call to it. So `Episode.objects.filter(...)` is, in effect, `Episode.objects.get_queryset().filter(...)`, and `Episode.objects.all()` returns `get_queryset()` directly. Django's own `Manager` class is built with `BaseManager.from_queryset(QuerySet)`, which is why almost every `QuerySet` method is callable on it. ## Manager versus QuerySet | | `Manager` (`Episode.objects`) | `QuerySet` (`Episode.objects.all()`) | |---|---|---| | Lives on | the model **class** | nowhere; created per call | | Iterable | no (`TypeError`) | yes; evaluating runs SQL | | Holds query state | no | yes: filters, ordering, result cache | | `delete()` | not available | available | | Typical use | starting point, table-level helpers | chaining filters, evaluating | `delete()` is marked `queryset_only`, so Django refuses to copy it onto managers; `Episode.objects.delete()` raises `AttributeError`, which protects you from wiping a table with one typo. Deleting everything must be spelled `Episode.objects.all().delete()`. ## Managers belong to the class Managers are **table-level**: they manage the collection of rows, not one row. Accessing one through an instance fails: - `episode.objects` raises `AttributeError: Manager isn't accessible via Episode instances`; - on an **abstract** model, managers can be defined but not called: `AbstractBase.objects.filter()` raises `AttributeError` because there is no table; - row-level behaviour belongs in **model methods**, table-level behaviour in managers or custom QuerySets. ## Naming: `objects` is only a default Django adds a manager named `objects` **only if the model and its parents declare no manager at all**. The rules: 1. No manager declared: Django adds `objects = models.Manager()` for you. 2. Any manager declared, under any name: no automatic `objects`. 3. A model **field** named `objects` with no custom manager: Django raises `ValueError` ("must specify a custom Manager, because it has a field named 'objects'"). So this model has no `objects`: ```python class Episode(models.Model): title = models.CharField(max_length=200) catalog = models.Manager() ``` `Episode.catalog.all()` works; `Episode.objects` raises `AttributeError`. People rename the manager when `objects` is taken by a field, or to make a domain word read well, but most teams keep `objects` as the plain manager and **add** extra managers next to it, so code and third-party apps that assume `objects` keep working. ## Several managers on one model A model may declare more than one manager, each with its own `get_queryset()`: ```python class Episode(models.Model): objects = models.Manager() published = PublishedManager() # get_queryset() filters status="published" ``` - `Episode.objects.all()` returns every episode; - `Episode.published.all()` returns only published ones; - the **first** declared manager becomes the model's **default manager**, which matters to the admin, `dumpdata` and related lookups, so the order of declaration is not cosmetic. ## Custom managers: extra methods and a new starting set There are two reasons to write a `Manager` subclass, and they can be combined: - **Add table-level methods.** A method such as `create_draft(podcast, title)` or `import_feed(url)` acts on the collection, not on one row, so it belongs on the manager. It may return anything: an instance, a count, a QuerySet. - **Change the initial QuerySet** by overriding `get_queryset()`, for example `return super().get_queryset().filter(status="published")`. Every call through that manager, including `all()`, then starts from the filtered set. Row-level behaviour, such as `episode.duration_display()`, stays a model method. A manager must also be shallow-copyable, because Django copies managers internally (for example in `db_manager()`); simple subclasses are, but overriding `__getattr__` can break that. ## What to say in an interview - A manager is the class-level gateway; a QuerySet is the lazy, chainable query it hands out. - `objects` is auto-added only when nothing else is declared. - Managers are not reachable from instances and cannot `delete()` directly. - For reusable filters, the modern answer is a custom `QuerySet` exposed with `as_manager()`, because methods defined only on a manager cannot be chained after `filter()`.
- In Django, why does Episode.objects.delete() raise AttributeError while Episode.objects.all().delete() works?When Django copies QuerySet methods onto a manager, it skips any method marked `queryset_only = True`, and `QuerySet.delete()` carries that mark. The design makes a whole-table delete explicit: you must first ask for a QuerySet with `all()` or `filter()`.
- In Django, how should generic code, such as a reusable app, fetch rows of a model it does not know?Use `model._default_manager` (or `model._base_manager` when every row is needed) instead of assuming `model.objects` exists. A project may have renamed or replaced `objects`, and the default manager is the one Django itself uses for the model.
saying these in an interview costs you the question
- A Manager is a QuerySet, so you can iterate over Episode.objects
- Django always adds objects, even when other managers are declared
- episode.objects.filter() works on an instance as well
- Episode.objects.delete() deletes every row in the table
- Renaming the manager also renames it in the database