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?
answer
- a cooperative chain of kwargs
- each link adds its own keys
- the chain ends in ContextMixin
- left of the generic view
basics
~10 sSeveral 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 sIn 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 linesfrom 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 = Episodego deeper
Remember the shape: context = super().get_context_data(**kwargs), add your keys, return context.
Explain the chain of links each adding keys, that ContextMixin ends it without super(), and why that forces custom mixins to the left.
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.
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