djust 1.0.0rc7

Pre-releaseReleased
Install
pip install djust==1.0.0rc7

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.

Fixed

  • VDOM {% if %}/{% elif %} branch swap no longer produces a doubled or stale subtree (#1550, #1552; #1552 was a P0 regression from 0.9.6rc2). Both bugs trace to a single root cause: dj-id counter collisions when last_vdom migrates across worker threads OR is msgpack-roundtripped through InMemoryStateBackend.get(). The thread-local djust_id counter generates monotonically-increasing ids during parse_html_continue, but the new thread's counter is independent of last_vdom's ids. The next parse generates ids 1..k that collide with surviving ids in last_vdom. The InsertSubtree.html patch then carries dj-ids matching other elements in the parent's child list, and the client's id-first :scope > [dj-id=N] resolver picks the newer element instead of the older one to remove — wrong subtree removed, old content survives. Why v0.9.6rc2 worked: pre-#1538 (commit 0a119962, serde-default fix), msgpack deserialize failed silently and state_backends/memory.py:118 returned None on every cache lookup, full-remounting on every event without running the diff path. Post-#1538 deserialize succeeds, the diff path runs, and the collision shape became reachable. (The earlier diff-layer hypothesis pursued in #1553child_d: None propagation post-#1538 — was empirically disconfirmed by VNode-level reproducers showing the differ correct at both 0.9.6rc2 AND 1.0.0rc4.) Fix: before each parse_html_continue in RustLiveViewBackend::render_with_diff and the text-fast-path entry, walk last_vdom, compute max_djust_id_in(old_vdom), and call ensure_id_counter_at_least(max + 1). The thread-local counter becomes effectively per-view, surviving thread handoff and msgpack roundtrip. Three new public helpers in djust_vdom: from_base62(s) (decode base62 string to u64), max_djust_id_in(node) (walk VNode tree for highest djust_id), ensure_id_counter_at_least(min) (monotonic counter advance). Covered by 12 Rust unit cases in crates/djust_vdom/tests/test_id_counter_monotonicity_1550_1552.rs (round-trip, invalid input rejection, max-walk on id-less / single / nested trees, monotonic semantics) plus 4 Python E2E cases in python/tests/test_if_elif_swap_e2e_1550_1552.py (collision-free InsertSubtree.html, uniquely-resolvable RemoveChild post-insert, if/else branch swap, msgpack-roundtrip counter advance).
  • Multi-line {# ... #} comments containing template-tag syntax no longer crash Django classical (#1551). djust ships two template renderers: the Rust engine (crates/djust_templates/src/lexer.rs:289-305) treats {# ... #} as opaque even across newlines, but Django's classical tokenizer (django.template.base.Lexer) uses a non-DOTALL regex — multi-line {# ... #} was NOT recognized as a single comment, so a {% if %} inside the comment body parsed as a real tag and raised TemplateSyntaxError. The mismatch was silent: templates rendered fine via the LiveView WebSocket path (Rust) and crashed via client.get() in pytest, Django's debug error renderer, or any view using render() directly. Follow-up to #1423. Fix: new djust.template.loaders.FilesystemLoader and djust.template.loaders.AppDirectoriesLoader — drop-in replacements for the standard Django loaders that preprocess {# ... #} blocks out of the template source before Django classical's tokenizer sees them. Single-line comments are stripped too (Django strips them anyway). Projects opt in by replacing the default loaders in TEMPLATES['OPTIONS']['loaders']. Performance: one regex pass per template load (<1ms); loaded templates are cached so this runs once per template, not per render. Covered by 10 regression cases in python/djust/tests/test_multiline_comment_parity_1551.py, including a control that pins the bug shape in vanilla Django classical.

All releases · Atom feed