Custom Managers & Meta Options
Every single-model query in this track so far has started with Task.objects or
Project.objects. That .objects is called a manager, and
Django attaches a default one to every model automatically — it's the entry point for every QuerySet
you build. This lesson looks at replacing that default with your own, and at a few model-level
settings that shape how a model behaves everywhere it's used.
Why Write a Custom Manager
Imagine TaskFlow's codebase filtering out completed tasks in a dozen different views:
Task.objects.filter(completed=False), typed out fresh each time. It works, but it's
repeated logic with no name attached to it — anyone reading a view has to recognize what that filter
means from the raw condition every single time. A custom manager lets you bundle a
reusable query into a method with a clear name, defined once on the model.
# taskflow/models.py
class TaskManager(models.Manager):
def incomplete(self):
return self.get_queryset().filter(completed=False)
class Task(models.Model):
title = models.CharField(max_length=200)
completed = models.BooleanField(default=False)
# ...other fields...
objects = TaskManager()
self.get_queryset() is the starting point every manager method builds from — it hands
back the base QuerySet for the model, which you then filter just like you would with
Task.objects directly. Assigning objects = TaskManager() replaces Django's
default manager with your own.
Calling the Custom Manager
Once it's defined, every view and query in the codebase can call the method by name instead of repeating the filter:
# Instead of Task.objects.filter(completed=False) everywhere...
open_tasks = Task.objects.incomplete()
Task.objects.incomplete() reads naturally, hides the filter condition behind a name that
describes intent, and means if the definition of "incomplete" ever needs to change — say, TaskFlow
later adds an "archived" status that should also count as not-open — you update it in exactly one
place: the manager method itself.
The class Meta Inner Class
Alongside managers, a model can define an inner class Meta: to configure model-level
options that aren't fields themselves — things like default ordering or how the model's name gets
displayed. A few of the most commonly used options:
| Meta option | What it controls |
|---|---|
ordering | A default sort order, applied automatically to every QuerySet for this model unless a query explicitly calls .order_by() itself. |
verbose_name / verbose_name_plural | A friendlier singular/plural name for the model, used in places like the Django admin — covered fully in The Django Admin: Basics. |
unique_together / constraints | Enforces a uniqueness rule across a combination of multiple fields together, rather than just one field on its own. |
A Full Meta Example for Task
Putting a couple of these together on TaskFlow's Task model — sorting the most urgent
(soonest due) tasks first by default, and giving the model a friendlier plural name:
class Task(models.Model):
title = models.CharField(max_length=200)
due_date = models.DateField(null=True, blank=True)
completed = models.BooleanField(default=False)
# ...other fields...
objects = TaskManager()
class Meta:
ordering = ['-due_date']
verbose_name_plural = 'Tasks'
With ordering = ['-due_date'] set, Task.objects.all() now comes back sorted
by due date, most recent first, with no .order_by() call needed anywhere that simply
wants the default order.
The __str__() Method
One more piece worth defining on every model: __str__(). It's a regular Python method,
not a Meta option, but it controls something you'll rely on constantly — how an instance of the model
displays as text, whenever Python needs a human-readable representation of it.
class Task(models.Model):
title = models.CharField(max_length=200)
# ...fields, manager, and Meta from above...
def __str__(self):
return self.title
Without __str__(), every Task would print as something unhelpful like
Task object (1) — its class name and primary key, nothing meaningful. With it defined,
the same task prints as its actual title. This becomes immediately visible in the very next lesson,
where every object you create or query in the interactive shell displays using exactly this method.