Typorithm · medium

Capstone: an owned, validated application flow

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

Step 1 of 4 · intro card

Capstone: an owned, validated application flow

0%

Concept and contract

Compose real framework boundaries so successful creation, rejected input and private-object access tell one consistent story.

The capstone combines the course’s pieces into one local application flow. An authenticated fictional owner obtains a CSRF token, submits a form, reads their selected collection and attempts to access another user’s private role.

It uses the real Django middleware, form, ORM and test client. The companion supplies only an owned in-memory setup; no production account, real vacancy or working site database is used as test data.

Mechanism and worked example

The collection’s GET applies owner scope before listing identifiers, while POST validates the title and assigns the trusted request user as owner.

The collection’s GET applies owner scope before listing identifiers, while POST validates the title and assigns the trusted request user as owner. A separate detail endpoint repeats the owner predicate.

The helper then verifies the actual saved row through the identifier returned by the HTTP response. That links response evidence to persisted state instead of assuming that a creation status alone proves the intended record and owner were stored.

Complete worked example

python

from bootstrap_lab import django_setup
django_setup(__name__)

from bootstrap_lab import install_models
from django.contrib.auth import get_user_model
from django.db import models

class Role(models.Model):
    title = models.CharField(max_length=80)
    owner = models.ForeignKey(get_user_model(), on_delete=models.CASCADE)
    class Meta:
        app_label = "framework_lab"

install_models(Role)

from django import forms
from django.contrib.auth.decorators import login_required
from django.http import JsonResponse
from django.middleware.csrf import get_token
from django.shortcuts import get_object_or_404
from django.test import Client
from django.urls import path
from django.views.decorators.http import require_http_methods

class RoleForm(forms.Form):
    title = forms.CharField(max_length=20)

@login_required
@require_http_methods(["GET", "POST"])
def collection(request):
    if request.method == "GET":
        own = Role.objects.filter(owner=request.user).order_by("id")
        return JsonResponse({"ids": list(own.values_list("id", flat=True)), "token": get_token(request)})
    form = RoleForm(request.POST)
    if not form.is_valid():
        return JsonResponse({"errors": sorted(form.errors)}, status=400)
    role = Role.objects.create(title=form.cleaned_data["title"], owner=request.user)
    return JsonResponse({"id": role.id, "title": role.title}, status=201)

@login_required
def detail(request, role_id):
    role = get_object_or_404(Role, pk=role_id, owner=request.user)
    return JsonResponse({"title": role.title})

urlpatterns = [path("roles/", collection), path("roles/<int:role_id>/", detail)]

def application_flow(title, include_token=True):
    Role.objects.all().delete()
    User = get_user_model()
    User.objects.all().delete()
    owner = User.objects.create_user(username="owner")
    other = User.objects.create_user(username="other")
    private = Role.objects.create(title="Other private role", owner=other)
    client = Client(enforce_csrf_checks=True)
    client.force_login(owner)
    token = client.get("/roles/").json()["token"]
    headers = {"HTTP_X_CSRFTOKEN": token} if include_token else {}
    response = client.post("/roles/", data={"title": title}, **headers)
    listing = client.get("/roles/").json()
    foreign = client.get(f"/roles/{private.id}/")
    saved = Role.objects.get(pk=response.json()["id"], owner=owner) if response.status_code == 201 else None
    return {"status": response.status_code, "owned_count": len(listing["ids"]), "foreign_status": foreign.status_code, "stored_title": saved.title if saved else None}
Reasoning
The collection’s GET applies owner scope before listing identifiers, while POST validates the title and assigns the trusted request user as owner. A separate detail endpoint repeats the owner predicate.

Counterexamples and common mistakes

The counterexample disables CSRF enforcement in the test client.

The counterexample disables CSRF enforcement in the test client. The otherwise convincing flow then accepts a missing-token mutation and makes its protection claim false.

Removing owner scope or writing raw form data would break different boundaries. Integration coverage should preserve the relevant negative cases rather than relying on an earlier unit test to protect every later composition. A private collection must not include another account’s record simply because it exists.

Counterexample: a runnable framework mistake

python

from bootstrap_lab import django_setup
django_setup(__name__)

from bootstrap_lab import install_models
from django.contrib.auth import get_user_model
from django.db import models

class Role(models.Model):
    title = models.CharField(max_length=80)
    owner = models.ForeignKey(get_user_model(), on_delete=models.CASCADE)
    class Meta:
        app_label = "framework_lab"

install_models(Role)

from django import forms
from django.contrib.auth.decorators import login_required
from django.http import JsonResponse
from django.middleware.csrf import get_token
from django.shortcuts import get_object_or_404
from django.test import Client
from django.urls import path
from django.views.decorators.http import require_http_methods

