Typorithm · medium

Capstone: a scoped, bounded technical-role API

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

Step 1 of 4 · intro card

Capstone: a scoped, bounded technical-role API

0%

Concept and contract

Join authenticated selection, public fields, validated creation and atomic conflict handling.

A practical API combines several independently testable boundaries. It resolves a principal, selects only that principal collection, validates filters and writes, bounds pages and emits a deliberate representation. A single successful request does not establish all of those guarantees.

The capstone uses the owned technical-role fixture and a ListCreateAPIView. TokenAuthentication is the declared credential mechanism, while test cases force an owned principal to isolate the service contract. Separate earlier lessons test real token lookup, revocation and cookie-session CSRF.

Mechanism and worked example

Collection selection validates country, filters by request.user and uses a stable order before pagination.

Collection selection validates country, filters by request.user and uses a stable order before pagination. The public serializer excludes owner, company authority and private_note. Creation supplies owner from the verified principal and company from trusted fixture context rather than accepting either from an arbitrary body field.

The create operation runs inside atomic and translates a uniqueness conflict to a deliberate 409 response after rollback. The test reads the stored owner and row count for accepted writes. This makes an incorrect service-level owner assignment observable even when ordinary input values remain valid.

Complete worked example

python

from drf_lab import setup
setup()

from django.db import IntegrityError, transaction
from rest_framework import generics, serializers
from rest_framework.authentication import TokenAuthentication
from rest_framework.pagination import LimitOffsetPagination
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from rest_framework.test import APIRequestFactory, force_authenticate
from drf_lab import reset_fixture
from drf_lab.models import Role

class Query(serializers.Serializer):
    country = serializers.ChoiceField(choices=["UA", "IL", "US", "CA"], default="UA")
class PublicRole(serializers.ModelSerializer):
    slots = serializers.IntegerField(min_value=1, max_value=3, default=1)
    class Meta:
        model = Role
        fields = ["id", "title", "country", "slots"]
        read_only_fields = ["id"]
class SmallPage(LimitOffsetPagination):
    default_limit = 1
    max_limit = 2
class Roles(generics.ListCreateAPIView):
    serializer_class = PublicRole
    permission_classes = [IsAuthenticated]
    authentication_classes = [TokenAuthentication]
    pagination_class = SmallPage
    def get_queryset(self):
        query = Query(data=self.request.query_params)
        query.is_valid(raise_exception=True)
        return Role.objects.filter(owner=self.request.user, country=query.validated_data["country"]).order_by("id")
    def perform_create(self, serializer):
        serializer.save(owner=self.request.user, company_id=1)
    def create(self, request, *args, **kwargs):
        try:
            with transaction.atomic():
                return super().create(request, *args, **kwargs)
        except IntegrityError:
            return Response({"detail": "Role already exists."}, status=409)

def error_codes(value):
    if isinstance(value, dict):
        return {key: error_codes(item) for key, item in value.items()}
    if isinstance(value, list):
        return [error_codes(item) for item in value]
    return getattr(value, "code", str(value))

def inspect_example(payload):
    alice, bob, _ = reset_fixture()
    factory = APIRequestFactory()
    method = payload.get("method", "get")
    request = factory.post("/roles/", payload.get("data", {}), format="json") if method == "post" else factory.get("/roles/", payload.get("query", {}))
    if payload.get("user"):
        force_authenticate(request, user=alice if payload["user"] == "alice" else bob)
    response = Roles.as_view()(request)
    if response.status_code >= 400:
        return {"status": response.status_code, "body": error_codes(response.data), "rows": Role.objects.count()}
    if method == "post":
        row = Role.objects.get(title=response.data["title"])
        return {"status": response.status_code, "keys": sorted(response.data), "title": row.title,
                "stored_owner": row.owner.username, "rows": Role.objects.count()}
    rows = response.data["results"]
    return {"status": response.status_code, "count": response.data["count"],
            "ids": [row["id"] for row in rows], "keys": sorted(rows[0]) if rows else []}
