Avoid Common Django Pitfalls: Performance, Validation, and Save Best Practices

This article walks through typical Django mistakes—inefficient ORM queries without select_related/prefetch_related, misuse of null on CharFields, ordering quirks, forgetting to call clean on save, and improper use of update_fields—offering concrete code examples and fixes to improve performance and data integrity.

MaGe Linux Operations
MaGe Linux Operations
MaGe Linux Operations
Avoid Common Django Pitfalls: Performance, Validation, and Save Best Practices

Django is a powerful web framework, but subtle errors can creep in when developers are unfamiliar with its nuances. This guide summarizes common pitfalls and demonstrates how to avoid them in a sample employee‑management application.

Model definitions

from django.contrib.auth import get_user_model
from django.core.exceptions import ValidationError
from django.db import models

User = get_user_model()

class Organization(models.Model):
    name = models.CharField(max_length=100)
    datetime_created = models.DateTimeField(auto_now_add=True, editable=False)
    is_active = models.BooleanField(default=True)

class Employee(models.Model):
    user = models.ForeignKey(User, on_delete=models.CASCADE, related_name="employees")
    organization = models.ForeignKey(Organization, on_delete=models.CASCADE, related_name="employees")
    is_currently_employed = models.BooleanField(default=True)
    reference_id = models.CharField(null=True, blank=True, max_length=255)
    last_clock_in = models.DateTimeField(null=True, blank=True)
    datetime_created = models.DateTimeField(auto_now_add=True, editable=False)

    def clean(self):
        try:
            if self.last_clock_in < self.datetime_created:
                raise ValidationError("Last clock in must occur after the employee entered the system.")
        except TypeError:
            # last_clock_in may be None
            pass

Inefficient loops without select_related / prefetch_related

Iterating over each organization and then over its employees without eager loading triggers a query for every employee, potentially causing thousands of database hits.

for org in Organization.objects.filter(is_active=True):
    for emp in org.employees.all():
        if emp.is_currently_employed:
            do_something(org, emp)

Adding prefetch_related("employees") to the organization query reduces the number of queries dramatically.

for org in Organization.objects.filter(is_active=True).prefetch_related("employees"):
    for emp in org.employees.all():
        if emp.is_currently_employed:
            do_something(org, emp)

When filtering from the employee side, select_related("organization") should be used to avoid extra queries for the organization.

for emp in Employee.objects.filter(
        organization__is_active=True, is_currently_employed=True
    ).select_related("organization"):
    do_something(emp.organization, emp)

Using null on CharField / TextField

Django recommends not setting null=True on CharField because it creates two possible “empty” values (null and empty string), which can lead to subtle bugs. The example shows a reference_id field defined with null=True and explains why checks like if employee.reference_id is not None can misbehave when the value is an empty string.

Ordering with order_by and latest

By default, order_by sorts ascending; prefixing a field with - sorts descending. The article demonstrates retrieving the oldest and newest organizations using both order_by and latest, noting that latest defaults to descending order.

# Ascending (oldest first)
oldest_organization_first = Organization.objects.order_by("datetime_created")
# Descending (newest first)
newest_organization_first = Organization.objects.order_by("-datetime_created")

# Equivalent using latest
oldest_org = Organization.objects.latest("-datetime_created")
newest_org = Organization.objects.latest("datetime_created")

Forgetting to call clean on save

The model’s save method does not automatically invoke validation methods such as clean. If a view updates last_clock_in and calls save without full_clean, invalid timestamps can be persisted.

employee.last_clock_in = clock_in_datetime
employee.full_clean()  # Ensures validation runs
employee.save()

Omitting update_fields in saves

Calling save() without update_fields updates every column, which can cause write contention in high‑traffic environments. Using update_fields limits the UPDATE statement to only the changed fields, reducing race conditions, especially when multiple processes (e.g., Gunicorn workers and Celery tasks) modify the same row.

# Update only the is_active flag
user.save(update_fields=["is_active"])

Conclusion

The author admits to having made all of these mistakes and hopes the article helps readers spot and prevent similar issues in their Django projects. While Django excels at building web applications, its complexity can hide subtle bugs that are easy to overlook.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

performancevalidationbest practices
MaGe Linux Operations
Written by

MaGe Linux Operations

Founded in 2009, MaGe Education is a top Chinese high‑end IT training brand. Its graduates earn 12K+ RMB salaries, and the school has trained tens of thousands of students. It offers high‑pay courses in Linux cloud operations, Python full‑stack, automation, data analysis, AI, and Go high‑concurrency architecture. Thanks to quality courses and a solid reputation, it has talent partnerships with numerous internet firms.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.