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.