skip to content

Method & Conditional Decorators

Decorators such as require_POST, condition(), etag() and vary_on_headers() restrict methods, answer conditional GETs and set Vary. Interviewers probe method_decorator on class-based views.

part ofDjangooverview, primer and where to startread it →
on this pageshow

explore

questions

5

In Django, what do require_POST, require_GET, require_safe and require_http_methods do, and what does a request with another method receive?

level: juniorimportance: must knowfreq 55%

answer

  1. django.views.decorators.http
  2. a whitelist of methods
  3. 405 with an Allow header
  4. safe means GET and HEAD

basics

~10 s

They are view decorators from django.views.decorators.http that let only the listed HTTP methods reach the view. Any other method gets an HttpResponseNotAllowed: a 405 response with an Allow header naming the permitted methods.

solid answer

~30 s

`require_http_methods(["GET", "POST"])` wraps a function view and compares `request.method` with the list before the view runs. `require_GET`, `require_POST` and `require_safe` are prebuilt instances for `["GET"]`, `["POST"]` and `["GET", "HEAD"]`. A non-matching request never reaches the view: the decorator returns `HttpResponseNotAllowed(methods)`, a **405** whose `Allow` header lists the permitted methods, and logs a "Method Not Allowed" warning through `django.request`. Method names must be uppercase, because `request.method` is. `require_safe` is preferred over `require_GET` for read-only views, since clients and monitors send `HEAD`. Since Django 5.0 the decorators also wrap `async def` views.

code

python · 17 lines
python
from django.shortcuts import get_object_or_404, redirect
from django.views.decorators.http import require_http_methods, require_POST

from .models import Product


@require_POST
def archive_product(request, pk):
    product = get_object_or_404(Product, pk=pk)
    product.archived = True
    product.save(update_fields=["archived"])
    return redirect("catalogue:index")


@require_http_methods(["GET", "HEAD", "POST"])  # uppercase, as request.method is
def product_review(request, pk):
    ...

go deeper

for a junior

Know the four decorators, that they live in django.views.decorators.http, and that a wrong method gets a 405 with an Allow header.

for a middle

Explain that the shortcuts are require_http_methods instances, why method names must be uppercase, and why require_safe beats require_GET.

for a senior

Use method guards on every state-changing function view and keep them distinct from CSRF, authentication and permission checks in reviews.

for a principal

Set a codebase convention for method restriction across function and class-based views so 405 behaviour is consistent for API clients.

