Model Managers

A Manager is a Django class that provides the interface between database query operations and a Django model. Each Django model is provided with a default Manager named objects. We used the default manager in Chapter 4 and again in this chapter every time we query the database, for example:

  1. >>> newevent = Event.objects.get(name="Xmas Barbeque")
  2. # and:
  3. >>> joneses = MyClubUser.objects.filter(last_name='Jones')

In each example, objects is the default Manager for the model instance.

You can customize the default Manager class by extending the base Manager class for the model. The two most common use-cases for customizing the default manager are:

  1. Adding extra manager methods; and
  2. Modifying initial QuerySet results.

Adding Extra Manager Methods

Extra manager methods add table-level functionality to models. To add row-level functions, i.e., methods that act on single instances of the model, you use model methods, which we cover in the next section of the chapter.

Extra manager methods are created by inheriting the Manager base class and adding custom functions to the custom Manager class. For example, let’s create an extra manager method for the Event model to retrieve the total number of events for a particular event type (changes in bold):

  1. # \myclub_root\events\models.py
  2. # ...
  3. 1 class EventManager(models.Manager):
  4. 2 def event_type_count(self, event_type):
  5. 3 return self.filter(name__icontains=event_type).count()
  6. 4
  7. 5
  8. 6 class Event(models.Model):
  9. 7 name = models.CharField('Event Name', max_length=120)
  10. 8 event_date = models.DateTimeField('Event Date')
  11. 9 venue = models.ForeignKey(Venue, blank=True, null=True, on_delete=models.CASCADE)
  12. 10 manager = models.ForeignKey(User, blank=True, null=True, on_delete=models.SET_NULL)
  13. 11 attendees = models.ManyToManyField(MyClubUser, blank=True)
  14. 12 description = models.TextField(blank=True)
  15. 13 objects = EventManager()
  16. 14
  17. 15 def __str__(self):
  18. 16 return self.name

Let’s have a look at this partial listing from your events app’s models.py file:

  • In line 1, we’ve entered a new class called EventManager that inherits from Django’s models.Manager base class.
  • Lines 2 and 3 define the event_type_count() custom manager method we’re adding to the model. This new method returns the total number of the specified event type. Note we’re using the icontains field lookup to return all events that have the key phrase in the title.
  • In line 13 we’re replacing the default manager with our new EventManager class. Note that EventManager inherits from the Manager base class, so all the default manager methods like all() and filter() are included in the custom EventManager() class.

Once it has been created, you can use your new manager method just like any other model method:

  1. # You must exit and restart the shell for this example to work.
  2. >>> from events.models import Event
  3. >>> Event.objects.event_type_count('Gala Day')
  4. 1
  5. >>> Event.objects.event_type_count('Presentation')
  6. 2

The Shell and Changing Model Code

Any changes made to models are not applied unless you restart the shell, so this applies not only to this example but any other examples that modify model code.

Renaming the Default Model Manager

While the base manager for each model is named objects by default, you can change the name of the default manager in your class declaration. For example, to change the default manager name for our Event class from “objects” to “events”, we just need to change line 13 in the code above from:

  1. 13 objects = EventManager()

To:

  1. 13 events = EventManager()

Now you can refer to the default manager like so:

  1. >>> from events.models import Event
  2. >>> Event.events.all()
  3. <QuerySet [<Event: Test Event>, <Event: Club Presentation - Juniors>, <Event: Club Presentation - Seniors>, <Event: Gala Day>]>
  4. >>>

Overriding Initial Manager QuerySets

To change what is returned by the default manager QuerySet, you override the Manager.get_queryset() method. This is easiest to understand with an example. Let’s say we regularly have to check what venues are listed in our local city. To cut down on the number of queries we have to write, we will create a custom manager for our Venue model (changes in bold):

  1. # \myclub_root\events\models.py
  2. 1 from django.db import models
  3. 2 from django.contrib.auth.models import User
  4. 3
  5. 4
  6. 5 class VenueManager(models.Manager):
  7. 6 def get_queryset(self):
  8. 7 return super(VenueManager, self).get_queryset().filter(zip_code='00000')
  9. 8
  10. 9
  11. 10 class Venue(models.Model):
  12. 11 name = models.CharField('Venue Name', max_length=120)
  13. 12 address = models.CharField(max_length=300)
  14. 13 zip_code = models.CharField('Zip/Post Code', max_length=12)
  15. 14 phone = models.CharField('Contact Phone', max_length=20, blank=True)
  16. 15 web = models.URLField('Web Address', blank=True)
  17. 16 email_address = models.EmailField('Email Address',blank=True)
  18. 17
  19. 18 venues = models.Manager()
  20. 19 local_venues = VenueManager()
  21. 20
  22. 21 def __str__(self):
  23. 22 return self.name
  24. # ...

Let’s look at the changes:

  • Lines 5 to 7 define the new VenueManager class. The structure is the same as the EventManager class, except this time we’re overriding the default get_queryset() method and returning a filtered list that only contains local venues. This assumes local venues have a “00000” zip code. In a real website, you would have a valid zip code here, or better still, a value for the local zip code saved in your settings file.
  • In line 18 we’ve renamed the default manager to venues.
  • In line 19 we’re adding the custom model manager (VenueManager).

Note there is no limit to how many custom managers you can add to a Django model instance. This makes creating custom filters for common queries a breeze. Once you have saved the models.py file, you can use the custom methods in your code. For example, the default manager method has been renamed, so you can use the more intuitive venues, instead of objects:

  1. >>> from events.models import Venue
  2. >>> Venue.venues.all()
  3. <QuerySet [<Venue: South Stadium>, <Venue: West Park>, <Venue: North Stadium>, <Venue: East Park>]>

And our new custom manager is also easily accessible:

  1. >>> Venue.local_venues.all()
  2. <QuerySet [<Venue: West Park>]> # Assuming this venue has a local zip code
  3. >>>