Reasoning
Collection selection validates country, filters by request.user and uses a stable order before pagination. The public serializer excludes owner, company authority and private_note. Creation supplies owner from the verified principal and company from trusted fixture context rather than accepting either from an arbitrary body field. The create operation runs inside atomic and translates a uniqueness conflict to a deliberate 409 response after rollback. The test reads the stored owner and row count for accepted writes. This makes an incorrect service-level owner assignment observable even when ordinary input values remain valid.

Focused decision: context is in the complete example

python

def get_queryset(self):
    query = Query(data=self.request.query_params)
    query.is_valid(raise_exception=True)
    return Role.objects.filter(
        owner=self.request.user, country=query.validated_data["country"],
    ).order_by("id")

def perform_create(self, serializer):
    serializer.save(owner=self.request.user, company_id=1)
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 saves new rows under bob instead of the resolved caller.

The counterexample saves new rows under bob instead of the resolved caller. The public creation response can look plausible, but readback exposes the incorrect owner. Meanwhile, list tests ensure country and principal scope agree with the returned count and IDs.

The fixture company ID is trusted classroom context, not a production authorization design. A real product must resolve employer or team authority appropriately. Likewise, a response model does not protect a private cache shared across principals; cache keys and storage queries must retain the same scope.

Controlled mistake: observe the actual wrong behavior

python

from drf_lab import setup
setup()

from django.db import IntegrityError, transaction
from rest_framework import generics, serializers
from rest_framework.authentication import TokenAuthentication
from rest_framework.pagination import LimitOffsetPagination
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from rest_framework.test import APIRequestFactory, force_authenticate
from drf_lab import reset_fixture
from drf_lab.models import Role

class Query(serializers.Serializer):
    country = serializers.ChoiceField(choices=["UA", "IL", "US", "CA"], default="UA")
class PublicRole(serializers.ModelSerializer):
    slots = serializers.IntegerField(min_value=1, max_value=3, default=1)
    class Meta:
        model = Role
        fields = ["id", "title", "country", "slots"]
        read_only_fields = ["id"]
class SmallPage(LimitOffsetPagination):
    default_limit = 1
    max_limit = 2
class Roles(generics.ListCreateAPIView):
    serializer_class = PublicRole
    permission_classes = [IsAuthenticated]
    authentication_classes = [TokenAuthentication]
    pagination_class = SmallPage
    def get_queryset(self):
        query = Query(data=self.request.query_params)
        query.is_valid(raise_exception=True)
        return Role.objects.filter(owner=self.request.user, country=query.validated_data["country"]).order_by("id")
    def perform_create(self, serializer):
        serializer.save(owner_id=2, company_id=1)
    def create(self, request, *args, **kwargs):
        try:
            with transaction.atomic():
                return super().create(request, *args, **kwargs)
        except IntegrityError:
            return Response({"detail": "Role already exists."}, status=409)

def error_codes(value):
    if isinstance(value, dict):
        return {key: error_codes(item) for key, item in value.items()}
    if isinstance(value, list):
        return [error_codes(item) for item in value]
    return getattr(value, "code", str(value))

def inspect_example(payload):
    alice, bob, _ = reset_fixture()
    factory = APIRequestFactory()
    method = payload.get("method", "get")
    request = factory.post("/roles/", payload.get("data", {}), format="json") if method == "post" else factory.get("/roles/", payload.get("query", {}))
    if payload.get("user"):
        force_authenticate(request, user=alice if payload["user"] == "alice" else bob)
    response = Roles.as_view()(request)
    if response.status_code >= 400:
        return {"status": response.status_code, "body": error_codes(response.data), "rows": Role.objects.count()}
    if method == "post":
        row = Role.objects.get(title=response.data["title"])
        return {"status": response.status_code, "keys": sorted(response.data), "title": row.title,
                "stored_owner": row.owner.username, "rows": Role.objects.count()}
    rows = response.data["results"]
    return {"status": response.status_code, "count": response.data["count"],
            "ids": [row["id"] for row in rows], "keys": sorted(rows[0]) if rows else []}
