djust 1.0.0rc12

Pre-releaseReleased
Install
pip install djust==1.0.0rc12

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

  • LiveView.abstract: bool = False class-attribute marker — opt out abstract base classes from per-class V/Q system checks (#1605). A common pattern is to define an abstract BaseLiveView(LiveView) that subclasses extend for shared mount/auth boilerplate. The base typically has no template_name and is never mounted directly, but V001 (missing template_name) and V005 (not in LIVEVIEW_ALLOWED_MODULES) still fired on it because the per-class check loop in python/djust/checks.py:check_liveviews had no abstract opt-out. The new abstract = True class attribute mirrors Django's Meta.abstract semantics: setting it on a subclass skips that class's per-class V/Q checks (V001/V005/V002/V003/V004/V007/Q007), and the marker is consulted via cls.__dict__.get("abstract") so it is NOT inherited — subclasses of an abstract base are still validated as concrete unless they redeclare abstract = True themselves. Both the abstract opt-out and the global suppress_checks mechanism (see ### Fixed below) work; choose abstract when the intent is "this specific class is boilerplate" and suppress_checks when the intent is "this check is the wrong shape for our codebase." Documented in docs/system-checks.md (new "Abstract base LiveView classes" section) and docs/guides/error-codes.md (V001 + V005 entries). Behavior change: NEW public API — LiveView gains an abstract: bool = False class attribute. Existing user code sees no change unless it opts in.

Fixed

  • V008 no longer false-fires on stdlib module functions like inspect.getsource, os.path.join, json.dumps, Path.read_text, datetime.isoformat (#1628). Follow-up to #1609/#1623. The bare-builtin fix in #1623 missed qualified calls because _get_call_name returns the dotted name (inspect.getsource) for attribute-access calls — those didn't match the bare-name SAFE_TYPES entries. Fix extends SAFE_TYPES with the cited qualified names: inspect.getsource/getsourcefile/getmodule/getdoc, os.path.join/basename/dirname/exists/isfile/isdir/abspath/relpath, os.getenv/getcwd, pathlib.Path.read_text/exists/is_file/is_dir, json.dumps, datetime.datetime.isoformat, datetime.date.isoformat. Also adds two bare method names (isoformat, read_text) for chained-call forms like Path(p).read_text() and datetime.now().isoformat() where _get_call_name returns just the method name. Bare exists/is_file/is_dir are intentionally NOT added (too ambiguous with user code — some_record.exists() etc.); use the qualified pathlib.Path.exists(p) form, os.path.exists(str(p)), or # noqa: V008. Reporter's alternative (annotation-based trust — -> str return annotation) is deferred — would require resolving imports + inspecting target module annotations at static-check time. Discovered building djust-org/djust-start on djust 1.0.0rc12. After this lands, the starter has zero local accommodations for framework quirks. 10 new regression cases in python/tests/test_checks_v008_stdlib_qualified_1628.py; 34 V008 tests green total (10 new + 9 from #1623 + 15 pre-existing). Gate-the-fix-off self-test (Action #1200/#1468) passes.

  • {% code_block %} now syntax-highlights code blocks inserted via djust WS patches (#1625). The per-instance inline <script> that lazy-loads highlight.js worked on initial HTTP page load but failed for any <code> element that arrived via a WS patch — modern browsers don't execute scripts inserted via innerHTML/DOM manipulation, so the inline highlight bootstrap never ran for re-inserted code blocks (they appeared plain-text). Fix installs a MutationObserver ONCE per page (gated by window.__djcHljsObserverInstalled) that watches document.body for added <pre><code class="language-*"> elements and highlights any unmarked ones via hljs.highlightElement. The observer is installed on each of the three hljs-ready paths in the existing bootstrap (already-loaded, first-load s.onload, parallel-load poll), so it lives wherever the bootstrap can reach. Per-instance inline scripts still run on initial HTTP page load — the observer is purely additive. Feature-detected via typeof MutationObserver === 'undefined' so very old browsers gracefully fall through. highlight=False path unchanged. 6 new regression cases in python/djust/components/tests/test_code_block_observer_1625.py (source-text gates pin the install + scope + selector + idempotency flag). Discovered building djust-org/djust-start on djust 1.0.0rc12 — companion to #1624.

  • {% theme_head %} now auto-loads djust-components's components.css when djust.components is in INSTALLED_APPS (#1624). Previously theme_head (from djust-theming) loaded only djust-theming's own components.css. djust-components ships a separate components.css at python/djust/components/static/djust_components/components.css with layout rules for {% code_block %}, {% card %}, {% dj_button %} spinners, etc. — but theme_head didn't link it, so components rendered in user templates fell back to default flow layout (looked broken). Fix detects djust.components via django.apps.apps.is_installed("djust.components") in build_theme_head_context and adds a conditional <link> next to the existing djust-theming link in theme_head.html. Detection is defensive — apps.is_installed() raises if the app registry isn't populated yet, so the call is wrapped in try/except and falls back to no link. Without djust.components installed, theme_head emits no extra link (graceful degradation, zero behavior change for users not on djust-components). 6 new regression cases in python/djust/tests/test_theme_head_components_link_1624.py; the #1123-style pre-mount/post-mount keyset invariant test (TestThemeMixinThemeHead::test_build_theme_head_context_keyset) updated to include the new context key. Discovered building djust-org/djust-start on djust 1.0.0rc12. Gate-the-fix-off self-test (Action #1200/#1468) passes.

  • V008 no longer false-fires on stdlib primitive-returning builtins (#1609). V008 (Non-primitive type assigned to self.X in mount()) inspected the bare call name against a SAFE_TYPES set that contained type-constructor names (list, dict, str, int, ...) but missed stdlib builtins that always return primitives. Result: self.online_count = max(1, len(...)) triggered the warning even though max(int, int) returns an int. Fix extends SAFE_TYPES with numeric builtins (max, min, sum, abs, round, pow, divmod, len, ord, hash, id), string-conversion builtins (bin, oct, hex, repr, chr, ascii, format), sorted (returns list, same element-serializability trust contract as list()), and frozenset / bytes (overlooked scalar/container primitives). Iterator-returning builtins (reversed, enumerate, zip, map, filter, range, iter) intentionally remain flagged — they return iterator/generator objects that aren't directly JSON-serializable when stored on a view; the user must materialize via list() first. complex and slice also remain flagged. The V006 (Warning) path is untouched. Discovered building djust-org/djust-start on djust 1.0.0rc7. 9 new regression cases in python/tests/test_checks_v008_builtins_1609.py; 15 existing V008 tests at python/tests/test_checks.py::TestV008NonPrimitiveInMount continue to pass unchanged. Gate-the-fix-off self-test (Action #1200/#1468) passes.

  • dj_button(variant="danger") now renders styled (#1619). dj_button previously produced class="btn btn-danger" unconditionally from the variant keyword, but djust-theming's components.css (loaded by theme_head) only ships rules for .btn-primary, .btn-secondary, .btn-destructive, .btn-ghost, and .btn-link — so variant="danger", variant="success", and variant="warning" rendered with class names that had no matching CSS rule. (scaffold.css DOES have .btn-danger/.btn-success rules but is not loaded by theme_head.) Fix introduces a _DJ_BUTTON_VARIANT_CLASS_MAP in python/djust/components/templatetags/djust_components.py mapping keyword variants to the canonical CSS class names; danger is now an alias for destructive (matching shadcn/Tailwind convention). Variants not in the map (including the now-deprecated success/warning keywords, plus user-defined custom variants) pass through as btn-<variant> via conditional_escape, preserving the existing security boundary and enabling user theme classes. The danger keyword alias keeps back-compat with existing templates (e.g., python/djust/components/gallery/examples.py:158). Docstring updated to list the 5 supported variants. Discovered building djust-org/djust-start on djust 1.0.0rc12. 5 new regression cases in python/djust/components/tests/test_dj_button_variant_1619.py (danger alias, destructive canonical, primary unchanged, unknown passthrough, XSS-escape preserved); gate-the-fix-off self-test (Action #1200/#1468) passes.

  • Render diff misrouted SetText patches when a template variable was adjacent to literal text (#1617). build_fragment_text_map (crates/djust_live/src/lib.rs:2597-2633) mapped each rendered fragment to the first VDOM text node whose content equalled the fragment. For {{ online_count }} online, the variable's rendered fragment ("1") doesn't equal the chip's full text content ("1 online"), so the matcher fell through to a sibling text node whose content happened to equal "1" (typically a bare reaction count). When the variable changed, the SetText patch landed on the wrong node — chip stayed at "1 online" forever while the unrelated reaction count visually became "2" (state still said 1). Fix maps each fragment by its byte position in the assembled HTML to the text node whose HTML range contains it, claiming the entry only when the fragment IS the entire text node (full-coverage check). Ambiguous cases (partial-overlap, whitespace-only fragments, fragments containing tags) fall through to the byte-level text_region_fast_path, which is already sound for this scenario. Bug class: any {{ var }}<literal>, <literal>{{ var }}, or {{ a }}{{ b }} template pattern. The reporter's state= works / handler= broken framing was refuted by code inspection: both push_to_view paths converge at _sync_state_to_rustset_changed_keysrender_with_diff and take the same text_fast_path; the fix addresses the root cause. Discovered building djust-org/djust-start on djust 1.0.0rc12. 3 new regression cases in python/djust/tests/test_text_fast_path_misroute_1617.py (bug repro, adjacent {{a}}{{b}}, pure-case regression backstop); 6 existing #1529 content-collapse regression cases still pass; wire-protocol invariants (Actions #1448/#1538/#1541) preserved — SetText struct + msgpack/JSON serialization unchanged. Gate-the-fix-off self-test (Action #1200/#1468) passes: reverting to content-equality matching reproduces the misroute, restoring the position-aware body makes it pass.

  • WS-mount HTML now properly applied to pre-rendered DOM (#1610). When the client signaled has_prerendered=true in the WS mount message, the server's WS-mount HTML was previously used ONLY to stamp dj-id attributes onto the existing pre-render DOM (_stampDjIds(data.html) at python/djust/static/djust/src/03-websocket.js:361). Any state that diverged between HTTP-prerender and WS-mount context — presence counts, _websocket_session_id-derived values, anything that only resolved in the WebSocket scope — was silently dropped, and the DOM stayed at the prerender values until a subsequent broadcast happened to mutate something else. Fix calls morphChildren (the same helper used by handleEmbeddedUpdate at 03-websocket.js:1127 and the html_recovery path at 03-websocket.js:641) to diff the pre-render DOM against the WS-mount HTML and apply the differences. morphChildren preserves keyed nodes by id, so the dj-id stamp step is folded into the morph. PR #1615's track_presence/untrack_presence auto-broadcast partially masked this bug for the specific online_count case (the broadcast synthesized a patch frame post-mount); this fix closes the general bug class for non-presence WS-context state. Discovered building djust-org/djust-start on djust 1.0.0rc7. 8 new JS regression cases in tests/js/ws-mount-prerender-divergence-1610.test.js (source-text gate + JSDOM live tests + sticky-exclusion + missing-container fallback) plus 3 server-side correctness pins in python/djust/tests/test_ws_mount_prerender_divergence_1610.py. Gate-the-fix-off self-test (Action #1200/#1468) passes: reverting the morphChildren call to _stampDjIds(data.html) makes the JSDOM live tests fail, restoring it makes them pass.

  • DJUST_CONFIG = {"suppress_checks": [...]} now silences V002, V003, V004, V007, and Q007 (#1607). Direct mechanical follow-up to #1604 — the same wiring oversight, on five additional check IDs that share the per-class loop in python/djust/checks.py::check_liveviews. V002 (no mount() method), V003 (wrong mount() signature), V004 (handler-like name without @event_handler), V007 (event handler missing **kwargs), and Q007 (overlapping static_assignstemporary_assigns) all emitted warnings without consulting the project-wide _is_check_suppressed() helper. With this PR every V/C/T/Y/Q emission site inside the per-class loop now honors the global DJUST_CONFIG['suppress_checks'] shortcut; the per-class abstract = True opt-out from #1605 already covered abstract classes but the global-by-ID escape hatch was missing. 10 regression cases in python/tests/test_checks_1607_suppress.py (suppress + regression per ID); gate-the-fix-off self-test (Action #1200/#1468) passes for each — reverting an individual guard makes the corresponding suppress test fail, restoring makes it pass.

  • DJUST_CONFIG = {"suppress_checks": ["V001", "V005"]} now silences V001 and V005 (#1604). V001 (python/djust/checks.py:1215-1244) and V005 (python/djust/checks.py:1382-1393) emitted warnings without consulting the project-wide _is_check_suppressed() helper that every other V/C/T/Y check (C003, C013, C014, C303, V008, V010, V011, Y001-4, T002, T012, ...) already honored. Result: the documented escape hatch DJUST_CONFIG = {"suppress_checks": ["V001", "V005"]} was silently a no-op for V001/V005 even though it worked for C003 (the original reporter's confusion). Fix wraps both emission sites with _is_check_suppressed("djust.V001") / _is_check_suppressed("djust.V005") guards matching the existing pattern. Discovered while building the djust-org/djust-start starter template, which ships a BaseLiveView pattern that hits both #1604 and #1605. Reporter's SILENCED_SYSTEM_CHECKS = ["djust.V001", "djust.V005"] workaround (Django's own mechanism) still works. Hint text for both checks updated to mention all three escape hatches (abstract = True, DJUST_CONFIG['suppress_checks'], and SILENCED_SYSTEM_CHECKS). 9 regression cases in python/tests/test_checks_1604_1605.py lock both fixes in (4 suppression cases, 4 abstract cases including non-inheritance and explicit-False, 1 base-class declaration check); gate-the-fix-off self-test (Action #1200/#1468) passes — reverting the V001 guard makes test_v001_suppressed_via_djust_config fail, restoring it makes it pass.

All releases · Atom feed