Custom Managers & QuerySets
A Django Manager is the interface attached to a model as Model.objects that provides database query operations and returns a QuerySet, making it the gateway through which every query begins.
Learn Custom Managers & QuerySets in our free Django course — a beginner-friendly interactive lesson with worked examples, a practice exercise and a quick…
Part of the free Django course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.
In this lesson you'll see what the default objects manager really is, write a custom Manager that adds methods like published() and overrides get_queryset() , and build a chainable custom QuerySet with as_manager() so calls like Post.objects.published().recent() just work.
Every Django model you define automatically gets a Manager attached under the name objects . A manager is the object that provides query operations — it is the gateway to the database. When you write Post.objects.all() or Post.objects.filter(...) , you are calling methods on that manager, and what comes back is a QuerySet representing rows.
The manager itself holds very little state. Its key method is get_queryset() , which builds and returns the base QuerySet that all() , filter() , and friends start from.
To stop repeating filter(status="published") everywhere, you subclass models.Manager and add a method that wraps it. Inside the method you call self.get_queryset() and filter that, so Post.objects.published() reads like plain English.
You can also override get_queryset() itself to change the default set the manager returns. A LiveManager whose get_queryset() excludes drafts means every query through it is already filtered:
A method on a manager can only be called first — Post.objects.published() works, but Post.objects.filter(...).published() does not, because filter() returns a plain QuerySet that has never heard of published() . The fix is to put the methods on a custom QuerySet subclass, where each method returns another QuerySet and therefore chains .
PostQuerySet.as_manager() builds a manager whose methods come from the QuerySet, so the same logic is available straight off objects and after any filter() — no duplication between a manager and a queryset.
Fill in the blank so published() keeps only the rows whose status equals "published" .
❌ "QuerySet object has no attribute 'published'"
You put published() on a Manager , then called it after filter() — but filter() returns a plain QuerySet.
✅ Fix: define the method on a QuerySet subclass and use objects = PostQuerySet.as_manager() so it chains.
❌ Rows vanish from the admin after a custom manager
You overrode get_queryset() on the default objects manager, so the admin and related lookups inherited the filter.
✅ Fix: keep objects = models.Manager() unfiltered and add the filtered behaviour under a second name like live .
❌ Manager method returns a list, breaking later .filter()
Your method returned list(...) instead of a QuerySet, so chaining a queryset method afterwards fails.
✅ Fix: always return self.get_queryset().filter(...) (a QuerySet), never a materialised list.
Build a chainable QuerySet so that objects.published().recent() returns only published posts, newest first. The methods must each return a QuerySet so they can be chained.
Lesson complete — you control how your models are queried!
You now know that Model.objects is a manager and the gateway to every query, how to add methods like published() and override get_queryset() on a custom Manager , and how a chainable QuerySet subclass exposed with as_manager() lets you write Post.objects.published().recent() .
🚀 Up next: Custom Template Tags & Filters — extend the template language with your own reusable tags.
Practice quiz
What name does Django give a model's default manager?
- objects
- manager
- rows
- query
Answer: objects. Every model gets a default manager called objects.
What does a manager return when you call all() or filter()?
- A list
- A QuerySet
- A dictionary
- A Model instance
Answer: A QuerySet. Managers hand back a QuerySet representing rows.
Which manager method builds the base QuerySet it starts from?
- build_query()
- base()
- get_queryset()
- all()
Answer: get_queryset(). get_queryset() returns the base QuerySet for the manager.
Where should chainable query logic like published() live to chain after filter()?
- In the view
- On a custom QuerySet subclass
- On a plain Manager
- In settings.py
Answer: On a custom QuerySet subclass. Methods on a QuerySet subclass return QuerySets, so they chain.
What does QuerySet.as_manager() do?
- Deletes the manager
- Runs the query immediately
- Builds a Manager whose methods come from the QuerySet
- Returns a list of rows
Answer: Builds a Manager whose methods come from the QuerySet. as_manager() turns a custom QuerySet into a Manager.
Why is overriding get_queryset() on the default objects manager risky?
- It speeds up queries too much
- It can hide rows from the admin and related lookups
- It deletes the database
- It is not allowed in Django
Answer: It can hide rows from the admin and related lookups. A filtered objects manager hides rows everywhere, including the admin.
What subclass do you extend to add a custom manager?
- models.Model
- models.QuerySet
- models.Manager
- models.Field
Answer: models.Manager. A custom manager subclasses models.Manager.
Why does Post.objects.filter(...).published() fail when published() is only on the Manager?
- filter() returns a plain QuerySet that lacks published()
- objects is read-only
- published() needs arguments
- filter() deletes the manager
Answer: filter() returns a plain QuerySet that lacks published(). filter() returns a plain QuerySet, which never had published().
A custom manager method should return what to allow further chaining?
- A materialised list
- None
- A string
- A QuerySet
Answer: A QuerySet. Returning a QuerySet (not a list) keeps later .filter() working.
How do projects commonly keep objects unfiltered but still hide drafts?
- Delete draft rows
- Disable the admin
- Add a second manager like live with the filter
- Override objects.get_queryset()
Answer: Add a second manager like live with the filter. Keep objects unfiltered and add a second filtered manager such as live.