Django managers

Django is all about DRY.

Hence, if you see yourself typing the same query more than twice, abstract it to a manager.

 

models.py

from django.db import models

from articles import choices, managers


class Article(models.Model):
    title = models.CharField()
    status = models.PositiveSmallIntegerField(choices=choices.STATUS_CHOICES, default=choices.STATUS_DRAFT)

    objects = managers.ArticleManager()

choices.py

STATUS_DRAFT = 0
STATUS_PUBLIC = 1

STATUS_CHOICES = (
    (STATUS_DRAFT, 'Draft'),
    (STATUS_PUBLIC, 'Public'),
)

managers.py

from django.db import models

from articles import choices


class ArticleManager(models.Manager):

    def all_public(self):
        """
        Return all public articles
        """
        return self.get_query_set().filter(status=choices.STATUS_PUBLIC)

See the difference in usage.

old way:

models.Article.objects.filter(status=choices.STATUS_PUBLIC)

new way:

models.Article.ojects.all_public()

Wrong usage of managers (hard to spot)

You might think it's a good idea to create a manager that return Articles that have a publication_date from today or earlier,  in other words, don't show Articles that haven't been published yet.
However, you can not use the datetime module in the manager itself, you can only give it with a parameter.

Why not you may ask? Well, it's because as soon as your Django application runs, the manager sets the date for that manager. It will not calculate the date again on a new request. Meaning, on the first day you use the manager everything seems fine. But after a week, you'll see that Articles that have a publication date of "today" are not shown. Actually, only Articles of last week are shown.
Once you restart the Django application everything seems back to normal, until you wait another day.

managers.py:  (the wrong way)

import datetime

from django.db import models


class ArticleManager(models.Manager):

    def all_public(self):
        """
        Return all articles with a publication date
        of today or in the past as long as you restart the Django app every day
        """
        return self.get_query_set().filter(publication_date__lte=datetime.date.today())


managers.py: (the correct way)


from django.db import models


class ArticleManager(models.Manager):

    def all_public(self, to_date):
        """
        Return all articles with a publication date
        of today or in the past
        """
        return self.get_query_set().filter(publication_date__lte=to_date)

Example usage the correct way:

import datetime

models.Article.ojects.all_public(to_date=datetime.date.today())