Course outline

Django / Request and data foundations / Lesson 3

Views, methods, and truthful HTTP responses

Write small request handlers whose methods, validation, content types, and status codes express the actual operation.

About 4 min readContent reviewed
On this page

Lesson goals

  • A view owns a request boundary
  • Restrict the declared method explicitly
  • Keep errors observable without leaking internals

A view owns a request boundary

A function-based view accepts a request and returns a response or raises an appropriate exception handled by the framework. Keep HTTP concerns such as method, query parsing, and response formatting visible while moving reusable domain calculations into ordinary functions. A view should not silently perform a write because a page was fetched. Safe methods such as GET should be free of requested state changes; creation and mutation belong to deliberate unsafe-method workflows with the relevant protection. HTTP method semantics and authorization are separate: using POST does not prove a caller may perform a change. Document both parts of the contract.

Read each input from the intended source

request.GET is a query-data mapping, while request.POST represents supported form-encoded input rather than every possible request body. A JSON API must decode and validate its body under an explicit size, media-type, and error policy. QueryDict can contain multiple values for one key, so decide whether duplicates are valid instead of accidentally accepting whichever value a convenience read returns. Convert numeric text deliberately and reject malformed or out-of-range values with useful feedback. Never pass an arbitrary ordering field, redirect target, or model attribute directly from request text into a privileged operation without a narrow allowlist.

Restrict the declared method explicitly

The first example applies require_GET to a simple status view and invokes it with synthetic GET and POST requests. The rejected method receives 405 rather than accidentally executing the read handler. This decorator does not add authentication, rate limits, or CSRF behavior; it expresses the method boundary only. The local direct-call fixture makes that limited result reproducible without a server. A production health or status endpoint also needs a deliberate information policy so it does not expose secrets or expensive diagnostics to every caller. A small success response should correspond to the limited condition it actually tested.

python Isolated executable example
from django.conf import settings
settings.configure(
    SECRET_KEY="synthetic-course-key",
    USE_I18N=False, USE_TZ=True,
    INSTALLED_APPS=[],
    DATABASES={"default": {"ENGINE": "django.db.backends.sqlite3", "NAME": ":memory:"}},
    DEFAULT_AUTO_FIELD="django.db.models.AutoField",
    ALLOWED_HOSTS=["testserver"],
)
import django
django.setup()

from django.http import HttpResponse
from django.test import RequestFactory
from django.views.decorators.http import require_GET

@require_GET
def status(request):
    return HttpResponse("ready", content_type="text/plain")

factory = RequestFactory()
print(status(factory.get("/status/")).status_code)
print(status(factory.post("/status/")).status_code)
Expected output
200
405

The decorator admits GET and rejects POST under the same direct-call fixture. No network or health dependency is contacted.

Return structured data under a clear schema

The second example returns a JsonResponse containing an explicit public projection. A dictionary is the normal safe top-level shape for this helper; other shapes require an intentional safe setting and API contract. Do not serialize an entire model or request simply because a JSON helper accepts data. Keep types stable, define missing values, and use a meaningful status. A 400 input rejection, 403 permission failure, 404 missing resource, and 500 unexpected server failure communicate different outcomes. A plain JSON view is not a full REST framework: pagination, authentication, schema evolution, and error conventions still need design.

python Isolated executable example
from django.conf import settings
settings.configure(
    SECRET_KEY="synthetic-course-key",
    USE_I18N=False, USE_TZ=True,
    INSTALLED_APPS=[],
    DATABASES={"default": {"ENGINE": "django.db.backends.sqlite3", "NAME": ":memory:"}},
    DEFAULT_AUTO_FIELD="django.db.models.AutoField",
    ALLOWED_HOSTS=["testserver"],
)
import django
django.setup()

import json
from django.http import JsonResponse

response = JsonResponse({"course": "Django", "seats": 4}, status=200)
payload = json.loads(response.content)
print(response.status_code)
print(payload["course"], payload["seats"])
Expected output
200
Django 4

The response carries only the selected public fields, and the numeric seat value remains a JSON number.

Keep errors observable without leaking internals

Catch only failures the view can classify meaningfully, and let unexpected exceptions reach the applicationโ€™s reviewed error-reporting boundary. Returning a successful empty list for every database exception can turn an outage into a false business answer. Log safe context such as an operation identifier and error class, not complete passwords, tokens, or submitted records. Tests should inspect status, content type, and the exact approved fields independently. Include unsupported methods, duplicate inputs, empty data, and failure recovery. Direct RequestFactory checks are useful for local logic but do not establish reverse-proxy, browser, or end-to-end middleware behavior.

Common errors and fixes

JSON input is absent from request.POST

Why it happens
The body is JSON rather than form-encoded data.
How to fix it
Decode and validate the declared body format explicitly.
Prevent it next time
Test each supported media type separately.

An outage looks like an empty successful catalog

Why it happens
A broad exception handler substitutes normal-looking data.
How to fix it
Report classified failures truthfully and preserve unexpected errors.
Prevent it next time
Test failure versus genuinely empty success.

A read URL changes records

Why it happens
GET was used to trigger mutation.
How to fix it
Move the change to an authorized protected write workflow.
Prevent it next time
Review method semantics with route tests.

Check your understanding

1. Why is returning status 200 with {"error": "denied"} a weak permission contract?

Show worked solution

Clients, caches, and monitoring can interpret the status as successful even though the body says otherwise. Use the documented permission status and safe error shape, and assert both in tests. Keep internal diagnostics restricted rather than exposing every reason or record detail publicly.

2. Design a numeric page-size parser for a bounded endpoint.

Show worked solution

Accept a declared text field, convert it under a narrow error boundary, and require an allowed range such as one through fifty. Decide how duplicate query keys and omitted values behave. Return a clear rejection for malformed data instead of allowing an unbounded database or memory request.

Key takeaways

  • Methods, input formats, and permissions are separate contracts.
  • Status codes must match real outcomes.
  • Serialize only intentional public fields.

Official references