djust 1.0.0rc14

Pre-releaseReleased
Install
pip install djust==1.0.0rc14

This is a pre-release. djust 1.0.0 has shipped since: read the djust 1.0.0 release notes.

Before you upgrade, read the upgrade guide.

Added

  • LiveViewTestClient.assert_http_ws_djid_parity() — test-infra harness for HTTP-GET vs WebSocket-mount dj-id parity (#1642). Builds two independent instances of a view (one exercising the HTTP initial-page path via render_full_template then render_with_diff; one the WebSocket-mount baseline via render_with_diff only) and asserts they assign an identical dj-id baseline in the dj-root subtree. A divergence is the getNodeByPath → null first-event patch-miss shape investigated in #1641; this locks the #1370 "marker IDs match between the initial HTTP DOM and subsequent WS diffs" invariant against regression and lets the divergence hypothesis be tested framework-side. See docs/website/guides/testing.md.

Fixed

  • djust new --with-db projects now create their tables on deploy (#1637). The scaffold ran migrate --run-syncdb but never makemigrations, so a --with-db app shipped with no migrations. --run-syncdb created the tables on the developer's machine (masking the gap), but a deploy runs migratewithout--run-syncdb, so the app's tables were never created and the first request 500'd (no such table: <app>_<model>). djust new now runs makemigrations before migrate (generating the app's migrations/__init__.py + 0001_initial.py, which ship in the deploy artifact), and explicitly writes <app>/migrations/__init__.py for model-bearing apps so the migrations directory is a real package — not a namespace package the migration loader silently skips — even when --no-install is used.

  • client.js no longer throws a re-declaration SyntaxError on live_redirect / bfcache restore (#1635).live_redirect keeps the document alive and morphdom can re-attach the <script src="client.js"> tag; a bfcache pageshow re-init follows the same path. Two classic <script> executions share one global lexical environment, so top-level const/let/class in the bundle re-declared at parse time on the second execution — Identifier '_TEXT_INPUT_TYPES' has already been declared — before the runtime window._djustClientLoaded guard could run. The bundle is now wrapped in a single IIFE (scripts/build-client.sh), so every top-level declaration is function-scoped; a second execution builds a fresh scope and the runtime guard short-circuits the re-init. Public API is still exported via explicit window.* / globalThis.djust.* assignments inside the IIFE — no observable change.

  • VDOM index patches no longer mis-resolve when a template has a plain HTML comment among siblings (#1640). The client's getSignificantChildren counted ALL comment nodes, while getNodeByPath (path traversal) and the Rust VDOM parser count only dj-if-family boundary markers and drop every other comment. Index-based patches (InsertChild/RemoveChild/MoveChild) resolve their index via getSignificantChildren, so a regular <!-- comment --> among siblings shifted the client index one slot off the server's — inserting/removing/moving the wrong node. getSignificantChildren now uses the same dj-if-only predicate as getNodeByPath. dj-if markers are still counted (server parity); regular comments are dropped.

  • Per-event object-permission check no longer false-denies on the first event (#1638). The per-event re-check (_validate_event_security) called check_object_permission synchronously from an async def, while the mount path wraps it in sync_to_async. The developer's sync get_object() — the canonical ADR-017 pattern doing Model.objects.get(...) — then raised SynchronousOnlyOperation in the event loop, and the fail-closed except translated it into a spurious "Access denied" permission_denied frame on the first event of every URL-bound LiveView with a sync get_object(). The per-event call is now wrapped in sync_to_async, mirroring the mount path. Who's affected: any LiveView following the documented ADR-017 object-permission pattern (get_object() doing a sync ORM read) on djust ≥ v0.9.5-1b. No migration needed — the fix is transparent; the false denial simply stops.

  • live_redirect() / live_patch() from a state-unchanging handler now reach the client (#1643). When an event handler reassigns no public attr (e.g. it only writes to a DB model, then calls live_redirect()), handle_event takes its auto-skip-render branch and returns via _send_noop. That branch — and the batched _dispatch_single_event skip branch, plus the server_push and db_notify broadcast paths — flushed only push_events/flash/page_metadata/pending_layout/deferred, silently dropping queued navigation / accessibility / i18n commands. The result: the handler ran (DB write succeeded) but the browser URL never changed. Root cause was duplication — the turn-end flush sequence was hand-copied across ~10 sites with inconsistent subsets. Consolidated every turn-end path onto a single _flush_all_pending() helper that flushes all queued side-effects in canonical order (draining is idempotent, so paths that also go through _send_update are unaffected). Net reduction of ~29 lines in websocket.py; also closes the same latent gap on the server_push and db_notify broadcast paths.

  • start_async completion now refreshes the VDOM recovery baseline (#1636). When a client VDOM patch fails (e.g. an {% if %} block that adds a sibling), the client requests html_recovery and the server serves — then clears — _recovery_html (one-time use). LiveViewConsumer._run_async_work re-renders and sends patches when a start_async background callback completes, but — unlike handle_event and server_push (the #1202 fix) — it never updated _recovery_html / _recovery_version. After a recovery had consumed the baseline, an async-triggered patch that also failed on the client found _recovery_html=None, got back "Recovery HTML unavailable", and the view froze at the transitional state (e.g. Status: fetching) even though the backend pipeline had advanced. _run_async_work now sets the recovery baseline after render_with_diff() in both the patches branch and the full-HTML fallback branch, mirroring the other two render paths, so async-callback state pushes keep reaching the client across patch-failure/recovery cycles.

All releases · Atom feed