Advanced Usage¶
Optimizing SerializerMethodField¶
A method field has no direct mapping to a model column, so the optimizer
can't infer what it needs. Declare it explicitly with requires_related:
from drf_partial_response_fields import requires_related
class ArticleSerializer(PartialFieldsModelSerializer):
editor_note = serializers.SerializerMethodField()
@requires_related(select_related=["editor__profile"])
def get_editor_note(self, obj):
return f"Reviewed by {obj.editor.profile.display_name}"
When editor_note is requested, the optimizer adds editor__profile to
select_related on the queryset — but only then, so unrelated requests
that don't need editor_note don't pay for the extra join. You can declare
prefetch_related paths the same way:
@requires_related(prefetch_related=["tags", "comments__author"])
def get_summary(self, obj):
...
Plain APIView integration¶
PartialResponseMixin needs get_queryset() / get_serializer_class() /
get_serializer_context() to hook into, which plain APIView doesn't
provide. Wire it up manually with parse_request_fields:
from rest_framework.views import APIView
from rest_framework.response import Response
from drf_partial_response_fields import parse_request_fields
from drf_partial_response_fields.mixins import PARTIAL_RESPONSE_FIELDS_CONTEXT_KEY
class ArticleDetailView(APIView):
def get(self, request, pk):
article = get_object_or_404(Article, pk=pk)
serializer = ArticleSerializer(
article,
context={
"request": request,
PARTIAL_RESPONSE_FIELDS_CONTEXT_KEY: parse_request_fields(request),
},
)
return Response(serializer.data)
This gets you output filtering; automatic queryset optimization is a
GenericAPIView-only feature (there's no generic get_queryset() to hook
into on a plain APIView). If you need it, call
drf_partial_response_fields.optimize_queryset() yourself:
from drf_partial_response_fields import optimize_queryset
tree = parse_request_fields(request)
queryset = optimize_queryset(Article.objects.all(), ArticleSerializer, tree)
OpenAPI documentation¶
Install the openapi extra first: pip install drf-partial-response-fields[openapi].
Per-view, using @extend_schema:
from drf_spectacular.utils import extend_schema
from drf_partial_response_fields.openapi import fields_query_parameter
@extend_schema(parameters=[fields_query_parameter()])
class ArticleViewSet(PartialResponseMixin, viewsets.ModelViewSet):
...
Automatically, for every action, using a custom AutoSchema:
from drf_partial_response_fields.openapi import PartialResponseAutoSchema
class ArticleViewSet(PartialResponseMixin, viewsets.ModelViewSet):
schema = PartialResponseAutoSchema()
...
Custom query parameter name¶
PARTIAL_RESPONSE_FIELDS = {"QUERY_PARAM": "select"}
GET /articles/?select=title,author(name)
Restricting depth for untrusted clients¶
If your API is public and you want to bound worst-case parsing/optimization cost regardless of what a client sends:
PARTIAL_RESPONSE_FIELDS = {"MAX_DEPTH": 3, "MAX_FIELDS_LENGTH": 500}
Combining with django-filter / custom get_queryset()¶
PartialResponseMixin.get_queryset() calls super().get_queryset()
first, then optimizes the result. It composes cleanly with any other
get_queryset() override earlier in the MRO (filtering, ordering,
tenant scoping, ...):
class ArticleViewSet(PartialResponseMixin, viewsets.ModelViewSet):
queryset = Article.objects.all()
serializer_class = ArticleSerializer
filterset_class = ArticleFilterSet # django-filter
def get_queryset(self):
# Any additional narrowing here still gets optimized afterward,
# since PartialResponseMixin.get_queryset() runs after this
# via super() in the MRO — put PartialResponseMixin first.
return super().get_queryset().filter(is_published=True)
Because PartialResponseMixin is listed first in the class bases, its
get_queryset() runs last in the MRO chain when you also override
get_queryset() on the view itself — Python's MRO calls the most-derived
get_queryset() first, and each override's super().get_queryset() call
walks toward the base class. In the example above, the view's own override
is the most-derived, so it runs first and calls super().get_queryset()
which reaches PartialResponseMixin.get_queryset(), which itself calls
super().get_queryset() to reach ModelViewSet's default. The net effect:
ModelViewSet gives you Article.objects.all(), PartialResponseMixin
optimizes it, and then your .filter(is_published=True) is applied on
top — meaning the optimization was computed before your extra filter. This
is safe (optimization doesn't change query results, only which columns
and joins are fetched), but keep in mind any relations referenced only by
your custom .filter() are not automatically added to select_related —
Django resolves query-only lookups without needing the relation object
materialized.
Testing views that use PartialResponseMixin¶
See Testing for
patterns using APIRequestFactory and rest_framework.test.APIClient.