Advanced Usage Guide

This guide covers advanced features and patterns for using django-brick-astley.

Type Validation

Brick kwargs are validated against their type hints. The behavior depends on your DEBUG setting:

  • DEBUG=True: Type mismatches raise BrickValidationError

  • DEBUG=False: Type mismatches log a warning but allow rendering to continue

Supported Types

from brickastley import Brick, register

@register
class MyBrick(Brick):
    # Basic types
    name: str
    count: int
    price: float
    active: bool

    # Optional types (can be None)
    subtitle: str | None = None

    # Union types
    value: str | int = 0

    # With defaults
    variant: str = "default"

Template Tag Values

In templates, values are parsed as follows:

{# Strings - use quotes #}
{% mybrick name="Hello World" %}
{% mybrick name='Single quotes work too' %}

{# Integers #}
{% mybrick count=42 %}
{% mybrick count=-5 %}

{# Floats #}
{% mybrick price=19.99 %}

{# Booleans #}
{% mybrick active=True %}
{% mybrick active=False %}

{# None #}
{% mybrick subtitle=None %}

{# Template variables #}
{% mybrick name=user.username %}
{% mybrick count=items|length %}

Extra Kwargs

Any kwargs passed to a brick that aren’t defined in the class are collected in extra, a dictionary available in the template context. This is useful for passing HTML attributes like class, id, data-*, or aria-*, or any other custom data:

@register
class Button(Brick):
    label: str
    variant: str = "primary"
    # Note: no class, id, or data-* defined

Use in template with extra kwargs:

{% button label="Click me" class="mt-4" id="submit-btn" data_action="submit" %}

The attrs Filter

Use the attrs filter to render extra kwargs as an HTML attributes string:

{# bricks/button.html #}
<button class="btn btn-{{ variant }}"{{ extra|attrs }}>
    {{ label }}
</button>

This renders as:

<button class="btn btn-primary" class="mt-4" id="submit-btn" data-action="submit">
    Click me
</button>

The attrs filter:

  • Converts underscores to hyphens (data_actiondata-action)

  • Renders boolean True as the attribute name (disabled=Truedisabled="disabled")

  • Skips False and None values

  • Returns a safe string with a leading space (so {{ extra|attrs }} works directly after a tag name)

Merging Extra with Existing Attributes

To merge extra with your own classes, access individual items:

{# bricks/button.html #}
<button
    class="btn btn-{{ variant }}{% if extra.class %} {{ extra.class }}{% endif %}"
    {% if extra.id %}id="{{ extra.id }}"{% endif %}
    {{ extra|attrs }}
>
    {{ label }}
</button>

Note

When using |attrs, be aware that class and id will be rendered again if present in extra. For complete control, iterate manually or filter out specific keys in your get_context_data() method.

Context Inheritance

By default, bricks are isolated from the parent template context. This prevents unwanted variable shadowing where parent context variables could override brick parameters.

For example:

@register
class MyBrick(Brick):
    name: str
    class_: str | None = None  # Optional parameter
{# Parent template has 'class' in context #}
{{ "parent-class" as class }}

{# Brick is isolated - 'class_' remains None #}
{% mybrick name="test" %}

Inheriting Specific Variables

If your brick needs access to parent context variables like request or user, use the inherit_context class attribute to explicitly declare which variables to inherit:

@register
class UserGreeting(Brick):
    inherit_context = ("request", "user")  # Inherit these from parent
    greeting: str = "Hello"

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        # Now 'request' and 'user' are available
        context["username"] = context.get("user").username if context.get("user") else "Guest"
        return context
{# Parent template #}
{% user_greeting %}  {# Can access request and user from parent #}

Note

  • If inherit_context is None (default), no parent context is inherited

  • If inherit_context is a list or tuple of strings, only those variables are inherited

  • Brick’s own context always overrides inherited values

This isolation prevents bugs where parent template variables unintentionally set optional brick parameters.

Custom Context Data

Override get_context_data() to add computed values or transform kwargs:

@register
class PriceTag(Brick):
    amount: float
    currency: str = "USD"

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        # Add computed values
        context["formatted_price"] = f"{self.currency} {self.amount:.2f}"
        context["is_free"] = self.amount == 0
        return context

Use in template:

{# bricks/price_tag.html #}
<span class="price{% if is_free %} price--free{% endif %}">
    {% if is_free %}
        Free
    {% else %}
        {{ formatted_price }}
    {% endif %}
</span>

Media (CSS and JavaScript)

Bricks support Django’s Media class for declaring CSS and JavaScript dependencies:

@register
class FancyButton(Brick):
    label: str

    class Media:
        css = {
            "all": ["css/fancy-button.css"],
        }
        js = ["js/fancy-button.js"]

You can access the media on brick instances:

button = FancyButton(label="Click")
print(button.media)  # Outputs CSS and JS tags

Note

Currently, media assets must be included manually in your base template. Automatic collection of brick media is planned for a future release.

Custom Brick Names

Use the name parameter to customize the template tag name:

@register(name="btn")
class Button(Brick):
    label: str

@register(name="cta")
class CallToAction(Brick):
    text: str
    url: str

Use in templates:

{% btn label="Click me" %}
{% cta text="Sign up now" url="/signup/" %}

Custom Template Paths

Set template_name to use a custom template location:

@register
class Button(Brick):
    label: str
    template_name = "components/buttons/primary.html"

@register
class Card(BlockBrick):
    title: str
    template_name = "ui/cards/basic.html"

Brick Inheritance

Bricks can inherit from other bricks:

class BaseButton(Brick):
    label: str
    disabled: bool = False

@register
class PrimaryButton(BaseButton):
    # Inherits label and disabled
    pass

@register
class DangerButton(BaseButton):
    # Inherits label and disabled, adds confirm
    confirm: bool = False

Note

Only bricks decorated with @register become template tags. Base classes without the decorator are not registered.

Accessing the Registry

You can programmatically access registered bricks:

from brickastley.registry import get_registry, get_brick

# Get all registered bricks
registry = get_registry()
for name, brick_class in registry.items():
    print(f"{name}: {brick_class}")

# Get a specific brick by name
ButtonClass = get_brick("button")
if ButtonClass:
    button = ButtonClass(label="Hello")

Nesting Bricks

Bricks can be nested inside block bricks:

{% card title="User Actions" %}
    <div class="button-group">
        {% button label="Edit" variant="primary" %}
        {% button label="Delete" variant="danger" %}
    </div>
{% endcard %}

Template Tag Loading

By default, you need to load the brickastley template tags:

{% load brickastley %}

To make bricks available in all templates without loading, add to your settings:

TEMPLATES = [
    {
        "BACKEND": "django.template.backends.django.DjangoTemplates",
        "OPTIONS": {
            "builtins": [
                "brickastley.templatetags.brickastley",
            ],
        },
    },
]

Autodiscovery

Brickastley automatically discovers bricks.py modules in all installed Django apps when the app is ready. This happens in the AppConfig.ready() method.

The autodiscovery process:

  1. Iterates through all installed apps

  2. Checks if each app has a bricks.py module

  3. Imports the module, triggering @register decorators

  4. Registers all discovered bricks as template tags

If you need to manually trigger discovery (e.g., in tests):

from brickastley.autodiscover import autodiscover
from brickastley.templatetags.brickastley import register_brick_tags

autodiscover()
register_brick_tags()

Testing Bricks

Test brick instantiation and context:

from myapp.bricks import Button

def test_button_defaults():
    button = Button(label="Click")
    assert button.label == "Click"
    assert button.variant == "primary"
    assert button.disabled is False

def test_button_context():
    button = Button(label="Click", variant="danger")
    context = button.get_context_data()
    assert context["label"] == "Click"
    assert context["variant"] == "danger"

Test validation:

import pytest
from brickastley import BrickValidationError
from myapp.bricks import Button

def test_missing_required_kwarg():
    with pytest.raises(BrickValidationError):
        Button()  # Missing required 'label'

Test extra kwargs:

def test_extra_collected():
    button = Button(label="Click", class_="mt-4", data_id="123")
    assert button.extra == {"class_": "mt-4", "data_id": "123"}

def test_extra_in_context():
    button = Button(label="Click", id="my-btn")
    context = button.get_context_data()
    assert context["extra"]["id"] == "my-btn"