How do you combine Django's SingleObjectMixin with ListView to paginate one podcast's episodes, and which pitfalls must you handle?
answer
- two querysets, two models
- set self.object inside get()
- pass queryset to get_object()
- context names collide
basics
~10 sSubclass SingleObjectMixin and ListView, set self.object = self.get_object(queryset=Podcast.objects.all()) in get() before super().get(), return that podcast's episodes from get_queryset(), and add the podcast to the context yourself.
solid answer
~30 s`class PodcastEpisodesView(SingleObjectMixin, ListView)` gets `ListView`'s pagination for the episodes and `SingleObjectMixin`'s lookup for the podcast. Both mixins define `get_queryset()`, and you override it to return `self.object.episodes.all()`, so `get_object()` must be given `queryset=Podcast.objects.all()` explicitly or it would search episodes. `self.object` must be set in `get()` before `super().get()`, because `BaseListView.get()` calls `get_queryset()` straight away. Both mixins also claim `context_object_name`, so leave it unset and add `context['podcast'] = self.object` in `get_context_data()`; the episodes arrive as `page_obj` and `object_list`, not `episode_list`. The template defaults to the episode list template. It works, but the Django docs themselves advise keeping each view within one generic group.
code
python · 21 linesfrom django.views.generic import ListView
from django.views.generic.detail import SingleObjectMixin
from .models import Podcast
class PodcastEpisodesView(SingleObjectMixin, ListView):
paginate_by = 20
template_name = 'podcasts/podcast_episodes.html'
def get(self, request, *args, **kwargs):
self.object = self.get_object(queryset=Podcast.objects.all())
return super().get(request, *args, **kwargs)
def get_queryset(self):
return self.object.episodes.order_by('-published_at')
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['podcast'] = self.object
return contextgo deeper
Recall that ListView can be combined with SingleObjectMixin to list objects that belong to one parent object.
Explain the recipe: set self.object in get() via get_object(queryset=...), return the children from get_queryset(), add the parent in get_context_data().
Name each clash (one get_queryset for two models, ordering in BaseListView.get, the shared context name) and when to prefer a plain ListView instead.
Treat cross-group mixin combinations as a code smell in review; the docs' one-group rule keeps views readable for the next maintainer.
## The goal A podcast page shows the podcast's details and a paginated list of its episodes. `ListView` brings pagination; `SingleObjectMixin` brings the URL-based lookup of one object. The Django documentation shows this combination, and it is a favourite interview probe because every hook clashes once. ## The two querysets problem Both `SingleObjectMixin` and `MultipleObjectMixin` (inside `ListView`) use `get_queryset()`: | Needs a queryset | For | Model | |---|---|---| | `SingleObjectMixin.get_object()` | finding the podcast from the URL | `Podcast` | | `BaseListView.get()` | the list to paginate | `Episode` | There is only one `get_queryset()` on the class, and you override it to return episodes. So `get_object()` must be called with an explicit `queryset=Podcast.objects.all()`; otherwise it would look for an episode with the podcast's pk or slug. ## The ordering problem `BaseListView.get()` starts with `self.object_list = self.get_queryset()`. Your `get_queryset()` reads `self.object`, so the podcast has to be loaded first: 1. Override `get()`. 2. Set `self.object = self.get_object(queryset=Podcast.objects.all())`. 3. Call `super().get(request, *args, **kwargs)`, which builds the list, paginates it and renders. Setting `self.object` anywhere later (in `get_context_data()`, say) is too late. ## The context-name problem Both mixins implement `get_context_object_name()` and both read `context_object_name`: - With `SingleObjectMixin` first in the bases, its version of `get_context_object_name()` wins for both calls. For the list, it receives a queryset, which is not a model instance, and returns `None`, so **no `episode_list` key is added**. - The episodes are still available as `object_list` and, when paginating, `page_obj`. - Setting `context_object_name` would make both mixins write the same key. Leave it unset and add `context['podcast'] = self.object` yourself after calling `super().get_context_data(**kwargs)`, which also keeps `paginator` and `page_obj`. ## The template problem `ListView` knows nothing about podcasts. Its template naming uses the list's model, so the default template is the episode list template in the episodes' app. Set `template_name` explicitly if the page is really about the podcast. ## Checking it works Because every clash in this view is silent, a short test pins the behaviour down: - request the podcast's URL and assert the response uses the intended template and has `podcast` in its context; - assert that `page_obj` holds only that podcast's episodes, and that a second page exists when there are more episodes than `paginate_by`; - request an unknown slug or pk and assert a 404, which proves `get_object()` searched podcasts rather than episodes. If someone later sets `context_object_name`, reorders the bases, or moves the `self.object` assignment, one of these assertions fails instead of a template quietly rendering an empty list. ## When not to do this The Django docs close the section with a hint: use mixins from one group of generics per view (detail, list, editing or date), because combining a single-object mixin with a multiple-object mixin gets complex quickly. Simpler alternatives: - a plain `ListView` of episodes whose `get_queryset()` looks up the podcast with `get_object_or_404()` and filters by it, adding the podcast to the context; - a `DetailView` of the podcast that paginates the episodes itself in `get_context_data()`. Both avoid the double `get_queryset()` and the context-name collision, at the cost of a few explicit lines.
- Why is episode_list missing from the context in this combined view?`SingleObjectMixin` is first in the bases, so its `get_context_object_name()` answers for the list too. Given a queryset rather than a model instance, it returns `None`, and `MultipleObjectMixin` skips the named key. Use `page_obj` or `object_list` in the template.
- What simpler design gives the same page without mixing generic groups?A plain `ListView` of episodes: its `get_queryset()` fetches the podcast with `get_object_or_404()`, stores it on `self` and filters episodes by it, and `get_context_data()` adds the podcast. One queryset hook, one context name, same pagination.
saying these in an interview costs you the question
- get_object() automatically uses the podcast model despite the overridden get_queryset
- self.object can be set later in get_context_data()
- The episodes will be in the context as episode_list
- Setting context_object_name = 'podcast' cleanly separates the two
- Combining any two generic mixins is always safe