Reasoning
The counterexample saves new rows under bob instead of the resolved caller. The public creation response can look plausible, but readback exposes the incorrect owner. Meanwhile, list tests ensure country and principal scope agree with the returned count and IDs. The fixture company ID is trusted classroom context, not a production authorization design. A real product must resolve employer or team authority appropriately. Likewise, a response model does not protect a private cache shared across principals; cache keys and storage queries must retain the same scope.

Independent practice

Increase the default page from one row to two while retaining a maximum of two and all write rules.

Increase the default page from one row to two while retaining a maximum of two and all write rules. This final small task changes a useful collection behavior without weakening identity, owner assignment, validation or conflict handling.

Check alice Ukrainian collection, bob Ukrainian collection and an accepted alice creation. The first should include two permitted rows, the second only bob row, and the write must still be stored under alice. Add your own invalid-country and duplicate-title cases before opening the solution.

Step 1: Default to two permitted rows while preserving trusted owner assignment, validation and atomic conflict handling. 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 django.db import IntegrityError, transaction
from rest_framework import generics, serializers
from rest_framework.authentication import TokenAuthentication
from rest_framework.pagination import LimitOffsetPagination
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from rest_framework.test import APIRequestFactory, force_authenticate
from drf_lab import reset_fixture
from drf_lab.models import Role

class Query(serializers.Serializer):
    country = serializers.ChoiceField(choices=["UA", "IL", "US", "CA"], default="UA")
class PublicRole(serializers.ModelSerializer):
    slots = serializers.IntegerField(min_value=1, max_value=3, default=1)
    class Meta:
        model = Role
        fields = ["id", "title", "country", "slots"]
        read_only_fields = ["id"]
class SmallPage(LimitOffsetPagination):
    default_limit = 1
    max_limit = 2
class Roles(generics.ListCreateAPIView):
    serializer_class = PublicRole
    permission_classes = [IsAuthenticated]
    authentication_classes = [TokenAuthentication]
    pagination_class = SmallPage
    def get_queryset(self):
        query = Query(data=self.request.query_params)
        query.is_valid(raise_exception=True)
        return Role.objects.filter(owner=self.request.user, country=query.validated_data["country"]).order_by("id")
    def perform_create(self, serializer):
        serializer.save(owner=self.request.user, company_id=1)
    def create(self, request, *args, **kwargs):
        try:
            with transaction.atomic():
                return super().create(request, *args, **kwargs)
        except IntegrityError:
            return Response({"detail": "Role already exists."}, status=409)

def error_codes(value):
    if isinstance(value, dict):
        return {key: error_codes(item) for key, item in value.items()}
    if isinstance(value, list):
        return [error_codes(item) for item in value]
    return getattr(value, "code", str(value))

def inspect_example(payload):
    alice, bob, _ = reset_fixture()
    factory = APIRequestFactory()
    method = payload.get("method", "get")
    request = factory.post("/roles/", payload.get("data", {}), format="json") if method == "post" else factory.get("/roles/", payload.get("query", {}))
    if payload.get("user"):
        force_authenticate(request, user=alice if payload["user"] == "alice" else bob)
    response = Roles.as_view()(request)
    if response.status_code >= 400:
        return {"status": response.status_code, "body": error_codes(response.data), "rows": Role.objects.count()}
    if method == "post":
        row = Role.objects.get(title=response.data["title"])
        return {"status": response.status_code, "keys": sorted(response.data), "title": row.title,
                "stored_owner": row.owner.username, "rows": Role.objects.count()}
    rows = response.data["results"]
    return {"status": response.status_code, "count": response.data["count"],
            "ids": [row["id"] for row in rows], "keys": sorted(rows[0]) if rows else []}

# Independent task: Default to two permitted rows while preserving trusted owner assignment, validation and atomic conflict handling.
Reasoning
Default to two permitted rows while preserving trusted owner assignment, validation and atomic conflict handling.
  • 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 the page default.

The solution changes the page default. Selection still precedes pagination, counts still describe the filtered owned scope and creation still crosses the same validated atomic boundary. Explicit public keys prevent a private canary from hiding inside a successful result.

