skip to content

Template & Redirect Generics

TemplateView renders a template with extra_context, and RedirectView sends a redirect built from a URL or a pattern name. Interviewers probe permanent vs temporary redirects and preserve_request.

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

explore

questions

4

In Django, how do you serve a mostly static About page with TemplateView, and how do you add data to its template context?

level: juniorimportance: must knowfreq 55%

answer

  1. no views.py needed at all
  2. template_name as an as_view() keyword
  3. a dict merged into context
  4. super() first, then add keys

basics

~10 s

Route to TemplateView.as_view(template_name="about.html"); it renders that template on GET. Add fixed values with extra_context, or subclass and override get_context_data(), calling super() first so URL kwargs, view and extra_context stay in the context.

solid answer

~30 s

`TemplateView` renders `template_name` for GET (and HEAD) requests, so a static page needs no view code: `path("about/", TemplateView.as_view(template_name="about.html"))`. Its context comes from `ContextMixin.get_context_data()`: the keyword arguments captured from the URL pattern, a `view` entry pointing at the view instance, and then `extra_context`, a dict you can set as a class attribute or pass to `as_view()`. For data that must be computed per request, subclass and override `get_context_data(**kwargs)`: call `super().get_context_data(**kwargs)` first, add keys to the returned dict and return it. If you forget `template_name` and do not override `get_template_names()`, the view raises `ImproperlyConfigured` on the first request.

code

python · 9 lines
python
from django.test import TestCase


class AboutPageTests(TestCase):
    def test_about_page_uses_template_and_context(self):
        response = self.client.get("/about/ethics/")
        self.assertEqual(response.status_code, 200)
        self.assertTemplateUsed(response, "pages/ethics.html")
        self.assertEqual(response.context["section"], "about")

go deeper

for a junior

Recall that TemplateView.as_view(template_name=...) serves a static page and that extra_context or get_context_data() adds template variables.

for a middle

Explain the context build order: URL kwargs, then view, then extra_context, then whatever your override adds after super().

for a senior

Separate constants from per-request data: extra_context is evaluated once per process, so anything dynamic belongs in get_context_data().

for a principal

Decide when a URLconf-only TemplateView is clearer than a named subclass, weighing discoverability and testability across a large project.

