Model managers
As you've learned throughout the
examples presented in this and the previous chapter, a Django
model's objects
reference or default model manager,
offers an extensive array of functionalities to execute database
operations.
Under most circumstances, Django
models don't require any modifications to their default model
manager or objects
reference. However, there can be
circumstances where the need arises to customize a Django model's
default model manager or inclusively create multiple model
managers.
Custom and multiple model managers
One of the main reasons to create custom Django model managers is to add custom manager methods, to make the execution of recurring queries on a model easier.
For example, running queries such
as Item.objects.filter(menu__name='Sandwiches')
or
Item.objects.filter(menu__name='Salads')
is simple,
but if you start writing these same queries over and over, the
process can become tiresome and error prone. This is particularly
true for raw SQL queries, which take more time to write and have a
higher degree of complexity.
A custom manager method allows
you to write a query once as part of a model, and later invoke the
custom manager method -- just like other model manager methods
(e.g. all()
, filter()
,
exclude()
) -- to trigger the query. Listing 8-61
illustrates a custom model manager class with a custom method,
including a model that uses it, in addition to various model
manager calls.
Listing 8-61. Django custom model manager with custom manager methods
from django.db import models # Create custom model manager class ItemMenuManager(models.Manager): def salad_items(self): return self.filter(menu__name='Salads') def sandwich_items(self): return self.filter(menu__name='Sandwiches') # Option 1) Override default model manager class Item(models.Model): menu = models.ForeignKey(Menu, on_delete=models.CASCADE) name = models.CharField(max_length=30) ... objects = ItemMenuManager() # Queries on default custom model manager Item.objects.all() Item.objects.salad_items() Item.objects.sandwich_items() # Option 2) Create new model manager field and leave default model manager as is menu = models.ForeignKey(Menu, on_delete=models.CASCADE) name = models.CharField(max_length=30) ... objects = models.Manager() menumgr = ItemMenuManager() # Queries on default and custom model managers Item.objects.all() Item.menumgr.salad_items() Item.menumgr.sandwich_items() # ERROR Item.objects.salad_items()
# 'Manager' object has no attribute 'salad_items' # ERROR Item.objects.sandwich_items()
# 'Manager' object has no attribute 'sandwich_items' Item.menumgr.all()
The first class in listing 8-61
is the ItemMenuManager
that functions as a custom
model manager. Notice how this class inherits its behavior from the
models.Manager
class, which is what gives it model
manager behavior. Next, the ItemMenuManager
class
declares two methods that return QuerySet
results.
Notice how the class methods reference self
--
representing the model class instance -- and call standard model
methods to trigger database queries.
It's worth mentioning custom
model managers don't necessarily need to use native model queries
or return QuerySet
data structures, custom model
managers can equally contain any logic (e.g. Python DB API calls)
or return any data structure (e.g. Python tuples).
Once you have a custom model
manager there are two options to assign it to a model class. The
first option illustrated in listing 8-61, consists of overriding a
model's default model manager objects
and explicitly
assigning it a custom model manager. Once this is done, you can use
the same objects
reference to call the custom model
manager methods. In addition, notice in listing 8-61 that even when
overriding the default model manager objects
, a model
continues to have access to the built-in model manager methods
(e.g. all()
) because the custom model inherits its
behavior from the parent models.Manager
class.
Next, listing 8-61 illustrates
the second option to integrate a custom model manager. This option
consists of adding a new model field to reference a custom manager
and leave the default manager objects
as is. In this
case, the custom model manager methods become accessible through
the new field reference (e.g.
Item.menumgr.salad_items()
) and the
objects
reference continues to work with its default
behavior.
Tip When you declare multiple model manager in a model, you can set the default model manager using thedefault_manager_name
meta option. See the previous chapter for additional details on model meta options.
Warning If you don't define a default model manager in a multi-manager model, Django choose the first manager declared in the model. This can have unexpected behaviors in model operations that can't explicitly chose model managers (e.g. dumpdata) unlike queries that can use dot-notation to choose a model manager.
Custom model managers and QuerySet classes with methods
Model managers are closely tied
to methods that return QuerySet
data structures. As
you've seen, nearly all methods chained to the default model
manager objects
(e.g. all()
,
filter()
) generate QuerySet
data
structures. When you create custom model managers, it's possible to
override the default behavior for these QuerySet
methods, as well as create your own custom QuerySet
classes and methods.
One of the most important
QuerySet
methods in model managers is the
get_queryset()
method, used to define a model's
initial QuerySet
or what's returned by a model
manager's all()
method. In custom model managers, the
get_queryset()
method is particularly important
because it let's you filter the initial QuerySet
depending on the purpose of a model manager.
Listing 8-62 illustrates multiple
custom model managers that define custom logic for the
get_queryset()
method.
Listing 8-62. Django custom model managers with custom get_queryset() method
class SanDiegoStoreManager(models.Manager): def get_queryset(self): return super(SanDiegoStoreManager, self).get_queryset().filter(city='San Diego') class LosAngelesStoreManager(models.Manager): def get_queryset(self): return super(LosAngelesStoreManager, self).get_queryset().filter(city='Los Angeles') class Store(models.Model): name = models.CharField(max_length=30) ... objects = models.Manager() sandiego = SanDiegoStoreManager() losangeles = LosAngelesStoreManager() # Call default manager all() query, backed by get_queryset() method Store.objects.all() # Call sandiego manager all(), backed by get_queryset() method Store.sandiego.all() # Call losangeles manager all(), backed by get_queryset() method Store.losangeles.all()
The first two classes in listing
8-62 represent custom model managers, however, notice that unlike
the custom model manager in listing 8-61, both the
SanDiegoStoreManager
and
LosAngelesStoreManager
classes define the
get_queryset()
method. In both cases, the
get_queryset()
method returns a QuerySet
generated by calling the parent model manager
get_queryset()
method (i.e. all()
) -- via
the super()
method -- and applying an additional
filter()
to the parent depending on the purpose of the
custom model manager (e.g. get stores by city).
Once the custom managers are
defined, listing 8-62 declares the custom model managers as
separate fields in the Store
model class. Finally, in
listing 8-62 you can see calls made to each of the model managers
using the all()
method, which return the appropriate
filtered results depending on the logic of the backing
get_queryset()
method.
An alternative to multiple custom
model managers, is to create a single custom manager and rely on a
custom QuerySet
class and methods to execute the same
logic, a technique that's illustrated in listing 8-63.
Listing 8-63. Django custom model manager with custom QuerySet class and methods
class StoreQuerySet(models.QuerySet): def sandiego(self): return self.filter(city='San Diego') def losangeles(self): return self.filter(city='Los Angeles') class StoreManager(models.Manager): def get_queryset(self): return StoreQuerySet(self.model, using=self._db) def sandiego(self): return self.get_queryset().sandiego() def losangeles(self): return self.get_queryset().losangeles() class Store(models.Model): name = models.CharField(max_length=30) ... objects = models.Manager() shops = StoreManager() Store.shops.all() Store.shops.sandiego() Store.shops.losangeles()
The StoreQuerySet
class in listing 8-63 is a custom QuerySet
class that
defines the sandiego()
and losangeles()
methods, both of which apply additional filters to its base
QuerySet
. Once you have a QuerySet
class,
it's necessary to associate it with a custom model manager. In
listing 8-63, you can see the StoreManager
class
represents a custom model manager, which defines its
get_queryset()
method to set its initial data through
the custom StoreQuerySet
class.
Next, notice how the custom model
manager StoreManager
class defines the additional
sandiego()
and losangeles()
methods, which are hooked
up to call the methods by the same name in the custom
StoreQuerySet
class.
Finally, the custom model manager
StoreManager
is set up as the shops
field
in the Store
model class, where you can observe how
calls are made via the shops
reference to trigger the
query methods backed by the custom StoreQuerySet
class.
As helpful as the technique in
listing 8-63 is to cut down on the amount model managers, if you
look carefully at listing 8-63, there's still a fair amount of
redundancy declaring similar named methods for both a custom model
manager and a custom QuerySet
class.
To cut down on redundant methods
when using custom model managers and custom QuerySet
classes, the latter type of class offers the
as_manager()
method to automatically convert a
QuerySet
class into a custom model manager, a
technique that's illustrated in listing 8-64.
Listing 8-64. Django custom model manager with custom QuerySet converted to manager
class StoreQuerySet(models.QuerySet): def sandiego(self): return self.filter(city='San Diego') def losangeles(self): return self.filter(city='Los Angeles') class Store(models.Model): name = models.CharField(max_length=30) ... objects = models.Manager() shops = StoreQuerySet.as_manager() Store.shops.all() Store.shops.sandiego() Store.shops.losangeles()
The example in listing 8-64
defines the same custom QuerySet
class as the one
in listing 8-63, however, notice the lack of a custom model manager class.
Instead, the Store
model definition in listing 8-64
directly references the custom StoreQuerySet
class and
calls the as_manager()
on it to convert the
QuerySet
class into a model manager. Finally, notice
how the calls made via the shops
reference are
identical to the ones in listing 8-63. In this manner, the
technique in listing 8-64 saves you the additional work of creating
explicit custom model managers if you're using custom
QuerySet
classes.
Custom reverse model managers for related models
Earlier in the CRUD relationship
records sub-section, you learned how models that have relationships
between one another, use reverse queries or _set
syntax to execute operations from the model that doesn't have the
relationship definition.
These reverse operations are
executed by a model manager dubbed RelatedManager
which is a subclass of a model's default manager. This means all
reverse queries or _set
syntax calls are based on the
objects
model manager reference or whatever default
model manager is used by a model.
If you configure a default model manager on a model, then all the reverse operations on a model will automatically use this same manager. However, it's possible to define a custom model manager exclusively for reverse operations, while ignoring the default model manager. This technique consists of explicitly declaring a model manager as part of the reverse operation, as shown in listing 8-65.
Listing 8-65. Django custom model manager for reverse query operations
from django.db import models class Item(models.Model): ... objects = models.Manager() # Default manager for direct queries reverseitems = CustomReverseManagerForItems() # Custom Manager for reverse queries # Get Menu record named Breakfast breakfast_menu = Menu.objects.get(name='Breakfast') # Fetch all Item records in the Menu, using Item custom model manager for reverse queries breakfast_menu.item_set(manager='reverseitems').all()
# Call on_sale_items() custom manager method in CustomReverseManagerForItems breakfast_menu.item_set(manager='reverseitems').on_sale_items()
Listing 8-65 first declares the
Item model with its default objects
model manager and
a custom model manager assigned to the reverseitems
field. Next, a query is made to get a Menu record, followed by
various queries to get the Menu
record's related
Item
records via the reverse _set
syntax.
However, notice how the reverse
query operation in listing 8-65 with _set
syntax, uses
the manager
argument to indicate which model manager
to use for reverse operations, in this case, the
reverseitems
model manager is used to execute the
queries, instead of the default objects
model
manager.