API Reference · 53 entries · self.
View API reference
The methods you call on self inside a LiveView to drive the page: stream content, track presence, accept uploads, manage forms, flash messages, push to the client, run background work, and navigate. Most become available once you mix in the matching capability (see the Mixins tab).
Streaming
Push incremental updates into the page from a handler. The stream() family manages large collections with insert/delete/prune ops instead of full re-renders; stream_text() / stream_to() stream content (like LLM tokens) into a dj-stream target at ~60fps.
self.stream()
Initialise a stream with a collection of items.
- Signature
- stream(name, items, dom_id=None, at=-1, reset=False, limit=None)
- Returns
- Stream
Sets up an efficient server-side stream; rows are emitted as insert ops instead of re-rendering the whole list, and cleared from server memory after render. at=-1 appends, at=0 prepends. Pairs with a dj-stream container. dom_id is a callable that derives each row's DOM id — if omitted, djust falls back to item.id, then item.pk, then id(item).
def mount(self, request, **kwargs):
self.stream('messages', Message.objects.all()[:50])
self.stream_insert()
Insert one item into a stream.
- Signature
- stream_insert(name, item, at=-1)
- Returns
- None
Adds a single item to an initialised stream. at=-1 appends (default), at=0 prepends. The item's id/pk becomes the DOM element id.
@event_handler()
def add_message(self, text: str = "", **kwargs):
msg = Message.objects.create(content=text)
self.stream_insert('messages', msg)
self.stream_delete()
Remove an item from a stream by item or id.
- Signature
- stream_delete(name, item_or_id)
- Returns
- None
Deletes a row from an initialised stream. Pass the item object or its id; the DOM id is derived from the stream name and item id/pk.
@event_handler()
def delete_message(self, msg_id: int = 0, **kwargs):
Message.objects.filter(pk=msg_id).delete()
self.stream_delete('messages', msg_id)
self.stream_reset()
Clear a stream, optionally repopulating it.
- Signature
- stream_reset(name, items=None)
- Returns
- None
Clears all of a stream's DOM children. If items is given, the stream is repopulated after clearing.
@event_handler()
def refresh(self, **kwargs):
self.stream_reset('messages', items=Message.objects.all()[:50])
self.stream_prune()
Cap the number of DOM elements in a stream.
- Signature
- stream_prune(name, limit, edge='top')
- Returns
- None
Trims a stream to at most limit elements. edge='top' removes from the start, 'bottom' from the end. Used for infinite scroll to keep the DOM bounded.
@event_handler()
def load_more(self, **kwargs):
self.stream('messages', next_batch, at=-1)
self.stream_prune('messages', limit=100, edge='top')
await self.stream_to()
Send a streaming partial update to the client.
- Signature
- async stream_to(stream_name, target=None, html=None)
- Returns
- None
Batched (~60fps) streaming update over the WebSocket. If html is given it is sent directly, else the target is re-rendered from context. target defaults to [dj-stream='name'].
@event_handler()
async def stream_response(self, prompt: str = "", **kwargs):
async for token in llm_stream(prompt):
self.output += token
await self.stream_to('output')
await self.stream_text()
Stream raw text into a target element.
- Signature
- async stream_text(name, text, mode='append', target=None)
- Returns
- None
Appends (or replaces / prepends) text into the stream target. target defaults to [dj-stream='name']. The workhorse for LLM token streaming.
async def stream_response(self, **kwargs):
async for chunk in fetch_stream():
await self.stream_text('output', chunk)
await self.stream_start()
Signal the start of a stream to the client.
- Signature
- async stream_start(name, target=None)
- Returns
- None
Fires a 'start' op — useful to show a spinner before tokens arrive. target defaults to [dj-stream='name'].
await self.stream_start('output')
self.streaming = True
await self.stream_done()
Signal the end of a stream to the client.
- Signature
- async stream_done(name, target=None)
- Returns
- None
Fires a 'done' op — useful to clear the loading state once streaming finishes.
await self.stream_done('output')
self.streaming = False
await self.stream_error()
Send an error state to a stream target.
- Signature
- async stream_error(name, error, target=None)
- Returns
- None
Fires an 'error' op while preserving partial content, so a failure is surfaced without wiping what was already streamed.
try:
await self.stream_to('output')
except Exception as e:
await self.stream_error('output', str(e))
Presence
Track who is viewing and what they're doing. track_presence() registers the user with optional metadata; list_presences() and presence_count() read the group; broadcast_to_presence() messages everyone; get_cursors() (with LiveCursorMixin) powers collaborative cursors.
self.track_presence()
Register this user in the presence group.
- Signature
- track_presence(meta=None)
- Returns
- None
Registers the user in the presence group identified by presence_key, with an optional meta dict (name, color, avatar…). No-op during HTTP prerender — only registers under WebSocket. Requires PresenceMixin.
def mount(self, request, **kwargs):
self.track_presence(meta={'name': request.user.username})
self.untrack_presence()
Remove this user from presence tracking.
- Signature
- untrack_presence()
- Returns
- None
Removes the user from the presence group. Called automatically on disconnect; call manually to leave a room without disconnecting.
@event_handler()
def leave_room(self, **kwargs):
self.untrack_presence()
self.list_presences()
List the active presences in the group.
- Signature
- list_presences()
- Returns
- list[dict]
Returns the active presence records (each a dict of the meta passed to track_presence). Empty if presence_key isn't set or the backend is unavailable.
def get_context_data(self, **kwargs):
ctx = super().get_context_data(**kwargs)
ctx['presences'] = self.list_presences()
return ctx
self.presence_count()
Count the active users in the group.
- Signature
- presence_count()
- Returns
- int
Imperative count of active presences. For templates prefer the auto-maintained {{ online_count }} attribute over calling this directly.
ctx['online'] = self.presence_count()
self.broadcast_to_presence()
Broadcast an event to everyone in the group.
- Signature
- broadcast_to_presence(event, payload=None)
- Returns
- None
Sends a named event to all active sessions in the presence group; the payload dict is delivered as kwargs to handlers listening for that event.
@event_handler()
def announce(self, message: str = "", **kwargs):
self.broadcast_to_presence('announcement', {'message': message})
self.get_cursors()
Get live cursor positions (collaborative).
- Signature
- get_cursors()
- Returns
- dict
Returns user_id -> cursor data (x, y, timestamp, meta), with stale cursors filtered. Only available on LiveCursorMixin.
ctx['cursors'] = self.get_cursors()
Uploads
Configure and consume file uploads server-side. Declare a slot with allow_upload() in mount() (it sets accept/multiple automatically), then read completed files with consume_uploaded_entries() in the handler that saves them.
self.allow_upload()
Configure a named file-upload slot.
- Signature
- allow_upload(name, accept='', max_entries=1, max_file_size=10_000_000, resumable=False)
- Returns
- UploadConfig
Declares an upload slot referenced as dj-upload='name'. accept filters extensions/MIME types; the input's accept/multiple attrs are set automatically. resumable=True survives WS reconnects. Requires UploadMixin.
def mount(self, request, **kwargs):
self.allow_upload('avatar', accept='.jpg,.png', max_file_size=5_000_000)
self.consume_uploaded_entries()
Iterate completed uploads, cleaning up after.
- Signature
- consume_uploaded_entries(name)
- Returns
- generator[UploadEntry]
Yields UploadEntry objects (client_name, client_size, file, data) for completed uploads, cleaning up temp files after iteration. Call from the handler that saves the files.
@event_handler()
def save_avatar(self, **kwargs):
for entry in self.consume_uploaded_entries('avatar'):
default_storage.save(entry.client_name, entry.file)
Forms
Work with a bound Django form inside a LiveView. Read live values and validation state with get_field_value() / get_field_errors() / has_field_errors(); reach the form object via form_instance; clear everything with reset_form(). Requires FormMixin.
self.reset_form()
Reset a bound form to its initial state.
- Signature
- reset_form()
- Returns
- None
Clears form_data, field_errors and form_errors and resets validity. Requires FormMixin.
@event_handler()
def clear(self, **kwargs):
self.reset_form()
self.form_instance
The live Django form instance (re-hydrated).
- Signature
- form_instance (property)
- Returns
- forms.Form
Returns the bound Django form, re-creating it from serialized state when needed. Requires FormMixin.
field = self.form_instance.fields.get('email')
self.get_field_value()
Get the current value of a form field.
- Signature
- get_field_value(field_name, default='')
- Returns
- Any
Returns form_data[field_name] or the default. Requires FormMixin.
email = self.get_field_value('email')
self.get_field_errors()
Get the validation errors for a field.
- Signature
- get_field_errors(field_name)
- Returns
- list[str]
Returns the field's error messages, or an empty list. Requires FormMixin.
errors = self.get_field_errors('email')
self.has_field_errors()
Check whether a field has errors.
- Signature
- has_field_errors(field_name)
- Returns
- bool
True if the field currently has validation errors. Requires FormMixin.
{% if has_field_errors('email') %}
<span class="error">Invalid email</span>
{% endif %}
Flash & push
Talk to the client outside the normal render. put_flash() queues a notification; page_title / page_meta update the document head via a side-channel; push_event() / push_commands() / trigger_submit() send events, JS command chains, and native form submits to the browser.
self.put_flash()
Queue a transient flash message.
- Signature
- put_flash(level, message)
- Returns
- None
Queues a notification; level ('success','error','info'…) becomes the CSS class dj-flash-{level}. Flushed to the client after the response. Requires FlashMixin (auto-included).
@event_handler()
def save(self, **kwargs):
self.put_flash('success', 'Saved!')
self.clear_flash()
Clear queued flash messages.
- Signature
- clear_flash(level=None)
- Returns
- None
Clears all flash messages, or only those of a given level.
self.clear_flash('error')
self.push_event()
Push a custom event to client JS hooks.
- Signature
- push_event(event, payload=None)
- Returns
- None
Dispatches to dj-hook handlers and as a CustomEvent on document; the payload dict becomes event.detail. Use for client-only side effects after a handler.
self.push_event('confetti', {'count': 50})
self.push_commands()
Push a JS command chain to run on the client.
- Signature
- push_commands(chain)
- Returns
- None
Runs a djust.js.JSChain on the client for DOM automation (show/hide/add_class/focus/dispatch) with no client hook needed.
from djust.js import JS
self.push_commands(
JS.add_class('highlight', to='#btn').focus('#btn')
)
self.trigger_submit()
Trigger a native form POST on the client.
- Signature
- trigger_submit(selector)
- Returns
- None
After the handler, the client calls .submit() on the matching form (which must carry dj-trigger-action). Used for OAuth or payment handoffs that need a real navigation.
@event_handler()
def checkout(self, **kwargs):
self.trigger_submit('#payment-form')
self.page_title
Update the document title (no re-render).
- Signature
- page_title (property)
- Returns
- str
Assigning page_title queues a title update over a side-channel — no VDOM diff required. Requires PageMetadataMixin (auto-included).
self.page_title = f'Chat ({self.unread} unread)'
self.page_meta
Update <meta> tags dynamically.
- Signature
- page_meta (property)
- Returns
- dict
Assigning a dict of name->content queues meta-tag updates (one command per key). Useful for OpenGraph/SEO on a reactive page.
self.page_meta = {'og:title': 'My Article'}
Async work
Run work off the request cycle. start_async() schedules a callback to run after the current state flushes (re-rendering when it finishes); cancel_async() stops it; defer() runs a one-shot callback after render/patch completes — good for telemetry and cleanup.
self.start_async()
Run a callback in the background after flushing.
- Signature
- start_async(callback, *args, name=None, **kwargs)
- Returns
- None
Schedules work to run after the current state flushes to the client, then re-renders on completion. Name tasks to run several concurrently. Requires AsyncWorkMixin (auto-included).
@event_handler()
def generate(self, **kwargs):
self.generating = True
self.start_async(self._do_generate, name='gen')
def _do_generate(self):
self.output = expensive_call()
self.generating = False
self.cancel_async()
Cancel a background task by name.
- Signature
- cancel_async(name)
- Returns
- None
Cancels a scheduled task; if it's already running, its completion re-render is skipped.
@event_handler()
def cancel(self, **kwargs):
self.cancel_async('gen')
self.generating = False
self.defer()
Run a callback once after render + patch.
- Signature
- defer(callback, *args, **kwargs)
- Returns
- None
Queues a sync callback to run after the render/patch cycle (no further re-render). Good for telemetry or cleanup that shouldn't block the response.
@event_handler()
def track(self, **kwargs):
self.count += 1
self.defer(self._record_metric)
Wizard
Step through a multi-step form. next_step() / prev_step() / go_to_step() navigate with per-step validation; submit_wizard() re-validates every step before calling on_wizard_complete(). Requires WizardMixin.
self.go_to_step()
Jump to a completed wizard step.
- Signature
- go_to_step(step_index=0)
- Returns
- None
Navigates to a previously-completed step for editing, skipping validation on the jump. Requires WizardMixin.
@event_handler()
def edit_step(self, step_index: int = 0, **kwargs):
self.go_to_step(step_index=step_index)
self.next_step()
Validate and advance to the next step.
- Signature
- next_step()
- Returns
- None
Validates the current step, marks it complete, and advances. Stops at the last step. Requires WizardMixin.
<button dj-click="next_step">Next</button>
self.prev_step()
Go back one wizard step.
- Signature
- prev_step()
- Returns
- None
Returns to the previous step without validation. Stops at the first step. Requires WizardMixin.
<button dj-click="prev_step">Back</button>
self.submit_wizard()
Validate all steps and finish the wizard.
- Signature
- submit_wizard()
- Returns
- None
Re-validates every step (guarding against tampering) and calls on_wizard_complete(step_data) when all pass. Requires WizardMixin.
def on_wizard_complete(self, step_data):
Claim.objects.create(**step_data['personal'])
JS commands
JS commands are djust's equivalent of Phoenix LiveView's JS module: build a chain of DOM operations in Python (from djust.js import JS), bind it to any event attribute (dj-click="{{ chain }}"), and it runs client-side with zero server round-trip — mix in .push() when you do want one. Every command accepts at most one targeting kwarg: to= (absolute selector), inner= (within the origin element), or closest= (walk up from the origin); with none, the command targets the element that fired the event. Chains are immutable and safe to share. Beyond the built-ins, JS.ext.<name>() invokes commands you register client-side with window.djust.commands.register() (djust 1.1+) — typos fail loud (built-ins raise AttributeError at mount; unknown custom commands show the DEBUG error overlay with a did-you-mean hint). To run a chain from a server handler instead of an event binding, see self.push_commands() under Flash & push.
JS.show()
Unhide the target element (clears display:none and the hidden attribute).
- Signature
- JS.show(to=None, *, inner=None, closest=None, display=None, transition=None, time=None)
- Returns
- None
Starts (or continues) a chain that makes the target visible client-side. Optional display= sets an explicit display value; transition=/time= apply a CSS class for the duration. Dispatches a djust:show CustomEvent on each target.
self.open_modal = JS.show("#modal").add_class("open", to="#overlay").focus("#modal-title")
<button dj-click="{{ open_modal }}">Open</button>
JS.hide()
Set the target element's display to none.
- Signature
- JS.hide(to=None, *, inner=None, closest=None, transition=None, time=None)
- Returns
- None
The closest= targeting makes reusable close buttons: the same button works inside every modal with no per-instance IDs. Dispatches djust:hide on each target.
<div class="modal">
<button dj-click="{{ JS.hide(closest='.modal') }}">Close</button>
</div>
JS.toggle()
Flip the target between hidden and shown.
- Signature
- JS.toggle(to=None, *, inner=None, closest=None, display=None)
- Returns
- None
Checks computed display (and the hidden attribute) and flips it — the one-liner dropdown.
<div>
<button dj-click="{{ JS.toggle(inner='.menu') }}">Menu</button>
<nav class="menu" style="display:none">…</nav>
</div>
JS.add_class()
Add one or more space-separated CSS classes to the target.
- Signature
- JS.add_class(names, *, to=None, inner=None, closest=None)
- Returns
- None
Pairs with JS.remove_class() for manual class control; for timed classes use JS.transition().
self.mark = JS.add_class("selected ring-2", closest=".card")
JS.remove_class()
Remove one or more space-separated CSS classes from the target.
- Signature
- JS.remove_class(names, *, to=None, inner=None, closest=None)
- Returns
- None
The inverse of JS.add_class(); chain both to move a class between elements in one click.
self.activate = JS.remove_class("active", to=".tab").add_class("active")
JS.transition()
Apply CSS classes for N milliseconds, then remove them.
- Signature
- JS.transition(names, *, to=None, inner=None, closest=None, time=200)
- Returns
- None
The animation primitive: add the class, wait time= ms for the CSS animation, remove it again.
self.saved = JS.transition("flash-green", time=400, to="#status")
JS.set_attr()
Set an HTML attribute on the target element.
- Signature
- JS.set_attr(name, value, *, to=None, inner=None, closest=None)
- Returns
- None
Useful for ARIA state that should update at click-time latency, ahead of the server render.
self.expand = JS.set_attr("aria-expanded", "true").show(inner=".panel")
JS.remove_attr()
Remove an HTML attribute from the target element.
- Signature
- JS.remove_attr(name, *, to=None, inner=None, closest=None)
- Returns
- None
The inverse of JS.set_attr() — e.g. drop disabled the instant the user acts.
self.unlock = JS.remove_attr("disabled", to="#submit")
JS.focus()
Move keyboard focus to the target element.
- Signature
- JS.focus(to=None, *, inner=None, closest=None)
- Returns
- None
Focuses the first matched target; chain it after JS.show() so the revealed input is ready to type into.
self.search = JS.show("#search").focus("#search-input")
JS.dispatch()
Fire a CustomEvent on the target element.
- Signature
- JS.dispatch(event, *, to=None, inner=None, closest=None, detail=None, bubbles=True)
- Returns
- None
The bridge to any third-party code already listening for DOM events — pass a payload via detail=.
self.notify = JS.dispatch("cart:updated", to="#cart", detail={"count": 3})
JS.push()
Send a server event as part of a client-side chain.
- Signature
- JS.push(event, *, value=None, target=None, page_loading=False)
- Returns
- None
The escape hatch back to the server: rides the normal event pipeline (debouncing, rate limiting, WebSocket/HTTP fallback). Enables optimistic UI — do the instant DOM work client-side, then tell the server. page_loading=True drives the page loading bar while the event is in flight.
self.place_order = (JS.hide("#checkout-modal")
.push("submit_order", value={"cart_id": self.cart_id}))
JS.ext.*
Invoke a user-registered custom command from a Python chain.
- Signature
- JS.ext.<name>(to=None, *, inner=None, closest=None, **params)
- Returns
- None
Opens the command set (djust 1.1+): any snake_case name under JS.ext serializes as a namespaced ext.<name> op and dispatches to the matching window.djust.commands.register() implementation — clipboard, scroll, confetti, or a wrapper around any third-party library action. Chainable with built-ins, same targeting kwargs, structurally collision-proof against future built-ins. Built-ins stay strict (JS.shwo raises AttributeError at mount); an unregistered custom command fails loud on first use (DEBUG error overlay with a did-you-mean hint).
self.copy_link = (JS.ext.clipboard_copy(text=self.share_url)
.add_class("copied")
.transition("pulse", time=400))
<button dj-click="{{ copy_link }}">Copy link</button>
window.djust.commands.register()
Register the client-side implementation of a custom JS command.
- Signature
- register(name, fn) // fn(targets, args, originEl)
- Returns
- None
The client half of JS.ext (djust 1.1+): register once in your own static JS — no build step, CSP-safe (no eval). The implementation receives the already-resolved target element list (same to/inner/closest rules as built-ins), the op args, and the origin element; a returned Promise is awaited before the next op in the chain runs. Registering a name that collides with a built-in throws immediately.
window.djust.commands.register('clipboard_copy', (targets, args) => {
navigator.clipboard.writeText(args.text);
});
window.djust.commands.register('fade_out', (targets) =>
Promise.all(targets.map(el =>
el.animate({opacity: [1, 0]}, 250).finished))
);
// Python: JS.ext.fade_out(closest='.row').hide(closest='.row').push('dismiss')
// the fade COMPLETES before the hide and the server push run