

Also from Kynth Studios


Also from Kynth Studios


Also from Kynth Studios
1# Django REST Framework — Cursor Rules2# Comprehensive rules for building APIs with Django and DRF34## Project Context5You are working on a Django application with Django REST Framework (DRF) for building6APIs. The project follows Django conventions, uses class-based views, and leverages7DRF's serialization, authentication, and permission systems. The codebase is structured8as a collection of Django apps with clear separation of concerns.910## Tech Stack11- Python 3.11+12- Django 5.0+13- Django REST Framework 3.15+14- PostgreSQL (recommended) or SQLite for development15- Celery for background tasks (with Redis broker)16- django-filter for queryset filtering17- drf-spectacular for OpenAPI schema generation18- pytest-django for testing1920## Coding Style2122### Naming Conventions23- Django apps: short snake_case nouns (e.g., `users`, `orders`, `payments`)24- Models: PascalCase singular (e.g., `User`, `Order`, `OrderItem`)25- Serializers: PascalCase with `Serializer` suffix (e.g., `UserSerializer`, `OrderCreateSerializer`)26- Views/ViewSets: PascalCase with `ViewSet` or `View` suffix (e.g., `UserViewSet`, `LoginView`)27- URL patterns: kebab-case (e.g., `/api/order-items/`, `/api/user-profiles/`)28- Template tags: snake_case (e.g., `{% user_avatar %}`)29- Management commands: snake_case (e.g., `sync_products`, `send_reports`)30- Signals: past tense (e.g., `order_created`, `payment_processed`)3132### Project Structure33```34project/35 config/ # Project settings36 settings/37 base.py38 local.py39 production.py40 urls.py41 wsgi.py42 celery.py43 apps/44 users/45 models.py46 serializers.py47 views.py48 urls.py49 admin.py50 signals.py51 services.py # Business logic (not in views or models)52 tests/53 test_models.py54 test_views.py55 test_services.py56 factories.py # Test data factories57 orders/58 ...59 common/ # Shared utilities60 permissions.py61 pagination.py62 exceptions.py63 mixins.py64```6566## Model Patterns6768### Model Definition69```python70from django.db import models71from django.utils import timezone7273class Order(models.Model):74 class Status(models.TextChoices):75 PENDING = "pending", "Pending"76 CONFIRMED = "confirmed", "Confirmed"77 SHIPPED = "shipped", "Shipped"78 DELIVERED = "delivered", "Delivered"79 CANCELLED = "cancelled", "Cancelled"8081 user = models.ForeignKey("users.User", on_delete=models.CASCADE, related_name="orders")82 status = models.CharField(max_length=20, choices=Status.choices, default=Status.PENDING)83 total = models.DecimalField(max_digits=10, decimal_places=2)84 created_at = models.DateTimeField(auto_now_add=True)85 updated_at = models.DateTimeField(auto_now=True)8687 class Meta:88 ordering = ["-created_at"]89 indexes = [90 models.Index(fields=["user", "status"]),91 models.Index(fields=["-created_at"]),92 ]9394 def __str__(self):95 return f"Order #{self.pk} - {self.user}"9697 @property98 def is_cancellable(self):99 return self.status in (self.Status.PENDING, self.Status.CONFIRMED)100```101102### Model Rules103- Keep models focused on data structure and simple properties104- Use `TextChoices` / `IntegerChoices` for choice fields105- Always define `__str__`, `Meta.ordering`, and relevant indexes106- Use `related_name` on all ForeignKey and M2M fields107- Move complex business logic to services, not model methods108- Use `F()` and `Q()` objects for complex queries109- Avoid `null=True` on string fields — use `blank=True` with default `""`110111## Serializer Patterns112113### Separate Read/Write Serializers114```python115class OrderListSerializer(serializers.ModelSerializer):116 user = UserMinimalSerializer(read_only=True)117 status_display = serializers.CharField(source="get_status_display", read_only=True)118119 class Meta:120 model = Order121 fields = ["id", "user", "status", "status_display", "total", "created_at"]122123class OrderCreateSerializer(serializers.ModelSerializer):124 class Meta:125 model = Order126 fields = ["items", "shipping_address"]127128 def validate_items(self, value):129 if not value:130 raise serializers.ValidationError("Order must have at least one item.")131 return value132133 def create(self, validated_data):134 # Delegate complex creation logic to a service135 return OrderService.create_order(136 user=self.context["request"].user,137 **validated_data,138 )139```140141## ViewSet Patterns142```python143from rest_framework import viewsets, permissions, status144from rest_framework.decorators import action145from rest_framework.response import Response146from django_filters.rest_framework import DjangoFilterBackend147148class OrderViewSet(viewsets.ModelViewSet):149 permission_classes = [permissions.IsAuthenticated]150 filter_backends = [DjangoFilterBackend, filters.OrderingFilter]151 filterset_fields = ["status"]152 ordering_fields = ["created_at", "total"]153 ordering = ["-created_at"]154155 def get_queryset(self):156 return Order.objects.filter(user=self.request.user).select_related("user")157158 def get_serializer_class(self):159 if self.action == "create":160 return OrderCreateSerializer161 return OrderListSerializer162163 @action(detail=True, methods=["post"])164 def cancel(self, request, pk=None):165 order = self.get_object()166 if not order.is_cancellable:167 return Response({"error": "Order cannot be cancelled"}, status=status.HTTP_400_BAD_REQUEST)168 OrderService.cancel_order(order)169 return Response(OrderListSerializer(order).data)170```171172## Service Layer Pattern173```python174# services.py — keep business logic out of views and models175class OrderService:176 @staticmethod177 def create_order(user, items, shipping_address):178 with transaction.atomic():179 order = Order.objects.create(user=user, total=0, shipping_address=shipping_address)180 total = Decimal("0")181 for item_data in items:182 product = Product.objects.select_for_update().get(id=item_data["product_id"])183 if product.stock < item_data["quantity"]:184 raise ValidationError(f"Insufficient stock for {product.name}")185 OrderItem.objects.create(order=order, product=product, quantity=item_data["quantity"], price=product.price)186 product.stock -= item_data["quantity"]187 product.save()188 total += product.price * item_data["quantity"]189 order.total = total190 order.save()191 order_created.send(sender=Order, order=order)192 return order193```194195## Error Handling196- Use DRF's built-in exception handling (`ValidationError`, `NotFound`, `PermissionDenied`)197- Create custom exception handler for consistent error format198- Use `transaction.atomic()` for operations that must be all-or-nothing199- Return structured error responses: `{"error": "message", "code": "ERROR_CODE"}`200- Log exceptions with full context in production201202## Security203- Always set `permission_classes` on views — never leave them open204- Use `select_related` / `prefetch_related` to prevent N+1 (and info leaks via lazy loading)205- Filter querysets by the authenticated user — never trust URL params alone206- Use `@action(permission_classes=[...])` for custom action permissions207- Validate file uploads: size, type, and content208- Use Django's CSRF protection for session-based auth209- Throttle API endpoints with DRF's `throttle_classes`210211## Testing212```python213import pytest214from rest_framework.test import APIClient215from apps.users.tests.factories import UserFactory216217@pytest.fixture218def api_client():219 return APIClient()220221@pytest.fixture222def authenticated_client(api_client):223 user = UserFactory()224 api_client.force_authenticate(user=user)225 return api_client, user226227@pytest.mark.django_db228def test_create_order(authenticated_client):229 client, user = authenticated_client230 response = client.post("/api/orders/", {"items": [{"product_id": 1, "quantity": 2}]}, format="json")231 assert response.status_code == 201232 assert Order.objects.filter(user=user).count() == 1233```234235## Performance Guidelines236- Use `select_related()` for ForeignKey joins, `prefetch_related()` for reverse/M2M237- Use `only()` / `defer()` for large models when you need few fields238- Implement cursor-based pagination for large datasets239- Cache expensive queries with Django's cache framework240- Use `bulk_create()` and `bulk_update()` for batch operations241- Run slow tasks async with Celery242- Use database indexes on filtered and ordered fields243244## Common Pitfalls245- N+1 queries from accessing related objects without `select_related`/`prefetch_related`246- Not filtering querysets by user — returning other users' data247- Fat views with business logic — extract to services248- Using `ModelSerializer` for both reads and writes when they need different fields249- Forgetting `@pytest.mark.django_db` on database tests250- Not using `transaction.atomic()` for multi-step writes251- Overriding `get_queryset()` but not calling `super()` when needed252- Exposing sensitive fields (password hash, tokens) in serializers253
One repository carrying more than one format is the comparison this product exists for: does anyone actually write different content in each file, or is one a copy of the other?
| Repository | Format | Stack | Covers | Score | Changed |
|---|---|---|---|---|---|
| survivorforge/cursor-rulesrules/ai-ml-python/.cursorrules · 17 | .cursorrules | teststylearchdeployment+2 | 81/100 | 13 days ago | |
| survivorforge/cursor-rulesrules/api-microservices/.cursorrules · 17 | .cursorrules | buildteststylearch+5 | 92/100 | 13 days ago | |
| survivorforge/cursor-rulesrules/database-sql/.cursorrules · 17 | .cursorrules | styletypessecuritydatabase+3 | 65/100 | 13 days ago | |
| survivorforge/cursor-rulesrules/devops-docker/.cursorrules · 17 | .cursorrules | setupbuildteststyle+4 | 93/100 | 13 days ago | |
| survivorforge/cursor-rulesrules/devops-infrastructure/.cursorrules · 17 | .cursorrules | buildteststylesecurity+3 | 93/100 | 13 days ago | |
| survivorforge/cursor-rulesrules/docker-devops/.cursorrules · 17 | .cursorrules | setupteststylearch+6 | 85/100 | 13 days ago | |
| survivorforge/cursor-rulesrules/flutter-dart/.cursorrules · 17 | .cursorrules | teststylearchtypes+5 | 89/100 | 13 days ago | |
| survivorforge/cursor-rulesrules/go-gin/.cursorrules · 17 | .cursorrules | testlint-formatstylearch+5 | 84/100 | 13 days ago | |
| survivorforge/cursor-rulesrules/langchain-ai/.cursorrules · 17 | .cursorrules | testlint-formatstylearch+4 | 84/100 | 13 days ago | |
| survivorforge/cursor-rulesrules/mobile-react-native/.cursorrules · 17 | .cursorrules | teststylearchtypes+7 | 89/100 | 13 days ago | |
| survivorforge/cursor-rulesrules/nextjs-14-app-router/.cursorrules · 17 | .cursorrules | teststyletypestesting-strategy+3 | 71/100 | 13 days ago | |
| survivorforge/cursor-rulesrules/nextjs-app-router/.cursorrules · 17 | .cursorrules | teststylearchtypes+5 | 81/100 | 13 days ago | |
| survivorforge/cursor-rulesrules/nextjs-typescript/.cursorrules · 17 | .cursorrules | teststylearchtypes+5 | 81/100 | 13 days ago | |
| survivorforge/cursor-rulesrules/nextjs/.cursorrules · 17 | .cursorrules | teststylearchtypes+5 | 81/100 | 13 days ago | |
| survivorforge/cursor-rulesrules/nodejs-express-typescript/.cursorrules · 17 | .cursorrules | setupteststylearch+7 | 81/100 | 13 days ago | |
| survivorforge/cursor-rulesrules/nodejs-express/.cursorrules · 17 | .cursorrules | testlint-formatstylearch+7 | 92/100 | 13 days ago | |
| survivorforge/cursor-rulesrules/performance-optimization/.cursorrules · 17 | .cursorrules | styledatabaseapiperformance+2 | 65/100 | 13 days ago | |
| survivorforge/cursor-rulesrules/python-django/.cursorrules · 17 | .cursorrules | testlint-formatstylearch+6 | 99/100 | 13 days ago | |
| survivorforge/cursor-rulesrules/python-fastapi/.cursorrules · 17 | .cursorrules | testlint-formatstylearch+6 | 99/100 | 13 days ago | |
| survivorforge/cursor-rulesrules/python-modern/.cursorrules · 17 | .cursorrules | testlint-formatstyletypes+3 | 88/100 | 13 days ago |
A badge carrying the measured quality of the strongest agent config file in this repository, out of 100. It reads from this index every time somebody loads your page, so it changes when the measurement changes and there is nothing to keep up to date. Free, no account, and the value is not something you or we can set by hand.
[](https://rulestack.kynth.studio/configs/survivorforge-cursor-rules-rules-django-rest-cursorrules)Would rather not hotlink us? Every badge is also served in shields.io’s endpoint schema, so shields renders the image and your readers never talk to our domain:
Published by Toolproof, the masthead over this index and eight others. The method behind the number is at toolproof.kynth.studio/methodology, and the whole thing is readable as JSON with no key at /api.