Filter concepts by levelShowing all levels.

Django · Section 35

Custom QuerySets

Level
intermediate
Read
18 min
Concepts
2

Subclassing models.QuerySet and writing methods that return self.filter(...) is what makes Order.objects.completed().recent() possible — but only once the manager's get_queryset() actually returns an instance of the custom class; a manager still returning the plain default QuerySet has no route to the custom methods at all, and a method returning a plain list (instead of another QuerySet) silently breaks further chaining. Manager.from_queryset(MyQuerySet) automates that wiring for the common case where a manager needs BOTH its own manager-only methods and the QuerySet's chainable ones — QuerySet.as_manager() is the simpler route when no manager-specific methods are needed at all. Type hints (QuerySet[Model]) are optional but valuable for a long chain of custom methods.

What is true here

  1. A custom QuerySet method must return self.filter(...) — another instance of the same class — to stay chainable with both built-in and other custom methods.
  2. The manager's get_queryset() must return an instance of the custom QuerySet class, or the custom methods are simply unreachable from Model.objects, regardless of how correctly they were written.
  3. A method returning a plain list (via list(self.filter(...))) instead of a QuerySet silently breaks any further chaining after it.
  4. Manager.from_queryset(MyQuerySet) generates a manager class whose get_queryset() already returns MyQuerySet — the shortcut when a manager needs its own methods plus the QuerySet's chainable ones (remember the trailing () to instantiate it).
  5. QuerySet.as_manager() is simpler than from_queryset() when no manager-only methods are needed at all — reach for from_queryset() only once real manager-specific logic exists.

What you will be able to do

  • Write a custom, chainable QuerySet class and wire it correctly through a manager's get_queryset()
  • Diagnose why a custom QuerySet method is unreachable or breaks a chain
  • Choose correctly between manually overriding get_queryset(), from_queryset(), and as_manager()

Custom QuerySet classes and chaining

Writing chainable, reusable filter methods, and the wiring that makes them reachable from a manager at all.

Custom QuerySet classes and chaining

coreintermediate

Subclassing models.QuerySet (not models.Manager) and adding methods that each return self.filter(...) (still a QuerySet) is what makes Order.objects.completed().recent() possible — CHAINING two custom methods back to back. This only works if the manager's get_queryset() actually returns an instance of the CUSTOM QuerySet class — a manager still returning the plain default QuerySet has no idea the custom methods even exist, breaking the chain the moment a second custom method is called.

Think of it as

filter()/exclude()/order_by() are chainable because each one returns another QuerySet of the SAME class — a custom method (completed()) is chainable in exactly the same way, as long as it also returns a QuerySet of that same (custom) class, not a plain list or a different type. The subtle trap is that WHERE the custom class lives matters just as much as writing it: Order.objects.completed() only reaches the custom completed() method if Order.objects (the manager) hands back an instance of the custom QuerySet class in the first place — a manager whose get_queryset() still returns the plain, unmodified QuerySet has no route to those custom methods at all, since Python method lookup only finds methods that exist on the actual returned object's class.

python
class MyQuerySet(models.QuerySet):
    def a_filter(self):
        return self.filter(...)   # returns self — stays chainable

class MyManager(models.Manager):
    def get_queryset(self):
        return MyQuerySet(self.model, using=self._db)   # required for chaining to work

What we're doing: Chain two custom, reusable filters together — completed() and recent() — matching the exact shape the roadmap itself calls out (Order.objects.completed().recent()).

orders/models.pypython
class OrderQuerySet(models.QuerySet):
    def completed(self):
        return self.filter(status="COMPLETED")

    def recent(self):
        return self.filter(placed_at__gte=timezone.now() - timedelta(days=30))

class OrderManager(models.Manager):
    def get_queryset(self):
        return OrderQuerySet(self.model, using=self._db)
2
completed() returns self.filter(...) — a NEW OrderQuerySet instance, not a plain list — which is exactly what makes calling .recent() on the result possible.
8
get_queryset() returning OrderQuerySet (not the plain default QuerySet) is the wiring step that makes Order.objects.completed() reachable at all — omitting this override means completed() simply does not exist from Order.objects's perspective.

