Custom Model Managers and QuerySets in Django
This article explains how to build custom model managers and chainable QuerySets in Django using Python. By extending Django’s default ORM behavior, you can encapsulate complex database queries into reusable, clean, and chainable methods directly accessible through your model interfaces.
1. Create a Custom QuerySet
To make queries chainable, define a class that inherits from
django.db.models.QuerySet. Each custom method within this
class must return a QuerySet instance (typically using
self.filter(), self.exclude(), or
self.annotate()), allowing multiple filters to be called
sequentially.
from django.db import models
from django.utils import timezone
class ArticleQuerySet(models.QuerySet):
def published(self):
return self.filter(status='published', published_at__lte=timezone.now())
def featured(self):
return self.filter(is_featured=True)
def recent(self, days=7):
cutoff = timezone.now() - timezone.timedelta(days=days)
return self.filter(published_at__gte=cutoff)2. Attach the QuerySet to a Manager
There are two primary methods to make these QuerySet methods
available on the model’s manager (e.g.,
Article.objects).
Method A: Using
as_manager() (Recommended)
The simplest approach is calling .as_manager() on your
custom QuerySet class. This automatically generates a
manager with all the QuerySet's custom methods available at the root
level.
class Article(models.Model):
title = models.CharField(max_length=255)
status = models.CharField(max_length=20, default='draft')
is_featured = models.BooleanField(default=False)
published_at = models.DateTimeField(null=True, blank=True)
# Attach custom manager
objects = ArticleQuerySet.as_manager()Method B:
Creating a Custom Manager with from_queryset()
If you need custom manager logic that does not return a QuerySet
(such as table-level helper methods), define a
models.Manager subclass alongside your QuerySet using
models.Manager.from_queryset():
class ArticleManager(models.Manager.from_queryset(ArticleQuerySet)):
def create_draft(self, title, **extra_fields):
return self.create(title=title, status='draft', **extra_fields)
class Article(models.Model):
title = models.CharField(max_length=255)
status = models.CharField(max_length=20, default='draft')
is_featured = models.BooleanField(default=False)
published_at = models.DateTimeField(null=True, blank=True)
objects = ArticleManager()3. Executing Chainable Queries
Once the setup is complete, you can chain the custom methods in any order directly from the model manager, just like native Django ORM methods.
# Call individual custom methods
published_articles = Article.objects.published()
# Chain multiple custom QuerySet methods together
featured_recent = Article.objects.published().featured().recent(days=14)
# Combine custom QuerySet methods with standard Django filters
specific_author_featured = (
Article.objects
.published()
.featured()
.filter(author__username='johndoe')
.order_by('-published_at')
)This pattern keeps business query logic inside the data layer rather than scattering database filtering throughout your views, serializers, or services.