Install
$ agentstack add skill-versoxbt-claude-initial-setup-drf-api-patterns ✓ scanned · ✓ verified — works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ✓ Network access No
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ✓ Environment & secrets No
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
About
DRF API Patterns
Build production-grade REST APIs with Django REST Framework. DRF provides serialization, authentication, permissions, pagination, and more out of the box, so you write less boilerplate and get consistent API behavior.
When to Use
- User builds REST API endpoints in Django
- User needs serialization, validation, or pagination
- User implements authentication and permissions
- User asks about ViewSets, routers, or DRF best practices
- User needs filtering, search, or ordering on list endpoints
Core Patterns
Serializers
Serializers handle validation and conversion between Python objects and JSON.
from rest_framework import serializers
class ArticleSerializer(serializers.ModelSerializer):
author_name = serializers.CharField(source="author.get_full_name", read_only=True)
comment_count = serializers.IntegerField(read_only=True)
tags = serializers.SlugRelatedField(
many=True, slug_field="name", queryset=Tag.objects.all()
)
class Meta:
model = Article
fields = [
"id", "title", "slug", "body", "status",
"author", "author_name", "tags", "comment_count",
"created_at", "updated_at",
]
read_only_fields = ["id", "slug", "created_at", "updated_at"]
def validate_title(self, value: str) -> str:
if len(value) dict:
"""Cross-field validation."""
if attrs.get("status") == "published" and not attrs.get("body"):
raise serializers.ValidationError(
{"body": "Published articles must have a body"}
)
return attrs
# Separate serializers for create vs read
class ArticleCreateSerializer(serializers.ModelSerializer):
class Meta:
model = Article
fields = ["title", "body", "tags", "status"]
def create(self, validated_data):
tags = validated_data.pop("tags", [])
article = Article.objects.create(
author=self.context["request"].user,
**validated_data,
)
article.tags.set(tags)
return article
ViewSets and Routers
ViewSets combine list, create, retrieve, update, and destroy into one class.
from rest_framework import viewsets, status
from rest_framework.decorators import action
from rest_framework.response import Response
from django.db.models import Count
class ArticleViewSet(viewsets.ModelViewSet):
serializer_class = ArticleSerializer
lookup_field = "slug"
def get_queryset(self):
return (
Article.objects
.select_related("author")
.prefetch_related("tags")
.annotate(comment_count=Count("comments"))
.order_by("-created_at")
)
def get_serializer_class(self):
if self.action == "create":
return ArticleCreateSerializer
return ArticleSerializer
@action(detail=True, methods=["post"])
def publish(self, request, slug=None):
article = self.get_object()
article.status = "published"
article.save(update_fields=["status"])
return Response({"status": "published"})
@action(detail=False, methods=["get"])
def trending(self, request):
qs = self.get_queryset().filter(
status="published"
).order_by("-view_count")[:10]
serializer = self.get_serializer(qs, many=True)
return Response(serializer.data)
# urls.py
from rest_framework.routers import DefaultRouter
router = DefaultRouter()
router.register("articles", ArticleViewSet, basename="article")
urlpatterns = [
path("api/v1/", include(router.urls)),
]
Permissions
from rest_framework.permissions import BasePermission, IsAuthenticated, SAFE_METHODS
class IsAuthorOrReadOnly(BasePermission):
"""Allow authors to edit, everyone else can only read."""
def has_object_permission(self, request, view, obj):
if request.method in SAFE_METHODS:
return True
return obj.author == request.user
class IsAdminOrReadOnly(BasePermission):
def has_permission(self, request, view):
if request.method in SAFE_METHODS:
return True
return request.user and request.user.is_staff
class ArticleViewSet(viewsets.ModelViewSet):
permission_classes = [IsAuthenticated, IsAuthorOrReadOnly]
def get_permissions(self):
if self.action == "list":
return [] # Public access for listing
if self.action == "publish":
return [IsAuthenticated(), IsAdminOrReadOnly()]
return super().get_permissions()
Pagination
from rest_framework.pagination import CursorPagination, PageNumberPagination
class StandardPagination(PageNumberPagination):
page_size = 25
page_size_query_param = "page_size"
max_page_size = 100
# Cursor pagination -- efficient for large datasets, no count query
class TimelinePagination(CursorPagination):
page_size = 50
ordering = "-created_at"
cursor_query_param = "cursor"
class ArticleViewSet(viewsets.ModelViewSet):
pagination_class = StandardPagination
# Global default in settings.py
REST_FRAMEWORK = {
"DEFAULT_PAGINATION_CLASS": "myapp.pagination.StandardPagination",
"PAGE_SIZE": 25,
}
Filtering with django-filter
import django_filters
from django_filters.rest_framework import DjangoFilterBackend
from rest_framework.filters import SearchFilter, OrderingFilter
class ArticleFilter(django_filters.FilterSet):
created_after = django_filters.DateFilter(
field_name="created_at", lookup_expr="gte"
)
created_before = django_filters.DateFilter(
field_name="created_at", lookup_expr="lte"
)
min_views = django_filters.NumberFilter(
field_name="view_count", lookup_expr="gte"
)
tags = django_filters.CharFilter(method="filter_by_tags")
class Meta:
model = Article
fields = ["status", "category", "author"]
def filter_by_tags(self, queryset, name, value):
tag_names = value.split(",")
return queryset.filter(tags__name__in=tag_names).distinct()
class ArticleViewSet(viewsets.ModelViewSet):
filter_backends = [DjangoFilterBackend, SearchFilter, OrderingFilter]
filterset_class = ArticleFilter
search_fields = ["title", "body", "author__username"]
ordering_fields = ["created_at", "view_count", "title"]
ordering = ["-created_at"]
Throttling
from rest_framework.throttling import UserRateThrottle, AnonRateThrottle
class BurstRateThrottle(UserRateThrottle):
scope = "burst"
class SustainedRateThrottle(UserRateThrottle):
scope = "sustained"
# settings.py
REST_FRAMEWORK = {
"DEFAULT_THROTTLE_CLASSES": [
"myapp.throttles.BurstRateThrottle",
"myapp.throttles.SustainedRateThrottle",
],
"DEFAULT_THROTTLE_RATES": {
"burst": "60/min",
"sustained": "1000/day",
"anon": "20/min",
},
}
API Versioning
# settings.py
REST_FRAMEWORK = {
"DEFAULT_VERSIONING_CLASS": "rest_framework.versioning.URLPathVersioning",
"ALLOWED_VERSIONS": ["v1", "v2"],
"DEFAULT_VERSION": "v1",
}
# urls.py
urlpatterns = [
path("api//", include(router.urls)),
]
# Conditional logic in views
class ArticleViewSet(viewsets.ModelViewSet):
def get_serializer_class(self):
if self.request.version == "v2":
return ArticleV2Serializer
return ArticleSerializer
Anti-Patterns
- Fat serializers with business logic: Serializers should validate and transform.
Keep business logic in services or model methods.
- Using
ModelViewSetfor non-CRUD endpoints: If you only need list and retrieve,
use mixins.ListModelMixin and mixins.RetrieveModelMixin with GenericViewSet.
- No pagination on list endpoints: Unbounded QuerySets will crash on large tables.
Always set a pagination class.
- Skipping selectrelated in getqueryset: DRF serializers accessing related fields
trigger N+1 queries. Always optimize the QuerySet.
- Testing with the Django test client instead of DRF's: Use
APIClientand
APIRequestFactory for proper content negotiation and authentication.
Quick Reference
| Feature | Configuration | |---|---| | Serializer | ModelSerializer with Meta.fields | | ViewSet | ModelViewSet + Router | | Custom action | @action(detail=True/False) | | Permission | BasePermission.has_object_permission | | Pagination | PageNumberPagination / CursorPagination | | Filtering | DjangoFilterBackend + FilterSet | | Search | SearchFilter + search_fields | | Ordering | OrderingFilter + ordering_fields | | Throttling | UserRateThrottle + DEFAULT_THROTTLE_RATES | | Versioning | URLPathVersioning + ALLOWED_VERSIONS |
Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: VersoXBT
- Source: VersoXBT/claude-initial-setup
- License: MIT
- Homepage: https://github.com/VersoXBT/claude-initial-setup#installation
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet — be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.