This is a pre-release. djust 1.2.0 has shipped since.
Part of djust 1.2 — read the 1.2 release notes.
Changed
- djust admin applies the
DjustModelAdminpermission hooks to every page, save and delete, and the default hooks follow Django's model permissions.has_view_permission,has_add_permission,has_change_permissionandhas_delete_permissionare checked before mount (list, add) or per object (change, delete), and again insave,form_valid,confirm_deleteand thedelete_selectedaction. The defaults now calluser.has_perm()with the model'sview_/add_/change_/delete_codename, as Django'sModelAdmindoes. Staff users who aren't superusers now need those permissions; previously any active staff account had full access. The admin index lists only models the user has some permission on. - Built-in components HTML-escape the values they render. Every component class,
{% ... %}component tag and Rust component renderer now passes interpolated values throughconditional_escape, in all CSS-framework variants. HTML passed to a content slot must be marked safe (mark_safe, or another component's rendered output), or it is shown as text: this affects modal/card/sheet/popover/tabs/accordion bodies, headers and footers, icons, and similar slots.href/srcvalues usingjavascript:,vbscript:or non-imagedata:render as#. The rich-text editor is the exception: its value is cleaned to an HTML allowlist (formatting kept, scripts and event attributes removed) instead of escaped. - The observability endpoints require a project token. Besides
DEBUGand a loopback client, a request must carry no proxy headers (X-Forwarded-For,Forwarded,X-Real-IP,X-Forwarded-Host,X-Forwarded-Proto) and must sendX-Djust-Observability-Token. The token is derived fromSECRET_KEY(override with theDJUST_OBSERVABILITY_TOKENenvironment variable) and printed bymanage.py djust_observability_token.manage.py djust_mcpsends it automatically; other tools and hand-written requests must add the header. - Back-navigation state snapshots are bound to a keyed digest of the session key instead of the key itself. Snapshots issued by earlier versions fail verification once, and the view mounts fresh.
Fixed
otp_inputworks: typing fills the boxes and fires the event (#2975). The component rendered six.otp-digitboxes and a hidden input carrying itsdj-change, but nothing on the client drove them. Typing left one digit in the first box and the event never fired.python/djust/components/static/djust_components/otp-input.jsadvances focus per digit, handles backspace, arrows and paste, and fills the hidden input once every box has a digit, which fires the change. Include it on the page; the catalogue now names it. Also fixed while testing every catalogue preview:multi_select's checkboxes sent only whether a box was ticked, never which one, so no handler could tell; each box now sendsoptiontoo, in both the class and the{% multi_select %}tag.NotificationCenternever opened, because its dropdown wasdisplay: nonewith nothing to show it; it gainsis_open. The catalogue's previews now answer every event they emit: dismiss, close and accept show "Show again", host-acted events say the view received them, and overlays preview open.- The HTTP event fallback and
djust.callsend a valid CSRF token when the cookie is renamed or unreadable (#2977). The client read only acsrfmiddlewaretokeninput or a cookie literally namedcsrftoken, so projects withCSRF_COOKIE_NAME,CSRF_COOKIE_HTTPONLYorCSRF_USE_SESSIONSsent an emptyX-CSRFTokenand got a 403 whenever an event went out before the WebSocket mounted. The injected bootstrap now emits<meta name="djust-csrf-cookie">and<meta name="djust-csrf-token">, and a sharedwindow.djust.csrfToken()reads the input, then the configured cookie, then the meta token. - Hover and active states on an accent background now use the accent's own text colour. Forty-nine rules in
djust_components/components.cssand the theming stylesheets drew--accentbehind the page's--foreground(or no colour at all), not behind--accent-foreground. With a neutral accent that goes unnoticed; with a vivid one it fails contrast: on djust.org's Workbench theme a hovered accordion trigger measured 1.45:1. - A component that needs JavaScript beyond djust's client now says so, and its catalogue preview loads it. Sixteen components ship a script in
djust_components/(countdown.js,scroll-spy.js,connection-status.js, …) that a page must include; nothing said so, and the catalogue did not include them either, so those previews never updated. Twenty components carry adj-hookthat no shipped script implements (SortableList,SignaturePad,MapPicker,ImageCropper, …): their markup renders, but the interaction their docstrings describe is left to the app.describe_componentnow reports both underclient(hook,script,hook_shipped), and each catalogue page loads the script and states either requirement. - The component catalogue and the generated reference now say only what the component does.
describe_component— which bothdjust.org/components/and the docs site's reference render — reported**kwargsas a required parameter on every component, and built its events list by scraping the first example's markup. That missed events emitted in a state the example does not show (notification_center's mark-read and clear,kanban_board'skanban_move) and listed events the example itself wrote into the markup (loading_overlay's demo button,dropdown_menu's per-item events). Events now come from the component's ownevent/*_eventparameters as well as its markup; server-to-clientstream_event/loading_eventnames are excluded. The usage snippets no longer write kwargs a component does not have (self.component.dismissed,NotificationCenter.is_open), and they answer every event the copied example emits. In the previews:dropdown_menuopens and closes (itstoggle_menuwas unanswered, a server error), the date picker's month arrows move frommonth=0,form_arrayadds a row, five components that drew an empty box now show content, the pages that cannot preview say why, and example links stay on the page instead of opening 404s.PromptEditor.render()rendered the prompt as a Django template ("Summarise for .") instead of the editor; it now always renders the editor. The catalogue's Parameters table prints types as you would write them (list | None, nottyping.Optional[list]) and shows**kwargsas whatComponent.__init__does with it rather than as a parameter namedkwargsof typetyping.Any; and the "Your view received …" note follows the preview instead of sitting under an open overlay's backdrop. - DataTable sorts, filters and groups only on declared columns.
on_table_sort,on_table_filter, group-by and expression filters now accept only keys listed intable_columns, checked again where the queryset is built. Sorting is on unless a column sets"sortable": False; filtering needs"filterable": True;table_default_sortis always accepted. Unknown column names are ignored. - A LiveView joins its channel groups (view, presence, db_notify) only after its permission checks and on_mount hooks pass. A refused mount, including one inside
mount_batch, leaves every group, and presence and client-push messages are no longer delivered to a connection with no mounted view.check_permissionsand on_mount hooks now run beforeon_view_mounted. - Buttons inside
{% modal %},{% confirm_dialog %},ExportDialogandBottomSheetpanels now reach the server. Each panel carried an inlineonclick="event.stopPropagation()", there to stop an inside click from bubbling to the backdrop's close event. djust delegatesdj-clickfrom the LiveView root, though, so the samestopPropagation()swallowed everydj-clickinside the panel: the close ×, Export, Cancel, and any button you put in a modal. The close event now sits on a.dj-scrimbehind the panel, a sibling rather than an ancestor, so an outside click still closes and an inside click reaches its own handler. The inline handler is gone too, so the markup also works under a strict CSP. - Resumable uploads record the session that started them. Resume over WebSocket and the HTTP upload-status endpoint answer only that session; the status endpoint previously returned 404 even to the owner. Uploads started before the upgrade, or whose session key changed (for example at login), restart from the beginning.
- A reused sticky
{% live_render %}child re-runs its view and object permission checks on every parent render, andlive_redirectcarry-over re-checks object permissions too. A child the user may no longer see is unmounted and the render fails the same way a fresh mount would. - Theme cookies that name an unregistered pack or an invalid layout fall back to the defaults, and
{% theme_css_link %}URL-encodes its query. An unknown pack now resolves to the session pack, then the configured default. Layout names must match[A-Za-z0-9_-]{1,64}.