Dynamic Page Injections¶
Extensible products often need to add content to a page, such as <script>
tags, CSS, notice banners, and navigation items. This needs to be factored
into a page’s ETags so that older content doesn’t get stuck in a browser
cache somewhere.
The djblets.pagestate module makes this easy by offering the
following:
Template-defined points where data can be injected.
A method for manually adding content to a template point.
Injector classes that let calls dynamically generate content for any template points.
We’ll walk through how this works.
Setting Up¶
You’ll first need to enable the template tag library and middleware. Add
the following to your project’s settings.py:
INSTALLED_APPS
...,
'djblets.pagestate',
...
]
MIDDLEWARE = [
...
'djblets.pagestate.middleware.PageStateMiddleware',
...
]
Making a Template Dynamic¶
To make a portion of your template dynamic, simply use the
{% page_hook_point %} template tag from the
djblets.pagestate.templatetags.djblets_pagestate template library,
and give it a name.
For example:
{% load djblets_pagestate %}
<html>
<head>
<title>Page Title</title>
{% page_hook_point "scripts" %}
</head>
<body>
...
{% page_hook_point "after-content" %}
</body>
</html>
This defines two points:
scriptsafter-content
Both of these will be replaced with content injected either manually or from registered injectors.
Manually Injecting Content¶
During your request/response cycle, you can manually inject content into a template hook point.
To do this:
Fetch the
PageStatefor theHttpRequest, usingPageState.for_request(request).Call
PageState.inject()with the content and optional ETag to inject.
For example:
from django.http import HttpRequest, HttpResponse
from django.shortcuts import render
from django.utils.html import mark_safe
from djblets.pagestate.state import PageState
def my_view(
request: HttpRquest,
) -> HttpResponse:
page_state = PageState.for_request(request)
page_state.inject('scripts', {
'content': mark_safe('<script>alert("hi!")</script>'),
})
page_state.inject('after-content', {
'content': build_some_content_html(),
'etag': build_some_content_etag(),
});
return render(request, 'base.html')
These will be placed in their respective template hook points.
Building Dynamic Injectors¶
You don’t have to manually inject content in every view. If you have content that’s going to be common across pages, you can create an injector.
Injectors are classes that adhere to PageStateInjectorProtocol. They register a unique
injector_id and implement iter_page_state_data().
They’re then registered in the djblets.pagestate.injectors.
page_state_injectors.
For example:
from collections.abc import Iterator
from django.http import HttpRequest
from django.template import Context
from django.utils.html import format_html
from djblets.pagestate.injectors import page_state_injectors
from djblets.pagestate.state import PageStateData
class MyInjector:
injector_id = 'my-injector'
def iter_page_state_data(
self,
*,
point_name: str,
request: HttpRequest,
context: Context,
) -> Iterator[PageStateData]
if point_name == 'scripts':
for i in range(10):
yield {
'content': format_html(
'<script>console.log("i = {}");</script>',
i),
'etag': str(i),
}
page_state_injectors.register(MyInjector())
This simple injector will add a series of console.log() statements
for the scripts template hook point, generating them dynamically based
on a range of numbers.
In practice, you might use an injector to look up data from a database, a registry, or another source.