class RoleForm(forms.Form):
    title = forms.CharField(max_length=20)

@login_required
@require_http_methods(["GET", "POST"])
def collection(request):
    if request.method == "GET":
        own = Role.objects.filter(owner=request.user).order_by("id")
        return JsonResponse({"ids": list(own.values_list("id", flat=True)), "token": get_token(request)})
    form = RoleForm(request.POST)
    if not form.is_valid():
        return JsonResponse({"errors": sorted(form.errors)}, status=400)
    role = Role.objects.create(title=form.cleaned_data["title"], owner=request.user)
    return JsonResponse({"id": role.id, "title": role.title}, status=201)

@login_required
def detail(request, role_id):
    role = get_object_or_404(Role, pk=role_id, owner=request.user)
    return JsonResponse({"title": role.title})

urlpatterns = [path("roles/", collection), path("roles/<int:role_id>/", detail)]

def application_flow(title, include_token=True):
    Role.objects.all().delete()
    User = get_user_model()
    User.objects.all().delete()
    owner = User.objects.create_user(username="owner")
    other = User.objects.create_user(username="other")
    private = Role.objects.create(title="Other private role", owner=other)
    client = Client()
    client.force_login(owner)
    token = client.get("/roles/").json()["token"]
    headers = {"HTTP_X_CSRFTOKEN": token} if include_token else {}
    response = client.post("/roles/", data={"title": title}, **headers)
    listing = client.get("/roles/").json()
    foreign = client.get(f"/roles/{private.id}/")
    saved = Role.objects.get(pk=response.json()["id"], owner=owner) if response.status_code == 201 else None
    return {"status": response.status_code, "owned_count": len(listing["ids"]), "foreign_status": foreign.status_code, "stored_title": saved.title if saved else None}
Reasoning
The counterexample disables CSRF enforcement in the test client. The otherwise convincing flow then accepts a missing-token mutation and makes its protection claim false.

Independent practice: one bounded framework change

Include titles alongside identifiers in the owner-scoped GET response and expose that observed list in the helper result.

Include titles alongside identifiers in the owner-scoped GET response and expose that observed list in the helper result. Build both from the same ordered queryset and preserve every rejected flow.

Start with a valid trimmed title, then invalid empty input and a missing-token submission. This adds one useful response field without changing identity, permission scope, CSRF configuration or the write validation boundary.

Step 1: Add titles to the same owner-scoped ordered collection response. Step 2: Observe that list without weakening any rejected request path.

Practice starter: solve before reading the solution

python

from bootstrap_lab import django_setup
django_setup(__name__)

from bootstrap_lab import install_models
from django.contrib.auth import get_user_model
from django.db import models

class Role(models.Model):
    title = models.CharField(max_length=80)
    owner = models.ForeignKey(get_user_model(), on_delete=models.CASCADE)
    class Meta:
        app_label = "framework_lab"

install_models(Role)

from django import forms
from django.contrib.auth.decorators import login_required
from django.http import JsonResponse
from django.middleware.csrf import get_token
from django.shortcuts import get_object_or_404
from django.test import Client
from django.urls import path
from django.views.decorators.http import require_http_methods

class RoleForm(forms.Form):
    title = forms.CharField(max_length=20)

@login_required
@require_http_methods(["GET", "POST"])
def collection(request):
    if request.method == "GET":
        own = Role.objects.filter(owner=request.user).order_by("id")
        return JsonResponse({"ids": list(own.values_list("id", flat=True)), "token": get_token(request)})
    form = RoleForm(request.POST)
    if not form.is_valid():
        return JsonResponse({"errors": sorted(form.errors)}, status=400)
    role = Role.objects.create(title=form.cleaned_data["title"], owner=request.user)
    return JsonResponse({"id": role.id, "title": role.title}, status=201)

@login_required
def detail(request, role_id):
    role = get_object_or_404(Role, pk=role_id, owner=request.user)
    return JsonResponse({"title": role.title})

urlpatterns = [path("roles/", collection), path("roles/<int:role_id>/", detail)]

def application_flow(title, include_token=True):
    # Remove this placeholder, then adapt the retained baseline:
    # Add titles to the same owner-scoped ordered collection response.
    raise NotImplementedError
    Role.objects.all().delete()
    User = get_user_model()
    User.objects.all().delete()
    owner = User.objects.create_user(username="owner")
    other = User.objects.create_user(username="other")
    private = Role.objects.create(title="Other private role", owner=other)
    client = Client(enforce_csrf_checks=True)
    client.force_login(owner)
    token = client.get("/roles/").json()["token"]
    headers = {"HTTP_X_CSRFTOKEN": token} if include_token else {}
    response = client.post("/roles/", data={"title": title}, **headers)
    listing = client.get("/roles/").json()
    foreign = client.get(f"/roles/{private.id}/")
    saved = Role.objects.get(pk=response.json()["id"], owner=owner) if response.status_code == 201 else None
    return {"status": response.status_code, "owned_count": len(listing["ids"]), "foreign_status": foreign.status_code, "stored_title": saved.title if saved else None}
