Claude-skill-registry django-orm-patterns
Use when Django ORM patterns with models, queries, and relationships. Use when building database-driven Django applications.
git clone https://github.com/majiayu000/claude-skill-registry
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/data/django-orm-patterns" ~/.claude/skills/majiayu000-claude-skill-registry-django-orm-patterns && rm -rf "$T"
skills/data/django-orm-patterns/SKILL.mdDjango ORM Patterns
Master Django ORM for building efficient, scalable database-driven applications with complex queries and relationships.
Model Definition
Define models with proper field types, constraints, and metadata.
from django.db import models from django.core.validators import MinValueValidator, MaxValueValidator class User(models.Model): email = models.EmailField(unique=True, db_index=True) name = models.CharField(max_length=100) is_active = models.BooleanField(default=True) created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) class Meta: ordering = ['-created_at'] indexes = [ models.Index(fields=['email']), models.Index(fields=['created_at', 'is_active']), ] verbose_name = 'User' verbose_name_plural = 'Users' def __str__(self): return self.email class Post(models.Model): title = models.CharField(max_length=200) content = models.TextField() author = models.ForeignKey(User, on_delete=models.CASCADE, related_name='posts') published = models.BooleanField(default=False) views = models.PositiveIntegerField(default=0) created_at = models.DateTimeField(auto_now_add=True) class Meta: ordering = ['-created_at'] indexes = [ models.Index(fields=['author', 'published']), ]
QuerySet API Basics
Use Django's QuerySet API for efficient database queries.
# All records users = User.objects.all() # Filtering active_users = User.objects.filter(is_active=True) inactive_users = User.objects.exclude(is_active=True) # Get single record (raises exception if not found or multiple found) user = User.objects.get(email='user@example.com') # Get or create user, created = User.objects.get_or_create( email='user@example.com', defaults={'name': 'John Doe'} ) # Update or create user, created = User.objects.update_or_create( email='user@example.com', defaults={'name': 'Jane Doe', 'is_active': True} ) # Chaining filters posts = Post.objects.filter(published=True).filter(author__is_active=True) # Order by users = User.objects.order_by('-created_at', 'name') # Limit results recent_users = User.objects.all()[:10] # Count user_count = User.objects.filter(is_active=True).count() # Exists has_active_users = User.objects.filter(is_active=True).exists()
Q Objects for Complex Queries
Build complex queries with Q objects for OR and NOT operations.
from django.db.models import Q # OR queries users = User.objects.filter( Q(name__icontains='john') | Q(email__icontains='john') ) # AND with OR users = User.objects.filter( Q(is_active=True) & (Q(name__icontains='john') | Q(email__icontains='john')) ) # NOT queries users = User.objects.filter(~Q(is_active=True)) # Complex combinations posts = Post.objects.filter( Q(published=True) & (Q(author__name__icontains='john') | Q(title__icontains='important')) & ~Q(views__lt=100) ) # Dynamic query building def search_users(name=None, email=None, is_active=None): query = Q() if name: query &= Q(name__icontains=name) if email: query &= Q(email__icontains=email) if is_active is not None: query &= Q(is_active=is_active) return User.objects.filter(query)
F Objects for Field References
Use F objects to reference model fields in queries and updates.
from django.db.models import F # Compare fields posts = Post.objects.filter(views__gt=F('author__posts__count')) # Update based on current value Post.objects.filter(published=True).update(views=F('views') + 1) # Avoid race conditions post = Post.objects.get(id=1) post.views = F('views') + 1 post.save() post.refresh_from_db() # Get updated value # Complex expressions from django.db.models import ExpressionWrapper, IntegerField Post.objects.annotate( adjusted_views=ExpressionWrapper( F('views') * 2 + 10, output_field=IntegerField() ) )
Aggregation and Annotation
Perform database-level calculations and add computed fields.
from django.db.models import Count, Sum, Avg, Max, Min # Simple aggregation from django.db.models import Avg avg_views = Post.objects.aggregate(Avg('views')) # Returns: {'views__avg': 42.5} # Multiple aggregations stats = Post.objects.aggregate( total_posts=Count('id'), avg_views=Avg('views'), max_views=Max('views'), min_views=Min('views') ) # Annotation (adds field to each object) users = User.objects.annotate( post_count=Count('posts'), total_views=Sum('posts__views') ) for user in users: print(f"{user.name}: {user.post_count} posts, {user.total_views} views") # Filter by annotation popular_users = User.objects.annotate( post_count=Count('posts') ).filter(post_count__gt=10) # Complex annotations from django.db.models import Case, When, Value, CharField User.objects.annotate( user_type=Case( When(post_count__gt=10, then=Value('prolific')), When(post_count__gt=5, then=Value('active')), default=Value('casual'), output_field=CharField() ) )
Prefetch and Select Related (N+1 Prevention)
Optimize queries by reducing database hits with eager loading.
# Select related (for ForeignKey and OneToOne) posts = Post.objects.select_related('author').all() for post in posts: print(post.author.name) # No additional query # Prefetch related (for ManyToMany and reverse ForeignKey) from django.db.models import Prefetch users = User.objects.prefetch_related('posts').all() for user in users: for post in user.posts.all(): # No additional query print(post.title) # Custom prefetch users = User.objects.prefetch_related( Prefetch( 'posts', queryset=Post.objects.filter(published=True).order_by('-created_at') ) ) # Multiple levels posts = Post.objects.select_related( 'author' ).prefetch_related( 'author__posts' # Prefetch all posts by the same author ) # Combining both Post.objects.select_related('author').prefetch_related('tags')
Custom Managers and QuerySets
Create reusable query logic with custom managers and querysets.
from django.db import models class PublishedQuerySet(models.QuerySet): def published(self): return self.filter(published=True) def recent(self): return self.order_by('-created_at')[:10] def by_author(self, author): return self.filter(author=author) class PublishedManager(models.Manager): def get_queryset(self): return PublishedQuerySet(self.model, using=self._db) def published(self): return self.get_queryset().published() def recent(self): return self.get_queryset().recent() class Post(models.Model): # fields... objects = models.Manager() # Default manager published_posts = PublishedManager() # Custom manager class Meta: base_manager_name = 'objects' # Usage Post.published_posts.published().recent() Post.published_posts.published().by_author(user) # Chaining custom methods class UserQuerySet(models.QuerySet): def active(self): return self.filter(is_active=True) def with_posts(self): return self.annotate(post_count=Count('posts')).filter(post_count__gt=0) User.objects.active().with_posts()
Transactions and Atomic Blocks
Ensure data consistency with database transactions.
from django.db import transaction # Atomic decorator @transaction.atomic def create_user_with_post(email, name, post_title): user = User.objects.create(email=email, name=name) Post.objects.create(title=post_title, author=user) return user # Context manager def update_user_posts(user_id): try: with transaction.atomic(): user = User.objects.select_for_update().get(id=user_id) user.posts.update(published=True) user.is_active = True user.save() except Exception as e: # Transaction is rolled back raise # Savepoints from django.db import transaction with transaction.atomic(): user = User.objects.create(email='user@example.com') sid = transaction.savepoint() try: Post.objects.create(title='Test', author=user) except: transaction.savepoint_rollback(sid) else: transaction.savepoint_commit(sid) # Select for update (locking) with transaction.atomic(): user = User.objects.select_for_update().get(id=1) user.is_active = False user.save()
Advanced Select and Prefetch Patterns
Master complex query optimization with advanced eager loading techniques.
from django.db.models import Prefetch, Count, Q # Basic select_related (ForeignKey, OneToOne) posts = Post.objects.select_related('author', 'category') # Multi-level select_related comments = Comment.objects.select_related('post__author__profile') # Prefetch with custom queryset users = User.objects.prefetch_related( Prefetch( 'posts', queryset=Post.objects.filter(published=True).select_related('category'), to_attr='published_posts' ) ) # Multiple prefetch with different filters authors = User.objects.prefetch_related( Prefetch( 'posts', queryset=Post.objects.filter(published=True), to_attr='published_posts' ), Prefetch( 'posts', queryset=Post.objects.filter(published=False), to_attr='draft_posts' ) ) # Nested prefetch posts = Post.objects.prefetch_related( Prefetch( 'comments', queryset=Comment.objects.select_related('author').prefetch_related( Prefetch( 'replies', queryset=Comment.objects.select_related('author') ) ) ) ) # Prefetch with annotations users = User.objects.prefetch_related( Prefetch( 'posts', queryset=Post.objects.annotate( comment_count=Count('comments') ).filter(comment_count__gt=0) ) )
Database Functions and Expressions
Leverage database functions for complex operations.
from django.db.models import F, Value, CharField, Case, When, Q from django.db.models.functions import Concat, Lower, Upper, Length, Substr, Coalesce # String operations users = User.objects.annotate( full_name=Concat('first_name', Value(' '), 'last_name') ) users = User.objects.annotate( email_lower=Lower('email'), name_upper=Upper('name') ) # String functions posts = Post.objects.annotate( title_length=Length('title') ).filter(title_length__gt=50) # Substring posts = Post.objects.annotate( title_preview=Substr('title', 1, 50) ) # Coalesce (return first non-null value) posts = Post.objects.annotate( display_name=Coalesce('custom_title', 'title', Value('Untitled')) ) # Date functions from django.db.models.functions import TruncDate, TruncMonth, ExtractYear, Now posts = Post.objects.annotate( created_date=TruncDate('created_at'), created_month=TruncMonth('created_at'), created_year=ExtractYear('created_at') ) # Date arithmetic from datetime import timedelta from django.utils import timezone recent_posts = Post.objects.filter( created_at__gte=timezone.now() - timedelta(days=7) ) # Mathematical functions from django.db.models.functions import Abs, Ceil, Floor, Round products = Product.objects.annotate( price_rounded=Round('price'), discount_abs=Abs('discount') ) # Conditional expressions User.objects.annotate( user_type=Case( When(posts__count__gt=100, then=Value('power_user')), When(posts__count__gt=10, then=Value('active')), When(posts__count__gt=0, then=Value('casual')), default=Value('lurker'), output_field=CharField() ) ) # Complex conditional updates Post.objects.update( status=Case( When(Q(published=True) & Q(views__gt=1000), then=Value('viral')), When(Q(published=True) & Q(views__gt=100), then=Value('popular')), When(published=True, then=Value('published')), default=Value('draft'), output_field=CharField() ) )
Advanced Aggregation Patterns
Perform complex database-level calculations.
from django.db.models import ( Count, Sum, Avg, Max, Min, StdDev, Variance, Q, F, Value, CharField, When, Case ) from django.db.models.functions import Coalesce # Multiple aggregations with filters stats = Post.objects.aggregate( total_posts=Count('id'), published_posts=Count('id', filter=Q(published=True)), draft_posts=Count('id', filter=Q(published=False)), avg_views=Avg('views'), max_views=Max('views'), total_views=Sum('views'), std_dev_views=StdDev('views') ) # Conditional aggregation User.objects.aggregate( active_users=Count('id', filter=Q(is_active=True)), inactive_users=Count('id', filter=Q(is_active=False)), avg_posts_active=Avg('posts__count', filter=Q(is_active=True)) ) # Annotation with conditional aggregation users = User.objects.annotate( published_post_count=Count('posts', filter=Q(posts__published=True)), draft_post_count=Count('posts', filter=Q(posts__published=False)), total_views=Sum('posts__views'), avg_post_views=Avg('posts__views') ).filter(published_post_count__gt=0) # Group by with annotation from django.db.models.functions import TruncDate daily_stats = Post.objects.annotate( date=TruncDate('created_at') ).values('date').annotate( post_count=Count('id'), total_views=Sum('views'), avg_views=Avg('views') ).order_by('-date') # Subquery aggregation from django.db.models import OuterRef, Subquery # Get latest comment for each post latest_comment = Comment.objects.filter( post=OuterRef('pk') ).order_by('-created_at') posts = Post.objects.annotate( latest_comment_date=Subquery(latest_comment.values('created_at')[:1]), latest_comment_author=Subquery(latest_comment.values('author__name')[:1]) ) # Complex nested aggregation User.objects.annotate( total_post_views=Sum('posts__views'), total_comment_count=Count('posts__comments'), avg_comments_per_post=Case( When(posts__count=0, then=Value(0)), default=Count('posts__comments') / Count('posts', distinct=True) ) )
Database Indexes and Optimization
Optimize query performance with proper indexing.
class Post(models.Model): title = models.CharField(max_length=200, db_index=True) author = models.ForeignKey(User, on_delete=models.CASCADE) published = models.BooleanField(default=False) created_at = models.DateTimeField(auto_now_add=True) class Meta: indexes = [ # Single field models.Index(fields=['created_at']), # Composite index models.Index(fields=['author', 'published']), # Descending index models.Index(fields=['-created_at']), # Named index models.Index(fields=['title'], name='post_title_idx'), # Partial index (PostgreSQL) models.Index( fields=['author'], name='published_posts_idx', condition=models.Q(published=True) ), # Expression index (PostgreSQL) models.Index( Lower('title'), name='post_title_lower_idx' ), # Multi-column with includes (PostgreSQL) models.Index( fields=['author'], name='author_includes_idx', include=['title', 'created_at'] ), ] # Unique together unique_together = [['author', 'title']] # Constraints (Django 2.2+) constraints = [ models.UniqueConstraint( fields=['author', 'slug'], name='unique_author_slug' ), models.CheckConstraint( check=Q(views__gte=0), name='views_non_negative' ), ] # Query optimization techniques # Only load needed fields posts = Post.objects.only('id', 'title', 'author_id') # Defer heavy fields posts = Post.objects.defer('content', 'metadata') # Values and values_list for dictionaries/tuples post_data = Post.objects.values('id', 'title', 'author__name') post_ids = Post.objects.values_list('id', flat=True) # Combine optimizations posts = Post.objects.select_related('author').only( 'title', 'author__name' ).filter( published=True ) # Use iterator() for large querysets for post in Post.objects.iterator(chunk_size=1000): process_post(post) # Use explain() to analyze queries print(Post.objects.filter(published=True).explain(analyze=True))
Model Inheritance Patterns
Implement proper model inheritance strategies.
from django.db import models # Abstract base classes (no database table) class TimeStampedModel(models.Model): created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) class Meta: abstract = True class Post(TimeStampedModel): title = models.CharField(max_length=200) content = models.TextField() # Inherits created_at and updated_at # Multi-table inheritance (separate tables, joins required) class BaseContent(models.Model): title = models.CharField(max_length=200) created_at = models.DateTimeField(auto_now_add=True) class Article(BaseContent): # Has implicit OneToOne to BaseContent body = models.TextField() published = models.BooleanField(default=False) class Video(BaseContent): duration = models.IntegerField() video_url = models.URLField() # Proxy models (same table, different behavior) class PublishedPostManager(models.Manager): def get_queryset(self): return super().get_queryset().filter(published=True) class Post(models.Model): title = models.CharField(max_length=200) published = models.BooleanField(default=False) class Meta: ordering = ['-created_at'] class PublishedPost(Post): objects = PublishedPostManager() class Meta: proxy = True ordering = ['-created_at'] def publish(self): self.published = True self.save() # When to use each: # - Abstract: Share fields/methods, no polymorphic queries # - Multi-table: Need polymorphic queries, different fields # - Proxy: Same fields, different managers/methods
Advanced QuerySet Methods
Master advanced QuerySet operations.
# Bulk operations for performance posts = [ Post(title=f'Post {i}', author=user) for i in range(1000) ] Post.objects.bulk_create(posts, batch_size=100) # Bulk update (Django 2.2+) posts = Post.objects.filter(author=user) for post in posts: post.views += 1 Post.objects.bulk_update(posts, ['views'], batch_size=100) # Bulk create with returning IDs (PostgreSQL) posts = Post.objects.bulk_create(posts, batch_size=100, ignore_conflicts=True) # Update with F expressions (atomic, no race conditions) Post.objects.filter(id=1).update(views=F('views') + 1) # Get or create with complex lookups user, created = User.objects.get_or_create( email='user@example.com', defaults={ 'name': 'John Doe', 'is_active': True } ) # Update or create post, created = Post.objects.update_or_create( author=user, slug='my-post', defaults={ 'title': 'My Post', 'content': 'Updated content' } ) # In bulk (Django 4.1+) Post.objects.bulk_create( posts, update_conflicts=True, update_fields=['title', 'content'], unique_fields=['author', 'slug'] ) # Union, intersection, difference published = Post.objects.filter(published=True) featured = Post.objects.filter(featured=True) all_posts = published.union(featured) # Posts that are published OR featured both = published.intersection(featured) # Posts that are both only_published = published.difference(featured) # Published but not featured # Distinct authors = Post.objects.values('author').distinct() # With PostgreSQL distinct on posts = Post.objects.order_by('author', '-created_at').distinct('author') # Reverse queryset recent_first = Post.objects.order_by('-created_at') oldest_first = recent_first.reverse() # None queryset empty = Post.objects.none() # Returns empty queryset
Raw SQL When Needed
Use raw SQL for complex queries that ORM cannot handle efficiently.
# Raw queries users = User.objects.raw('SELECT * FROM app_user WHERE is_active = %s', [True]) for user in users: print(user.name) # Execute custom SQL from django.db import connection def get_user_stats(): with connection.cursor() as cursor: cursor.execute(""" SELECT u.id, u.name, COUNT(p.id) as post_count FROM app_user u LEFT JOIN app_post p ON p.author_id = u.id GROUP BY u.id, u.name HAVING COUNT(p.id) > 5 """) columns = [col[0] for col in cursor.description] return [dict(zip(columns, row)) for row in cursor.fetchall()] # Combining ORM with raw SQL User.objects.raw(""" SELECT * FROM app_user WHERE id IN ( SELECT DISTINCT author_id FROM app_post WHERE published = TRUE ) """)
Migrations Best Practices
Manage database schema changes safely and efficiently.
# Create migration # python manage.py makemigrations # Custom migration from django.db import migrations def forwards_func(apps, schema_editor): User = apps.get_model('app', 'User') for user in User.objects.all(): user.is_active = True user.save() def reverse_func(apps, schema_editor): pass class Migration(migrations.Migration): dependencies = [ ('app', '0001_initial'), ] operations = [ migrations.RunPython(forwards_func, reverse_func), ] # Add field with default class Migration(migrations.Migration): operations = [ migrations.AddField( model_name='user', name='status', field=models.CharField(max_length=20, default='active'), ), ] # Rename field operations = [ migrations.RenameField( model_name='user', old_name='name', new_name='full_name', ), ] # Add index operations = [ migrations.AddIndex( model_name='post', index=models.Index(fields=['author', 'created_at']), ), ]
When to Use This Skill
Use django-orm-patterns when building modern, production-ready applications that require advanced patterns, best practices, and optimal performance.
Signal Patterns and Best Practices
Use Django signals carefully for decoupled event handling.
from django.db.models.signals import post_save, pre_save, post_delete, m2m_changed from django.dispatch import receiver from django.db import transaction # Basic signal receiver @receiver(post_save, sender=Post) def post_created_handler(sender, instance, created, **kwargs): if created: # Send notification notify_followers(instance.author, instance) # Pre-save validation @receiver(pre_save, sender=User) def normalize_email(sender, instance, **kwargs): if instance.email: instance.email = instance.email.lower() # Conditional signal execution @receiver(post_save, sender=Post) def update_stats(sender, instance, created, update_fields, **kwargs): # Skip if only certain fields updated if update_fields and 'views' in update_fields: return # Update statistics instance.author.update_post_count() # M2M changed signal @receiver(m2m_changed, sender=Post.tags.through) def tags_changed(sender, instance, action, **kwargs): if action == 'post_add': # Tags were added pass elif action == 'post_remove': # Tags were removed pass # Avoid signals in transactions @receiver(post_save, sender=Order) def send_confirmation_email(sender, instance, created, **kwargs): if created: # Wait for transaction to commit transaction.on_commit(lambda: send_email(instance)) # Disconnect signals when needed from django.test import TestCase class PostTestCase(TestCase): def setUp(self): # Disconnect signal for testing post_save.disconnect(post_created_handler, sender=Post) def tearDown(self): # Reconnect signal post_save.connect(post_created_handler, sender=Post)
Custom Field Types
Create reusable custom field types for complex data.
from django.db import models import json # JSON field (before Django 3.1) class JSONField(models.TextField): def from_db_value(self, value, expression, connection): if value is None: return value return json.loads(value) def to_python(self, value): if isinstance(value, dict): return value if value is None: return value return json.loads(value) def get_prep_value(self, value): return json.dumps(value) # Encrypted field from cryptography.fernet import Fernet class EncryptedField(models.TextField): def __init__(self, *args, **kwargs): self.cipher_suite = Fernet(settings.FIELD_ENCRYPTION_KEY) super().__init__(*args, **kwargs) def from_db_value(self, value, expression, connection): if value is None: return value return self.cipher_suite.decrypt(value.encode()).decode() def get_prep_value(self, value): if value is None: return value return self.cipher_suite.encrypt(value.encode()).decode() # Usage class User(models.Model): metadata = JSONField(default=dict) ssn = EncryptedField() # Array field (PostgreSQL) from django.contrib.postgres.fields import ArrayField class Post(models.Model): tags = ArrayField(models.CharField(max_length=50), default=list) ratings = ArrayField(models.IntegerField(), default=list) class Meta: indexes = [ models.Index(fields=['tags']), ] # Query array fields posts = Post.objects.filter(tags__contains=['django']) posts = Post.objects.filter(tags__overlap=['python', 'django'])
Query Debugging and Profiling
Debug and optimize database queries effectively.
from django.db import connection, reset_queries from django.test.utils import override_settings import time # Log all queries @override_settings(DEBUG=True) def analyze_queries(func): def wrapper(*args, **kwargs): reset_queries() start = time.time() result = func(*args, **kwargs) end = time.time() print(f"Function: {func.__name__}") print(f"Number of queries: {len(connection.queries)}") print(f"Time taken: {end - start:.2f}s") for query in connection.queries: print(f"SQL: {query['sql']}") print(f"Time: {query['time']}s\n") return result return wrapper # Usage @analyze_queries def get_user_posts(user_id): user = User.objects.get(id=user_id) posts = user.posts.all() return list(posts) # Django Debug Toolbar integration # Add to INSTALLED_APPS INSTALLED_APPS = [ 'debug_toolbar', ] # Middleware MIDDLEWARE = [ 'debug_toolbar.middleware.DebugToolbarMiddleware', ] # Explain queries queryset = Post.objects.filter(published=True) print(queryset.explain()) # Basic explain print(queryset.explain(verbose=True)) # Verbose print(queryset.explain(analyze=True)) # Actually run query # Query count assertion in tests from django.test import TestCase from django.test.utils import override_settings class PostTestCase(TestCase): def test_query_count(self): with self.assertNumQueries(3): # Should execute exactly 3 queries user = User.objects.get(id=1) posts = list(user.posts.all()) comments = list(Comment.objects.filter(post__in=posts)) # Find duplicate queries def find_duplicate_queries(): from collections import Counter queries = [q['sql'] for q in connection.queries] duplicates = [q for q, count in Counter(queries).items() if count > 1] for sql in duplicates: print(f"Duplicate query: {sql}")
Advanced Manager Patterns
Build sophisticated custom managers for complex business logic.
from django.db import models from django.db.models import Q, Count, Avg class PostQuerySet(models.QuerySet): def published(self): return self.filter(published=True) def draft(self): return self.filter(published=False) def by_author(self, author): return self.filter(author=author) def popular(self, min_views=100): return self.filter(views__gte=min_views) def recent(self, days=7): from django.utils import timezone from datetime import timedelta cutoff = timezone.now() - timedelta(days=days) return self.filter(created_at__gte=cutoff) def with_stats(self): return self.annotate( comment_count=Count('comments'), avg_rating=Avg('ratings__score') ) def optimized(self): return self.select_related('author').prefetch_related('comments') class PostManager(models.Manager.from_queryset(PostQuerySet)): def get_queryset(self): return super().get_queryset().filter(deleted_at__isnull=True) def with_deleted(self): return super().get_queryset() class Post(models.Model): title = models.CharField(max_length=200) author = models.ForeignKey(User, on_delete=models.CASCADE) published = models.BooleanField(default=False) views = models.IntegerField(default=0) deleted_at = models.DateTimeField(null=True, blank=True) created_at = models.DateTimeField(auto_now_add=True) objects = PostManager() def soft_delete(self): from django.utils import timezone self.deleted_at = timezone.now() self.save() # Usage - methods chain naturally recent_popular = Post.objects.published().recent().popular().with_stats() author_drafts = Post.objects.by_author(user).draft().optimized() # Multiple manager pattern class AllPostsManager(models.Manager): def get_queryset(self): return super().get_queryset() class Post(models.Model): # ... fields ... objects = PostManager() # Default, excludes deleted all_objects = AllPostsManager() # Includes deleted
Database-Specific Features
Leverage PostgreSQL-specific features when available.
from django.contrib.postgres.fields import ArrayField, JSONField, HStoreField from django.contrib.postgres.search import SearchVector, SearchQuery, SearchRank from django.contrib.postgres.aggregates import ArrayAgg, StringAgg # Full-text search class Post(models.Model): title = models.CharField(max_length=200) content = models.TextField() search_vector = SearchVectorField(null=True) class Meta: indexes = [ GinIndex(fields=['search_vector']), ] # Update search vector from django.contrib.postgres.search import SearchVector Post.objects.update( search_vector=SearchVector('title', weight='A') + SearchVector('content', weight='B') ) # Search query = SearchQuery('django') posts = Post.objects.annotate( rank=SearchRank('search_vector', query) ).filter(search_vector=query).order_by('-rank') # Array aggregation authors = User.objects.annotate( post_titles=ArrayAgg('posts__title', distinct=True), tags_list=StringAgg('posts__tags', delimiter=', ', distinct=True) ) # JSON operations from django.contrib.postgres.fields.jsonb import KeyTextTransform users = User.objects.annotate( city=KeyTextTransform('city', 'metadata') ).filter(city='New York') # Range fields from django.contrib.postgres.fields import IntegerRangeField, DateRangeField class Event(models.Model): name = models.CharField(max_length=200) date_range = DateRangeField() capacity = IntegerRangeField() from django.db.models import Q from datetime import date # Find events happening on a specific date events = Event.objects.filter(date_range__contains=date(2024, 1, 15)) # Find overlapping events events = Event.objects.filter( date_range__overlap=(date(2024, 1, 1), date(2024, 1, 31)) )
Django ORM Best Practices
- Use select_related and prefetch_related - Always optimize queries to prevent N+1 problems
- Index frequently queried fields - Add database indexes for fields used in filters and joins
- Use get_or_create carefully - Wrap in transactions when dealing with race conditions
- Avoid queries in loops - Batch operations and use bulk methods when possible
- Use only() and defer() wisely - Load only necessary fields for large models
- Leverage F() expressions - Use database-level operations to avoid race conditions
- Use transactions for data integrity - Wrap related operations in atomic blocks
- Create custom managers - Encapsulate common query patterns in reusable managers
- Use exists() for checks - More efficient than count() when only checking existence
- Monitor query performance - Use django-debug-toolbar to identify slow queries
- Implement soft deletes with managers - Use custom managers to hide deleted records
- Use database functions - Leverage Django's database functions for complex operations
- Batch database operations - Use bulk_create and bulk_update for large datasets
- Use iterator() for large datasets - Avoid loading entire querysets into memory
- Apply database constraints - Use CheckConstraint and UniqueConstraint for data integrity
Django ORM Common Pitfalls
- N+1 query problem - Forgetting to use select_related or prefetch_related causes excessive queries
- Loading too much data - Using .all() without pagination can cause memory issues
- Inefficient updates - Using save() in loops instead of bulk_update or update()
- Missing database indexes - Slow queries on unindexed fields in large tables
- Incorrect use of get() - Not handling DoesNotExist or MultipleObjectsReturned exceptions
- Lazy evaluation confusion - Querysets are lazy; understand when they actually execute
- Transaction isolation issues - Not using select_for_update when needed for locking
- Mixing F() with save() - Must call refresh_from_db() after saving F() expressions
- Inefficient aggregations - Running Python calculations instead of database aggregations
- Migration conflicts - Not coordinating migrations in team environments
- Signal performance issues - Signals in tight loops can cause performance problems
- Overusing signals - Prefer explicit calls over implicit signal-based logic
- Not using transactions with signals - Signals fire before transaction commit by default
- Incorrect distinct() usage - Using distinct() without understanding its implications
- Ignoring database-specific features - Missing out on PostgreSQL full-text search, arrays, etc.