Skip to content
docs.djust.org

Component Migration Guide

Breaking changes in the built-in component catalog.

Full Changelog

Every release, additions, fixes, and deprecations.

Security Policy

Reporting vulnerabilities.

Migration Guide

This guide helps you upgrade between major versions of djust.

Upgrading to 1.0 — Breaking Changes

1. VDOM tracking attribute renamed: data-dj-iddj-id

The internal VDOM node-tracking attribute has been renamed from data-dj-id to dj-id to be consistent with all other dj- prefixed attributes (dj-view, dj-click, dj-model, etc.).

Impact: Only affects you if your code reads or queries data-dj-id directly (e.g. custom JavaScript that relies on the attribute for DOM lookups). djust-generated HTML and the client patch engine are updated automatically.

Migration: Replace any querySelector('[data-dj-id="..."]') calls with querySelector('[dj-id="..."]'). A system check (djust.T011) will warn you about data-dj-id attributes found in your templates.

2. Keyed VDOM diffing: id= fallback removed; use explicit dj-key

Previously, elements with an id= attribute had that value silently used as a keyed diffing key, even when the developer intended id= purely as a CSS/JS selector handle. This caused surprising DOM reuse behaviour when unrelated elements happened to share IDs across renders.

The implicit fallback is removed in v1.0. Use the explicit dj-key attribute to opt in to keyed diffing:

Before (relied on implicit id= key):

<ul>
  {% for item in items %}
    <li id="item-{{ item.id }}">{{ item.name }}</li>
  {% endfor %}
</ul>

After (explicit opt-in):

<ul>
  {% for item in items %}
    <li id="item-{{ item.id }}" dj-key="{{ item.id }}">{{ item.name }}</li>
  {% endfor %}
</ul>

The legacy data-key attribute continues to work as an explicit opt-in.

Impact: If you had id= attributes on list items and were relying on keyed diffing to preserve element state (focus, scroll position, animations) across re-renders, add dj-key="{{ item.pk }}" to those elements.

Migration checklist:

  1. Identify dynamic lists in your templates (loops that render repeated elements)
  2. On list items where identity-stable DOM reuse matters, add dj-key="{{ item.pk }}" or dj-key="{{ item.id }}"
  3. Run python manage.py check --deploy — no new warnings should appear

Upgrading to 0.2.1 — Event Handler Security

Version 0.2.1 defaults event_security to "strict": only methods decorated with @event_handler are callable via WebSocket or HTTP POST. Undecorated handler methods will be silently blocked.

Step 1: Enable warn mode (optional)

To identify which handlers need decorators without breaking anything, temporarily use warn mode:

# settings.py
LIVEVIEW_CONFIG = {
    "event_security": "warn",  # Log warnings for undecorated handlers
}

Then check your Django logs for messages like:

WARNING Deprecation: handler 'increment' on CounterView is not decorated with @event_handler.
This will be blocked in strict mode.

Step 2: Add @event_handler to all handler methods

Before:

from djust import LiveView

class CounterView(LiveView):
    def mount(self, request):
        self.count = 0

    def increment(self):      # ← blocked in strict mode!
        self.count += 1

    def decrement(self):      # ← blocked in strict mode!
        self.count -= 1

After:

from djust import LiveView
from djust.decorators import event_handler

class CounterView(LiveView):
    def mount(self, request):
        self.count = 0

    @event_handler
    def increment(self):      # ← allowed
        self.count += 1

    @event_handler
    def decrement(self):      # ← allowed
        self.count -= 1

Methods that are not called via WebSocket (mount, get_context_data, private _helpers) do not need the decorator.

If you already use @debounce, @throttle, @optimistic, @cache, or @rate_limit, add @event_handler as the outermost decorator:

@event_handler
@debounce(wait=0.5)
def search(self, value: str = "", **kwargs):
    ...

Step 3: Switch to strict mode

Remove the "warn" override (or set "strict" explicitly). This is the default, so you can simply delete the event_security key.

Rate limiting configuration

New rate-limit settings are available (all optional, shown with defaults):

LIVEVIEW_CONFIG = {
    "rate_limit": {
        "rate": 100,                  # Tokens per second
        "burst": 20,                  # Max burst capacity
        "max_warnings": 3,            # Warnings before disconnect (code 4429)
        "max_connections_per_ip": 10,  # Max concurrent WebSocket connections per IP
        "reconnect_cooldown": 5,       # Seconds before a rate-limited IP can reconnect
    },
}

