Complete worked example
python
from drf_lab import setup
setup()
from rest_framework import generics, serializers
from rest_framework.permissions import BasePermission, IsAuthenticated
from rest_framework.test import APIRequestFactory, force_authenticate
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 Owned(BasePermission):
def has_object_permission(self, request, view, obj):
return obj.owner_id == request.user.pk
class Scope:
serializer_class = PublicRole
permission_classes = [IsAuthenticated, Owned]
def get_queryset(self):
return Role.objects.filter(owner=self.request.user).order_by("id")
class RoleList(Scope, generics.ListAPIView):
pass
class RoleDetail(Scope, generics.RetrieveAPIView):
pass
def inspect_example(payload):
alice, bob, _ = reset_fixture()
if payload.get("deactivate"):
Role.objects.filter(pk=2).update(active=False)
request = APIRequestFactory().get("/roles/")
force_authenticate(request, user=bob if payload.get("user") == "bob" else alice)
response = RoleDetail.as_view()(request, pk=payload["id"]) if "id" in payload else RoleList.as_view()(request)
return {"status": response.status_code, "body":
{"error": response.data["detail"].code} if response.status_code >= 400 else response.data}- Reasoning
- The test factory deliberately force-authenticates an owned principal. This bypasses credential verification to isolate authorization; the previous token lesson tests that separate boundary. The list returns the selected public rows, and get_object in the detail generic invokes the object check after queryset lookup. Under the chosen policy, a foreign object is outside the selected queryset and returns 404. That avoids disclosing its existence through this route. A product may deliberately select another disclosure policy, but it should remain consistent and independently tested.