Reasoning
Add titles to the same owner-scoped ordered collection response. Observe that list without weakening any rejected request path.
  • Hint 1: Select both response fields from the same queryset.
  • Hint 2: Keep invalid and missing-token writes from creating a row.

Solution, tests and trade-offs

The cases inspect HTTP status, observed own-collection count, rejected foreign-object access and the actual stored title.

The cases inspect HTTP status, observed own-collection count, rejected foreign-object access and the actual stored title. Practice adds the observed title list and keeps it empty for failed writes.

Those jointly expose a response that claims success while storing the wrong data, or a listing that mixes owners. The fixture is deliberately small; concurrent create requests, recovery procedures and an operational deployment still need additional bounded evidence.

Practice solution and acceptance checks

python

from bootstrap_lab import django_setup
django_setup(__name__)

from bootstrap_lab import install_models
from django.contrib.auth import get_user_model
from django.db import models

class Role(models.Model):
    title = models.CharField(max_length=80)
    owner = models.ForeignKey(get_user_model(), on_delete=models.CASCADE)
    class Meta:
        app_label = "framework_lab"

install_models(Role)

from django import forms
from django.contrib.auth.decorators import login_required
from django.http import JsonResponse
from django.middleware.csrf import get_token
from django.shortcuts import get_object_or_404
from django.test import Client
from django.urls import path
from django.views.decorators.http import require_http_methods

class RoleForm(forms.Form):
    title = forms.CharField(max_length=20)

@login_required
@require_http_methods(["GET", "POST"])
def collection(request):
    if request.method == "GET":
        own = Role.objects.filter(owner=request.user).order_by("id")
        return JsonResponse({"ids": list(own.values_list("id", flat=True)), "titles": list(own.values_list("title", flat=True)), "token": get_token(request)})
    form = RoleForm(request.POST)
    if not form.is_valid():
        return JsonResponse({"errors": sorted(form.errors)}, status=400)
    role = Role.objects.create(title=form.cleaned_data["title"], owner=request.user)
    return JsonResponse({"id": role.id, "title": role.title}, status=201)

@login_required
def detail(request, role_id):
    role = get_object_or_404(Role, pk=role_id, owner=request.user)
    return JsonResponse({"title": role.title})

urlpatterns = [path("roles/", collection), path("roles/<int:role_id>/", detail)]

def application_flow(title, include_token=True):
    Role.objects.all().delete()
    User = get_user_model()
    User.objects.all().delete()
    owner = User.objects.create_user(username="owner")
    other = User.objects.create_user(username="other")
    private = Role.objects.create(title="Other private role", owner=other)
    client = Client(enforce_csrf_checks=True)
    client.force_login(owner)
    token = client.get("/roles/").json()["token"]
    headers = {"HTTP_X_CSRFTOKEN": token} if include_token else {}
    response = client.post("/roles/", data={"title": title}, **headers)
    listing = client.get("/roles/").json()
    foreign = client.get(f"/roles/{private.id}/")
    saved = Role.objects.get(pk=response.json()["id"], owner=owner) if response.status_code == 201 else None
    return {"status": response.status_code, "owned_count": len(listing["ids"]), "foreign_status": foreign.status_code, "stored_title": saved.title if saved else None, "listed_titles": listing["titles"]}
Reasoning
The cases inspect HTTP status, observed own-collection count, rejected foreign-object access and the actual stored title. Practice adds the observed title list and keeps it empty for failed writes.

Interview reasoning and next step

Explain the complete path from session identity and CSRF checks through form cleaning, owner assignment, persistence and selected readback.

Explain the complete path from session identity and CSRF checks through form cleaning, owner assignment, persistence and selected readback. Then state which responsibilities are intentionally outside this capstone: a production secret source, database migration plan, deployment checks, background delivery and monitoring.

Use the course’s negative cases as a starting regression set. A later Django REST Framework track can extend the same ownership and validation model to serializers and API-specific permissions.

Optional stretch — after the core task works: Add a separately authorized update route and a concurrent-write regression test.

Computer practice: https://applydjinn.com/typorithm/python-frameworks-lab-20261105.zip. Install the pinned dependencies in an isolated environment, run verify.py --track django, then edit the retained exercise baseline and check it with --practice dj-capstone. The lab uses owned in-memory databases, local framework clients and tiny CPU tensors. It never reads the working ApplyDjinn database, contacts production feeds or downloads models.

Compose real framework boundaries so successful creation, rejected input and private-object access tell one consistent story.