Django Time-Based Lookups: A Performance Trap

Django’s field lookups are one of the ORM’s best features, but time-based lookups can quietly bypass database indexes, turning fast queries into expensive full table scans. A Slow Production Query I ran into this while debugging a 30 second query on a large table (~25 million rows): class Event(models.Model): timestamp = models.DateTimeField(db_index=True) Event.objects.filter(timestamp__date=datetime.date(2026, 1, 5)).count() It generates SQL like this: SELECT COUNT(*) FROM event WHERE timestamp::date='2026-01-05'; At first glance, it looked totally reasonable — a simple filter on an indexed field. But after checking the query plan, I discovered the issue: the query can’t use the index and falls back to a full table scan because it casts the field to a date. ...

January 19, 2026

Avoiding Duplicate Objects in Django Querysets

When filtering Django querysets across relationships, you can easily end up with duplicate objects in your results. This is a common gotcha that happens with both one-to-many (1:N) and many-to-many (N:N) relationships. Let’s explore why this happens and the best way to avoid it. The Problem When you filter a queryset by traversing a relationship, Django performs a SQL JOIN. If a parent object has multiple related objects that match your filter, the parent object appears multiple times in the result set. ...

January 3, 2026

The Django db_default Gotcha That Breaks Your Code

Starting in Django 5.0, you can use db_default to define database-level default values for model fields. It’s a great feature but comes with a subtle and dangerous gotcha that can silently break your code before the object is ever saved. The Gotcha in Action Let’s say you’re building a simple task manager, and you want to track whether a task is completed: from django.db import models class Task(models.Model): is_completed = models.BooleanField(db_default=False) The db_default option ensures the database applies the default in raw INSERT queries and prevents errors during a rolling deployment. ...

August 3, 2025

Stop Using Django's squashmigrations: There's a Better Way

Squashing merges multiple database migrations into a single consolidated file to speed up database setup and tidy up history. Django’s squashmigrations command promises to handle this, but it’s error-prone and unnecessarily complex. A clean reset is faster and simpler. ⚠️ Why squashmigrations Falls Short Broken in Practice In medium-to-large projects, I’ve consistently seen squashmigrations generate poor results, such as failing to optimize basic no-ops (like adding and deleting the same field) and even breaking migrations. The official docs acknowledge this: ...

July 26, 2025

Speed Up Django Queries with values() over only()

If your Django queries feel slow, the problem might not be your database — it might be your ORM. Recently, I was working with a query that took 25 seconds to run through the Django ORM, but the underlying SQL completed in just 2 seconds. With a single change, I got the ORM query down to 2 seconds as well — and reduced the memory footprint by 70%. Let’s walk through what happened, and why using .values() instead of .only() can dramatically improve performance. ...

June 29, 2025