# Example: Django REST API Skill This is a complete example of a Fullstack skill for Django REST API development. --- ```yaml --- name: django-api description: Creates Django REST Framework endpoints with serializers, views, and URL routing. Use when building REST APIs in Django. argument-hint: [resource-name] [fields...] allowed-tools: Read, Write, Edit, Glob, Grep --- # Django REST API Generator Generate production-ready Django REST Framework endpoints with proper serializers, views, URL routing, and tests. ## When to Use - Creating new API endpoints for a resource - Adding CRUD operations to an existing model - Setting up API authentication and permissions - Implementing filtering, pagination, and search ## Prerequisites - Django REST Framework installed (`djangorestframework`) - Model exists or will be created - URL configuration set up for API routes ## Instructions ### Step 1: Analyze the Model Read the existing model or create one based on the resource name and fields provided in `$ARGUMENTS`. ### Step 2: Create Serializer Generate a serializer in `serializers.py`: ```python from rest_framework import serializers from .models import ResourceName class ResourceNameSerializer(serializers.ModelSerializer): class Meta: model = ResourceName fields = ['id', 'field1', 'field2', 'created_at', 'updated_at'] read_only_fields = ['id', 'created_at', 'updated_at'] def validate_field1(self, value): """Add custom validation if needed.""" return value ``` ### Step 3: Create Views Generate function-based views in `views_func.py`: ```python from rest_framework import status from rest_framework.decorators import api_view, permission_classes from rest_framework.permissions import IsAuthenticated from rest_framework.response import Response from drf_spectacular.utils import extend_schema from .models import ResourceName from .serializers import ResourceNameSerializer @extend_schema( request=ResourceNameSerializer, responses={201: ResourceNameSerializer}, tags=['resource-name'], ) @api_view(['POST']) @permission_classes([IsAuthenticated]) def create_resource(request): """Create a new resource.""" serializer = ResourceNameSerializer(data=request.data) if serializer.is_valid(): serializer.save(created_by=request.user) return Response(serializer.data, status=status.HTTP_201_CREATED) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) @extend_schema( responses={200: ResourceNameSerializer(many=True)}, tags=['resource-name'], ) @api_view(['GET']) @permission_classes([IsAuthenticated]) def list_resources(request): """List all resources for the authenticated user.""" resources = ResourceName.objects.filter(created_by=request.user) serializer = ResourceNameSerializer(resources, many=True) return Response(serializer.data) @extend_schema( responses={200: ResourceNameSerializer}, tags=['resource-name'], ) @api_view(['GET']) @permission_classes([IsAuthenticated]) def get_resource(request, pk): """Get a single resource by ID.""" try: resource = ResourceName.objects.get(pk=pk, created_by=request.user) except ResourceName.DoesNotExist: return Response( {'error': 'Resource not found'}, status=status.HTTP_404_NOT_FOUND ) serializer = ResourceNameSerializer(resource) return Response(serializer.data) ``` ### Step 4: Configure URLs Add URL patterns in `urls.py`: ```python from django.urls import path from . import views_func urlpatterns = [ path('resources/', views_func.list_resources, name='resource-list'), path('resources/create/', views_func.create_resource, name='resource-create'), path('resources//', views_func.get_resource, name='resource-detail'), ] ``` ### Step 5: Add Tests Generate tests in `tests/test_api.py`: ```python import pytest from django.urls import reverse from rest_framework import status from rest_framework.test import APIClient from .factories import ResourceNameFactory, UserFactory @pytest.fixture def api_client(): return APIClient() @pytest.fixture def authenticated_client(api_client): user = UserFactory() api_client.force_authenticate(user=user) return api_client, user class TestResourceAPI: @pytest.mark.django_db def test_create_resource(self, authenticated_client): client, user = authenticated_client url = reverse('resource-create') data = {'field1': 'value1', 'field2': 'value2'} response = client.post(url, data) assert response.status_code == status.HTTP_201_CREATED assert response.data['field1'] == 'value1' @pytest.mark.django_db def test_list_resources(self, authenticated_client): client, user = authenticated_client ResourceNameFactory.create_batch(3, created_by=user) url = reverse('resource-list') response = client.get(url) assert response.status_code == status.HTTP_200_OK assert len(response.data) == 3 ``` ## Patterns & Best Practices ### Error Response Format Always use consistent error responses: ```python { "error": "Human-readable message", "code": "MACHINE_READABLE_CODE", "details": {} # Optional additional context } ``` ### Pagination Use cursor pagination for large datasets: ```python REST_FRAMEWORK = { 'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.CursorPagination', 'PAGE_SIZE': 20, } ``` ### Filtering Use django-filter for query parameter filtering: ```python from django_filters import rest_framework as filters class ResourceFilter(filters.FilterSet): created_after = filters.DateTimeFilter(field_name='created_at', lookup_expr='gte') class Meta: model = ResourceName fields = ['status', 'created_after'] ``` ## Common Pitfalls - **N+1 Queries**: Use `select_related()` and `prefetch_related()` in querysets - **Missing Permissions**: Always add `permission_classes` decorator - **No Validation**: Add custom validation in serializer methods - **Inconsistent Responses**: Use the same response format across all endpoints ## Verification Test the endpoints: ```bash # Create resource curl -X POST http://localhost:8000/api/resources/create/ \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -d '{"field1": "value1"}' # List resources curl http://localhost:8000/api/resources/ \ -H "Authorization: Bearer $TOKEN" ``` ```