Filter concepts by levelShowing all levels.

Django · Function-Based Views

Function-Based Views

Building responses, reading request data, and branching on method and auth state

  • render()/redirect()/JsonResponsethree shortcuts, one HttpResponse contract
  • GET/POST are QueryDictsgetlist() for repeated keys
  • One function, every HTTP methodbranch on request.method yourself
  • request.user is never Nonecheck .is_authenticated, not truthiness

1.render(), redirect(), HttpResponse, JsonResponse

render(request, template, context) builds an HttpResponse from a template; redirect(to) builds a 302 to a URL, view name, or model; HttpResponse(content) wraps raw content directly; JsonResponse(data) serializes a dict to JSON with the right content type.

Key features

  • render(request, template_name, context) is render_to_string() + HttpResponse() in one call
  • redirect(to) accepts a URL string, a view name (resolved via reverse()), or a model instance (via get_absolute_url())
  • JsonResponse defaults safe=True, which only accepts a dict — pass safe=False to serialize a list or other JSON-serializable object
  • HttpResponse accepts str, bytes, or nothing (then use response.write()) — content_type defaults to "text/html"
  • redirect() defaults to a 302 (temporary) — pass permanent=True for a 301

Response helpers at a glance

Response helpers at a glance
HelperReturnsTypical use
render(request, tpl, ctx)HttpResponse, rendered templatea normal HTML page
redirect(to)HttpResponseRedirect (302)after a successful POST
redirect(to, permanent=True)HttpResponsePermanentRedirect (301)a URL that moved for good
HttpResponse(content)raw content, any content_typeplain text, a non-HTML body
JsonResponse(data)JSON-serialized dict, application/jsonan API endpoint
Four factories for the one thing every view must return
render()
template → HttpResponse
HttpResponse()
raw content, any content_type
redirect()
302 to a URL, view name, or model
JsonResponse()
dict → JSON, safe=False for a list
  • render(): same page, HTML — template → HttpResponse
  • HttpResponse(): same page, between HTML and data — raw content, any content_type
  • redirect(): somewhere else / raw data, HTML — 302 to a URL, view name, or model
  • JsonResponse(): somewhere else / raw data, data — dict → JSON, safe=False for a list

Command

python
from django.shortcuts import redirect

redirect("articles:detail", pk=5)      # by view name
redirect("/articles/5/")               # by literal URL
redirect(article_instance)             # by model, via get_absolute_url()

render() builds a template response, redirect() builds a 302 (to a URL, view name, or model), JsonResponse defaults to dict-only unless given safe=False.

2.Reading data off the request

request.GET and request.POST are both QueryDict objects — dict-like, but a repeated key keeps every value (getlist()), and indexing returns only the last one. request.FILES holds uploaded files separately; request.headers and request.COOKIES are both read like plain dicts.

Key features

  • request.GET/request.POST are QueryDict — indexing (["key"]) returns the LAST value for a repeated key; getlist("key") returns all of them
  • request.POST never contains uploaded files, even with the right form fields — use request.FILES for those
  • request.FILES is empty unless the request was POST (or PUT/PATCH) with enctype="multipart/form-data"
  • request.headers is case-insensitive: request.headers["User-Agent"] and request.headers["user-agent"] both work
  • request.COOKIES is a plain dict of string key/value pairs — no special QueryDict behavior

Reading request data

Reading request data
AttributeTypeHolds
request.GETQueryDict (immutable)query string parameters
request.POSTQueryDict (immutable)form-encoded body fields — never files
request.FILESdict-like of UploadedFileuploaded files from a multipart form
request.headerscase-insensitive dict-likeHTTP request headers
request.COOKIESplain dictcookies sent with the request
Where each piece of request data lives

page = request.GET.get("page", "1") selected = request.GET.getlist("tag") avatar = request.FILES.get("avatar") ua = request.headers.get("User-Agent")

