Innovate anywhere, anytime withruncode.io Your cloud-based dev studio.
Django

How to Add a Custom Managers in Django

2022-07-25

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='employee@gmail.com')
    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.