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).

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.