Why this works: Order.objects.completed() returns an OrderQuerySet (because completed() calls self.filter(), inheriting the class it was called on) — and since THAT object is also an OrderQuerySet, .recent() is available on it too, letting the two custom filters compose exactly like built-in QuerySet methods do.

Writing custom QuerySet methods but forgetting to override get_queryset() on the manager

Wrong

python
class OrderQuerySet(models.QuerySet):
    def completed(self):
        return self.filter(status="COMPLETED")

class Order(models.Model):
    objects = models.Manager()   # the PLAIN manager — never wired to OrderQuerySet

Order.objects.completed()   # AttributeError: 'QuerySet' object has no attribute 'completed'

Better

python
class OrderManager(models.Manager):
    def get_queryset(self):
        return OrderQuerySet(self.model, using=self._db)

class Order(models.Model):
    objects = OrderManager()

What you see: AttributeError: 'QuerySet' object has no attribute 'completed' — raised the moment the custom method is called, even though it is clearly defined on OrderQuerySet.

Why: Order.objects is a plain models.Manager instance whose get_queryset() returns a plain, un-customized QuerySet — the OrderQuerySet class exists in the file, but nothing ever tells Django's manager to actually USE it, so its methods are simply unreachable from Order.objects, regardless of how correctly they were written.

Each custom method returns the same class, so the chain continues

Order.objects

OrderManager

get_queryset()

returns OrderQuerySet(...)

.completed()

self.filter(status=...) — still OrderQuerySet

.recent()

self.filter(placed_at__gte=...)

  • Order.objects — OrderManager
    • leads to get_queryset()
  • get_queryset() — returns OrderQuerySet(...)
    • leads to .completed()
  • .completed() — self.filter(status=...) — still OrderQuerySet
    • leads to .recent()
  • .recent() — self.filter(placed_at__gte=...)

Manager method vs QuerySet method

Manager method vs QuerySet method
ApproachChainable after another method?
A method directly on models.Managerno — only reachable from the manager itself, not from the QuerySet it returns
A method on a custom models.QuerySet subclass, wired through get_queryset()yes — reachable from the manager AND from any QuerySet in the resulting chain

Together

python
class OrderQuerySet(models.QuerySet):
    def completed(self):
        return self.filter(status="COMPLETED")

    def recent(self):
        return self.filter(placed_at__gte=timezone.now() - timedelta(days=30))

class OrderManager(models.Manager):
    def get_queryset(self):
        return OrderQuerySet(self.model, using=self._db)   # returns the CUSTOM class

class Order(models.Model):
    objects = OrderManager()

Order.objects.completed().recent()   # chains freely — both methods are on OrderQuerySet

Remember: A custom QuerySet method should return self.filter(...) — another instance of the same class, keeping it chainable. The manager's get_queryset() must return an instance of that CUSTOM QuerySet class, or the custom methods are simply unreachable from Model.objects. Never return a plain list from a method meant to stay chainable.

See also: manager and queryset pairing · custom managers and get queryset · laziness chaining and caching

Advertisement

Manager.from_queryset() and type hints

The standard shortcut for combining manager-only methods with a custom QuerySet's chainable ones, plus optional type hints for a long chain.

Manager.from_queryset(), and type hints

standardintermediate

Manager.from_queryset(MyQuerySet) generates a manager class that automatically returns MyQuerySet from get_queryset() — the shortcut for the manual "override get_queryset() to return the custom class" pattern, needed only when a custom Manager (not just the plain default) also needs custom QuerySet methods. Type hints — QuerySet[Model] with a generic parameter — help an IDE/type checker know what a chained custom method actually returns, which matters more once several custom methods are chained together.

Think of it as