This fixture is deliberately small and local. Production readiness also needs realistic query and latency measurements, credential lifecycle, request-size limits, observability and tested recovery. Character typing on the PC and reasoning checks in the app build recall; the downloadable lab runs the actual acceptance cases.

Practice solution and acceptance checks

python

from drf_lab import setup
setup()

from django.db import IntegrityError, transaction
from rest_framework import generics, serializers
from rest_framework.authentication import TokenAuthentication
from rest_framework.pagination import LimitOffsetPagination
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from rest_framework.test import APIRequestFactory, force_authenticate
from drf_lab import reset_fixture
from drf_lab.models import Role

class Query(serializers.Serializer):
    country = serializers.ChoiceField(choices=["UA", "IL", "US", "CA"], default="UA")
class PublicRole(serializers.ModelSerializer):
    slots = serializers.IntegerField(min_value=1, max_value=3, default=1)
    class Meta:
        model = Role
        fields = ["id", "title", "country", "slots"]
        read_only_fields = ["id"]
class SmallPage(LimitOffsetPagination):
    default_limit = 2
    max_limit = 2
class Roles(generics.ListCreateAPIView):
    serializer_class = PublicRole
    permission_classes = [IsAuthenticated]
    authentication_classes = [TokenAuthentication]
    pagination_class = SmallPage
    def get_queryset(self):
        query = Query(data=self.request.query_params)
        query.is_valid(raise_exception=True)
        return Role.objects.filter(owner=self.request.user, country=query.validated_data["country"]).order_by("id")
    def perform_create(self, serializer):
        serializer.save(owner=self.request.user, company_id=1)
    def create(self, request, *args, **kwargs):
        try:
            with transaction.atomic():
                return super().create(request, *args, **kwargs)
        except IntegrityError:
            return Response({"detail": "Role already exists."}, status=409)

def error_codes(value):
    if isinstance(value, dict):
        return {key: error_codes(item) for key, item in value.items()}
    if isinstance(value, list):
        return [error_codes(item) for item in value]
    return getattr(value, "code", str(value))

def inspect_example(payload):
    alice, bob, _ = reset_fixture()
    factory = APIRequestFactory()
    method = payload.get("method", "get")
    request = factory.post("/roles/", payload.get("data", {}), format="json") if method == "post" else factory.get("/roles/", payload.get("query", {}))
    if payload.get("user"):
        force_authenticate(request, user=alice if payload["user"] == "alice" else bob)
    response = Roles.as_view()(request)
    if response.status_code >= 400:
        return {"status": response.status_code, "body": error_codes(response.data), "rows": Role.objects.count()}
    if method == "post":
        row = Role.objects.get(title=response.data["title"])
        return {"status": response.status_code, "keys": sorted(response.data), "title": row.title,
                "stored_owner": row.owner.username, "rows": Role.objects.count()}
    rows = response.data["results"]
    return {"status": response.status_code, "count": response.data["count"],
            "ids": [row["id"] for row in rows], "keys": sorted(rows[0]) if rows else []}
Reasoning
The solution changes the page default. Selection still precedes pagination, counts still describe the filtered owned scope and creation still crosses the same validated atomic boundary. Explicit public keys prevent a private canary from hiding inside a successful result. This fixture is deliberately small and local. Production readiness also needs realistic query and latency measurements, credential lifecycle, request-size limits, observability and tested recovery. Character typing on the PC and reasoning checks in the app build recall; the downloadable lab runs the actual acceptance cases.

Interview reasoning and next step

Review the complete path and name a failing case for each promise: missing identity, foreign-owner selection, invalid country, excessive slots, duplicate title, incorrect page count and leaked internal fields.

Review the complete path and name a failing case for each promise: missing identity, foreign-owner selection, invalid country, excessive slots, duplicate title, incorrect page count and leaked internal fields. Explain the distinction between a test principal and credential verification.

Continue with NumPy and pandas from the curriculum audit, then dedicated pytest/quality and database persistence depth. Keep the same reasoning about scope, representation and verification when moving from APIs to market analytics. A row count must always say what it actually counts.

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

Join authenticated selection, public fields, validated creation and atomic conflict handling.