skip to content

In Django, when does a plain function-based view beat a class-based view, and when does a generic class-based view earn its keep?

level: middleimportance: must knowfreq 72%

answer

  1. same contract, different packaging
  2. explicit flow versus hook methods
  3. generic CRUD patterns
  4. 405 for free, decorators need wrapping

basics

~20 s

Function views win for bespoke flows, one-off endpoints and readability; generic class-based views win when a page matches a standard list, detail or form pattern or when many views share behaviour through mixins. Both satisfy the same request-in, response-out contract.

solid answer

~40 s

Both are just callables to the URLconf: `as_view()` turns a class into a function that creates a fresh instance per request and dispatches to `get()`, `post()` and so on. A function view reads top to bottom, takes decorators directly and suits bespoke flows such as a library checkout that touches several models. A class-based view pays off when the page fits a generic pattern, since `ListView`, `DetailView`, `CreateView` and friends supply querying, pagination and form handling, or when a mixin can share behaviour across many views. It also answers unsupported methods with 405 automatically. The cost is implicit flow spread across hooks and parent classes. The Django docs say CBVs do not replace FBVs; a mixed codebase is normal.

code

python · 12 lines
python
from django.contrib.auth.mixins import LoginRequiredMixin
from django.views.generic import ListView

from .models import Loan


class MyLoansView(LoginRequiredMixin, ListView):
    template_name = "loans/mine.html"
    paginate_by = 20

    def get_queryset(self):
        return Loan.objects.filter(member=self.request.user).select_related("copy")

go deeper

for a junior

Recall that both are callables for the URLconf, that as_view() builds the function for a class, and name a few generic views.

for a middle

Explain dispatch to get()/post(), the automatic 405, per-request instances, and why decorators need method_decorator on classes.

for a senior

Choose per view: generic CBVs for standard CRUD and shared mixins, function views for bespoke multi-model flows, and avoid deep custom hierarchies.

for a principal

Set a team convention that favours consistency within an app over a project-wide rule, and judge when custom base classes cost more than they save.

## Two ways to satisfy one contract Both kinds of view satisfy the same contract: a callable that takes an `HttpRequest` and returns an `HttpResponse`. A **function-based view (FBV)** is that callable directly. A **class-based view (CBV)** is a class whose `as_view()` class method builds the callable; on each request that function creates a **new instance** of the class, and `dispatch()` sends the request to the method named after the HTTP verb, such as `get()` or `post()`. The Django docs are explicit that class-based views **do not replace** function-based views; they are an alternative with different strengths. ## Where each one wins | Concern | Function view | Class-based view | |---|---|---| | Reading the flow | top to bottom in one function | spread across hook methods (`get_queryset()`, `get_context_data()`, `form_valid()`) and parent classes | | HTTP methods | every method reaches the body; restrict with an `if` or a decorator | only methods with a handler run; others get 405 from `http_method_not_allowed()` | | Reuse | helper functions and decorators | inheritance and mixins | | Standard CRUD pages | written by hand each time | generic views (`ListView`, `DetailView`, `CreateView`, `UpdateView`, `DeleteView`) do most of it | | Decorators | applied directly | need `method_decorator` or wrapping the `as_view()` result | | Debugging | the stack trace points at your code | the trace passes through framework methods | ## When a function view is the better choice - **Bespoke flows.** A library checkout that loads a copy, checks the member's standing, records the loan and redirects with a message has no generic shape to fit. Forcing it into a `FormView` means overriding several hooks to express what a function says in fifteen lines. - **One-off endpoints.** A health check, a webhook receiver or a small JSON endpoint gains nothing from a class hierarchy. - **Readers new to the code.** Explicit control flow is easy to review; there is no need to know which of eight mixin methods runs when. - **Several models or forms in one page.** Generic views are built around one model or one form; juggling two is often clearer by hand. ## When a class-based view earns its keep - **The page is a standard pattern.** A paginated list of titles, a detail page, a create or edit form: generic views provide the query, the pagination, the form handling and the success redirect, and you set attributes such as `model`, `template_name`, `paginate_by` and `success_url`. - **Many views share behaviour.** A mixin that scopes every queryset to the signed-in member, applied across a dozen views, removes the risk of forgetting the filter in one of them. - **Method separation matters.** `get()` and `post()` as separate methods, with automatic 405 for anything else, replace an `if request.method` ladder. ## Pitfalls on each side 1. **Copy-pasted FBVs.** Ten near-identical list views with the same pagination code are a sign that a generic view or a shared helper is due. 2. **Deep CBV hierarchies.** A custom base class with five mixins, written to save two lines per view, makes each view impossible to read without opening all of them. 3. **Overriding the wrong hook.** Putting per-request filtering in a class attribute `queryset` instead of `get_queryset()` is a CBV-specific bug with no FBV equivalent. 4. **Forgetting method restriction in an FBV.** A plain function view that changes data should reject GET, typically with `require_POST`. ## Testing each kind - **Function view:** build a request with `RequestFactory` and call the function, `checkout(request, copy_id=42)`. - **Class-based view, end to end:** call the generated function, `MyLoansView.as_view()(request)`. - **Class-based view, one hook at a time:** instantiate the class, call `view.setup(request)`, then test `get_queryset()` or `get_context_data()` directly. This finer-grained testing is a real advantage of CBVs when the hooks carry logic. - **Either kind through the stack:** the test client runs URL resolution and middleware, which is what you want for permissions and redirects. ## A defensible rule of thumb Start with a **generic class-based view when the page matches a generic pattern**, and with a **function view when it does not**. Mixed codebases are normal and healthy; consistency within one app matters more than a project-wide rule. The URLconf does not care which one a route points at.

  • In Django, what does a function view do with a request method it was not written for?
    It runs its body anyway: a plain function view receives every method, so a GET to a data-changing view executes the change unless the code checks `request.method` or uses a decorator such as `require_POST`. A class-based `View` instead answers methods without a handler with 405 through `http_method_not_allowed()`.
  • Is a Django class-based view instance shared between requests?
    No. The function returned by `as_view()` creates a new instance for every request, then calls `setup()` and `dispatch()`. Storing per-request state on `self` is safe for that reason, while mutable class attributes are shared and are a classic source of cross-request bugs.

A function view is a recipe written out step by step; a generic class-based view is a meal kit where you only swap the ingredients you care about, which is quicker when you want the standard dish and awkward when you want something the kit was not designed for.

saying these in an interview costs you the question

  • Function-based views are deprecated in favour of class-based views.
  • A class-based view instance is reused across requests for speed.
  • Class-based views cannot be decorated at all.
  • A plain function view rejects PUT and DELETE automatically.
  • Generic views should wrap every custom flow, however bespoke.