django-models
Django Model Patterns
Core Philosophy: Fat Models, Thin Views
Business logic belongs in models and managers, not views. Views orchestrate workflows; models implement domain behavior. This principle creates testable, reusable code that stays maintainable as complexity grows.
Good: Model methods handle business rules, state transitions, validation Bad: Views contain if/else logic for domain rules, calculate derived values
Model Design
Structure Your Models Around Domain Concepts
- Use
TextChoices/IntegerChoicesfor status fields and enums - Add
get_absolute_url()for canonical object URLs - Include
__str__()for readable representations - Set proper
orderingin Meta for consistent default sorting - Add database indexes for frequently filtered/sorted fields
- Use abstract base models for shared fields (timestamps, soft deletes, etc.)
Field Selection Guidelines
- Use
blank=True, default=""for optional text fields (avoid null) - Use
null=True, blank=Truefor optional foreign keys - For unique optional fields, use
null=Trueto avoid collision issues - Leverage
JSONFieldfor flexible metadata (avoid creating many optional fields) - Set appropriate
max_lengthbased on actual data needs
Encapsulate Business Logic in Model Methods
- State transitions:
post.publish(),order.cancel() - Permission checks:
post.is_editable_by(user) - Complex calculations:
invoice.calculate_total() - Use properties for computed read-only values
- Specify
update_fieldswhen saving partial changes
QuerySet Patterns: The Power of Composition
Custom QuerySet classes are your secret weapon. They make queries reusable, chainable, and testable.
Pattern: QuerySet as Manager
Define a QuerySet subclass with domain-specific filter methods
Attach it to your model: objects = YourQuerySet.as_manager()
Chain methods for composable queries
Benefits
- Reusable query logic across views, tasks, management commands
- Chainable methods enable expressive, readable queries
- Easy to test in isolation
- Encapsulates query complexity away from views
Common QuerySet Methods
- Filtering by status/state
- Date range queries (recent, upcoming, expired)
- User-scoped queries (owned_by, visible_to)
- Combined lookups (published_and_recent)
Query Optimization: Avoid N+1 Queries
The Golden Rules
- select_related(): Use for ForeignKey and OneToOneField (creates SQL JOIN)
- prefetch_related(): Use for ManyToManyField and reverse ForeignKeys (separate query + Python join)
- only(): Load specific fields when you don't need the whole object
- defer(): Exclude heavy fields (TextField, JSONField) you won't use
- Prefetch() object: Customize prefetch with filters and select_related
Efficient Counting and Existence Checks
- Use
.exists()instead ofif queryset:orif len(queryset): - Use
.count()instead oflen(queryset.all()) - Both perform database-level operations without loading objects
Aggregation and Annotation
annotate(): Add computed fields to each object (Count, Sum, Avg, etc.)aggregate(): Compute values across entire queryset- Use
F()expressions for database-level updates (views=F('views') + 1) - Combine annotate with filter for "objects with at least N related items"
Managers vs QuerySets
Use QuerySets for chainable query logic. Use Managers for model-level operations that don't return querysets.
Manager: Think "factory methods" - User.objects.create_user()
QuerySet: Think "filters and transformations" - Post.objects.published().recent()
Most of the time, you want a custom QuerySet, not a custom Manager.
Signals: Use Sparingly
Signals create implicit coupling and make code harder to follow. Prefer explicit method calls.
When Signals Make Sense
- Audit logging (track all changes to a model)
- Cache invalidation (clear cache when model changes)
- Decoupling apps (third-party app needs to react to your models)
When to Avoid Signals
- Business logic that should be in model methods
- Logic tightly coupled to the calling code (just call the function directly)
- Complex workflows (use explicit service layer instead)
Rule of thumb: If you control both the trigger and the reaction, don't use a signal.
Migrations
Workflow
- Run
makemigrationsafter model changes - Review generated migration files before applying
- Run
migrateto apply migrations - Migrations should be reversible when possible
Data Migrations
Create with makemigrations --empty app_name. Use apps.get_model() to access models (not direct imports). Write both forward and reverse operations.
Use data migrations for: Populating new fields, transforming data, migrating between fields.
Anti-Patterns to Avoid
Query Anti-Patterns
- Iterating over objects and accessing relations without
select_related()/prefetch_related() - Using
if queryset:instead of.exists() - Using
len()to count instead of.count() - Loading entire objects when you only need specific fields
Design Anti-Patterns
- Business logic in views instead of models
- Views performing calculations that belong in model methods
- Overusing signals for synchronous operations
- Creating new models when JSONField would suffice
- Forgetting to add indexes for filtered/sorted fields
Integration
Works with:
- pytest-django-patterns: Factory-based model testing
- celery-patterns: Async operations on models (pass IDs, not instances)
- django-forms: ModelForm validation and saving
More from kjnez/claude-code-django
htmx-patterns
HTMX patterns for Django including partial templates, hx-* attributes, and dynamic UI without JavaScript. Use when building interactive UI, handling AJAX requests, or creating dynamic components.
31django-templates
Django template patterns including inheritance, partials, tags, and filters. Use when working with templates, creating reusable components, or organizing template structure.
30pytest-django-patterns
pytest-django testing patterns, Factory Boy, fixtures, and TDD workflow. Use when writing tests, creating test factories, or following TDD red-green-refactor cycle.
24django-forms
Django form handling patterns including ModelForm, validation, clean methods, and HTMX form submission. Use when building forms, implementing validation, or handling form submission.
16celery-patterns
Celery task patterns including task definition, retry strategies, periodic tasks, and best practices. Use when implementing background tasks, scheduled jobs, or async processing.
15django-extensions
Django-extensions management commands for project introspection, debugging, and development. Use when exploring URLs, models, settings, database schema, running scripts, or profiling performance. Triggers on questions about Django project structure, model fields, URL routes, or requests to run development servers.
12