djust 1.0.0rc8

Pre-releaseReleased
Install
pip install djust==1.0.0rc8

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

Before you upgrade, read Deprecated below and the upgrade guide.

Tests

  • Framework-level invariant pin for #1552 {% if/elif %} + {% include %} swap (3 cases, all PASS on main). The #1552 reporter verified the user-visible bug (post-swap DOM contains BOTH step subtrees) still reproduces on rc7 even after PR #1555's dj-id counter fix. Investigation in this PR confirmed: at the framework level, with the bit-exact template shapes the reporter described, the differ produces correct Remove+Insert patches — including the full {% extends %} + {% block %} + {% if/elif %} + {% include %} inheritance shape. The user-visible bug must live in another layer (WS save block / sticky-child persistence / JS patch application / unsampled interaction); pursuing it requires reporter-side data, not more synthetic-shape framework theorizing (per CLAUDE.md Bug-report triage rule #1 and the multi-reopen rule #1389 / PR #1086 precedent). The 3 new framework-pin tests in python/tests/test_if_elif_include_swap_framework_pin_1552.py (test_framework_include_swap_emits_correct_remove_then_insert_1552, test_diagnostic_patch_op_summary_1552_include_swap, test_framework_include_swap_with_extends_and_block_1552) lock in the framework's current correctness at these shapes — if a future change regresses any of them, the tests catch it fast. The #1552 issue stays OPEN and will receive a follow-up comment requesting the reporter share a BugCapture URL (iter A feature from PR #1563, shipped the same day) capturing state_before + state_after + vdom_patches from the moment of the broken transition; comparing their actual patches against the framework reproducers will identify the divergent layer.

