Template components¶
Working with objects that know how to render themselves as elements on an HTML template is a common pattern seen throughout the Wagtail admin. For example, the admin homepage is a view provided by the central wagtail.admin
app, but brings together information panels sourced from various other modules of Wagtail, such as images and documents (potentially along with others provided by third-party packages). These panels are passed to the homepage via the construct_homepage_panels
hook, and each one is responsible for providing its own HTML rendering. In this way, the module providing the panel has full control over how it appears on the homepage.
Wagtail implements this pattern using a standard object type known as a component. A component is a Python object that provides the following methods and properties:
- render_html(self, parent_context=None)¶
Given a context dictionary from the calling template (which may be a Context
object or a plain dict
of context variables), returns the string representation to be inserted into the template. This will be subject to Django’s HTML escaping rules, so a return value consisting of HTML should typically be returned as a SafeString
instance.
- media¶
A (possibly empty) form media object defining JavaScript and CSS resources used by the component.
Note
Any object implementing this API can be considered a valid component; it does not necessarily have to inherit from the Component
class described below, and user code that works with components should not assume this (for example, it must not use isinstance
to check whether a given value is a component).
Creating components¶
The preferred way to create a component is to define a subclass of wagtail.admin.ui.components.Component
and specify a template_name
attribute on it. The rendered template will then be used as the component’s HTML representation:
from wagtail.admin.ui.components import Component
class WelcomePanel(Component):
template_name = 'my_app/panels/welcome.html'
my_welcome_panel = WelcomePanel()
my_app/templates/my_app/panels/welcome.html
:
<h1>Welcome to my app!</h1>
For simple cases that don’t require a template, the render_html
method can be overridden instead:
from django.utils.html import format_html
from wagtail.admin.components import Component
class WelcomePanel(Component):
def render_html(self, parent_context):
return format_html("<h1>{}</h1>", "Welcome to my app!")
Passing context to the template¶
The get_context_data
method can be overridden to pass context variables to the template. As with render_html
, this receives the context dictionary from the calling template:
from wagtail.admin.ui.components import Component
class WelcomePanel(Component):
template_name = 'my_app/panels/welcome.html'
def get_context_data(self, parent_context):
context = super().get_context_data(parent_context)
context['username'] = parent_context['request'].user.username
return context
my_app/templates/my_app/panels/welcome.html
:
<h1>Welcome to my app, {{ username }}!</h1>
Adding media definitions¶
Like Django form widgets, components can specify associated JavaScript and CSS resources using either an inner Media
class or a dynamic media
property:
class WelcomePanel(Component):
template_name = 'my_app/panels/welcome.html'
class Media:
css = {
'all': ('my_app/css/welcome-panel.css',)
}
Using components on your own templates¶
The wagtailadmin_tags
tag library provides a {% component %}
tag for including components on a template. This takes care of passing context variables from the calling template to the component (which would not be the case for a basic {{ ... }}
variable tag). For example, given the view:
from django.shortcuts import render
def welcome_page(request):
panels = [
WelcomePanel(),
]
render(request, 'my_app/welcome.html', {
'panels': panels,
})
the my_app/welcome.html
template could render the panels as follows:
{% load wagtailadmin_tags %}
{% for panel in panels %}
{% component panel %}
{% endfor %}
Note that it is your template’s responsibility to output any media declarations defined on the components. For a Wagtail admin view, this is best done by constructing a media object for the whole page within the view, passing this to the template, and outputting it via the base template’s extra_js
and extra_css
blocks:
from django.forms import Media
from django.shortcuts import render
def welcome_page(request):
panels = [
WelcomePanel(),
]
media = Media()
for panel in panels:
media += panel.media
render(request, 'my_app/welcome.html', {
'panels': panels,
'media': media,
})
my_app/welcome.html
:
{% extends "wagtailadmin/base.html" %}
{% load wagtailadmin_tags %}
{% block extra_js %}
{{ block.super }}
{{ media.js }}
{% endblock %}
{% block extra_css %}
{{ block.super }}
{{ media.css }}
{% endblock %}
{% block content %}
{% for panel in panels %}
{% component panel %}
{% endfor %}
{% endblock %}