Render Queues
Render queues let panel views enqueue scripts, stylesheets, or arbitrary HTML fragments. The layout template then renders each queue exactly once, deduplicated, in a guaranteed order.
Why render queues?
Without queues, if three panels each need the same JavaScript library, you either include it three times (bad) or hard-code it in the layout template regardless of which panels are active (wasteful). Render queues solve this:
- Each panel enqueues what it needs
- Duplicates are automatically removed (by exact match)
- Output order is deterministic: content view first, then panels in definition order
- Template tags produce no output themselves — they only enqueue
Setup
Declare queue instances as class attributes on your Layout:
from dj_layouts import Layout
from dj_layouts.queues import ScriptQueue, StyleQueue
class DefaultLayout(Layout):
template = "myapp/layout.html"
scripts = ScriptQueue()
styles = StyleQueue()
!!! warning "Queues must be explicitly declared"
Queues do not appear automatically. Each ScriptQueue() / StyleQueue() declared as a class attribute is a config object (factory). Each incoming request gets its own fresh set of queue instances — the class-level object is never mutated.
The attribute name (scripts, styles) is the queue name used in template tags.
Queue types
ScriptQueue
Collects <script> tags or inline script blocks. Rendered with {% renderscripts %} or {% renderqueue "scripts" %}.
Conventional name: scripts (to match {% renderscripts %}).
StyleQueue
Collects <link rel="stylesheet"> tags or inline style blocks. Rendered with {% renderstyles %} or {% renderqueue "styles" %}.
Conventional name: styles (to match {% renderstyles %}).
RenderQueue
A generic queue for arbitrary HTML strings. Rendered with {% renderqueue "name" %}.
from dj_layouts.queues import RenderQueue
class DefaultLayout(Layout):
template = "myapp/layout.html"
breadcrumbs = RenderQueue()
RenderQueue templates receive items (list of strings) in context. Use {{ item|safe }} since items are raw HTML strings:
{# myapp/breadcrumb_queue.html #}
<nav aria-label="breadcrumb">
<ol>
{% for item in items %}
{{ item|safe }}
{% endfor %}
</ol>
</nav>
!!! warning "{{ item|safe }} is required"
RenderQueue items are raw HTML strings. Without |safe, Django will escape them and you'll see literal <li> tags in your page. Always use {{ item|safe }} in RenderQueue templates.
Python API
add_script(request, src)
Enqueue an external script URL into the queue named "scripts":
from dj_layouts.queues import add_script
def my_panel(request):
add_script(request, "/static/chart.js")
return HttpResponse(...)
Raises KeyError if there is no queue named "scripts" on the active layout.
add_style(request, href)
Enqueue an external stylesheet URL into the queue named "styles":
from dj_layouts.queues import add_style
def my_panel(request):
add_style(request, "/static/chart.css")
return HttpResponse(...)
Raises KeyError if there is no queue named "styles" on the active layout.
add_to_queue(request, name, item)
Enqueue an arbitrary HTML string into a named queue:
from dj_layouts.queues import add_to_queue
def my_panel(request):
add_to_queue(request, "breadcrumbs", "<li><a href='/'>Home</a></li>")
return HttpResponse(...)
item must be a string. Raises KeyError with a helpful message if name doesn't match any declared queue.
Template tags
Load the layouts tag library first:
{% load layouts %}
{% addscript src %}
Enqueue an external script from a template:
{% addscript "/static/myapp/chart.js" %}
{% addscript "https://cdn.example.com/lib.js" %}
Produces no output. Adds the URL to the "scripts" queue.
{% addstyle href %}
Enqueue an external stylesheet from a template:
{% addstyle "/static/myapp/theme.css" %}
Produces no output. Adds the URL to the "styles" queue.
{% enqueue "name" %} ... {% endenqueue %}
Enqueue an arbitrary HTML block into a named queue:
{% enqueue "breadcrumbs" %}
<li><a href="/">Home</a></li>
<li>Products</li>
{% endenqueue %}
Produces no output. The block content (.strip()ped) is added to the named queue.
{% renderscripts %}
Render the "scripts" queue at the current position in the template. Typically placed at the end of <body> or <head>:
{% renderscripts %}
</body>
If the queue is empty, produces no output (no-op).
{% renderstyles %}
Render the "styles" queue at the current position. Typically placed in <head>:
{% renderstyles %}
</head>
If the queue is empty, produces no output (no-op).
{% renderqueue "name" %}
Render any named queue:
{% renderqueue "breadcrumbs" %}
If the queue is empty, produces no output (no-op).
Deduplication
Items are deduplicated by exact content hash.
ScriptItemandStyleItemare frozen dataclasses — hashable by default- For inline blocks (enqueued via
{% enqueue %}oradd_to_queue()), the content is.strip()ped before storing, so minor whitespace differences don't create duplicates - Deduplication is per-request — each request starts with empty queues
If two panels enqueue add_script(request, "/static/chart.js"), the script appears exactly once in the rendered output.
Ordering guarantee
Content view items always come first, then panels in definition order.
Even under @async_layout where panels run concurrently, the queue merge happens after asyncio.gather completes. The merge order is the definition order of panels on the Layout class, not their completion order. You get deterministic output regardless of which panel finishes first.
class DefaultLayout(Layout):
template = "myapp/layout.html"
scripts = ScriptQueue()
sidebar = Panel("myapp:sidebar") # sidebar items come before footer items
footer = Panel("myapp:footer") # ...even if footer finishes first
Complete example
Layout class:
from dj_layouts import Layout, Panel
from dj_layouts.queues import ScriptQueue, StyleQueue
class DefaultLayout(Layout):
template = "myapp/layout.html"
scripts = ScriptQueue()
styles = StyleQueue()
sidebar = Panel("myapp:sidebar")
Panel view template (myapp/sidebar.html):
{% load layouts %}
{% addstyle "/static/myapp/sidebar.css" %}
{% addscript "/static/myapp/sidebar.js" %}
<nav class="sidebar">
...
</nav>
Layout template (myapp/layout.html):
{% load layouts %}
<!doctype html>
<html>
<head>
<meta charset="utf-8">
{% renderstyles %}
</head>
<body>
<aside>{% panel "sidebar" %}{% endpanel %}</aside>
<main>{% panel "content" %}{% endpanel %}</main>
{% renderscripts %}
</body>
</html>
The sidebar's stylesheet and script appear exactly once, even if another panel also enqueues the same files.
Queues and panel caching
When a panel is cached (via Panel(..., cache=...)) the render queue items it would have enqueued are cached alongside the HTML and replayed on cache hits. This means {% renderscripts %} and {% renderstyles %} produce correct output even when the panel view is not re-executed.
Deduplication is applied on replay just as on a normal render — the same script or style will not appear twice in the output.
See Panels — Panel caching for the full caching reference.