Added

  • djust.bug_capture — share a broken djust transition via a URL fragment a teammate can paste back to reproduce, no source-tree access required (B7 iter A, refs #1552; v1.1.0 Path D). Promotes B7 (Time-travel sharable URLs) from "killer demo idea" in the v1.1 readiness session to a load-bearing v1.1 capability, triggered by the #1552 reporter's upstream-bug-velocity data point ("the gap between 'I see it broken' and 'you can see it broken' is the full source tree"). The v1.1 readiness session recommended Path E (defer until launch-soak data exists) with the hedge "refuse to commit before data exists"; the #1552 filing supplied that data. Iter A (this release) ships the foundation: a BugCapture dataclass holding the 3 minimal fields needed to reproduce a broken transition (state_before, state_after, vdom_patches), an encode() / decode() URL-fragment round-trip using a versioned djbug1.<base64-urlsafe> wire format, a scrub hook with a ready-made scrub_fields(*names) helper for PII redaction, a wire-visible scrubbed_fields list (names only, never values) so reviewers know what was held back, and an encode_view_state(view, scrub=...) convenience that pulls the latest event snapshot + VDOM patches from a view with time_travel_enabled = True. Security model is load-bearing: the module docstring leads with a 3-paragraph "READ THIS BEFORE USING" warning; encoded blobs may contain user PII and are NOT authenticated; encode() raises RuntimeError in production (DEBUG=False) unless the deployer explicitly opts in via DJUST_BUG_CAPTURE_PROD_OPT_IN = True (literal True only, not truthy — defensive against typo-enable); the wire format is JSON + base64-urlsafe, never pickle, and a regression test pins this; the decoder treats all input as untrusted (validates types, requires fields, rejects malformed base64 with validate=True against the urlsafe alphabet, rejects malformed JSON, rejects non-object payload, rejects unknown outer version AND mismatched inner "v" field). Iter B (read-only replay viewer at /__djust__/replay/<blob> + share button in the debug panel) and iter C (Redis snapshot store + djust replay CLI + framework-level LiveView.time_travel_excluded_fields class attribute with djust check V012 enforcement) are tracked as separate v1.1.0 issues #1561 and #1562. New python/djust/bug_capture.py module; new docs page docs/website/guides/bug-capture.md linked from _config.yaml and index.md. Framework integration trade-off: encode_view_state() takes patches as an explicit required parameter (the caller obtains them from view.render_with_diff() and passes in). The original sketch read view._last_vdom_patches / view._last_patches, but PR #1563's Stage 11 reviewer correctly caught (Action #1101) that no framework code actually writes those attributes — render_with_diff() returns patches directly into the WS/SSE/runtime frame paths without stashing them on the view. Iter B (#1561) will add a debug-panel button that calls render_with_diff() + encode_view_state() in one click, eliminating the caller burden. scrub_fields() scopes to top-level keys only (documented as such) — nested paths like state["user"]["password"] need a custom callable; iter C (#1562) will add framework-level time_travel_excluded_fields declarative scrub. Covered by 36 regression cases in python/djust/tests/test_bug_capture.py across 5 test classes (TestRoundTrip 6, TestScrub 6, TestDebugGate 4, TestUntrustedInput 10, TestEncodeViewState 10 — the EncodeViewState class grew by 2 after the Stage 11 fix-pass: test_raises_on_malformed_patches_json and test_raises_on_patches_wrong_type pin the new _coerce_patches boundary), including a gate-off self-test (#254 / #1468) confirming 2 of 4 DEBUG-gate tests fail without the _enforce_prod_gate() call (the other 2 are intentionally tautology-safe: prod-opt-in-allowed exercises the bypass path; decode-regardless tests decode, which is gate-independent).

Deprecated

  • django-tenants (schema-per-tenant) integration is now deprecated as a multi-tenancy strategy for djust applications (follow-up to #1556). djust ships its own row-level multi-tenancy in djust.tenants (subdomain/path/header/session resolvers + TenantMixin / TenantScopedMixin + tenant-scoped state backends + presence isolation), and this is the supported and recommended path going forward. The external django-tenants library implements schema-per-tenant isolation via SET search_path on every request, which is a documented production footgun under ASGI + LiveView — every WebSocket event (tick_interval, push_to_view, presence, @notify_on_save) re-enters TenantMainMiddleware and issues a Postgres roundtrip, exhausting the connection pool under sustained load (#1556 was the prod 503 incident that motivated this deprecation). The djust.tenants row-level path does not have this failure mode by construction (no SET search_path in the per-event path). Existing django-tenants integrations continue to work, but no new ASGI-correctness or LiveView integration work will be done on that path; new applications should not adopt it. A dedicated migration guide is tracked as #1559 for v1.1.0, covering the schema-to-row data migration, code/middleware swap, and rollout strategy. The djust.C014 system check (also in this release; see ### Added below) is the in-product breadcrumb pointing existing django-tenants users at the deprecation + migration. Reflected in docs/website/guides/multi-tenant.md (the "Choosing Your Multi-Tenancy Strategy" section explicitly marks django-tenants as deprecated under djust and frames TENANT_LIMIT_SET_CALLS = True as a stopgap, not a fix). Behavior change: framework-level — none. Documentation/messaging change: substantial.

Changed

  • djust.C014 hint and docs/website/guides/multi-tenant.md upgraded from soft "consider djust.tenants" framing to hard deprecation framing for django-tenants (follow-up to #1556). The first cut of C014 (shipped in this release; see ### Added) described django-tenants as one of two viable strategies. After the deprecation decision (see ### Deprecated above), the messaging now leads with migration as the recommended path and treats TENANT_LIMIT_SET_CALLS = True as a stopgap rather than a long-term fix. Specific changes: C014's primary warning message now explicitly flags django-tenants as deprecated (visible in manage.py check output without expanding hints); C014's hint leads with migration to djust.tenants + link to the strategy guide, then describes the TENANT_LIMIT_SET_CALLS = True stopgap; C014's fix_hint reorders to lead with migration and labels the django-tenants config path as a stopgap. The multi-tenant guide's "Choosing Your Multi-Tenancy Strategy" section is rewritten to mark the django-tenants subsection as > **Deprecated.**, lists why (production footgun + scope mismatch with djust's mixins), points at the migration tracking issue, and presents the stopgap settings explicitly inside a "stopgap only; migrate to djust.tenants for long-term support" boundary. Covered by 5 new/updated hint-quality test cases in python/djust/tests/test_c014_multi_tenant_asgi.py::TestC014HintQuality (16 total, up from 11): hint mentions djust.tenants, hint links the strategy guide, hint marks django-tenants deprecated, fix_hint leads with migration and treats the flag as stopgap, and the Warning message itself surfaces the deprecation.

Added

  • New system check djust.C014 — flag django-tenants integration as deprecated and warn when the stopgap TENANT_LIMIT_SET_CALLS = True is missing (#1556). Surfaces both the deprecation (see ### Deprecated above) and the misconfiguration that caused a production 503 on djustlive: under ASGI + django-tenants, every WebSocket event re-enters TenantMainMiddlewareset_tenant()SET search_path. LiveView amplifies this — tick_interval polling, push_to_view re-mounts, presence updates, and @notify_on_save listener re-mounts each re-enter the middleware. Without TENANT_LIMIT_SET_CALLS = True, every re-entry issues a fresh Postgres roundtrip; under load the Postgres pool exhausts and pods serve 503 simultaneously. The check fires when ALL of these hold: (1) django_tenants is in INSTALLED_APPS OR TENANT_MODEL is set, (2) ASGI_APPLICATION is set, (3) TENANT_LIMIT_SET_CALLS is unset or False. Emits a DjustWarning whose primary message explicitly flags django-tenants as deprecated; the hint leads with the migration recommendation (link to docs/website/guides/multi-tenant.md and tracking issue #1559) and describes TENANT_LIMIT_SET_CALLS = True as the stopgap; the fix_hint follows the same order. Suppressible via DJUST_CONFIG = {'suppress_checks': ['C014']}. The framework-level safety improvement for users still on django-tenants during the migration window — caching the tenant per WS session at LiveView mount time (option a from #1556) — is tracked separately in #1557 (security-review label) for v1.1.0. New helper _check_multi_tenant_asgi_set_calls in python/djust/checks.py. Covered by 16 regression cases in python/djust/tests/test_c014_multi_tenant_asgi.py across 4 classes (trigger conditions, negative cases, suppression by short and full ID, and hint quality — the hint-quality class grew from 3 to 8 across two iterations of strategy-steering then deprecation-framing), including a gate-off self-test (#254 / #1468) confirming behavior-meaningful tests fail without the check.

All releases · Atom feed