djust 1.0.0rc13

Pre-releaseReleased
Install
pip install djust==1.0.0rc13

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

  • dj_button accepts a confirm="" kwarg (#1621). When non-empty, emits the standard dj-confirm="<message>" attribute consumed by djust's client.js (python/djust/static/djust/src/09-event-binding.js:7) — clicking the button shows a JS confirm() dialog with the message; on OK the event fires, on Cancel nothing happens. Closes a small DX gap where users wanting the dialog had to either skip dj_button (losing the theme integration / variant→class mapping from #1619) or wrap the tag in ad-hoc JS. The underlying dj-confirm primitive was already wired in client.js across multiple directives (dj-click, dj-submit, etc.); this PR just exposes it through the component tag. Emission is independent of event= value — the attribute is useful on event-less buttons users have wired up via other directives. conditional_escape neutralizes XSS surface, matching the existing event attr escaping. Preset override supported via the existing preset-priority pattern (preset values fill defaults; explicit kwargs win). Discovered building djust-org/djust-start's reset-demo button on djust 1.0.0rc12. 5 new regression cases in python/djust/components/tests/test_dj_button_confirm_1621.py; gate-the-fix-off self-test (Action #1200/#1468) passes.

  • PresenceMixin.online_count instance attribute — zero-config {{ online_count }} template binding (#1611). PresenceMixin now auto-maintains self.online_count (an integer count of presences in the group) inside track_presence, untrack_presence, _restore_presence, and the new _on_presence_change broadcast handler. Templates can use {{ online_count }} with zero scaffolding — no get_context_data override required. The attribute is set on the view instance (NOT via get_context_data) because djust's diff dirty-tracking watches instance attribute mutations, and a value that only lives in the context dict doesn't trigger patches. Discovered building djust-org/djust-start on djust 1.0.0rc7. Behavior change: NEW public attribute on PresenceMixin users. Existing code that already does self.online_count = ... will be transparently overwritten by the mixin's auto-set; rename your attribute if you need different semantics.

  • PresenceMixin.presence_unique_per_connection: bool = False opt-in flag for anonymous-tab uniqueness (#1613). By default, two browser tabs of the same anonymous user share a Django session and therefore one anon_<session_key> user_id — the presence count stays at "1 online" no matter how many tabs are open. This is correct for an authenticated user collaborating with themselves but wrong for a demo. When presence_unique_per_connection = True, anonymous users get anon_conn_<ws_session_id> derived from the per-WebSocket-connection ephemeral UUID instead, so each tab counts as a distinct presence. Authenticated users always use request.user.id regardless of the flag — logged-in tab collapse is intentional. Behavior change: NEW class-level opt-in flag. Existing apps unchanged (False default).

  • PresenceMixin._on_presence_change default @event_handler for auto-broadcast fanout (#1614). track_presence and untrack_presence now fire a push_to_view broadcast to a well-known handler name _on_presence_change on the view class. PresenceMixin ships a default @event_handler-decorated _on_presence_change that refreshes self.online_count on the receiving session. Body is exclusively self._refresh_online_count() — explicitly does NOT call track_presence, so the broadcast loop terminates after one hop. Subclasses may override but should call super()._on_presence_change(**kwargs) to preserve count refresh. Behavior change: every track_presence / untrack_presence call now sends one extra channel-layer broadcast. No regression on existing apps; the broadcast is no-op for any session whose view class doesn't have an _on_presence_change listener.

Fixed

  • C003 now accepts uvicorn or hypercorn as an ASGI server (#1630). The check previously fired an INFO whenever daphne was missing from INSTALLED_APPS, telling users to pip install daphne. But djust's canonical recommendation has been uvicorn since the README rewrite (uvicorn myproject.asgi:application) and djust-org/djust-start ships uvicorn by default — every uvicorn-based project was forced to ship a permanent "C003" in DJUST_CONFIG['suppress_checks'] to silence guidance that pointed at the non-recommended server. New _has_asgi_server() helper probes daphne/uvicorn/hypercorn via importlib.util.find_spec (no actual import, can't ImportError-break manage.py check); C003 only fires when none is installed, and the hint now points at uvicorn as the canonical pick. The existing daphne-ordering branch (when daphne IS in INSTALLED_APPS but after staticfiles) is unchanged. New cases in TestC003AsgiServers1630 cover uvicorn-installed/hypercorn-installed/daphne-via-find_spec/no-server-at-all + helper unit tests. Updated test_no_suppress_by_default to stub _has_asgi_server per Action #1200 so the suppression contract is isolated from the broadening. Impact on djust-start: suppress_checks drops from ["C003", "T002"] to ["T002"].

  • Broke the four-module presets/registry/manager/css_generator cyclic-import SCC in djust.theming (CodeQL alerts #2352/#2351/#1900/#1883). Extracted the built-in theme imports + the THEME_PRESETS registry dict into a new python/djust/theming/_builtin_presets.py leaf module. registry._do_discover now imports THEME_PRESETS from _builtin_presets (not presets), and manager.py / css_generator.py defer their get_preset / get_theme_config imports to call sites and pull ThemePreset / ThemeTokens annotations from the leaf _types module. _builtin_presets has no runtime dependency on presets/registry/manager/css_generator, so the back-edge that closed every cycle in the SCC is gone. Back-compat preserved: presets.py re-exports the named *_THEME constants via from ._builtin_presets import * so external code doing from djust.theming.presets import BLUE_THEME keeps working, and the djust.theming.__init__ public surface is unchanged. 7 new AST-based regression cases in python/djust/tests/test_theming_no_cyclic_import.py pin every broken edge so a future refactor can't silently re-introduce the cycle; gate-the-fix-off self-test (Action #1200/#1468) confirmed each is non-tautological.

Changed

  • PresenceMixin.track_presence() now no-ops during the HTTP-prerender phase (#1612). Every djust LiveView's mount(request, **kwargs) runs twice per page load — once for HTTP prerendering and again when the WebSocket connects. Each run creates a separate view instance. Previously, code that called track_presence() in mount() registered the connection twice; the HTTP-mount entry was an orphan that lingered for PRESENCE_TIMEOUT (~60s) until stale cleanup. After this PR, track_presence early-returns when _websocket_session_id is absent (set only by LiveViewConsumer on the WS path), so presence registers only once. Reporter's existing if hasattr(self, "_websocket_session_id"): workaround is now built-in. Behavior change: anyone calling track_presence() outside the WebSocket lifecycle (unit tests, management commands) will see a silent skip; the new reproducer tests + downstream fixture updates set _websocket_session_id = "test_ws" to opt back in.

All releases · Atom feed