Skip to content

Layouts

Reference for the Layout class — the central configuration object that assembles a full page from panels.

Declaring a Layout

Subclass dj_layouts.Layout and set template:

# myapp/layouts.py
from dj_layouts import Layout, Panel

class DefaultLayout(Layout):
    template = "myapp/layout.html"

template is required. Omitting it raises TypeError at class definition time.

Class attributes

template

Type: str | Required

Path to the layout template, relative to Django's template directories.

template = "myapp/layout.html"

Override get_template() if you need to select the template dynamically.

error_template

Type: str | Default: "layouts/error.html"

Template rendered by on_panel_error() when a panel fails in production mode (non-debug). dj-layouts ships with a default layouts/error.html that shows a collapsible error box. Override on your Layout class to use a custom template:

error_template = "myapp/panel_error.html"

The template receives a single context variable error — a PanelError instance with panel_name, source, exception, and traceback_str attributes.

See Error Handling for full details.

layout_context_defaults

Type: dict[str, Any] | Default: {}

Static key/value pairs available in the layout template and in the content view via request.layout_context. These are the lowest-priority defaults; they are overridden by get_layout_context() and then by the content view's own writes to request.layout_context.

class DefaultLayout(Layout):
    template = "myapp/layout.html"
    layout_context_defaults = {
        "site_name": "My Site",
        "theme": "light",
    }

Panel class attributes

Panel(...) instances assigned as class attributes become the layout's named panels:

class DefaultLayout(Layout):
    template = "myapp/layout.html"

    sidebar = Panel("myapp:sidebar")
    footer  = Panel("myapp:footer")
    banner  = Panel(None)  # empty by default, template fallback is used

Attribute name = panel name used in {% panel "name" %} in the template.

Panels are inherited by subclasses; a subclass's own panel definitions take precedence over parent class panels of the same name.

ScriptQueue / StyleQueue / RenderQueue

Render queue instances declared as class attributes on the Layout. See Render Queues for setup and usage.

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. You must declare them as class attributes to use them. The attribute name becomes the queue name used in template tags like {% renderqueue "scripts" %}.

Overridable methods

get_layout_context(request)

Return a dict of extra context variables. Called after layout_context_defaults is applied. The content view can still override individual keys by writing to request.layout_context afterwards.

def get_layout_context(self, request):
    return {
        "current_user": request.user,
        "page_title": "My Site",
    }

Return type: dict[str, Any]

get_template(request)

Return the template path to render. Override this when the template varies per-request (e.g. different templates for different device types):

def get_template(self, request):
    if request.META.get("HTTP_HX_REQUEST"):
        return "myapp/layout_partial.html"
    return self.template

Return type: str

on_panel_error(request, error)

Called in production (non-debug) mode when a panel raises an exception. Return an HTML string to use as the panel's output (typically an error message or empty string).

def on_panel_error(self, request, error):
    # error.panel_name, error.source, error.exception, error.traceback_str
    logger.error("Panel %s failed", error.panel_name, exc_info=error.exception)
    return ""  # silently suppress the panel

The default implementation logs the error and renders self.error_template.

In debug mode (DJ_LAYOUTS["DEBUG_ERRORS"] = True or DEBUG = True), this method is bypassed entirely — a PanelRenderError is raised so Django's debug error page appears. See Error Handling.

Parameters:

  • request — the original (main view) request
  • error — a PanelError dataclass

Return type: str

Registration and autodiscovery

Automatic registration

Subclassing Layout registers the class automatically. You do not call a register function yourself.

Registration key: "<app_label>.<ClassName>"

The app_label is the first segment of the module's dotted path. For myapp.layouts.DefaultLayout, the key is "myapp.DefaultLayout".

# myapp/layouts.py
class DefaultLayout(Layout):   # registered as "myapp.DefaultLayout"
    template = "myapp/layout.html"

Autodiscovery

dj-layouts uses Django's autodiscover_modules("layouts") — the same mechanism used by django.contrib.admin. On startup it imports every layouts.py in your installed apps, which triggers subclass registration.

You must place Layout classes in layouts.py (or import them there) for autodiscovery to work. A Layout defined in views.py or models.py will not be registered until that module is imported.

Listing registered layouts

from dj_layouts.base import _registry
print(list(_registry.keys()))
# ['myapp.DefaultLayout', 'otherapp.BlogLayout', ...]

Resolving by dotted string

from dj_layouts import Layout

layout_cls = Layout.resolve("myapp.DefaultLayout")

Raises KeyError with a helpful message listing available layouts if the key is not found.

Inheritance

Layout classes can inherit from each other. Panel definitions are inherited; a subclass can override individual panels:

class BaseLayout(Layout):
    template = "myapp/base.html"
    sidebar = Panel("myapp:sidebar")
    footer  = Panel("myapp:footer")

class BlogLayout(BaseLayout):
    template = "myapp/blog.html"
    # Inherits footer from BaseLayout, overrides sidebar
    sidebar = Panel("blog:sidebar")

BlogLayout._panels will be {"sidebar": Panel("blog:sidebar"), "footer": Panel("myapp:footer")}.

Dotted string references

Passing a Layout class directly to @layout creates an import-time dependency between views.py and layouts.py. Use a dotted string to avoid circular imports:

# views.py
from dj_layouts import layout

@layout("myapp.DefaultLayout")   # resolved lazily from the registry
def homepage(request):
    ...

The string is resolved when the first request hits the view, by which time autodiscovery has run and the class is registered.

!!! warning "String refs require autodiscovery to have run" Dotted string refs fail with KeyError if the Layout class has not been imported yet. This is normally fine — startup autodiscovery handles it. If you're using Layout.resolve() in tests or management commands, make sure AppConfig.ready() has run (or import the layouts module manually).