Added
@actiondecorator — React 19 Server Actions equivalent (v0.8.0) — mark a method as a Server Action and_action_state[<method_name>]is auto-populated with{pending, error, result}at handler entry/exit. Templates access the state via context injection: each action's name becomes a context variable. Pairs with the v0.8.0dj-form-pendingattribute (PR #1023):dj-form-pendingcovers the in-flight client UX (during the network round-trip),@actioncovers the post-completion server state (after the handler returns). Together: React 19-level form ergonomics with zero per-handler wiring.from djust import action class TodoView(LiveView): @action def create_todo(self, title: str = "", **kwargs): if not title: raise ValueError("Title is required") todo = Todo.objects.create(title=title, user=self.request.user) return {"created": todo.id}{% if create_todo.error %} <div class="error">{{ create_todo.error }}</div> {% elif create_todo.result %} <div class="success">Todo {{ create_todo.result.created }} created!</div> {% endif %}Implementation:
- New
@actiondecorator indjust.decorators. Wraps the underlying@event_handler(every action is also an event handler — same dispatch path, parameter coercion, permissions, rate limits) and adds the action-state tracking layer. - On entry:
self._action_state[name] = {pending: True, error: None, result: None}. - On success return:
{pending: False, error: None, result: <return_value>}. - On exception:
{pending: False, error: str(exc) or exc.__class__.__name__, result: None}and re-raises. LiveView.__init__initializes_action_state: Dict[str, Dict] = {}.ContextMixin.get_context_data()injects each action's state under its name (after the public-attribute walk + JIT serialization, so action names that collide with user-defined attrs win — actions are always the canonical reading of that name).- Re-running an action resets state (clears previous result on a failure retry, clears previous error on a success retry — the template never sees stale state alongside fresh state).
- Both bare-form
@actionand called-form@action(description=...)supported. - New
is_action(func)helper for runtime detection. - Exposed as top-level imports:
from djust import action, is_action.
Covered by 18 regression tests in
tests/test_action_decorator.py(decorator metadata + event-handler/action distinction, sync success / exception / re-raise / class-name fallback, multiple actions independent state, retry success-after-failure + failure-after-success, both decorator forms, end-to-end context injection viaContextMixin.get_context_data()).- New
dj-form-pendingattribute — React 19useFormStatusequivalent (v0.8.0) — any element nested inside a<form dj-submit>can declaredj-form-pending="hide|show|disabled"and react automatically when the ancestor form's submit handler is in-flight. No prop drilling, no per-button wiring, no client-side state. The form itself gets adata-djust-form-pending="true"attribute while pending so CSS selectors (form[data-djust-form-pending] .spinner) can hook in without JS. Modes:hide— element is hidden via thehiddenattribute while pending (idle label that disappears during submit)show— element is hidden by default and visible while pending (loading spinner / "Saving…" text)disabled—disabled = truewhile pending; original disabled state restored on resolve. User-disabled elements stay disabled (the helper tracks pre-pending state indata-djust-form-pending-was-disabled).
State is set BEFORE the network round-trip and cleared in a
finallyblock so it always resolves regardless of error. Scope isolation: only[dj-form-pending]descendants of the actually- submitting form react; sibling<form dj-submit>forms on the same page are unaffected. Unknown modes are silently ignored (forward-compatible). Implemented in09-event-binding.js—_setFormPending(form, pending)helper + 1-line wiring into_handleDjSubmit. Bundle delta: ~80 B gzipped. Covered by 8 JS regression tests intests/js/dj-form-pending.test.js(data-djust-form-pending toggle, hide/show/disabled modes, user-disabled preservation, plain-form no-op, scope isolation, error-path cleanup, unknown-mode forward-compat).