如何为Django REST Framework视图集的自定义返回对象构建文档?
Got it, this is a super common pain point when building APIs with DRF—you’ve got a mix of standard model-backed views and custom endpoints that return non-standard JSON, and the auto-documentation tools (which rely on model metadata) don’t pick up the latter. Let’s walk through the best ways to fix this using the two most popular DRF documentation libraries: drf-spectacular (the modern, recommended choice) and drf-yasg (still widely used in older projects).
Using drf-spectacular (Recommended)
drf-spectacular is built to handle custom scenarios like this with ease. Here’s the step-by-step:
1. Set Up drf-spectacular First
First, install and configure the package:
pip install drf-spectacular
Add it to your INSTALLED_APPS in settings.py:
INSTALLED_APPS = [ # ... other apps 'drf_spectacular', ]
Then set it as your default schema class in DRF settings:
REST_FRAMEWORK = { # ... other settings 'DEFAULT_SCHEMA_CLASS': 'drf_spectacular.openapi.AutoSchema', }
Don’t forget to add the schema URL to your urls.py:
from drf_spectacular.views import SpectacularAPIView, SpectacularSwaggerView, SpectacularRedocView urlpatterns = [ # ... other URLs path('api/schema/', SpectacularAPIView.as_view(), name='schema'), path('api/docs/', SpectacularSwaggerView.as_view(url_name='schema'), name='swagger-ui'), path('api/redoc/', SpectacularRedocView.as_view(url_name='schema'), name='redoc'), ]
2. Define a Serializer for Your Custom Response
Even if your data isn’t tied to a Django model, create a regular Serializer (not ModelSerializer) to define the structure of your custom JSON. This tells the documentation tool exactly what the response will look like:
from rest_framework import serializers class CustomResponseSerializer(serializers.Serializer): status = serializers.CharField(help_text="Status of the request (success/failure)") data = serializers.DictField(child=serializers.CharField(), help_text="Custom payload data") timestamp = serializers.DateTimeField(help_text="Timestamp of the response")
You can use any DRF serializer fields here—ListField, nested serializers, etc.—to match your actual response structure.
3. Attach the Serializer to Your View/Action
Use the @extend_schema decorator to link your custom serializer to the view or viewset action.
For a Standalone APIView:
from drf_spectacular.utils import extend_schema, OpenApiParameter from rest_framework.views import APIView from rest_framework.response import Response from django.utils import timezone class CustomDataView(APIView): @extend_schema( responses=CustomResponseSerializer, description="Returns custom non-model data with status and timestamp", parameters=[ OpenApiParameter(name="filter", type=str, description="Filter results by value"), ] ) def get(self, request): # Your custom logic to generate non-model data custom_data = { "status": "success", "data": {"user_id": request.user.id, "preferences": ["dark_mode", "notifications"]}, "timestamp": timezone.now() } return Response(custom_data)
For a ViewSet Custom Action:
If you have a mix of standard ModelViewSet actions and custom ones, just decorate the custom action:
from rest_framework import viewsets from drf_spectacular.utils import extend_schema from django.utils import timezone class MyModelViewSet(viewsets.ModelViewSet): queryset = MyModel.objects.all() serializer_class = MyModelSerializer @extend_schema( responses=CustomResponseSerializer, description="Custom action that returns aggregated non-model data", ) @action(detail=False, methods=["get"]) def aggregated_stats(self, request): # Custom logic to calculate stats not tied to a single model stats = { "status": "success", "data": {"total_users": User.objects.count(), "active_orders": Order.objects.filter(status="active").count()}, "timestamp": timezone.now() } return Response(stats)
Using drf-yasg
If you’re still using drf-yasg, the approach is similar—define a serializer and use a decorator to attach it:
1. Set Up drf-yasg
Install and configure:
pip install drf-yasg
Add to INSTALLED_APPS:
INSTALLED_APPS = [ # ... other apps 'drf_yasg', ]
Add the swagger/redoc URLs to urls.py:
from drf_yasg.views import get_schema_view from drf_yasg import openapi from rest_framework import permissions schema_view = get_schema_view( openapi.Info( title="Your API", default_version='v1', description="API documentation", ), public=True, permission_classes=(permissions.AllowAny,), ) urlpatterns = [ # ... other URLs path('swagger/', schema_view.with_ui('swagger', cache_timeout=0), name='schema-swagger-ui'), path('redoc/', schema_view.with_ui('redoc', cache_timeout=0), name='schema-redoc'), ]
2. Define the Custom Serializer
Same as with drf-spectacular—create a Serializer for your custom response structure.
3. Decorate Your View
Use @swagger_auto_schema to specify the response serializer:
from drf_yasg.utils import swagger_auto_schema from drf_yasg import openapi from rest_framework.views import APIView from rest_framework.response import Response class CustomDataView(APIView): @swagger_auto_schema( responses={200: CustomResponseSerializer()}, operation_description="Returns custom non-model data", manual_parameters=[ openapi.Parameter('filter', openapi.IN_QUERY, description="Filter results", type=openapi.TYPE_STRING) ] ) def get(self, request): # Custom logic here custom_data = { "status": "success", "data": {"key": "value"}, "timestamp": timezone.now() } return Response(custom_data)
Pro Tips
- Always prefer serializers: Even if you could define the schema manually with OpenAPI types, using serializers keeps your documentation in sync with your actual response validation (if you add it later).
- Reuse serializers: If multiple views return the same custom structure, reuse the same serializer to keep docs consistent.
- Add help text: Use the
help_textparameter in serializer fields to make your documentation more informative for end-users.
内容的提问来源于stack exchange,提问作者Alex Alifimoff

