Typorithm · medium

Page the selected scope and name its count honestly

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

Step 1 of 4 · intro card

Page the selected scope and name its count honestly

0%

Concept and contract

Bound limit/offset requests without turning page length into collection size.

Pagination separates a selected collection from the bounded portion returned in one response. Its count should describe that selected scope before the page slice, not the entire database and not merely the number of displayed rows. Stable ordering makes the selected page understandable.

The fixture selects alice roles in a requested country and orders by ID. LimitOffsetPagination uses a default page size of one and a maximum of two. The teaching count describes this owned filtered collection only, not all roles on a live job market.

Mechanism and worked example

The generic list view applies pagination to the queryset and uses the public serializer for the page.

The generic list view applies pagination to the queryset and uses the public serializer for the page. DRF returns count, next, previous and results. The helper reports row IDs and whether navigation links exist, keeping page membership and collection size separately observable.

A request for limit=99 is bounded by max_limit rather than granting unlimited rows. This backend clamps some values to its configured limit; do not teach it as a universal validation-error policy. Request normalization behavior belongs in the documented API contract.

Complete worked example

python

from drf_lab import setup
setup()

from rest_framework import generics, serializers
from rest_framework.pagination import LimitOffsetPagination
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 SmallPage(LimitOffsetPagination):
    default_limit = 1
    max_limit = 2
class RoleList(generics.ListAPIView):
    serializer_class = PublicRole
    pagination_class = SmallPage
    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(query):
    reset_fixture()
    response = RoleList.as_view()(APIRequestFactory().get("/roles/", query))
    return {"count": response.data["count"], "ids": [row["id"] for row in response.data["results"]],
            "next": response.data["next"] is not None, "previous": response.data["previous"] is not None}
Reasoning
The generic list view applies pagination to the queryset and uses the public serializer for the page. DRF returns count, next, previous and results. The helper reports row IDs and whether navigation links exist, keeping page membership and collection size separately observable. A request for limit=99 is bounded by max_limit rather than granting unlimited rows. This backend clamps some values to its configured limit; do not teach it as a universal validation-error policy. Request normalization behavior belongs in the documented API contract.

Focused decision: context is in the complete example

python

class SmallPage(LimitOffsetPagination):
    default_limit = 1
    max_limit = 2

class RoleList(generics.ListAPIView):
    serializer_class = PublicRole
    pagination_class = SmallPage
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 replaces count with len(data) in the paginated response.

The counterexample replaces count with len(data) in the paginated response. A one-row page then claims the whole selected collection has one row even though another page exists. A visually correct first row can hide incorrect totals and navigation semantics.

Offset pages may shift when concurrent inserts or removals change the collection. A cursor design can address different requirements but needs a suitable stable ordering and does not magically provide a universal total. Choose the representation for the product and test its actual scope and freshness.

Controlled mistake: observe the actual wrong behavior

python

from drf_lab import setup
setup()

from rest_framework import generics, serializers
from rest_framework.pagination import LimitOffsetPagination
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 SmallPage(LimitOffsetPagination):
    default_limit = 1
    max_limit = 2
    def get_paginated_response(self, data):
        response = super().get_paginated_response(data)
        response.data["count"] = len(data)
        return response
class RoleList(generics.ListAPIView):
    serializer_class = PublicRole
    pagination_class = SmallPage
    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(query):
    reset_fixture()
    response = RoleList.as_view()(APIRequestFactory().get("/roles/", query))
    return {"count": response.data["count"], "ids": [row["id"] for row in response.data["results"]],
            "next": response.data["next"] is not None, "previous": response.data["previous"] is not None}
Reasoning
The counterexample replaces count with len(data) in the paginated response. A one-row page then claims the whole selected collection has one row even though another page exists. A visually correct first row can hide incorrect totals and navigation semantics. Offset pages may shift when concurrent inserts or removals change the collection. A cursor design can address different requirements but needs a suitable stable ordering and does not magically provide a universal total. Choose the representation for the product and test its actual scope and freshness.

Independent practice

Increase the default page size from one to two while keeping max_limit=2 and explicit limit=1 supported.

Increase the default page size from one to two while keeping max_limit=2 and explicit limit=1 supported. Preserve the selected country, owner and stable ordering. The default page should now contain both Ukrainian fixture roles without changing the selected collection count.

Test the default page, offset=1 and an explicit one-row request. Keep count equal to two for that Ukrainian selection. Do not change it to the displayed size or modify fixture records to make both numbers happen to match.

Step 1: Default to two rows without changing the maximum, explicit smaller limits or selected collection count. 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.pagination import LimitOffsetPagination
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 SmallPage(LimitOffsetPagination):
    default_limit = 1
    max_limit = 2
class RoleList(generics.ListAPIView):
    serializer_class = PublicRole
    pagination_class = SmallPage
    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(query):
    reset_fixture()
    response = RoleList.as_view()(APIRequestFactory().get("/roles/", query))
    return {"count": response.data["count"], "ids": [row["id"] for row in response.data["results"]],
            "next": response.data["next"] is not None, "previous": response.data["previous"] is not None}

# Independent task: Default to two rows without changing the maximum, explicit smaller limits or selected collection count.
Reasoning
Default to two rows without changing the maximum, explicit smaller limits or selected collection count.
  • 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 default_limit only.

The solution changes default_limit only. The returned default page grows, navigation updates accordingly and the count remains a property of the same filtered collection. Explicit smaller pages still work under the unchanged maximum.

A production query should count and fetch from a compatible snapshot or clearly document eventual consistency. Cache keys must include filters, order and page state; a fast cached page is still wrong if its count comes from another country or authorization scope. This fixture tests semantics rather than a performance claim.

Practice solution and acceptance checks

python

from drf_lab import setup
setup()

from rest_framework import generics, serializers
from rest_framework.pagination import LimitOffsetPagination
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 SmallPage(LimitOffsetPagination):
    default_limit = 2
    max_limit = 2
class RoleList(generics.ListAPIView):
    serializer_class = PublicRole
    pagination_class = SmallPage
    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(query):
    reset_fixture()
    response = RoleList.as_view()(APIRequestFactory().get("/roles/", query))
    return {"count": response.data["count"], "ids": [row["id"] for row in response.data["results"]],
            "next": response.data["next"] is not None, "previous": response.data["previous"] is not None}
Reasoning
The solution changes default_limit only. The returned default page grows, navigation updates accordingly and the count remains a property of the same filtered collection. Explicit smaller pages still work under the unchanged maximum. A production query should count and fetch from a compatible snapshot or clearly document eventual consistency. Cache keys must include filters, order and page state; a fast cached page is still wrong if its count comes from another country or authorization scope. This fixture tests semantics rather than a performance claim.

Interview reasoning and next step

Explain how a one-item page can truthfully have count=2 and identify the test that catches a page-length substitution.

Explain how a one-item page can truthfully have count=2 and identify the test that catches a page-length substitution. Then describe the difference between a bounded requested limit and a complete market estimate.

Next, examine related-object loading. A bounded page can still issue too many database queries if serialization fetches each related object separately. Measure the whole rendered page before selecting an optimization.

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-pagination. 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.

Bound limit/offset requests without turning page length into collection size.