[Django]-What is a "Manager" in django?

32👍

A manager is usually something hidden away from django programmers that django uses to interface between model code and the database backend.

When you query the django ORM, you do so through calls to

from my_app.models import MyModel

mms = MyModel.objects.all()

In this case, the objects part of the function is what is returned by the manager. If you wanted MyModel to only ever get blue MyModel instances (the database might contain red models too) then you could create a manager and hack your model thus

class BlueManager(models.Manager):
    def get_query_set(self):
        return super(BlueManager, self).get_query_set().filter(colour='Blue')

class MyModel(models.Model):
     colour = models.CharField(max_length=64)
     blue_objects = BlueManager()

and calling

MyModel.blue_objects.all()

would only return objects with colour as blue. Note, this is a very poor way to filter models!

One would usually need to modify a Manager interface if they were going to modify the QuerySets that a manager would usually return or if you needed to add “table” level queries (rather than regular django “row” level). The documentation for managers is quite complete and contains several examples.

12👍

Manager is some kind of ‘gate’ between application and database. One of nice thing is that you can define you own base queryset for model. For example: if you have model ‘Book’ with ‘availability’ field, you can prepare your own queryset, which filters specific kind of availability type:

models.py:

class AvailableBookManager(models.Manager):
    def get_query_set(self):
        return super(AvailableBookManager, self).get_query_set().filter(availability=1)

class Book(models.Model):
   (...)#fields definition
 
   objects = models.Manager() #default manager
   available_objects = AvailableBookManager() #own manager

and now you can use like this:

books = Book.available_objects.all()

instead of using:

books = Book.objects.filter(available=1)

10👍

Definition

From the docs:

A Manager is a Django class that provides the interface between database query operations and a Django model.

In other words, in a Django model, the manager is the interface that interacts with the database. For example, when you want to retrieve objects from your database, you need to construct a QuerySet via a Manager on your model class.

By default, the manager is available through the Model.objects property. This manager is the django.db.models.Manager. However, it is very simple to extend it and change the default manager.

Custom managers

From the docs:

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 (none of them are exclusive):

  1. to add extra Manager methods
  2. to modify the initial QuerySet the Manager returns

Example: adding extra methods to a Manager

from django.db import models

class DocumentManager(models.Manager):
    def pdfs(self):
        return self.filter(file_type='pdf')

    def smaller_than(self, size):
        return self.filter(size__lt=size)


class Document(models.Model):
    name = models.CharField(max_length=30)
    size = models.PositiveIntegerField(default=0)
    file_type = models.CharField(max_length=10, blank=True)

    objects = DocumentManager()

    def __str__(self) -> str:
        return self.name

Example: modifying the initial QuerySet the Manager returns

from django.db import models


class AuthorManager(models.Manager):
    def get_queryset(self):
        return super().get_queryset().filter(first_name__startswith='M')

class Author(models.Model):
    first_name = models.CharField(max_length=100)
    last_name = models.CharField(max_length=100)
    email = models.EmailField()

    objects = AuthorManager()

    def __str__(self) -> str:
        return f"{self.first_name} {self.last_name}"

Example: multiple managers at the same time

It is possible to have multiple managers for the same model.

from django.db import models
from django.db.models.functions import Length

class BookTitleManager(models.Manager):
    def short_titles(self):
        return self.annotate(length=Length('title')).filter(length__lte=20)

    def long_titles(self):
        return self.annotate(length=Length('title')).filter(length__gt=20, length__lte=30)
    
    def very_long_titles(self):
        return self.annotate(length=Length('title')).filter(length__gt=30)

    def starts_with(self, letter):
        return self.filter(title__startswith=letter)


class BookPagesManager(models.Manager):
    def small_books(self):
        return self.filter(pages__lt=200)
    
    def medium_books(self):
        return self.filter(pages__gte=200, pages__lt=300)
    
    def large_books(self):
        return self.filter(pages__gte=300, pages__lte=500)


class Book(models.Model):
    title = models.CharField(max_length=255)
    author = models.CharField(max_length=255)
    pages = models.IntegerField()

    objects = models.Manager()
    titles = BookTitleManager()
    sizes = BookPagesManager()

    def __str__(self) -> str:
        return f'{self.title} by {self.author}'

In the previous code sample, there are 3 managers: the default models.Manager, BookTitleManager and BookPagesManager assigned to objects, titles and sizes respectively.

The problem with the previous managers is that you cannot chain them as follows:

>>> Book.titles.long_titles().starts_with('P')
AttributeError: 'QuerySet' object has no attribute 'starts_with'

Example: Custom manager and queryset (chains are allowed)

If you want to chain methods defined in managers, you should define a custom QuerySet as follows:

from django.db import models
from django.db.models.functions import Length


class AuthorQuerySet(models.QuerySet):
    def long_first_name(self):
        return self.annotate(length=Length("first_name")).filter(length__gte=10)

    def short_last_name(self):
        return self.annotate(length=Length("last_name")).filter(length__lte=10)


class AuthorManager(models.Manager):
    def get_queryset(self):
        return AuthorQuerySet(self.model, using=self._db)

    def long_first_name(self):
        return self.get_queryset().long_first_name()

    def short_last_name(self):
        return self.get_queryset().short_last_name()


class Author(models.Model):
    first_name = models.CharField(max_length=100)
    last_name = models.CharField(max_length=100)
    email = models.EmailField()

    objects = AuthorManager()

    def __str__(self) -> str:
        return f"{self.first_name} {self.last_name}"

Example: Custom queryset used as manager

When defining just custom QuerySets in the manager, it is possible to simply extend the QuerySet and set it as the manager.

from django.db import models
from django.db.models.functions import Length


class PublisherQuerySet(models.QuerySet):
    def long_name(self):
        return self.annotate(length=Length("name")).filter(length__gte=15)

    def long_address(self):
        return self.annotate(length=Length("address")).filter(length__gte=25)

    def country_starts_with(self, letter):
        return self.filter(country__startswith=letter)


class Publisher(models.Model):
    name = models.CharField(max_length=100)
    address = models.CharField(max_length=255)
    country = models.CharField(max_length=100)

    objects = PublisherQuerySet.as_manager() # uses QuerySet as Manager

    def __str__(self) -> str:
        return self.name

Also to keep in mind

  • If you want to use objects as a field name, or if you want to use a name other than 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.
class Document(models.Model):
    name = models.CharField(max_length=30)
    size = models.PositiveIntegerField(default=0)
    file_type = models.CharField(max_length=10, blank=True)

    stuff = models.Manager()

    def __str__(self) -> str:
        return self.name

In the previous code sample, calling Document.objects will generate an AttributeError exception because the default manager has been renamed, what will work now is Document.stuff.

  • Managers are accessible only via model classes, rather than from model instances, to enforce a separation between “table-level” operations and “record-level” operations.

  • If a model has a ForeignKey, instances of the foreign-key model will have access to a Manager that returns all instances of the first model. By default, this Manager is named FOO_set, where FOO is the source model name, lowercased.

Leave a comment