Typorithm · easy

HTTP methods and explicit response statuses

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

Step 1 of 4 · intro card

HTTP methods and explicit response statuses

0%

Concept and contract

Make allowed operations and response status part of the view contract instead of assuming a valid path permits every method.

A route identifies an endpoint, but its HTTP method identifies an intended operation. Reading and creating a resource should not be interchangeable simply because they share a URL.

This lesson allows GET and POST and gives the creation-shaped response a distinct status. The endpoint is fictional and changes no database state. Its purpose is to make method dispatch, rejection and response metadata observable before adding input validation.

Mechanism and worked example

The decorator rejects methods outside the declared list with a method-not-allowed response and an Allow header.

The decorator rejects methods outside the declared list with a method-not-allowed response and an Allow header. The view runs only for accepted operations and returns JSON with its explicit status.

The test client’s generic method lets us exercise both accepted and rejected requests. These early client calls do not enforce CSRF; that protection has a dedicated later lesson with enforcement enabled and a real token exchange.

Complete worked example

python

from bootstrap_lab import django_setup
django_setup(__name__)

from django.http import JsonResponse
from django.test import Client
from django.urls import path
from django.views.decorators.http import require_http_methods

@require_http_methods(["GET", "POST"])
def roles(request):
    return JsonResponse({"method": request.method}, status=201 if request.method == "POST" else 200)

urlpatterns = [path("roles/", roles)]

def request_method(method):
    response = Client().generic(method, "/roles/")
    return {"status": response.status_code, "allow": response.headers.get("Allow")}
Reasoning
The decorator rejects methods outside the declared list with a method-not-allowed response and an Allow header. The view runs only for accepted operations and returns JSON with its explicit status.

Counterexamples and common mistakes

The counterexample removes the decorator, so a DELETE request reaches a view that was only designed for reading and creation.

The counterexample removes the decorator, so a DELETE request reaches a view that was only designed for reading and creation. A broad success response can then conceal an unsupported operation.

Do not rely on a frontend hiding a button as a server method restriction. Also remember that HEAD and OPTIONS have framework-specific behavior: test the declared method set rather than assuming every framework adds identical operations automatically.

Counterexample: a runnable framework mistake

python

from bootstrap_lab import django_setup
django_setup(__name__)

from django.http import JsonResponse
from django.test import Client
from django.urls import path
from django.views.decorators.http import require_http_methods


def roles(request):
    return JsonResponse({"method": request.method}, status=201 if request.method == "POST" else 200)

urlpatterns = [path("roles/", roles)]

def request_method(method):
    response = Client().generic(method, "/roles/")
    return {"status": response.status_code, "allow": response.headers.get("Allow")}
Reasoning
The counterexample removes the decorator, so a DELETE request reaches a view that was only designed for reading and creation. A broad success response can then conceal an unsupported operation.

Independent practice: one bounded framework change

Add HEAD to the allowed methods while keeping POST at 201 and DELETE rejected.

Add HEAD to the allowed methods while keeping POST at 201 and DELETE rejected. Begin with the new safe read-shaped method, then repeat the existing accepted and rejected cases.

The helper checks only status and the Allow header because a HEAD response must not be treated as a JSON body to parse. You are extending one HTTP contract, not implementing a complete REST resource or a mutation workflow.

Step 1: Allow HEAD without accepting DELETE. Step 2: Preserve creation status and the framework-generated Allow header.

Practice starter: solve before reading the solution

python

from bootstrap_lab import django_setup
django_setup(__name__)

from django.http import JsonResponse
from django.test import Client
from django.urls import path
from django.views.decorators.http import require_http_methods

@require_http_methods(["GET", "POST"])
def roles(request):
    return JsonResponse({"method": request.method}, status=201 if request.method == "POST" else 200)

urlpatterns = [path("roles/", roles)]

def request_method(method):
    # Remove this placeholder, then adapt the retained baseline:
    # Allow HEAD without accepting DELETE.
    raise NotImplementedError
    response = Client().generic(method, "/roles/")
    return {"status": response.status_code, "allow": response.headers.get("Allow")}
Reasoning
Allow HEAD without accepting DELETE. Preserve creation status and the framework-generated Allow header.
  • Hint 1: Extend the decorator list rather than returning a fake status from the helper.
  • Hint 2: Do not try to decode a HEAD response body as JSON.

Solution, tests and trade-offs

The cases inspect the actual framework-generated rejection response as well as successful statuses.

The cases inspect the actual framework-generated rejection response as well as successful statuses. That prevents an implementation from returning a locally computed method label while ignoring Django’s decorator.

The practice checks the updated Allow header and preserves rejection of DELETE. Authentication, CSRF and object authorization remain separate requirements; a correctly restricted method list by itself does not make a later data-changing view safe.

Practice solution and acceptance checks

python

from bootstrap_lab import django_setup
django_setup(__name__)

from django.http import JsonResponse
from django.test import Client
from django.urls import path
from django.views.decorators.http import require_http_methods

@require_http_methods(["GET", "HEAD", "POST"])
def roles(request):
    return JsonResponse({"method": request.method}, status=201 if request.method == "POST" else 200)

urlpatterns = [path("roles/", roles)]

def request_method(method):
    response = Client().generic(method, "/roles/")
    return {"status": response.status_code, "allow": response.headers.get("Allow")}
Reasoning
The cases inspect the actual framework-generated rejection response as well as successful statuses. That prevents an implementation from returning a locally computed method label while ignoring Django’s decorator.

Interview reasoning and next step

Explain why status codes and headers are part of an API contract alongside its JSON shape.

Explain why status codes and headers are part of an API contract alongside its JSON shape. A creation response, an unsupported method and a missing object communicate different outcomes.

A browser client may additionally need redirect behavior, while a machine client often needs structured error bodies. Keep these distinctions explicit as the course moves into forms, models and state-changing requests rather than turning every outcome into an apparently successful response.

Optional stretch — after the core task works: Define an OPTIONS policy and test it independently of browser button visibility.

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

Make allowed operations and response status part of the view contract instead of assuming a valid path permits every method.