Skip to content

Defining template context

To define fake context you need to create a YAML file alongside your template file. For example, for the template big_red_button.html you need to create a file called big_red_button.yaml.

Let's imagine that your big_red_button.html template looks like this:

<a href="{{ button_link }}" class="button button--red">
    <span>{{ button_text }}</span>
</a>

The big_red_button.yaml can be something like this:

context:
    button_link: https://example.com/
    button_text: Example link

In the same way you can provide context in more complex templates. Here is an example on how you can define fake context that pretends to be a QuerySet.

Let's assume you have the following template:

{% if my_objects.exists %}
    {{ items_title }}
    <ul>
        {% for obj in my_objects.all %}
            <li>
                <a href="{{ obj.link }}">
                    {{ obj.title }}
                </a>
            </li>
        {% endfor %}
    </ul>
{% endif %}

You might define a YAML file similar to this to provide fake data:

name: My example pattern

context:
    items_title: Related pages
    my_objects:
        exists: true  # simulate `QuerySet`'s `exists` method
        all:          # simulate `QuerySet`'s `all` method
            - title: Page 1
              link: /page1
            - title: Page 2
              link: /page2

You can define a list or a dict or anything that PyYAML allows you to create in YAML format without creating a custom objects.

Modifying template contexts with Python

While most objects can be faked with YAML, Django has a few common constructs that are difficult to replicate. For example: Form and Paginator instances. To help with this, django-pattern-library allows you to register any number of 'context modifiers'. Context modifiers are simply Python functions that accept the context dictionary generated from the YAML file, and can make additions or updates to it as necessary. For convenience, they also receive the current HttpRequest as request.

Context modifiers can easily be registered using the register_context_modifier decorator. Here is a simple example:

# myproject/core/pattern_contexts.py

from pattern_library import register_context_modifier
from myproject.core.forms import SearchForm, SignupForm

@register_context_modifier
def add_common_forms(context, request):
    if 'search_form' not in context:
        context["search_form"] = SearchForm()
    if 'signup_form' not in context:
        context["signup_form"] = SignupForm()

Context modifiers are also great for reducing the amount of template tag patching that is needed. The following examples are from a Wagtail project:

# myproject/core/pattern_contexts.py

from django.core.paginator import Paginator
from wagtail.images import get_image_model
from pattern_library import register_context_modifier


@register_context_modifier
def add_page_images(context, request):
    """
    Replace some common 'image' field values on pages with real `Image`
    instances, so that the {% image %} template tag will work.
    """
    Image = get_image_model()
    if "page" in context:
        if "hero_image" in context["page"]:
            context["hero_image"] = Image.objects.all().order("?").first()
        if "main_image" in context["page"]:
            context["main_image"] = Image.objects.all().order("?").first()


@register_context_modifier
def replicate_pagination(context, request):
    """
    Replace lists of items using the 'page_obj.object_list' key
    with a real Paginator page, and add a few other pagination-related
    things to the context (like Django's `ListView` does).
    """
    object_list = context.pop('page_obj.object_list', None)
    if object_list is None:
        return

    original_length = len(object_list)

    # add dummy items to force pagination
    for i in range(50):
        object_list.append(None)

    # paginate and add ListView-like values
    paginator = Paginator(object_list, original_length)
    context.update(
        paginator=paginator,
        page_obj=paginator.page(1),
        is_paginated=True,
        object_list=object_list
    )

Registering a context modifier for a specific template

By default, context modifiers are applied to all pattern library templates. If you only wish for a context modifier to be applied to a specific pattern, you can use the template parameter to indicate this. For example:

# myproject/accounts/pattern_contexts.py

from pattern_library import register_context_modifier
from my_app.accounts.forms import SubscribeForm


@register_context_modifier(template="patterns/subscribe/form.html")
def add_subscribe_form(context, request):
    """
    Adds an unbound form to 'form.html'
    """
    context["form"] = SubscribeForm()


@register_context_modifier(template="patterns/subscribe/form_invalid.html")
def add_invalid_subscribe_form(context, request):
    """
    Adds a bound form with invalid data to 'form_invalid.html'
    """
    context["form"] = SubscribeForm(data={
        "email": 'invalid-email',
        "name": ''
    })

Controlling the order in which context modifiers are applied

By default, context modifiers are applied in the order they were registered (which can be difficult to predict across multiple apps), with generic context modifiers being applied first, followed by template-specific ones. If you need to control the order in which a series of context modifiers are applied, you can use the order parameter to do this.

In the following example, a generic context modifier is registered with an order value of 1, while others receive the default value of 0. Because 1 is higher than 0, the generic context modifier will be applied after the others.

# myproject/sums/pattern_contexts.py


from pattern_library import register_context_modifier


@register_context_modifier(template='patterns/sums/single_number.html')
def add_single_number(context, request):
    context['first_number'] = 933


@register_context_modifier(template='patterns/sums/two_numbers.html')
def add_two_numbers(context, request):
    context['first_number'] = 125
    context['second_number'] = 22


@register_context_modifier(template='patterns/sums/three_numbers.html')
def add_three_numbers(context, request):
    context['first_number'] = 125
    context['second_number'] = 22
    context['third_number'] = 9


@register_context_modifier(order=1)
def add_total(context, request):
    if 'total' not in context:
        first_num = context.get('first_number', 0)
        second_num = context.get('second_number', 0)
        third_num = context.get('third_number', 0)
        context['total'] = first_num + second_num + third_num

Last update: 2024-01-17