request.GET.get("page"

request.GET — QueryDict — indexing returns the LAST value for a repeated key

request.GET.getlist("tag")

getlist() — returns every value for a repeated key

request.FILES.get("avatar")

request.FILES — uploaded files — never in request.POST

request.headers.get("User-Agent")

request.headers — case-insensitive dict-like

  • Whole: page = request.GET.get("page", "1") selected = request.GET.getlist("tag") avatar = request.FILES.get("avatar") ua = request.headers.get("User-Agent")
  • request.GET.get("page" — request.GET: QueryDict — indexing returns the LAST value for a repeated key
  • request.GET.getlist("tag") — getlist(): returns every value for a repeated key
  • request.FILES.get("avatar") — request.FILES: uploaded files — never in request.POST
  • request.headers.get("User-Agent") — request.headers: case-insensitive dict-like

Command

python
def upload(request):
    if request.method == "POST":
        f = request.FILES["document"]
        for chunk in f.chunks():
            ...

GET/POST are QueryDicts — indexing returns the last of a repeated key, getlist() returns all; POST never carries files (use FILES); check request.method, not `if request.POST`, to detect a POST.

3.Handling HTTP methods in an FBV

A function-based view receives every HTTP method (GET, POST, PUT, DELETE...) through the same function and must branch on request.method itself — Django provides require_GET/require_POST/require_http_methods decorators to reject the wrong method with a proper 405 instead of writing that check by hand.

Key features

  • request.method is always an uppercase string: "GET", "POST", "PUT", "PATCH", "DELETE", "HEAD"
  • @require_GET, @require_POST, and @require_http_methods(["GET", "POST"]) reject other methods with a 405 automatically
  • A 405 response from these decorators includes the Allow header, listing which methods are actually accepted
  • Without a method check, a view meant for GET-only display can be POSTed to and silently proceed
  • Class-based views dispatch by method (get()/post()/...) automatically — this manual branching is specifically an FBV concern

Method-restriction decorators

Method-restriction decorators
DecoratorAcceptsOn mismatch
@require_GETGET only405, with Allow: GET
@require_POSTPOST only405, with Allow: POST
@require_safeGET and HEAD405
@require_http_methods([...])exactly the listed methods405, with Allow header listing them

Command

python
from django.views.decorators.http import require_POST

@require_POST
def delete_article(request, pk):
    Article.objects.filter(pk=pk).delete()
    return redirect("articles:list")

An FBV receives every HTTP method through the same function — branch on request.method explicitly, or use @require_POST/@require_http_methods to reject the wrong method with a proper 405.

4.Session and authentication context in a view

request.session is a dict-like, per-visitor store set by SessionMiddleware; request.user is the current user (or AnonymousUser) set by AuthenticationMiddleware. Both are only present because their middleware ran first — remove either from MIDDLEWARE and the attribute disappears.

Key features

  • request.session behaves like a dict — request.session["key"] = value, request.session.get("key")
  • request.session persists across requests for the same visitor, backed by a cookie plus a server-side store
  • request.user is set by AuthenticationMiddleware — an authenticated user's model instance, or an AnonymousUser
  • AnonymousUser.is_authenticated is always False — checking it never raises, even for a logged-out visitor
  • Both attributes require their middleware in MIDDLEWARE, in the correct order (Session before Auth) — see Middleware

request.session vs request.user

request.session vs request.user
AttributeSet byAbsent visitor gets
request.sessionSessionMiddlewarea fresh, empty session — never missing entirely
request.userAuthenticationMiddlewarean AnonymousUser instance, never None

Command

python
request.session["cart_id"] = cart.id
cart_id = request.session.get("cart_id")
del request.session["cart_id"]

request.session is a dict-like per-visitor store; request.user is always a real user or AnonymousUser, never None — check .is_authenticated, never `if request.user:`.

Remember

  • render() builds a template response, redirect() builds a 302, JsonResponse defaults to dict-only unless given safe=False.
  • GET/POST are QueryDicts — indexing returns the last of a repeated key, getlist() returns all.
  • An FBV receives every HTTP method through the same function — branch on request.method, or use @require_POST.
  • request.user is always a real user or AnonymousUser, never None — check .is_authenticated.

Pro tip

  • request.POST never carries files, even with the right form fields — use request.FILES.
  • Check request.method == "POST", never `if request.POST:` — an empty QueryDict is falsy either way.
  • A state-changing view with no method check can be triggered by a plain GET from a crawler or link preview.

Next up

  • Class-Based Views
  • Templates
  • Forms

Sources

  • Request and response objects — docs.djangoproject.com/en/5.2/ref/request-response/
  • Every claim checked against Django 5.2 LTS docs · checked 2026-08-21

Spotted a mistake on this page? Tell us.