Typorithm · medium

Generic views need a scoped queryset

pythondjangodrfindependent-practice
Saved on this device
Full lesson · 4 learning steps Open any exercise for focused practice
1

Step 1 of 4 · intro card

Generic views need a scoped queryset

0%

Concept and contract

Let the framework handle list responses without leaking another owner collection.

Generic views combine recurring API behavior with declared serializers and querysets. ListAPIView can handle a GET collection and serialize its results, but it cannot infer which rows the caller is permitted to see. The query boundary remains application policy.

The fixture resolves an owned alice context and selects her technical roles in a requested country. It uses real querysets and a small public serializer. The local request factory exercises DRF dispatch, including the unsupported-method path, without starting a web server.

Mechanism and worked example

get_queryset runs for the request and applies owner and country predicates before ordering.

get_queryset runs for the request and applies owner and country predicates before ordering. The view serializes that selected collection with the declared serializer. The test sees only id, title and country, making both row selection and field projection observable.

A generic view can derive behavior from a class queryset, but avoid evaluating and reusing a cached result accidentally. Request-specific selection belongs in get_queryset. A production user context must come from verified authentication; the hard-coded principal is an explicit fixture simplification.

Complete worked example

python

from drf_lab import setup
setup()

from rest_framework import generics, serializers
from rest_framework.test import APIRequestFactory
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 RoleList(generics.ListAPIView):
    serializer_class = PublicRole
    def get_queryset(self):
        return Role.objects.filter(owner__username="alice", country=self.request.query_params.get("country", "UA")).order_by("id")

def inspect_example(payload):
    reset_fixture()
    factory = APIRequestFactory()
    request = getattr(factory, payload.get("method", "get"))("/roles/", {"country": payload.get("country", "UA")})
    response = RoleList.as_view()(request)
    body = {"error": response.data["detail"].code} if response.status_code >= 400 else list(response.data)
    return {"status": response.status_code, "body": body}
Reasoning
get_queryset runs for the request and applies owner and country predicates before ordering. The view serializes that selected collection with the declared serializer. The test sees only id, title and country, making both row selection and field projection observable. A generic view can derive behavior from a class queryset, but avoid evaluating and reusing a cached result accidentally. Request-specific selection belongs in get_queryset. A production user context must come from verified authentication; the hard-coded principal is an explicit fixture simplification.

Focused decision: context is in the complete example

python

class RoleList(generics.ListAPIView):
    serializer_class = PublicRole
    def get_queryset(self):
        return Role.objects.filter(
            owner__username="alice",
            country=self.request.query_params.get("country", "UA"),
        ).order_by("id")
Focused decision
Reconstruct this focused decision after the warm-up. The complete imports, model or application, and test client remain in the worked example. Typing records practice; only the downloadable computer lab executes Python.

Counterexamples and common mistakes

The counterexample removes the owner predicate while keeping the country filter.

The counterexample removes the owner predicate while keeping the country filter. The Ukrainian response then includes bob record. Public fields and a valid response status do not compensate for selecting someone else data.

Object-level permission methods are not automatically called for every row in a list. Collection scoping must be deliberate. Likewise, filtering after a page has already been selected can produce missing matches and misleading counts. Keep selection close to the database boundary.

Controlled mistake: observe the actual wrong behavior

python

from drf_lab import setup
setup()

from rest_framework import generics, serializers
from rest_framework.test import APIRequestFactory
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 RoleList(generics.ListAPIView):
    serializer_class = PublicRole
    def get_queryset(self):
        return Role.objects.filter(country=self.request.query_params.get("country", "UA")).order_by("id")

def inspect_example(payload):
    reset_fixture()
    factory = APIRequestFactory()
    request = getattr(factory, payload.get("method", "get"))("/roles/", {"country": payload.get("country", "UA")})
    response = RoleList.as_view()(request)
    body = {"error": response.data["detail"].code} if response.status_code >= 400 else list(response.data)
    return {"status": response.status_code, "body": body}
Reasoning
The counterexample removes the owner predicate while keeping the country filter. The Ukrainian response then includes bob record. Public fields and a valid response status do not compensate for selecting someone else data. Object-level permission methods are not automatically called for every row in a list. Collection scoping must be deliberate. Likewise, filtering after a page has already been selected can produce missing matches and misleading counts. Keep selection close to the database boundary.