## What TemplateView is for A news site has pages that are almost pure template: About, Contact, Editorial Standards. Writing a function that only calls `render()` for each one is boilerplate. Django's **`TemplateView`** (in `django.views.generic`) is a ready-made class-based view that renders one template on `GET`, and on `HEAD` through the base `View` alias. Its class hierarchy explains everything it does: - **`TemplateResponseMixin`** supplies `template_name`, `get_template_names()` and `render_to_response()`. - **`ContextMixin`** supplies `extra_context` and `get_context_data()`. - **`View`** supplies `as_view()`, `setup()` and `dispatch()`. `TemplateView.get()` is two lines: build the context with `get_context_data(**kwargs)`, then return `render_to_response(context)`. ## Zero-code usage in the URLconf ```python from django.urls import path from django.views.generic import TemplateView urlpatterns = [ path("about/", TemplateView.as_view(template_name="pages/about.html"), name="about"), path( "about/ethics/", TemplateView.as_view( template_name="pages/ethics.html", extra_context={"section": "about"}, ), ), ] ``` Both `template_name` and `extra_context` are existing attributes on the class, so `as_view()` accepts them as keyword overrides. Without a `template_name` (and without an overridden `get_template_names()`), the first request raises **`ImproperlyConfigured`** saying the mixin requires either `template_name` or an implementation of `get_template_names()`. ## What ends up in the context `ContextMixin.get_context_data(**kwargs)` builds the dict in this order: 1. Start from the **keyword arguments captured by the URL pattern** — `TemplateView.get()` passes them straight through, so `path("about/<slug:section>/", ...)` makes `{{ section }}` available. 2. `setdefault("view", self)` — the **view instance** is available as `{{ view }}`, so the template can call view methods or read attributes. 3. `update(extra_context)` — **`extra_context`** is merged last, so a key there overrides a URL kwarg of the same name. Context processors, such as the one that adds `request`, run later when the template is rendered; they are not part of this dict. | Source | Set where | Evaluated when | |---|---|---| | URL kwargs | The `path()` pattern | Every request | | `view` | Automatically | Every request | | `extra_context` | Class attribute or `as_view()` keyword | Once, when the class or URLconf is loaded | | Keys added in `get_context_data()` | Your subclass | Every request | The last column is the practical difference: `extra_context` is for **constants**, `get_context_data()` is for anything computed. ## Overriding get_context_data correctly ```python from django.views.generic import TemplateView class AboutView(TemplateView): template_name = "pages/about.html" def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) context["show_staff_links"] = self.request.user.is_staff return context ``` - **Call `super()` first.** It returns the dict with URL kwargs, `view` and `extra_context`; building a fresh dict throws those away, and with mixins in the chain it also drops whatever they contribute. - **Accept and forward `**kwargs`.** Other callers, including subclasses, pass extra keys through them. - **Return the dict.** Forgetting `return` hands `None` to `render_to_response()`, and the page renders with an empty context: every variable silently comes out blank. ## Using the view from the template Because the context always contains `view`, a template can read attributes and call zero-argument methods on the view instance. That is a tidy way to expose small computed values without widening the context: - `{{ view.template_name }}` shows which template is rendering, handy in a debug footer. - A method such as `def page_title(self): return "About the newsroom"` becomes `{{ view.page_title }}`; the template engine calls callables that take no arguments. - The method runs at render time, per request, so it can read `self.request` safely. Keep this for presentation helpers. Heavy work hidden in a template-called method is hard to find when a page gets slow. ## Choosing the lightest tool | Need | Use | |---|---| | A fixed page with no data | `TemplateView.as_view(template_name=...)` in the URLconf | | A fixed page plus a few constants | The same, with `extra_context={...}` | | Values computed per request | A `TemplateView` subclass overriding `get_context_data()` | | One model object or a list of them | The display generics, not `TemplateView` | ## What TemplateView does not do - It answers `GET` and `HEAD`; a `POST` to it gets **405 Method Not Allowed** because it defines no `post()`. - It does not fetch a model object or list; that is what the display generics are for. - It returns a `TemplateResponse`, which is rendered lazily after the view returns, so middleware and tests can still inspect `response.context_data` and `response.template_name` before rendering.

  • A URL kwarg and an extra_context key have the same name. Which value does the template see?
    The `extra_context` value. `ContextMixin.get_context_data()` starts from the URL kwargs, adds `view` with `setdefault`, then calls `update(self.extra_context)`, so the later update wins. A subclass that sets the key after calling `super().get_context_data()` would override both.
  • When is it worth subclassing TemplateView instead of passing everything to as_view()?
    When the context needs per-request work, such as reading `self.request.user` or querying the database, or when the same page is mounted at several URLs and you want one named class to test. `extra_context` is evaluated once when the URLconf loads, so anything that changes over time belongs in an overridden `get_context_data()`.

saying these in an interview costs you the question

  • Writing a function view that only calls render() for every static page
  • Overriding get_context_data() without calling super() and returning a fresh dict
  • Passing the template name positionally to as_view()
  • Believing TemplateView also handles POST submissions
  • Putting per-request data such as database queries into extra_context
open as a page

How does Django's RedirectView build its target from url or pattern_name, and what happens to captured URL arguments and the query string?

level: middleimportance: should knowfreq 42%

basics

~20 s

RedirectView.get_redirect_url() interpolates URL kwargs into url with %-formatting, or else reverses pattern_name with the same captured args and kwargs. The query string is dropped unless query_string=True, and with neither url nor pattern_name it returns 410 Gone.

open as a page

On a Django news site, a TemplateView's extra_context shows the same headlines and copyright year until the server restarts; why, and how do you fix it?

level: seniorimportance: should knowfreq 38%

basics

~20 s

extra_context is evaluated once, when the class body or URLconf loads, and the same dict is merged into every request's context. A computed year freezes, and a QuerySet stored there caches its rows on first render. Compute such values in get_context_data().

open as a page

In Django 6.1, which status codes can RedirectView send, and how do permanent and preserve_request choose between them?

level: middleimportance: nice to knowfreq 26%

basics

~10 s

RedirectView sends 302 by default, 301 with permanent=True, and, since Django 6.1, 307 or 308 when preserve_request=True, which tells the client to repeat the same method and body at the new URL.

open as a page