In Django, when you call methods like .all() or .get() on a manager (e.g., Post.objects.all()), the manager does not actually implement these methods itself. Instead, it dynamically delegates them to the QuerySet returned by the manager's get_queryset() method.
Method Delegation
Django uses inspection to find all public methods on the QuerySet class. It then creates wrapper methods on the Manager that follow this pattern:
- Call
self.get_queryset() to get the base QuerySet. - Call the requested method (like
.all()) on that QuerySet.
This allows custom managers to override get_queryset() to filter results automatically (e.g., a PublishedManager that only returns posts with status=PUBLISHED) while still providing access to all standard QuerySet API methods.
Model Association
A manager is often instantiated before the model it belongs to is fully defined. Django handles this via the contribute_to_class method. When a manager is attached to a model class, Django:
- Injects the model into the manager instance.
- Replaces the manager attribute on the model class with a
ManagerDescriptor.
Access Restrictions
The ManagerDescriptor enforces two important Django behaviors:
- Class-level access only: Managers can only be accessed via the Model class (e.g.,
Book.objects), not via model instances (e.g., my_book.objects will raise an AttributeError). - Abstract Model protection: Managers are not available on abstract models.
- Swapped Model protection: Managers are not available if the model has been swapped for another model.
class PublishedManager(models.Manager):
def get_queryset(self):
# This custom logic is what .all(), .filter(), etc. will use
return super().get_queryset().filter(status=Post.Status.PUBLISHED)
class Post(models.Model):
status = models.CharField(max_length=10)
objects = models.Manager() # Default manager
published = PublishedManager() # Custom manager
# Usage:
# Post.published.all() will now only return published posts