| name | django |
| description | Load when writing, reviewing, or debugging Django code โ models, views, DRF serializers, viewsets, URL routing, signals, or admin. Covers the patterns Claude consistently gets wrong: queryset filtering, N+1 via ORM relationships, DRF serializer validation, permission classes, and queryset ownership scoping. Load alongside testing-django when writing tests.
|
Django: What Claude Gets Wrong in Production Code
This skill covers Django 4.x+ with Django REST Framework. It's a correction guide,
not a tutorial.
Queryset Scoping: Filter by Ownership in get_queryset()
The most common DRF mistake: returning all objects to all authenticated users.
Ownership filtering belongs in get_queryset(), not in individual actions.
class OrderViewSet(viewsets.ModelViewSet):
queryset = Order.objects.all()
serializer_class = OrderSerializer
permission_classes = [IsAuthenticated]
class OrderViewSet(viewsets.ModelViewSet):
serializer_class = OrderSerializer
permission_classes = [IsAuthenticated]
def get_queryset(self):
return Order.objects.filter(user=self.request.user)
N+1 Queries: select_related and prefetch_related
Django's ORM lazy-loads relationships. Accessing a FK or M2M field on 100 objects
fires 100 queries.
orders = Order.objects.all()
for order in orders:
print(order.user.email)
orders = Order.objects.select_related("user").all()
orders = Order.objects.prefetch_related("items__product").all()
orders = (
Order.objects
.select_related("user")
.prefetch_related("items__product", "tags")
.filter(status="pending")
)
Use Django Debug Toolbar or django.db.connection.queries to verify query count.
Serializers: Explicit Fields, Never __all__
fields = "__all__" exposes every model field including password hashes, internal
fields, and future fields you haven't considered yet.
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = "__all__"
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ["id", "email", "name", "created_at"]
read_only_fields = ["id", "created_at"]
class UserCreateSerializer(serializers.ModelSerializer):
password = serializers.CharField(write_only=True, min_length=8)
class Meta:
model = User
fields = ["email", "name", "password"]
def create(self, validated_data):
return User.objects.create_user(**validated_data)
perform_create: Inject Request Context
Auto-populated fields (current user, parent FK) belong in perform_create, not
in the serializer.
class CommentSerializer(serializers.ModelSerializer):
class Meta:
model = Comment
fields = ["id", "order", "author", "content"]
class CommentViewSet(viewsets.ModelViewSet):
def perform_create(self, serializer):
serializer.save(author=self.request.user)
class CommentSerializer(serializers.ModelSerializer):
author = UserSerializer(read_only=True)
class Meta:
model = Comment
fields = ["id", "order", "author", "content"]
Object-Level Permissions: Custom Permission Classes
DRF's IsAuthenticated checks that a user is logged in. For "can this user touch
this object," write a custom permission class.
class CommentViewSet(viewsets.ModelViewSet):
def destroy(self, request, pk=None):
comment = self.get_object()
if comment.author != request.user and not request.user.is_staff:
return Response(status=403)
comment.delete()
return Response(status=204)
class IsOwnerOrAdmin(permissions.BasePermission):
def has_object_permission(self, request, view, obj):
if request.user.is_staff:
return True
return obj.author == request.user
class CommentViewSet(viewsets.ModelViewSet):
permission_classes = [IsAuthenticated, IsOwnerOrAdmin]
Model Design: Explicit on_delete, Indexes, __str__
class Order(models.Model):
user = models.ForeignKey(User)
product = models.ForeignKey(Product)
created_at = models.DateTimeField(auto_now_add=True)
import uuid
class Order(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
user = models.ForeignKey(
User,
on_delete=models.CASCADE,
related_name="orders",
db_index=True,
)
status = models.CharField(
max_length=20,
choices=[("pending", "Pending"), ("confirmed", "Confirmed"), ("shipped", "Shipped")],
default="pending",
db_index=True,
)
created_at = models.DateTimeField(auto_now_add=True)
class Meta:
ordering = ["-created_at"]
indexes = [
models.Index(fields=["user", "status"]),
]
def __str__(self) -> str:
return f"Order {self.id} by {self.user_id} ({self.status})"
Signals: Use Sparingly, Document Why
Signals create invisible coupling. Prefer explicit method calls over signals for
anything that's part of a predictable business flow.
@receiver(post_save, sender=Order)
def send_confirmation(sender, instance, created, **kwargs):
if created:
send_email(instance.user.email, ...)
@receiver(post_save, sender=Product)
def invalidate_product_cache(sender, instance, **kwargs):
cache.delete(f"product:{instance.id}")
def create_order(user, payload) -> Order:
order = Order.objects.create(user=user, ...)
send_confirmation_email(order)
return order
get_object_or_404 vs get()
order = Order.objects.get(id=order_id)
order = Order.objects.filter(id=order_id).first()
if not order:
return Response(status=404)
from django.shortcuts import get_object_or_404
order = get_object_or_404(Order, id=order_id, user=request.user)