What is a Model Manager?
For every model in Django, there is an associated manager which is an interface for interacting with the database. To access the manager of a model, you simply need to use the
objects property on the model e.g.
FootballTeam.objects, however, this property is customisable.
teams, you would simply declare an attribute named
Managerwithin the model.
from django.db import models FootballTeam(models.Model): teams = models.Manager()
There is a very big use case for creating a custom model manager for each model in your django application and this is a beneficial habit to get into for both yourself and other developers that have to work with the data models. This use case is maintenance.
Why are Model Managers so important?
As mobile app developers, we should be writing clean, efficient, maintainable code and follow the DRY (Don’t Repeat Yourself) software architecture principle. The django model managers play a good part in ensuring you tick all of these boxes; being more vital for maintenance and adhering to the DRY principle.
The reason it ticks these boxes is that it keeps the querying in your django application in a centralised place; queries that your application perform come from one place instead of being wrote on the fly in each View that requires the query.
Consider the following example:
from django.db import models.DateField import datetime class FootballTeam(models.Model):
PREMIER_LEAGUE = 0 CHAMPIONSHIP = 1 LEAGUE_ONE = 2 LEAGUE = ( (PREMIER_LEAGUE, "Premier League"), (CHAMPIONSHIP, "Championship"), (LEAGUE_ONE, "League One"), ) name = models.CharField("Name", max_length=255, default="") nickname = models.CharField("Nickname", max_length=255, default="") league = models.IntegerField("League", choices=LEAGUE, default=0) founded = models.DateField("Founded", default = datetime.date.today)
Note: League would normally be a separate model here, however, for the purpose of this example, using a ChoiceField will suffice.
Querying the FootballTeam Model
from rest_framework.generics import ListAPIView from .models import FootballTeam class TopFootballTeamList(ListAPIView): def get_queryset(self): return FootballTeam.objects.filter(league=FootballTeam.PREMIER_LEAGUE)
from django.views import generic from .models import FootballTeam class TopFootballTeamListView(generic.ListView): template_name = 'football/teams.html' model = FootballTeam def get_queryset(self): return FootballTeam.objects.filter(league=FootballTeam.PREMIER_LEAGUE)
The issue with the code above is there are two separate views which require the same functionality and therefore perform identical queries in the
get_queryset method. This goes against the DRY principle and also causes extra development work when requiring to maintain these views.
If a requirement was to change and the “top football teams” were classed as Premier League and Championship teams rather than just Premier League teams, there are two views that have to be maintained regarding this query which leads to modifications having to be made in both respective views. The
get_queryset method in both of these would have to be changed to the following:
from django.db.models import Q # View def get_queryset(self): return FootballTeam.objects.filter(Q(league=FootballTeam.PREMIER_LEAGUE) | Q(FootballTeam.CHAMPIONSHIP))
This may seem like a very small problem having to alter the query in two places, however, this is a very simplified example. In large systems where there will be more views and more complex queries, this type of maintenance takes up development and testing time, makes debugging harder, goes against the DRY principle and due to a query potentially being missed, can lead to bugs.
How can this problem be solved?
To solve this issue, we can make use of model managers to create a centralised area for retrieving the top football teams using the
FootballTeam model. This is achieved by the following:
from django.db import models from django.db.models import Q import datetime class FootballTeamManager(models.Manager): def top_teams(self, **kwargs): return self.filter(Q(league=FootballTeam.PREMIER_LEAGUE)) FootballTeam(models.Model): PREMIER_LEAGUE = 0 CHAMPIONSHIP = 1 LEAGUE_ONE = 2 LEAGUE = ( (PREMIER_LEAGUE, "Premier League"), (CHAMPIONSHIP, "Championship"), (LEAGUE_ONE, "League One"), ) name = models.CharField("Name", max_length=255, default="") nickname = models.CharField("Nickname", max_length=255, default="") league = models.IntegerField("League", choices=LEAGUE, default=0) founded = models.DateField("Founded", default=datetime.date.today) objects = FootballTeamManager()
In the code snippet above, a model manager has been created named
FootballTeamManager. This manager will be the sole place for implementing queries regarding the
FootballTeam model, and by following this, we have created our centralised place for queries regarding this model. This follows the DRY principle and also ensures we have a central place for all developers on the project to go to when they need to modify a query; this is maintainable code.
To associate the
FootballTeamManager with the
FootballTeam model, replace the default model manager with our custom manager by adding the following code to the
objects = FootballTeamManager().
To integrate the new manager methods into our views, we simply have to modify the
get_queryset methods to use the following:
def get_queryset(self): return FootballTeam.objects.top_teams()
Now, when the requirement to change the definition of top football teams to be football teams both in the Premier League and the Championship, we only have to modify the
top_teams method of the model manager, rather than the two separate views.
def top_teams(self, **kwargs): return self.filter(Q(league=FootballTeam.PREMIER_LEAGUE) | Q(league=FootballTeam.CHAMPIONSHIP))
Once this type of development is enforced into a django project, it means that any developer can come onto that project and have confidence that they can change the query in one place and do not have to have knowledge of all views in the application that use the query.
Use model managers! Once in the habit of writing and using model managers, you will find development time speeds up, you have a cleaner and maintainable codebase, less occurrences of bugs regarding queries and future developers on the project will be very appreciative of your work; even more so in bigger projects.
You may also be interested in our blogpost discussing Custom stackView as an easy way to lay out components