Hello, welcome to my website! For most people, the best place to start is by visiting https://btao.org/welcome. There, you'll find what you're looking for. It also includes quick summaries of everything on this website for quick reading. For some extra fun personalization, append your name or email to the URL, like this: https://btao.org/welcome#[email protected] or https://btao.org/welcome#name=JohnDoe Don't worry, this data isn't logged, it just makes the page a little more fun and personal.

A sieve sitting on a kitchen table

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.