from_queryset() exists because manually writing get_queryset() to return a custom QuerySet class is a one-line, entirely mechanical override every time — from_queryset() automates exactly that boilerplate, generating a Manager subclass whose get_queryset() already does the right thing. It only becomes NECESSARY (rather than just convenient) when a manager ALSO needs its own manager-only methods on top of the QuerySet-level ones — for a model that only ever needs QuerySet-level methods with no manager-specific behavior, QuerySet.as_manager() (mentioned in §34) is the even simpler direct route. Type hints matter here specifically because a long custom method chain (Order.objects.completed().recent().high_value()) is exactly the kind of code where an editor/type-checker catching a typo'd method name, or confirming the final result is genuinely an Order-shaped QuerySet, pays off the most.

python
class MyModel(models.Model):
    objects = models.Manager.from_queryset(MyQuerySet)()   # note the trailing ()

What we're doing: Give Order both a manager-only convenience method (create_default) and the chainable QuerySet methods, using from_queryset() instead of manually writing get_queryset().

orders/models.pypython
class OrderQuerySet(models.QuerySet):
    def completed(self):
        return self.filter(status="COMPLETED")

    def recent(self):
        return self.filter(placed_at__gte=timezone.now() - timedelta(days=30))

class OrderManager(models.Manager.from_queryset(OrderQuerySet)):
    def create_default(self, **kwargs):
        return self.create(status="PENDING", **kwargs)

class Order(models.Model):
    objects = OrderManager()
8
models.Manager.from_queryset(OrderQuerySet) generates a base class whose get_queryset() already returns OrderQuerySet — this manager inherits that wiring automatically, without writing get_queryset() by hand.
12
objects = OrderManager() now supports BOTH create_default() (a manager-only method) AND completed()/recent() (the QuerySet's chainable methods) — from_queryset() combined both cleanly.

Why this works: Manually writing this manager would require an explicit get_queryset() override returning OrderQuerySet(self.model, using=self._db) — from_queryset() generates exactly that override automatically, letting the rest of OrderManager focus on its own manager-specific method (create_default) without repeating the wiring boilerplate.

Forgetting the trailing () when assigning a manager built with from_queryset()

Wrong

python
class Order(models.Model):
    objects = models.Manager.from_queryset(OrderQuerySet)   # missing the trailing () — this is a CLASS, not an instance

Better

python
class Order(models.Model):
    objects = models.Manager.from_queryset(OrderQuerySet)()   # instantiated

What you see: Assigning models.Manager.from_queryset(OrderQuerySet) (a CLASS) instead of an instance of it as objects produces confusing errors the moment Order.objects is actually used — a manager attribute needs to be an instance, not the class itself.

Why: from_queryset() returns a manager CLASS (a dynamically-generated subclass of Manager) — like any other manager class (models.Manager itself), it needs to be instantiated with () before being assigned as a class attribute; forgetting the trailing parentheses is an easy, purely mechanical slip that looks almost identical to the correct version.

from_queryset() vs manually overriding get_queryset()

from_queryset() vs manually overriding get_queryset()
ApproachWhen to use
Manually override get_queryset()the manager also has other custom logic worth writing out explicitly
Manager.from_queryset(MyQuerySet)the manager needs its own methods PLUS the QuerySet's chainable ones — a shortcut for the same wiring
QuerySet.as_manager()no manager-specific methods are needed at all — just expose the QuerySet's methods directly

Together

python
class OrderQuerySet(models.QuerySet):
    def completed(self):
        return self.filter(status="COMPLETED")

class OrderManager(models.Manager):
    def create_default(self, **kwargs):
        return self.create(status="PENDING", **kwargs)   # a manager-only method

OrderManager = OrderManager.from_queryset(OrderQuerySet)   # now has BOTH kinds of methods

class Order(models.Model):
    objects = OrderManager()

Order.objects.create_default(total=50)   # the manager-only method
Order.objects.completed().recent()       # the QuerySet's chainable methods, still reachable

Remember: Manager.from_queryset(MyQuerySet) generates a manager class whose get_queryset() already returns MyQuerySet — needed when a manager wants its own methods PLUS the QuerySet's chainable ones (remember the trailing () to instantiate it). QuerySet.as_manager() is simpler when no manager-only methods are needed at all. Type hints (QuerySet[Model]) are optional but valuable for a long, chained custom-method call.

See also: custom queryset classes · custom managers and get queryset · default vs base manager

Advertisement