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.
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
passInefficient 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.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
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.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
