Don't filter in Django's qet_queryset

In Django models, it’s easy to customize the queryset that’s returned by a model manager. Say that you’re working on a blog, and you have a model that looks something like this:

class Post(models.Model):
    content = models.TextField()
    created_at = models.DateTimeField(auto_now_add=True)

You might decide that you always want to return blog posts sorted by most recent. You can use a custom model manager such that Post.objects.all() will always include this ordering by default:

class PostManager(models.Manager):
    def get_queryset(self):
        return super().get_queryset().order_by("-created_at")

class Post(models.Model):
    objects = PostManager()

    ...

However, overriding get_queryset() also makes it easy to shoot yourself in the foot. Perhaps you implement the ability to archive old posts, and you then figure that you’ll make your code a bit cleaner by only returning those by default:

class PostManager(models.Manager):
    def get_queryset(self):
        return super().get_queryset()
            .filter(archived=False)
            .order_by("-created_at")

class Post(models.Model):
    objects = PostManager()
    unscoped = models.Manager()

    archived = models.BooleanField(default=False)
    ...

Unfortunately, this just made it easy to introduce bugs in the future! Now, whenever a developer is working with the Post model, they need to remember to handle two cases: archived posts and un-archived posts.

Say that a year down the line, you need to write a data migration that converts the content of your blog posts from Markdown to HTML. An engineer might write something like:

for post in Post.objects.all():
    post.convert_to_html()

This looks fine on the surface, but it will leave all archived posts un-converted. And worse, you’ll only catch this if you remember to test the code with both archived and non-archived posts, or if you remember to use the unscoped model manager.

A good principle is to minimize the number of unusual behaviours that developers need to keep in mind. Custom scopes do the opposite, and add a lot of risk for little payoff.