# Django Patterns

> When to activate: Django, Django ORM, DRF, views, serializers, migrations, Django admin, class-based views

- Skill: `mattakushi432/django-patterns` (Agent Skill)
- Install (CLI): `npx skillmds@latest add mattakushi432/django-patterns`
- Raw SKILL.md: https://api.skillmd.com/api/skills/mattakushi432/django-patterns/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: Mattakushi432 (https://skillmd.com/u/mattakushi432)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/mattakushi432/django-patterns

---


# 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
```python
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, on_delete=models.PROTECT, 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
```python
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
```python
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
```python
# 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

