Django Patterns
Project Layout
myproject/
├── config/
│ ├── settings/
│ │ ├── base.py
│ │ ├── development.py
│ │ └── production.py
│ ├── urls.py
│ └── wsgi.py
├── apps/
│ ├── users/
│ │ ├── models.py
│ │ ├── views.py
│ │ ├── serializers.py
│ │ ├── urls.py
│ │ ├── admin.py
│ │ └── tests/
│ └── orders/
└── manage.py
Model Best Practices
from django.db import models
from django.contrib.auth import get_user_model
User = get_user_model() # always use get_user_model(), not direct import
class TimestampedModel(models.Model):
"""Abstract base for created_at/updated_at."""
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
class Meta:
abstract = True
class Order(TimestampedModel):
user = models.ForeignKey(User, related_name="orders")
status = models.CharField(
max_length=20,
choices=[("pending", "Pending"), ("complete", "Complete"), ("cancelled", "Cancelled")],
default="pending",
db_index=True,
)
total_amount = models.DecimalField(max_digits=10, decimal_places=2)
class Meta:
ordering = ["-created_at"]
indexes = [models.Index(fields=["user", "status"])]
def __str__(self) -> str:
return f"Order #{self.pk} ({self.status})"
DRF Serializers
from rest_framework import serializers
class OrderSerializer(serializers.ModelSerializer):
user_name = serializers.CharField(source="user.full_name", read_only=True)
class Meta:
model = Order
fields = ["id", "user_name", "status", "total_amount", "created_at"]
read_only_fields = ["id", "created_at"]
def validate_total_amount(self, value):
if value <= 0:
raise serializers.ValidationError("Amount must be positive")
return value
class OrderCreateSerializer(serializers.ModelSerializer):
class Meta:
model = Order
fields = ["status", "total_amount"]
def create(self, validated_data):
validated_data["user"] = self.context["request"].user
return super().create(validated_data)
DRF ViewSets
from rest_framework import viewsets, permissions, filters
from django_filters.rest_framework import DjangoFilterBackend
class OrderViewSet(viewsets.ModelViewSet):
permission_classes = [permissions.IsAuthenticated]
filter_backends = [DjangoFilterBackend, filters.OrderingFilter]
filterset_fields = ["status"]
ordering_fields = ["created_at", "total_amount"]
ordering = ["-created_at"]
def get_queryset(self):
# Always scope to current user
return Order.objects.filter(user=self.request.user).select_related("user")
def get_serializer_class(self):
if self.action == "create":
return OrderCreateSerializer
return OrderSerializer
Database Optimization
# Avoid N+1 with select_related (FK) and prefetch_related (M2M/reverse FK)
orders = Order.objects.filter(status="pending")\
.select_related("user")\
.prefetch_related("items__product")\
.only("id", "status", "created_at", "user__email") # only needed fields
# Bulk operations
Order.objects.bulk_create([Order(**data) for data in order_data_list], batch_size=500)
Order.objects.filter(status="pending").update(status="processing") # no Python loop
# Use values() for read-only aggregations
from django.db.models import Count, Avg
stats = Order.objects.values("status").annotate(count=Count("id"), avg=Avg("total_amount"))
Anti-Patterns
- Calling
Model.objects.all() without .filter() on large tables
- N+1 queries — always check Django Debug Toolbar
- Logic in templates or views instead of model methods
on_delete=models.CASCADE without thinking about consequences
- Missing
select_related in list views