skip to content

When writing a custom Django class-based view mixin that adds template context, why must get_context_data() call super() and return the merged dict?

level: middleimportance: should knowfreq 44%

answer

  1. a cooperative chain of kwargs
  2. each link adds its own keys
  3. the chain ends in ContextMixin
  4. left of the generic view

basics

~10 s

Several classes in a Django view contribute context through one cooperative get_context_data() chain. A mixin that skips super() or returns its own dict drops what the others add, such as object, form or page_obj.

solid answer

~40 s

In a generic view, `get_context_data(**kwargs)` is a chain: `SingleObjectMixin` adds `object`, `FormMixin` adds `form`, `MultipleObjectMixin` adds `object_list` and the pagination keys, and `ContextMixin` at the end adds `view` and `extra_context`. Each link should take the incoming kwargs, add its keys and pass them on with `super()`. A custom mixin should do the same: `context = super().get_context_data(**kwargs)`, add its keys, `return context`. If it builds a fresh dict instead, the template loses everything the other links would have added. The mixin should also sit to the left of the generic view: `ContextMixin.get_context_data()` does not call `super()`, so a mixin placed after it is never reached. The same cooperative rule applies to `get_queryset()` and `dispatch()`.

code

python · 23 lines
python
from django.shortcuts import get_object_or_404
from django.views.generic import ListView

from .models import Episode, Podcast


class PodcastContextMixin:
    def get_podcast(self):
        if not hasattr(self, '_podcast'):
            self._podcast = get_object_or_404(Podcast, slug=self.kwargs['podcast_slug'])
        return self._podcast

    def get_queryset(self):
        return super().get_queryset().filter(podcast=self.get_podcast())

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        context['podcast'] = self.get_podcast()
        return context


class EpisodeListView(PodcastContextMixin, ListView):
    model = Episode

go deeper

for a junior

Remember the shape: context = super().get_context_data(**kwargs), add your keys, return context.

for a middle

Explain the chain of links each adding keys, that ContextMixin ends it without super(), and why that forces custom mixins to the left.

for a senior

Diagnose the silent failures: a mixin that returns a fresh dict, a mixin placed on the right, two mixins writing the same key or attribute.

for a principal

Keep a small set of single-purpose project mixins with tests, and prefer extra_context or plain helpers over deep mixin stacks nobody can reorder safely.

## Context is built by a chain, not one method A Django generic view is assembled from mixins that each own part of the template context. When a handler calls `self.get_context_data()`, Python finds the first implementation in the method resolution order (MRO), and each implementation hands on to the next with `super()`: | Link | Adds | |---|---| | `SingleObjectMixin` | `object` and a name such as `episode` | | `FormMixin` | `form`, if the caller did not pass one | | `MultipleObjectMixin` | `object_list`, `paginator`, `page_obj`, `is_paginated`, and a name such as `episode_list` | | `ContextMixin` (last) | `view`, then the keys in `extra_context`, and returns the dict | `ContextMixin.get_context_data()` is the end of the line: it sets its keys and returns without calling `super()`. ## Writing a well-behaved mixin A custom mixin for a podcast site might add the current podcast and its sidebar links to every episode page. The cooperative version: 1. Accepts `**kwargs` and calls `context = super().get_context_data(**kwargs)`. 2. Adds its own keys to that dict. 3. Returns the dict. Rules that keep it composable: - **Inherit from `object`**, not from `View` or a generic view. A mixin is a slice of behaviour; making it a full view drags `View.dispatch()` into odd places in the MRO. - **Place it left of the generic view.** Methods whose generic chain ends without `super()` (`get_context_data()` in `ContextMixin`, `dispatch()` in `View`) never reach a mixin placed to their right. - **Use distinctive attribute and key names.** Two mixins that both set `self.podcast` or both write `context['podcast']` overwrite each other silently. - **Do the same for other hooks.** A mixin that narrows `get_queryset()` should return `super().get_queryset().filter(...)`, so a later mixin's filtering still applies. ## What breaks when a mixin does not cooperate A mixin that returns `{'podcast': self.podcast}` without calling `super()` cuts the chain at itself. With `PodcastContextMixin, UpdateView`, the template then has no `form` and no `object`, and the page renders an empty form area with no error. The bug usually appears only when the mixin is reused on a different generic view, which is why it slips through review. A subtler variant calls `super()` but ignores its return value, building and returning a separate dict. The effect is the same. ## Mixins versus extra_context For static values, `extra_context` (a class attribute, or passed to `as_view()`) is simpler than a mixin: `ContextMixin` merges it for you. Reach for a mixin when the value depends on the request or URL, or when the same logic applies to several views. Keep each mixin small and single-purpose; a mixin that adds context, filters the queryset and checks access is three concerns that will be hard to reorder later.

  • What happens if the same custom mixin is listed after ListView instead of before it?
    Its `get_context_data()` and `get_queryset()` are never called. `ListView`'s chain reaches `ContextMixin.get_context_data()`, which returns without `super()`, and `MultipleObjectMixin.get_queryset()` does not call `super()` either. The page renders every episode with no `podcast` in the context and no error.
  • When is extra_context a better choice than a custom mixin?
    When the value is static, such as a page title or a feature flag known at import time. `ContextMixin` merges `extra_context` into every render. A mixin earns its place when the value depends on the request, the URL kwargs or the object, or is shared across several views.

A cooperative get_context_data() chain is like a clipboard passed along a production line: each station adds its notes and passes the same clipboard on. A station that starts a fresh clipboard throws away everything written before it.

saying these in an interview costs you the question

  • A mixin can return its own context dict since Django merges them
  • Custom mixins should subclass View so they get request and kwargs
  • Mixins belong after the generic view so the view sets up first
  • Calling super() is optional when the mixin is the only custom one