Upgrading from 0.1.x to 0.2.0

Version 0.2.0 includes several breaking changes to improve API consistency. Follow this guide to update your code.

1. Event Binding Syntax (0.2.0-alpha.1)

Change: All event bindings now use dj- prefix instead of @ prefix.

Before:

<button @click="increment">Click</button>
<input @input="search" @keydown.enter="submit">
<div @loading.class="opacity-50">Loading...</div>

After:

<button dj-click="increment">Click</button>
<input dj-input="search" dj-keydown.enter="submit">
<div dj-loading.class="opacity-50">Loading...</div>

Migration: Find and replace in your templates:

  • @clickdj-click
  • @inputdj-input
  • @changedj-change
  • @submitdj-submit
  • @blurdj-blur
  • @focusdj-focus
  • @keydowndj-keydown
  • @keyupdj-keyup
  • @loadingdj-loading

2. Data Attribute Renames

Change: Data attributes renamed for consistency.

BeforeAfter
dj-liveview-rootdj-root
data-live-viewdj-view
data-live-lazydj-lazy
data-djdata-dj-id

Migration: Update any custom JavaScript or templates that reference these attributes.

3. Component Import Path

Change: The legacy python/djust/component.py has been removed.

Before:

from djust.component import Component  # Old path

After:

from djust import Component  # Recommended
# or
from djust.components.base import Component  # Explicit

Migration: Update import statements. The djust.Component import still works but now imports from components/base.py.

4. LiveComponent Method Rename

Change: get_context() renamed to get_context_data() for Django consistency.

Before:

class MyComponent(LiveComponent):
    def get_context(self):
        return {"items": self.items}

After:

class MyComponent(LiveComponent):
    def get_context_data(self):
        return {"items": self.items}

Migration: Rename all get_context() methods to get_context_data() in your LiveComponent subclasses.

5. Decorator Attribute Changes

Change: Deprecated decorator attributes removed.

Before (checking decorator metadata):

if hasattr(method, '_is_event_handler'):
    event_name = method._event_name

if hasattr(method, '_debounce_seconds'):
    wait = method._debounce_seconds

After:

if hasattr(method, '_djust_decorators'):
    if 'event_handler' in method._djust_decorators:
        event_name = method._djust_decorators['event_handler']['name']

    if 'debounce' in method._djust_decorators:
        wait = method._djust_decorators['debounce']['wait']

Migration: Update any code that inspects decorator attributes to use the _djust_decorators dict.

6. WebSocket Message Types

Change: Message types renamed for consistency.

BeforeAfter
connectedconnect
mountedmount
hotreload.messagehotreload

Migration: If you have custom WebSocket handling code, update the message type names.

Quick Migration Script

For templates, you can use these commands to update event bindings:

# macOS/BSD sed
find . -name "*.html" -exec sed -i '' \
  -e 's/@click=/dj-click=/g' \
  -e 's/@input=/dj-input=/g' \
  -e 's/@change=/dj-change=/g' \
  -e 's/@submit=/dj-submit=/g' \
  -e 's/@blur=/dj-blur=/g' \
  -e 's/@focus=/dj-focus=/g' \
  -e 's/@keydown/dj-keydown/g' \
  -e 's/@keyup/dj-keyup/g' \
  -e 's/@loading/dj-loading/g' \
  {} \;

# GNU/Linux sed
find . -name "*.html" -exec sed -i \
  -e 's/@click=/dj-click=/g' \
  -e 's/@input=/dj-input=/g' \
  -e 's/@change=/dj-change=/g' \
  -e 's/@submit=/dj-submit=/g' \
  -e 's/@blur=/dj-blur=/g' \
  -e 's/@focus=/dj-focus=/g' \
  -e 's/@keydown/dj-keydown/g' \
  -e 's/@keyup/dj-keyup/g' \
  -e 's/@loading/dj-loading/g' \
  {} \;

For Python files with get_context:

# macOS/BSD sed
find . -name "*.py" -exec sed -i '' 's/def get_context(self)/def get_context_data(self)/g' {} \;

# GNU/Linux sed
find . -name "*.py" -exec sed -i 's/def get_context(self)/def get_context_data(self)/g' {} \;

Need Help?

If you encounter issues during migration:

  1. Check the CHANGELOG for detailed change descriptions
  2. Open an issue on GitHub