Fixing the get_context must return a dict error in Wagtail CMS

Summary

During a recent deployment of a high-traffic Wagtail CMS feature, we encountered a critical production failure where users were met with 500 Internal Server Errors instead of a graceful fallback. The intent was to implement error handling within a get_context method to redirect users to a custom error page if data retrieval failed. However, the implementation violated the expected method signature and return type requirements of the Wagtail/Django framework, leading to a secondary exception: TypeError: get_context must return a dict.

Root Cause

The failure stemmed from a fundamental misunderstanding of the Django template context lifecycle.

  • Return Type Mismatch: The get_context method in Wagtail is designed to augment the template context. The framework expects a dict to be returned so it can merge it with existing data.
  • Improper Control Flow: By attempting to return a HttpResponseRedirect (or calling redirect()) from within get_context, the developer broke the contract. The framework received a redirect object where it strictly required a dictionary.
  • Exception Bubbling: When the data retrieval logic failed, the error handling logic attempted to “exit” the method early via a redirect, which is an invalid operation for a context provider.

Why This Happens in Real Systems

In complex, data-driven CMS environments, this happens due to leaky abstractions:

  • Mixing Concerns: Developers often conflate Data Preparation (which belongs in get_context) with Request/Response Orchestration (which belongs in the View layer).
  • Implicit Expectations: Frameworks like Wagtail provide “magic” hooks. It is easy to forget that get_context is a sub-routine of a larger rendering process, not a standalone request handler.
  • Complexity Creep: As models become more dependent on external APIs or heavy database queries, the temptation to “fix” failures within the model layer increases.

Real-World Impact

  • Service Unavailability: Instead of seeing a “Friendly Error Page,” users see a generic, unbranded browser error page.
  • Broken SEO: Improper error handling can cause search engine crawlers to index 500 error pages, damaging the site’s search engine ranking.
  • Increased On-Call Fatigue: These errors often bypass standard application logic and trigger generic exception monitors, leading to “noise” in alerting systems.

Example or Code

The following example demonstrates the incorrect approach and the proper structural fix.

# INCORRECT APPROACH
from django.shortcuts import redirect

class DataHeavyPage(Page):
    def get_context(self, request):
        context = super().get_context(request)
        try:
            context['data'] = external_api_call()
        except Exception:
            # THIS CAUSES THE "get_context must return a dict" ERROR
            return redirect('error_page')
        return context

# CORRECT APPROACH
from django.shortcuts import redirect

class DataHeavyPage(Page):
    def serve(self, request):
        try:
            # Logic that requires redirect capabilities belongs in serve()
            # or a custom view, not get_context()
            return super().serve(request)
        except DataRetrievalError:
            return redirect('error_page')

    def get_context(self, request):
        context = super().get_context(request)
        try:
            context['data'] = external_api_call()
            context['error_occurred'] = False
        except Exception:
            # Signal the error to the template instead of breaking the flow
            context['data'] = None
            context['error_occurred'] = True
        return context

How Senior Engineers Fix It

Senior engineers apply the Principle of Single Responsibility:

  1. Decouple Logic: Move complex, error-prone data fetching out of the Page model and into a Service Layer.
  2. Leverage the serve method: Recognize that serve() is the entry point for handling the HttpRequest and HttpResponse. If a redirect is required, it must happen here.
  3. Graceful Degradation: Instead of a hard redirect, use get_context to pass an error flag to the template. This allows the template to render a partial error state or a localized error message, which is much more resilient.
  4. Strict Typing/Contract Adherence: Always verify the return type requirements of framework hooks before implementing control flow logic.

Why Juniors Miss It

  • Mental Model Mismatch: Juniors often view a Page model as a “controller” (like in MVC), assuming they can control the entire request lifecycle from any method.
  • Focus on “The Happy Path”: They focus on how to get the data, and when they encounter an error, they use the most familiar tool (a redirect) without considering the calling context.
  • Lack of Framework Deep-Dives: They often rely on high-level documentation that explains how to use a method, but rarely on the low-level source code that defines what the method must return.

Leave a Comment