| name | django-orm |
| description | [Applies to: **/*.py] This guide provides opinionated, actionable best practices for writing efficient, maintainable, and performant Django ORM code, focusing on common pitfalls and modern patterns. |
| source | cursor_mdc |
django-orm Best Practices
These rules are your definitive guide for writing robust and performant Django ORM code. Adhere to them strictly to ensure consistency, efficiency, and maintainability across our projects.
1. Model Structure and Naming Conventions
Always follow the official Django style guide for models. Consistency is paramount.
- Naming:
- Models:
PascalCase and singular.
- Fields:
snake_case.
- Ordering: Maintain a consistent order within your model definitions.
from django.db import models
class books(models.Model):
BookTitle = models.CharField(max_length=200)
author_name = models.CharField(max_length=100)
def __str__(self):
return self.BookTitle
class Meta:
ordering = ['BookTitle']
from django.db import models
from django.urls import reverse
class Book(models.Model):
class Status(models.TextChoices):
DRAFT = "DR", "Draft"
PUBLISHED = "PB", "Published"
title = models.CharField(max_length=200, verbose_name="Book Title")
author = models.ForeignKey('Author', on_delete=models.CASCADE, related_name="books")
status = models.CharField(max_length=2, choices=Status.choices, default=Status.DRAFT)
created_at = models.DateTimeField(auto_now_add=True)
class Meta:
verbose_name = "book"
verbose_name_plural = "books"
ordering = ["-created_at", "title"]
indexes = [
models.Index(fields=['title']),
models.Index(fields=['author', 'status']),
]
def __str__(self):
return f"{self.title} by {self.author.name}"
def get_absolute_url(self):
return reverse("book_detail", args=[str(self.id)])
def is_published(self):
return self.status == self.Status.PUBLISHED
2. Explicit Field Definitions
Always be explicit with null, blank, verbose_name, and related_name. Avoid relying on Django's defaults for clarity and future maintainability.
null=True vs blank=True: null affects the database (can store NULL), blank affects form validation (can be empty). Use them correctly.
verbose_name: Provide human-readable names for fields.
related_name: Always define for ForeignKey, ManyToManyField, OneToOneField to avoid _set suffixes and prevent naming clashes. Use a plural, descriptive name.
choices: Use TextChoices or IntegerChoices for readability and type safety.
class Product(models.Model):
name = models.CharField(max_length=100)
description = models.TextField()
category = models.ForeignKey('Category', on_delete=models.CASCADE)
from django.db import models
class Category(models.Model):
name = models.CharField(max_length=50, unique=True, verbose_name="Category Name")
def __str__(self):
return self.name
class Product(models.Model):
class AvailabilityStatus(models.TextChoices):
IN_STOCK = "IS", "In Stock"
OUT_OF_STOCK = "OOS", "Out of Stock"
PRE_ORDER = "PO", "Pre-Order"
name = models.CharField(max_length=100, verbose_name="Product Name")
description = models.TextField(blank=True, null=True, verbose_name="Product Description")
price = models.DecimalField(max_digits=10, decimal_places=2, verbose_name="Unit Price")
category = models.ForeignKey(
Category,
on_delete=models.PROTECT,
related_name="products",
verbose_name=
)
status = models.CharField(
max_length=,
choices=AvailabilityStatus.choices,
default=AvailabilityStatus.IN_STOCK,
verbose_name=
)
():
.name
3. Query Optimization: Avoid N+1 Queries
The N+1 query problem is a major performance killer. Always pre-fetch related data.
select_related(): For ForeignKey and OneToOneField relationships (single object joins). Fetches related objects in the same SQL query.
prefetch_related(): For ManyToManyField and reverse ForeignKey relationships (many-to-many or many-to-one). Performs a separate query for each related table and then joins them in Python.
products = Product.objects.all()
for product in products:
print(f"{product.name} ({product.category.name})")
products = Product.objects.select_related('category').all()
for product in products:
print(f"{product.name} ({product.category.name})")
book_authors = Book.objects.all()
for book in book_authors:
authors = ", ".join([author.name for author in book.authors.all()])
book_authors = Book.objects.prefetch_related('authors').all()
for book in book_authors:
authors = ", ".join([author.name for author in book.authors.all()])
4. Efficient Data Retrieval
Retrieve only the data you need. Avoid hydrating full model instances when not necessary.
values() / values_list(): Use when you only need a subset of fields and don't require full model instances. values_list(..., flat=True) for single column lists.
exists() / count(): Use exists() when you only need to check if any records match a query, and count() when you need the total number. Avoid len(queryset) for counting.
book_titles = [book.title for book in Book.objects.all()]
book_titles = Book.objects.values_list('title', flat=True)
if len(Book.objects.filter(author__name="Jane Austen")) > 0:
print("Jane Austen has books.")
if Book.objects.filter(author__name="Jane Austen").exists():
print("Jane Austen has books.")
total_books = len(Book.objects.all())
total_books = Book.objects.count()
5. Database-Level Operations
Leverage the database for aggregations and atomic updates.
annotate() / aggregate(): Perform calculations (e.g., Count, Sum, Avg) directly in the database.
F() expressions: For atomic updates, preventing race conditions.
from django.db.models import Count, Avg, F
authors = Author.objects.all()
for author in authors:
author.book_count = author.books.count()
authors_with_book_counts = Author.objects.annotate(total_books=Count('books'))
for author in authors_with_book_counts:
print(f"{author.name}: {author.total_books} books")
product = Product.objects.get(id=1)
product.stock += 1
product.save()
Product.objects.filter(id=1).update(stock=F('stock') + 1)
6. Bulk Operations
For creating, updating, or deleting many objects, use bulk operations to minimize database round-trips.
authors_data = [
{"name": "Leo Tolstoy"},
{"name": "Jane Austen"},
]
for data in authors_data:
Author.objects.create(**data)
authors_to_create = [
Author(name="Leo Tolstoy"),
Author(name="Jane Austen"),
]
Author.objects.bulk_create(authors_to_create)
Book.objects.filter(published_date__year__lt=2000).update(status=Book.Status.DRAFT)
7. Indexing
Add database indexes to frequently filtered or ordered fields. Profile to determine actual benefits.
- Use
db_index=True on individual fields or Meta.indexes for multi-column indexes.
class Order(models.Model):
customer = models.ForeignKey('Customer', on_delete=models.CASCADE)
order_date = models.DateTimeField(auto_now_add=True)
status = models.CharField(max_length=20)
class Order(models.Model):
customer = models.ForeignKey('Customer', on_delete=models.CASCADE)
order_date = models.DateTimeField(auto_now_add=True, db_index=True)
status = models.CharField(max_length=20, db_index=True)
class Meta:
indexes = [
models.Index(fields=['customer', 'order_date']),
]
8. Asynchronous ORM (Django 5.x+)
For high-concurrency applications, leverage Django 5.x's native async ORM capabilities. Wrap synchronous ORM calls in sync_to_async if working in an async context with older Django versions or mixed codebases.
import asyncio
from django.http import JsonResponse
from .models import Book
async def get_books_blocking(request):
books = Book.objects.all()
data = [{"title": book.title} for book in books]
return JsonResponse(data, safe=False)
from django.http import JsonResponse
from .models import Book
async def get_books_async(request):
books = await Book.objects.filter(status=Book.Status.PUBLISHED).values_list('title', flat=True)
return JsonResponse(list(books), safe=False)
from asgiref.sync import sync_to_async
from django.http import JsonResponse
from .models import Book
async def get_books_sync_to_async(request):
books = await sync_to_async(Book.objects.all)()
data = sync_to_async(: [{: book.title} book books])()
JsonResponse(data, safe=)
9. Debugging and Profiling
Always use the Django Debug Toolbar to inspect queries. It's your most valuable tool for identifying N+1 problems and slow queries.
10. Security
The ORM provides robust protection against SQL injection. Avoid raw SQL unless absolutely necessary, and always use parameterized queries if you do.
from django.db import connection
def get_user_bad(username):
with connection.cursor() as cursor:
cursor.execute(f"SELECT * FROM myapp_user WHERE username = '{username}'")
return cursor.fetchone()
from .models import User
def get_user_good(username):
return User.objects.filter(username=username).first()
from django.db import connection
def get_user_safe_raw_sql(username):
with connection.cursor() as cursor:
cursor.execute("SELECT * FROM myapp_user WHERE username = %s", [username])
return cursor.fetchone()