django-orm-patterns

安装量: 72
排名: #10741

安装

npx skills add https://github.com/thebushidocollective/han --skill django-orm-patterns

Django 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:

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. Resources Django ORM Documentation Django QuerySet API Reference Django Database Optimization Django Migration Guide Django Database Transactions

返回排行榜