Custom Model Managers

+15 Mana ✨

Managers are the interface through which database query operations are provided to Django models. You can customize them to add reusable query methods and change default behavior.

Default Manager

Every model has at least one manager called objects:

python
# Default manager
Article.objects.all()
Article.objects.filter(status='published')

Creating a Custom Manager

python
from django.db import models


class ArticleManager(models.Manager):
    """Custom manager with additional methods."""
    
    def published(self):
        """Return only published articles."""
        return self.filter(status='published', pub_date__lte=timezone.now())
    
    def drafts(self):
        """Return draft articles."""
        return self.filter(status='draft')
    
    def by_author(self, author):
        """Return articles by a specific author."""
        return self.filter(author=author)
    
    def popular(self, min_views=1000):
        """Return articles with many views."""
        return self.filter(views__gte=min_views)
    
    def recent(self, days=7):
        """Return articles from the last N days."""
        cutoff = timezone.now() - timedelta(days=days)
        return self.filter(pub_date__gte=cutoff)


class Article(models.Model):
    title = models.CharField(max_length=200)
    status = models.CharField(max_length=20, default='draft')
    pub_date = models.DateTimeField(null=True, blank=True)
    views = models.IntegerField(default=0)
    author = models.ForeignKey('Author', on_delete=models.CASCADE)
    
    # Use custom manager
    objects = ArticleManager()

Using Custom Manager Methods

python
# Chainable methods
Article.objects.published()  # QuerySet of published articles
Article.objects.published().filter(category='tech')  # Chain with filter
Article.objects.by_author(user).published()  # Chain custom methods

# Combine with other QuerySet methods
Article.objects.popular().order_by('-pub_date')[:10]

Modifying Default QuerySet

Change what all() returns:

python
class PublishedManager(models.Manager):
    def get_queryset(self):
        return super().get_queryset().filter(status='published')


class Article(models.Model):
    title = models.CharField(max_length=200)
    status = models.CharField(max_length=20)
    
    # Default manager (includes all)
    objects = models.Manager()
    
    # Alternative manager (only published)
    published = PublishedManager()
python
# Uses different managers
Article.objects.all()     # All articles
Article.published.all()   # Only published

Multiple Managers

python
class Article(models.Model):
    title = models.CharField(max_length=200)
    status = models.CharField(max_length=20)
    
    # First manager is the default
    objects = ArticleManager()  # Default
    published = PublishedManager()
    drafts = DraftManager()
    
    class Meta:
        # Explicitly set default manager
        default_manager_name = 'objects'

Manager with Initial QuerySet Filtering

python
class SoftDeleteManager(models.Manager):
    """Manager that excludes soft-deleted objects."""
    
    def get_queryset(self):
        return super().get_queryset().filter(deleted_at__isnull=True)
    
    def with_deleted(self):
        """Include soft-deleted objects."""
        return super().get_queryset()
    
    def deleted_only(self):
        """Only soft-deleted objects."""
        return super().get_queryset().filter(deleted_at__isnull=False)


class Document(models.Model):
    title = models.CharField(max_length=200)
    deleted_at = models.DateTimeField(null=True, blank=True)
    
    objects = SoftDeleteManager()
    all_objects = models.Manager()  # Bypass soft delete

Manager Methods with Complex Logic

python
class OrderManager(models.Manager):
    def pending_review(self):
        """Orders that need review."""
        return self.filter(
            status='submitted',
            total__gte=1000
        ).select_related('customer')
    
    def for_shipping(self):
        """Orders ready to ship."""
        return self.filter(
            status='paid',
            items__in_stock=True
        ).distinct().prefetch_related('items')
    
    def revenue_by_month(self):
        """Aggregate revenue by month."""
        from django.db.models.functions import TruncMonth
        return self.filter(
            status='completed'
        ).annotate(
            month=TruncMonth('completed_at')
        ).values('month').annotate(
            total=Sum('total'),
            count=Count('id')
        ).order_by('month')
```\n\n## Common Pitfalls\n\n1. **Not testing edge cases** — Always test custom model managers with empty querysets, NULL values, and boundary conditions.\n2. **Premature optimization** — Profile queries with `.explain()` before applying complex optimizations.\n3. **Ignoring database-specific behavior** — Some custom model managers features behave differently across PostgreSQL, MySQL, and SQLite.\n\n## Best Practices\n\n1. **Keep queries readable** — Use meaningful variable names and chain methods logically.\n2. **Test with realistic data** — Create fixtures that match production data patterns for accurate performance testing.\n3. **Document complex queries** — Add comments explaining the business logic behind non-obvious query patterns.\n\n## Summary\n\n- Custom Model Managers is a core Django ORM feature for building efficient database queries.\n- Always consider query performance and use `.explain()` to verify query plans.\n- Test edge cases including empty results, NULL values, and large datasets.\n- Refer to the Django documentation for database-specific behavior and limitations.

Code Examples

python
# Default manager
Article.objects.all()
Article.objects.filter(status='published')
✓ Completed