Django Custom Managers
A Manager is used to query database operations which will be provided to Django models. Every model in Django application consists at least one Manager by default.
Regarding the Manager names:
By default, Django adds a Manager with the name "objects" to every Django model class. However, if you want to use "objects" as a field name (or) if you want to use a name instead of "objects" for the Manager, you can rename it on a per-model basis. To rename the Manager for a given class, define a class attribute of type models.Manager() on that model.
For example:
from django.db import models
class Employee(models.Model):
gender_choices = (
("M", "Male"),
("F", "Female")
)
roles_choices = (
("J", "Junior"),
("S", "Senior"),
)
first_name = models.CharField(max_length=200)
last_name = models.CharField(max_length=200)
email = models.CharField(max_length=250)
gender = models.CharField(max_length=1, choices=gender_choices)
role = models.CharField(max_length=120, choices=roles_choices, default="J")
active = models.BooleanField(default=True)
# custom manager replaces objects manger
all_employees = models.Manager()
def __str__(self):
return str(self.first_name) + str(self.last_name)
Using the above example model, Employee.objects will throw you an AttributeError exception, but Employee.all_employees.all() will provide a list of all Employee objects.
Now, here we go for the Custom Managers:
You can use a custom Manager in a particular model by extending the base Manager class and instantiating your custom Manager in your model.
There are two reasons you might want to customize a Manager: to add extra Manager methods, and/or to modify the initial QuerySet the Manager returns.
Note: Manager methods can access self.model to get the model class to which they’re attached.
Here I'm gonna explain Modifying a manager’s initial QuerySet for now:
A Manager’s base QuerySet returns all objects in the system. For example, from the above example using Employee model (except the Custom Manager name):
the statement Employee.objects.all() will return all employees in the database.
You can override a Manager’s base QuerySet by overriding the Manager.get_queryset() method. get_queryset() should return a QuerySet with the properties you require.
For example, the following model has two Managers – one that returns all objects, and one that returns only the active employees:
# First, define the Manager subclass.
class EmployeeManager(models.Manager):
def get_queryset(self):
return super(EmployeeManager, self).get_queryset().filter(active=True)
# Then hook it into the Employee model explicitly.
class Employee(models.Model):
.....
active = models.BooleanField(default=True)
objects = models.Manager() # The default manager.
active_objects = EmployeeManager() # The EmployeeManager manager.
With this sample model, Employee.objects.all() will return all employees in the database, but Employee.active_objects.all() will only return the ones who are in active state.
Of course, because get_queryset() returns a QuerySet object, you can use filter(), exclude() and all the other QuerySet methods on it. So these statements are all legal:
Employee.active_objects.all()
Employee.active_objects.filter(email='[email protected]')
Employee.active_objects.count()
This example also pointed out another interesting technique: using multiple managers on the same model. You can attach as many Manager() instances to a model as you’d like. This is an easy way to define common “filters” for your models.
For example:
class SeniorManager(models.Manager):
def get_queryset(self):
return super(SeniorManager, self).get_queryset().filter(role='S')
class JuniorManager(models.Manager):
def get_queryset(self):
return super(JuniorManager, self).get_queryset().filter(role='J')
class Employee(models.Model):
...
roles_choices = (
("J", "Junior"),
("S", "Senior"),
)
....
role = models.CharField(max_length=1, choices=role_choices)
all_employees = models.Manager()
seniors = SeniorManager()
juniors = JuniorManager()
This example allows you to request Employee.all_employees.all(), Employee.seniors.all() and Employee.juniors.all() yielding predictable results.
Calling custom QuerySet methods from the manager:
While most methods from the standard QuerySet are accessible directly from the Manager, this is only the case for the extra methods defined on a custom QuerySet if you also implement them on the Manager:
class EmployeeQuerySet(models.QuerySet):
def juniors(self):
return self.filter(role='J')
def seniors(self):
return self.filter(role='S')
class EmployeeManager(models.Manager):
def get_queryset(self):
return EmployeeQuerySet(self.model, using=self._db)
def juniors(self):
return self.get_queryset().juniors()
def seniors(self):
return self.get_queryset().seniors()
class Employee(models.Model):
...
roles_choices = (
("J", "Junior"),
("S", "Senior"),
)
....
role = models.CharField(max_length=1, choices=role_choices)
all_employees = EmployeeManager()
This example allows you to call both juniors() and seniors() directly from the manager Employee.all_employees.