Independent practice

Reverse the stable ordering from ascending id to descending id.

Reverse the stable ordering from ascending id to descending id. Preserve owner and country selection and the public field set. This small change teaches how an ordering contract appears in a real response without introducing unrelated CRUD behavior.

Check alice Ukrainian collection, her Israeli collection and an unsupported POST. The first should reverse its two rows; the single Israeli row is unchanged. POST should still be rejected, because a list-only view does not become a creation endpoint through an ordering change.

Step 1: Return the same scoped collection in descending stable-ID order without adding unsupported methods. Step 2: Retain the working imports and application structure. Change the stated policy, run the supplied practice cases, then explain the newly accepted or rejected boundary.

Practice starter: solve before reading the solution

python

from drf_lab import setup
setup()

from rest_framework import generics, serializers
from rest_framework.test import APIRequestFactory
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 RoleList(generics.ListAPIView):
    serializer_class = PublicRole
    def get_queryset(self):
        return Role.objects.filter(owner__username="alice", country=self.request.query_params.get("country", "UA")).order_by("id")

def inspect_example(payload):
    reset_fixture()
    factory = APIRequestFactory()
    request = getattr(factory, payload.get("method", "get"))("/roles/", {"country": payload.get("country", "UA")})
    response = RoleList.as_view()(request)
    body = {"error": response.data["detail"].code} if response.status_code >= 400 else list(response.data)
    return {"status": response.status_code, "body": body}

# Independent task: Return the same scoped collection in descending stable-ID order without adding unsupported methods.
Reasoning
Return the same scoped collection in descending stable-ID order without adding unsupported methods.
  • Hint 1: Find the one field or branch that owns this policy.
  • Hint 2: Compare the first changed acceptance case with the retained baseline.

Solution, tests and trade-offs

The solution changes order_by to -id.

The solution changes order_by to -id. It retains both selection predicates and the same serializer. The exact returned sequence confirms the changed contract, while the foreign-owner record remains absent and the method failure remains explicit.

ListCreateAPIView and retrieve/update generics provide other operation combinations. Select the smallest view that matches the intended methods, and review writes separately for ownership, validation and transactions. A useful default is not proof that every inherited method should be public.

Practice solution and acceptance checks

python

from drf_lab import setup
setup()

from rest_framework import generics, serializers
from rest_framework.test import APIRequestFactory
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 RoleList(generics.ListAPIView):
    serializer_class = PublicRole
    def get_queryset(self):
        return Role.objects.filter(owner__username="alice", country=self.request.query_params.get("country", "UA")).order_by("-id")

def inspect_example(payload):
    reset_fixture()
    factory = APIRequestFactory()
    request = getattr(factory, payload.get("method", "get"))("/roles/", {"country": payload.get("country", "UA")})
    response = RoleList.as_view()(request)
    body = {"error": response.data["detail"].code} if response.status_code >= 400 else list(response.data)
    return {"status": response.status_code, "body": body}
Reasoning
The solution changes order_by to -id. It retains both selection predicates and the same serializer. The exact returned sequence confirms the changed contract, while the foreign-owner record remains absent and the method failure remains explicit. ListCreateAPIView and retrieve/update generics provide other operation combinations. Select the smallest view that matches the intended methods, and review writes separately for ownership, validation and transactions. A useful default is not proof that every inherited method should be public.

Interview reasoning and next step

Explain why a custom object permission alone would not fix a leaked list.

Explain why a custom object permission alone would not fix a leaked list. Then show where the owner predicate and stable ordering enter the query. An HTTP-level assertion should catch both a foreign row and an unexpected sequence.

Next, group related operations in a viewset and router. Automatic route generation changes how names and detail actions are registered, but it does not change the need for scoped querysets or reviewed permissions.

Optional stretch — after the core task works: Add one independently reasoned boundary case and explain its expected value before running it.

Computer practice: https://applydjinn.com/typorithm/drf-lab-20261105.zip. Install pinned dependencies in an isolated Python environment. Run verify.py, then change the working exercise baseline and run --practice drf-generics. The real DRF clients, serializers, schemas and SQLite transactions use owned fixtures only. No production database, feed, account or paid service is used. This lab executes Python; the PC typing trainer and native reading progress do not execute it.

Let the framework handle list responses without leaking another owner collection.