## What the decorators are The module `django.views.decorators.http` provides a small family of guards for **function-based views**: | Decorator | Allowed methods | |---|---| | `require_http_methods(list)` | exactly the methods in `list` | | `require_GET` | `GET` | | `require_POST` | `POST` | | `require_safe` | `GET`, `HEAD` | The last three are not separate implementations; they are simply `require_http_methods([...])` called once at import time. That is why you write `@require_POST` without parentheses but `@require_http_methods(["GET", "POST"])` with them. ## What happens on a request The wrapper runs before your view body: 1. It reads `request.method` (always uppercase in Django). 2. If the method is in the list, it calls your view with the original arguments and returns whatever the view returns. 3. If not, it builds `HttpResponseNotAllowed(request_method_list)`, logs `"Method Not Allowed (%s): %s"` with the method and path through `log_response()`, and returns the response without calling the view. `HttpResponseNotAllowed` has status **405** and sets the `Allow` header to the comma-joined list, so a client can see what would have worked. ## Why use them - **Correctness.** A view that deletes a product or submits an order should not run on `GET`: links, prefetchers and crawlers issue `GET` freely. `@require_POST` makes that impossible instead of relying on the template to use a form. - **Clear errors.** A 405 with `Allow` is more honest than a view that silently treats a `PUT` as a `GET`. - **Less branching.** The view body no longer needs `if request.method != "POST": return ...` boilerplate. They are not a security control on their own. CSRF protection, authentication and permission checks are separate mechanisms, and `require_POST` does not replace any of them. ## Common mistakes - **Lowercase names.** `require_http_methods(["get", "post"])` rejects everything, because `request.method` is `"GET"`, not `"get"`. The docstring says method names should be uppercase. - **`require_GET` on read-only pages.** `HEAD` requests then get 405. Health checks and link checkers often use `HEAD`, so `require_safe` is usually the better choice. The view can build its normal response: HTTP forbids a body on a `HEAD` response, and Django's development server and test client drop it, as production servers do. - **Forgetting `OPTIONS`.** The decorators do not answer `OPTIONS` themselves; if a client needs it, include it in the list and handle it. - **Putting them straight on a class-based view method.** They expect `request` as the first argument, so on a method they receive `self` instead; class-based views need `method_decorator` or the view's own `http_method_names` attribute. ## Async views Before Django 5.0 these decorators always produced a synchronous wrapper, which hid an `async def` view from Django's coroutine detection. Since 5.0 each decorator checks `iscoroutinefunction(func)` and builds an `async` wrapper for coroutine views, so `@require_POST` on `async def submit(request)` works as expected. ## Stacking and testing Decorators run from the outside in: the one written on top sees the request first. A method guard is cheap, so it is usually placed near the top, where it rejects wrong methods before more expensive decorators run. The exception is anything that must also shape the 405 itself, such as a decorator that adds headers to every response, which then goes above it. The behaviour is easy to pin down in a test: - `response = client.get(url)` on a `@require_POST` view should give `response.status_code == 405`; - `response["Allow"]` should equal `"POST"`; - a `client.head(url)` against a `@require_safe` view should return 200. A test like this also catches the lowercase-list mistake immediately, because the allowed method starts failing too. ## Typical usage in a product catalogue ```python from django.views.decorators.http import require_POST, require_safe @require_safe def product_detail(request, slug): ... @require_POST def add_to_basket(request, product_id): ... ``` The read view accepts `GET` and `HEAD`; the state-changing view accepts only `POST` and answers anything else with a 405 listing `POST`.

  • Why is require_safe usually better than require_GET for a read-only page?
    `require_GET` rejects `HEAD` with a 405, but health checks, link checkers and some caches send `HEAD` to probe a URL. `require_safe` allows both `GET` and `HEAD`, and the view can return its normal response because no body is sent for `HEAD`, so the same view serves both.
  • What exactly does a client receive when it sends PUT to a view decorated with @require_POST?
    An `HttpResponseNotAllowed(["POST"])`: status 405 with an `Allow: POST` header and an empty body. The view function is never called, and Django logs a `Method Not Allowed (PUT): /path/` warning through the `django.request` logger.

saying these in an interview costs you the question

  • require_POST also performs CSRF validation
  • A disallowed method gets a 404 so the endpoint stays hidden
  • Method names in require_http_methods are case-insensitive
  • require_GET also allows HEAD requests
  • @require_POST can be put directly on a class-based view's post() method
open as a page

In Django, why can't you put a function view decorator like require_POST directly on a class-based view method, and how does method_decorator fix it?

level: middleimportance: must knowfreq 52%

basics

~20 s

Function view decorators expect request as the first argument, but a method receives self first, so the decorator inspects the wrong object. method_decorator adapts the decorator to methods; apply it to dispatch() or to the class with name="dispatch".

open as a page

In Django, what do vary_on_headers() and vary_on_cookie() add to a response, and when must a view use them explicitly?

level: middleimportance: should knowfreq 30%

basics

~20 s

They add header names to the response's Vary header after the view runs, merging with any existing value. A view needs them when its output depends on a request header or cookie that no Django middleware already declares.

open as a page

A Django product-detail view is expensive to render; how would you use the condition() decorator so unchanged pages return 304 without running the view, and what pitfalls apply?

level: seniorimportance: should knowfreq 42%

basics

~20 s

Decorate the view with condition(etag_func=..., last_modified_func=...) using cheap callbacks that take the view's arguments; on a matching conditional GET Django returns 304 without calling the view. Use one condition() rather than stacked etag() and last_modified(), and keep vary or cache_control decorators above it.

open as a page

In Django 5.0 and later, what changed for built-in view decorators on async def views, and which trap with condition() callbacks remains?

level: seniorimportance: nice to knowfreq 20%

basics

~20 s

Since Django 5.0 the built-in view decorators detect async def views and return an async wrapper, so the view stays a coroutine function. condition() still calls its etag and last-modified callbacks synchronously, so they cannot be async or use the sync ORM.

open as a page