Complete worked example
python
from drf_lab import setup
setup()
from django.test import override_settings
from django.urls import clear_url_caches
from rest_framework import serializers, viewsets
from rest_framework.decorators import action
from rest_framework.response import Response
from rest_framework.routers import DefaultRouter
from rest_framework.test import APIClient
from drf_lab import reset_fixture
from drf_lab.models import Role
class PublicRole(serializers.ModelSerializer):
class Meta:
model = Role
fields = ["id", "title", "country"]
class Roles(viewsets.ReadOnlyModelViewSet):
serializer_class = PublicRole
def get_queryset(self):
return Role.objects.filter(owner__username="alice").order_by("id")
@action(detail=False, methods=["get"])
def countries(self, request):
return Response({"countries": sorted(set(self.get_queryset().values_list("country", flat=True)))})
router = DefaultRouter()
router.register("roles", Roles, basename="owned-role")
urlpatterns = router.urls
def inspect_example(path):
reset_fixture()
with override_settings(ROOT_URLCONF=__name__):
clear_url_caches()
response = APIClient().get(path)
body = {"error": response.data["detail"].code} if response.status_code >= 400 else response.json()
clear_url_caches()
return {"status": response.status_code, "body": body}- Reasoning
- The action has detail=False, so it is registered on the collection rather than under an object identifier. The router also supplies the standard list and retrieve paths. APIClient passes through the real owned URL configuration, while override_settings and clear_url_caches keep each inspection isolated. The country list is derived from the same get_queryset policy rather than querying every owner independently. A summary action can leak information even when ordinary detail reads are safe, so custom endpoints need the same review as generated ones.