---
title: "djust 1.2 release notes"
slug: release-1.2
section: releases
order: 1
level: reference
description: "What's new in djust 1.2 (released 2026-09-23), the backwards incompatible changes, the security fixes, and a checklist for upgrading from 1.1."
---

# djust 1.2 release notes

*September 23, 2026*

Welcome to djust 1.2!

These notes cover the [new features](#whats-new-in-djust-12), the
[backwards incompatible changes](#backwards-incompatible-changes-in-12) you
should know about when upgrading from djust 1.1, and the
[security fixes](#security) in this release. When you're ready, follow the
[upgrade checklist](#upgrading-from-11).

djust 1.2 has three themes:

- **The Rust template engine now behaves like Django's.** It supports every
  built-in tag and filter, loads your own template tag libraries, and raises
  the errors Django raises. Templates that relied on djust being more lenient
  than Django will now fail. Most of the incompatible changes are in this
  area.
- **Components are state.** A component assigned on a view binds to that
  view, handles its own events, re-renders when a handler writes to it, and
  can patch just its own part of the page.
- **Security hardening.** 1.2.0 fixes eleven published advisories. Several
  of the fixes change a default, so read
  [Security hardening that changes behaviour](#security-hardening-that-changes-behaviour)
  before you deploy.

djust 1.1.4 was released the same day with the same security fixes for the
1.1 line; see the [1.1.4 release notes](1.1.4.md). If you are coming from
0.9.x, read [Upgrading to djust 1.0](../guides/upgrade-to-1.0.md) first.

## Python and Django compatibility

djust 1.2 supports Python 3.10, 3.11, 3.12, 3.13 and 3.14, and Django 4.2
(4.2.29 or later), 5.0, 5.1 and 5.2. Django 6.0 is not supported yet. This
is the same range as djust 1.1.

The only new runtime dependency is `packaging>=21`, used by the update
notice.

## What's new in djust 1.2

### Django template compatibility

djust renders LiveView templates, and optionally every template in your
project through `DjustTemplateBackend`, with a Rust engine. In 1.2 that
engine closes nearly all of its remaining gaps with Django:

- **Every built-in tag and filter.** All 25 of Django's built-in tags and 57
  built-in filters are supported, including `{% autoescape %}`,
  `{% filter %}`, `{% ifchanged %}`, `{% cycle %}` with `{% resetcycle %}`,
  `{% querystring %}`, `{% lorem %}`, `{% debug %}` and `{% cache %}`.
- **Your own template tags and filters.** `{% load app_tags %}` imports the
  Django template library and makes its tags and filters available to the
  Rust engine, in included and extended templates too.
- **Translations.** `{% translate %}`, `{% blocktranslate %}`, the
  `_("...")` literal and the `i18n`, `l10n` and `tz` libraries work. They run
  Django's own code, so catalogs, plural rules and escaping are Django's.
- **Lookups against the live object.** A dotted lookup such as
  `{{ order.customer.get_full_name }}` resolves one segment at a time against
  the Python object, as Django's `Variable._resolve_lookup` does: attributes,
  properties, zero-argument methods and callables all work.
- **Values render as Django renders them.** `{{ flag }}` shows `True`,
  `{{ items }}` shows `[1, 2]`, aware datetimes are converted to the current
  time zone, numbers follow the active locale, and `Decimal` values keep
  every digit.

The score to watch is Django's own `template_tests` suite run against
`DjustTemplateBackend`: **1032 of the 1047 tests that reach a template
engine pass (98.57%)**. When the scoreboard was first measured during the
1.2 cycle the figure was 43.55%. The remaining differences are listed in
[`docs/TEMPLATE_COMPATIBILITY_LIMITS.md`](https://github.com/djust-org/djust/blob/main/docs/TEMPLATE_COMPATIBILITY_LIMITS.md),
and [`docs/TEMPLATE_BACKEND.md`](https://github.com/djust-org/djust/blob/main/docs/TEMPLATE_BACKEND.md)
describes the backend.

Rendering also got cheaper. A render no longer copies the whole view state,
and a `{% for %}` loop no longer copies it once per iteration, so the cost of
a render depends on what the template reads rather than on everything the
view holds (#2732, #2735, #2737). Large lists and querysets are no longer
converted up front (#2717), and tags bridged from Django reuse one converted
context per render (#2914).

### Components are state

Three changes, each described by an ADR, make components behave like the
rest of a view's state. See [Components](../core-concepts/components.md) and
the [components guide](../guides/components.md#class-level-components).

- **Class-level components bind per view**
  ([ADR-031](https://github.com/djust-org/djust/blob/main/docs/adr/031-class-level-component-rendering.md)).
  `nav = Tabs(active="overview")` on a LiveView class gives each view
  instance its own bound component, `self.nav`, whose state is
  `self.nav.state`. Event handlers defined on the component class receive
  the bound component as `self`. `{{ nav }}` renders the component's
  template.
- **Component-scoped patches**
  ([ADR-032](https://github.com/djust-org/djust/blob/main/docs/adr/032-component-scoped-rendering.md)).
  When an event only changes one bound component and the page template uses
  it only as `{{ nav }}`, djust renders that component alone and patches its
  subtree instead of re-rendering the page.
- **Plain components hold state**
  ([ADR-033](https://github.com/djust-org/djust/blob/main/docs/adr/033-plain-component-state-and-identity.md)).
  `self.rating.value = 5` in a handler re-renders, and the value survives a
  reconnect. `Component.event_attrs()` writes typed `dj-value-*` attributes,
  so a handler receives `4`, not `"4"`. `Component(name="row-7")` lets one
  handler serve several instances. Large components can narrow change
  detection with `fingerprint_fields`.

### The component catalogue

`djust.theming` ships a browsable catalogue of the built-in components at
`/theme/components/`. Each page shows a live preview with controls for the
example's arguments, the view and template code to copy, a parameter table,
events, accessibility notes, slots, and the source and stylesheet files that
define the component. Moving between pages uses `dj-navigate`.

The same data is available from
`djust.theming.gallery.component_registry.describe_component(name)`, which
also reports whether a component needs a script beyond djust's client. A
project can wrap the catalogue in its own navigation by overriding the
template `djust_theming/catalogue/_document.html`.

### Bug capture and replay

Bug capture, introduced in 1.1, grows three pieces
([Bug capture guide](../guides/bug-capture.md)):

- `LiveView.time_travel_excluded_fields` names state that must never leave
  the server in a shared capture. The new `djust.V014` check warns when a
  view with `time_travel_enabled = True` holds a field that looks like
  personal data (an email address, a phone number) and doesn't exclude it.
- `djust replay <capture>` opens a capture in the browser, and
  `--inspect` / `--diff` print it or diff its before and after state in the
  terminal.
- `LIVEVIEW_CONFIG["bug_capture_store"]` sends captures too large for a URL
  by reference instead.

### Getting started

- **`djust init`** adds djust to an existing Django project: it appends a
  marked settings block, replaces a stock `asgi.py` with WebSocket routing,
  installs `djust`, `channels` and `uvicorn[standard]`, and runs
  `manage.py check`. `--dry-run` shows the changes. Files with uncommitted
  changes are left alone unless you pass `--force`. See
  [Installation](../getting-started/installation.md).
- **`djust new`** generates a themed starter page built from `djust.theming`
  components, with a theme switcher. `--bare` gives a one-button page
  instead. The generated `Makefile` runs from the project's `.venv`, so
  `cd <name> && make dev` works without activating it.
- **Update and security notices.** `djust new`, `djust init`, the
  development server and `manage.py check` (as `djust.U001`) print one line
  when a newer release exists or the installed version has a published
  advisory. See [Update and security notices](../guides/update-check.md).

## Minor features

### Templates

- `DjustTemplateBackend` raises `djust.template.DjustTemplateSyntaxError`,
  a subclass of Django's `TemplateSyntaxError`, when a template is loaded,
  with Django's `template_debug` data for the debug page.
- Importing `DjustTemplateBackend` no longer imports the LiveView stack
  (Channels, presence, the WebSocket consumer). `from djust import LiveView`
  is unchanged.
- The new `djust.C016` check warns when a `DjangoTemplates` entry comes
  before `DjustTemplateBackend` in `TEMPLATES`, which hides every template
  djust could render, or when the admin is installed without a
  `DjangoTemplates` entry.
- The new `djust.T018` check warns when a LiveView template references a
  variable the view never provides. See
  [Type-safe template validation](../guides/typecheck.md).

### Events and the client

- `@debounce` and `@throttle` are implemented in the client. The settings
  reach the browser on the mount frame, and a pending send is flushed on
  `dj-submit` and at page teardown.
- `dj-mouseenter` and `dj-mouseleave` are event directives, with the
  platform's non-bubbling semantics. See
  [Events](../core-concepts/events.md).
- The HTTP event fallback and `djust.call()` find the CSRF token when
  `CSRF_COOKIE_NAME`, `CSRF_COOKIE_HTTPONLY` or `CSRF_USE_SESSIONS` is set.
  djust injects `<meta name="djust-csrf-cookie">` and
  `<meta name="djust-csrf-token">`, and `window.djust.csrfToken()` returns
  the token.
- Cmd, Ctrl, Shift and middle clicks on `dj-navigate` and `dj-patch` links
  open a new tab, and the back button returns to the previous view.
- A page restored from the browser's back/forward cache reconnects
  immediately.
- A `live_redirect` flushes what the new view queued in `mount()`, such as
  `page_title` and flash messages.

### Components

- `TableComponent` gains filtering (`filterable=True`, and per column),
  `aria-sort` on sortable headers, a caption, a footer summary row and class
  hooks for each part of the table.
- `ModalComponent` accepts a `footer`.
- The theming `tabs`, `dropdown` and `modal` components take their state
  from the view, so `{% theme_tabs active=tabs.active %}` drives them.
- `theme_nav_group`, `theme_breadcrumb` and `theme_nav_item` items can set
  `navigate` to render their link with `dj-navigate`.
- `otp_input` works; include `djust_components/otp-input.js` on the page.
- `LiveComponent.trigger_update()` re-renders the parent.

### Audio

The opt-in `AudioMixin` and the `{% djust_audio %}` tag play short sound
effects from a validated sound bank, activated by a user gesture. See
[Declarative audio](../guides/audio.md).

### Theming

- The theming types (`ThemePack`, `DesignSystem`, `LayoutStyle` and the
  others) are importable from `djust.theming`.
- `djust.theming` provides request-scoped helpers: `get_active_pack`,
  `set_active_pack`, `get_active_mode`, `set_active_mode`,
  `reset_to_defaults` and `get_theme_css_url`. `ThemeManager` gains
  `set_pack()` and `reset()`.
- A theme pack's icon style no longer restyles charts drawn by djust's
  components.

### Testing

- `djust.testing.LiveComponentTestClient` tests a `LiveComponent` without a
  parent view: `LiveComponentTestClient(MyComponent).mount(**props)`, then
  `send_event()`, `get_state()` and `render()`.
- `LiveViewTestClient.mount()` exercises the WebSocket mount, and records
  which branch it took in `client.via_websocket`. See
  [Testing LiveViews](../guides/testing.md) and the incompatible change
  below.

### Observability and tooling

- `manage.py djust_observability_token` prints the token the observability
  endpoints now require. `manage.py djust_mcp` sends it automatically.
- `djust.V008` accepts a return annotation such as `-> str` on a helper
  function or method defined in the same module as the view.

## Backwards incompatible changes in 1.2

Most of the changes below make djust do what Django or the documentation
already said it did. They are still breaking for code that depended on the
old behaviour.

### Security hardening that changes behaviour

Each of these closes a published [advisory](#security).

**Built-in components escape their content.** Every built-in component
class, component template tag and Rust component renderer passes
interpolated values through `conditional_escape`. HTML you pass into a
content slot (a modal, card, sheet, popover, tab or accordion body, a header
or footer, an icon) is shown as text unless it is marked safe.

Who is affected: anyone passing markup strings to built-in components.

What to do: pass `mark_safe(...)` or `format_html(...)` output, or another
component's rendered output, for slots that should contain HTML. `href` and
`src` values with a `javascript:`, `vbscript:` or non-image `data:` scheme
now render as `#`; a value marked safe is passed through unchanged. The
rich-text editor is the exception: its value is cleaned to an HTML allowlist
that keeps formatting and removes scripts and event attributes.

**djust admin enforces model permissions.** `DjustModelAdmin` checks
`has_view_permission`, `has_add_permission`, `has_change_permission` and
`has_delete_permission` before mounting each page and again on every save,
delete and `delete_selected` action. The default hooks now call
`user.has_perm()` with the model's `view_`, `add_`, `change_` and `delete_`
permission, as Django's `ModelAdmin` does. The index lists only the models
the user has a permission on.

Who is affected: staff users who aren't superusers. Before 1.2, any active
staff account had full access.

What to do: give those users or their groups the model permissions, or
override the hooks on your `DjustModelAdmin` subclass.

**The observability endpoints require a token.** Besides `DEBUG` and a
loopback client, a request to `/_djust/observability/` must carry none of
the proxy headers (`X-Forwarded-For`, `Forwarded`, `X-Real-IP`,
`X-Forwarded-Host`, `X-Forwarded-Proto`) and must send the
`X-Djust-Observability-Token` header. The token is derived from
`SECRET_KEY`; set the `DJUST_OBSERVABILITY_TOKEN` environment variable to
choose your own.

What to do: nothing if you use `manage.py djust_mcp`, which sends the
header. For other tools, print the token with
`manage.py djust_observability_token` and add the header.

**Custom filters registered with `is_safe=True` keep unsafe input
escaped.** As in Django, the flag preserves safety; it doesn't grant it. A
filter that returns a plain `str` built from user input is escaped even if
it is registered `is_safe=True`.

What to do: a filter that produces markup must return `mark_safe(...)` or
`format_html(...)` itself.

**Custom tag return values are escaped.** A `simple_tag` (or other custom
tag handler) that returns a plain `str` is escaped, as Django escapes it.
Return `format_html(...)` or `mark_safe(...)` for markup.

**`DataTable` sorts, filters and groups only on declared columns.**
`on_table_sort`, `on_table_filter`, group-by and expression filters accept
only keys listed in `table_columns`. A column is sortable unless it sets
`"sortable": False`, and filterable only when it sets `"filterable": True`.
`table_default_sort` is always accepted. Unknown column names are ignored.

What to do: add `"filterable": True` to every column users filter on, and
list every column you sort or group by in `table_columns`.

**Channel groups are joined after authorization.** A LiveView joins its
view, presence and `db_notify` groups only after `check_permissions` and the
`on_mount` hooks pass, and those now run before `on_view_mounted`. A refused
mount leaves every group.

Who is affected: code in `on_view_mounted` that assumed permission checks
hadn't run yet.

**Sticky children are re-authorized.** A reused sticky `{% live_render %}`
child re-runs its view and object permission checks on every parent render,
and `live_redirect` carry-over re-checks object permissions. A child the user
may no longer see is unmounted and the render fails as a fresh mount would.

**Back-navigation state snapshots are bound to a digest of the session
key.** Snapshots issued by djust 1.1 fail verification once after the
upgrade, and the view mounts fresh. No action is needed.

**Resumable uploads belong to the session that started them.** Resuming
over the WebSocket and the HTTP upload-status endpoint answer only that
session. Uploads started before the upgrade, or whose session key changed
(for example at login), restart from the beginning.

**Theme cookies are validated.** A cookie naming an unregistered theme pack
falls back to the session's pack, then the configured default. Layout names
must match `[A-Za-z0-9_-]{1,64}`.

### Templates are checked like Django's

These templates rendered on djust 1.1 and now raise, as they do on Django.
Most raise `TemplateSyntaxError` when the template is loaded, even if the
offending part is in a branch that never renders.

- An unknown filter name, or an unknown tag, even inside `{% if False %}`.
- A variable or attribute that starts with an underscore: `{{ _x }}`,
  `{{ obj._private }}`, `{{ obj.__class__ }}`.
- A filter with the wrong number of arguments (`{{ name|upper:"x" }}`), or
  with two arguments.
- A filter argument that can't be parsed or resolved, such as a misspelt
  variable name (`{{ text|wordwrap:widht }}`). This raises at render time.
- An empty `{{ }}`, a duplicate `{% block %}` name, and malformed `{% if %}`,
  `{% url %}`, `{% include %}` and `{{ }}` expressions.
- `{% cycle 'a' %}` with a single value that isn't a named cycle.

These raise at render time:

- `{% for x in value %}` where `value` isn't iterable (for example an
  integer). `None` still renders the `{% empty %}` block.
- `first`, `last`, `random`, `escapeseq`, `safeseq`, `unordered_list` and
  `phone2numeric` on a value they can't iterate, index or lowercase (for
  example `{{ 5|first }}`), and `get_digit` and `divisibleby` on a date or
  datetime.
- `{% url %}` with a name or arguments that don't reverse raises
  `NoReverseMatch`. Use `{% url ... as var %}` where a failed reverse is
  expected; `var` is then `''`.
- A zero-argument method that raises during a lookup propagates the
  exception. `PermissionDenied` and `Http404` raised while rendering give a
  403 or 404 rather than a 500.

What to do: run your test suite and load every template. The error messages
are Django's.

### Templates render values the way Django does

Pages may look different after the upgrade:

- **Time zones.** With `USE_TZ = True`, aware datetimes are converted to the
  current time zone before formatting, as Django does. djust 1.1 printed them
  in UTC. Naive datetimes are unchanged.
- **Localization.** Numbers use the active language's decimal separator, and
  thousand separators when `USE_THOUSAND_SEPARATOR` is on.
- **Plain values.** `{{ value }}` renders `True`, `None`, `[1, 2]`,
  `{'a': 1}` and `(1, 2)` like Django. Set
  `LIVEVIEW_CONFIG["django_value_repr"] = False` to restore the 1.1
  rendering while you update templates.
- **Deeper lookups.** Templates can reach attributes, properties and methods
  that used to render empty. A custom tag handler that received a model from
  the context as a `dict` now receives an object, so `ctx["user"]["username"]`
  must become `ctx["user"].username`. Names starting with `_` stay refused.
- **Callables** in the context are called, as in Django, instead of
  rendering `None`.
- `|date` formats a full datetime instead of echoing it.
- `{% cache %}` skips its body on a cache hit, including any side effects in
  it.

### Values sent to the browser

If client JavaScript reads LiveView state, check these:

- Booleans arrive as `true` and `false`, not `1` and `0`.
- `Decimal` values arrive as strings with every digit, not as floats.
- Datetimes are formatted like `DjangoJSONEncoder`: milliseconds, and `Z`
  for UTC.

### Change detection and rendering

- **In-place mutation re-renders.** `self.items.append(x)` or
  `self.columns[key].append(card)` in a handler now produces a patch.
  Previously it could be missed. Views that relied on the missed render will
  see more updates.
- **Lists of model instances aren't re-queried on every event.** A
  `list[Model]` assigned in `mount()` keeps its instances, so an unsaved edit
  to a row renders instead of being overwritten from the database. Call
  `set_changed_keys("rows")` after mutating an instance in place, and
  re-query explicitly when you want fresh rows.
- **The tick loop honours `_skip_render`.** A `handle_tick()` that sets
  `self._skip_render = True` no longer renders. A handler that sets both
  `_skip_render` and a forced render now renders on every path.
- **URL keyword arguments are decoded.** A LiveView mounted over the
  WebSocket receives `"Core UI"`, not `"Core%20UI"`. Remove any `unquote()`
  you added to work around this.

### Components

- **Class-level components are bound.** `self.nav` is now a
  `BoundComponent`, so `isinstance(self.nav, Tabs.State)` is `False`; read
  `self.nav.state`. Attribute reads and writes are forwarded to the state.
- **`state` is a reserved `Component` keyword argument** and raises
  `TypeError`.
- Built-in components emit typed `dj-value-*` attributes instead of
  `data-value`. The client still reads `data-value`.
- `TableComponent.sort_by()` takes `column` (was `column_key`),
  `TabsComponent.activate_tab()` takes `tab=`, and table row checkboxes carry
  `dj-value-row-id` instead of `data-row-id`.
- `{% modal %}`, `{% confirm_dialog %}`, `ExportDialog` and `BottomSheet`
  draw their backdrop as a separate `.dj-scrim` element and no longer use an
  inline `onclick`. Update any CSS that targeted the old structure.
- `multi_select` checkboxes also send `option`; a handler with a strict
  signature must accept it.
- Sixteen components need a script from `djust_components/` on the page
  (for example `countdown.js` or `scroll-spy.js`). `describe_component()`
  and the catalogue say which.

### The theme gallery moved

- The component storybook is now the component catalogue at
  `/theme/components/` (was `/theme/gallery/storybook/`), and the theme
  gallery is at `/theme/themes/` (was `/theme/gallery/`). The old paths
  redirect permanently.
- The older `djust.components` gallery moved from `/theme/components/` to
  `/theme/components-legacy/`, without a redirect, because the catalogue
  now uses that path.
- Python names were renamed to match: `theming.gallery.storybook` is
  `theming.gallery.catalogue`, `Storybook*View` is `Components*View`,
  `{% storybook_preview %}` and `{% storybook_thumbnail %}` are
  `{% component_preview %}` and `{% component_thumbnail %}`, URL names
  `storybook*` are `components*`, and the pages' CSS prefix `sb-` is `dc-`.

### Testing utilities

These come from real upgrades; expect a handful of test failures that are
not app regressions.

- **`LiveViewTestClient.mount()` takes the WebSocket branch by default.** It
  sets `_websocket_session_id` and the other attributes a live connection
  has, so code guarded by `hasattr(self, "_websocket_session_id")`
  (claiming a seat, presence tracking, subscriptions) now runs in tests.
  Each client gets its own synthetic session id. Call
  `client.mount(via_websocket=False)` for a test about the HTTP prerender.
- **`send_event()` raises `djust.testing.NoHandlerFoundError`** for an event
  with no handler, instead of returning `{"success": False, ...}`. Pass
  `raise_on_missing=False` to get the old return value.
- Anonymous presence for a session that has no key uses a per-view
  identity, so two test clients count as two presences instead of one.

### System checks

`manage.py check` can report new messages after the upgrade. They are
warnings or information, not errors, unless you run with `--fail-level`:

- **`djust.T018`** warns about template variables the view never provides.
  It skips templates that use `{% extends %}` and says so in one `Info`
  message; run `manage.py djust_typecheck` for those. Silence a name with
  `{# djust_typecheck: noqa name #}`, or the whole check with
  `DJUST_CONFIG = {"suppress_checks": ["T018"]}`.
- **`djust.C016`** warns about the order of `TEMPLATES` entries.
- **`djust.V014`** warns about personal-data fields on views with
  `time_travel_enabled = True` that aren't in `time_travel_excluded_fields`.
- **`djust.U001`** reports a newer release or a published advisory. It
  caches the advisory list for a day (in `~/.cache/djust/updates.json`
  unless `XDG_CACHE_HOME` or `DJUST_CACHE_DIR` is set), so after an advisory
  is corrected it can still report it until the cache expires. Delete the
  file to refresh it now. Turn the notice off with
  `DJUST_CONFIG = {"update_check": False}` or `DJUST_NO_UPDATE_CHECK=1`.
- **`djust.C013`** warns that the collected `client.min.js` is older than
  the installed one. It fires after any upgrade until you run
  `collectstatic`.
- **`djust.V008`** now accepts a primitive return annotation on a helper or
  method defined in the same module. An annotation on a helper imported
  from another module is still not read, so keep the `# noqa: V008` there.

## Features removed in 1.2

- `LIVEVIEW_CONFIG["template_resolve_lazy"]` is gone. Earlier 1.2 release
  candidates used it to switch off live-object lookups. Setting it now does
  nothing and raises no warning.
- The static files `djust/security.js`, `djust/decorators.js` and
  `djust/js/pwa.js` are no longer shipped. djust's client never loaded them;
  remove any `<script>` tag or `{% static %}` reference to them. The
  `dj-offline="..."` value form they implied is not supported.
- The client-side `StateBus` module was deleted. It was never exposed on
  `window.djust`. `@client_state` and `@optimistic` remain inert
  decorators.

## Deprecated features

djust 1.2 deprecates nothing new. The three symbols deprecated in 0.9.7,
`@event`, `LiveViewForm` and the `_legacy` theming module, still work and
still warn. See [API stability and deprecations](../guides/api-stability.md).

## Security

djust 1.2.0 and 1.1.4 include fixes for the following advisories. Each
advisory has the details and affected versions.

| Advisory | Area |
| --- | --- |
| [GHSA-fccp-5h88-g34j](https://github.com/djust-org/djust/security/advisories/GHSA-fccp-5h88-g34j) | `DataTable` sorting, filtering and grouping |
| [GHSA-jv2m-fcq9-94xf](https://github.com/djust-org/djust/security/advisories/GHSA-jv2m-fcq9-94xf) | djust admin (`admin_ext`) permissions |
| [GHSA-c44q-w252-mr67](https://github.com/djust-org/djust/security/advisories/GHSA-c44q-w252-mr67) | Observability endpoints |
| [GHSA-hc2m-gvfj-x6r3](https://github.com/djust-org/djust/security/advisories/GHSA-hc2m-gvfj-x6r3) | Built-in components |
| [GHSA-r372-rrpw-5cgj](https://github.com/djust-org/djust/security/advisories/GHSA-r372-rrpw-5cgj) | Built-in components |
| [GHSA-j23m-jxwp-m3vq](https://github.com/djust-org/djust/security/advisories/GHSA-j23m-jxwp-m3vq) | Theming |
| [GHSA-6q7c-hvpc-ff2q](https://github.com/djust-org/djust/security/advisories/GHSA-6q7c-hvpc-ff2q) | Presence and channel groups |
| [GHSA-5ffg-p52h-v2ph](https://github.com/djust-org/djust/security/advisories/GHSA-5ffg-p52h-v2ph) | Back-navigation state snapshots |
| [GHSA-74vj-mpp4-45cg](https://github.com/djust-org/djust/security/advisories/GHSA-74vj-mpp4-45cg) | Sticky `{% live_render %}` children |
| [GHSA-7fcf-23mf-rhhm](https://github.com/djust-org/djust/security/advisories/GHSA-7fcf-23mf-rhhm) | Resumable uploads |
| [GHSA-p9vp-rh5f-2cvq](https://github.com/djust-org/djust/security/advisories/GHSA-p9vp-rh5f-2cvq) | Custom template filters (fixed in 1.2.0rc2) |

[GHSA-3hp5-hxf8-qc48](https://github.com/djust-org/djust/security/advisories/GHSA-3hp5-hxf8-qc48),
the HTTP POST fallback skipping authorization, was fixed during the 1.2
cycle in 1.2.0rc8 and on the 1.1 line in 1.1.3.

The template engine work also closed several hardening gaps, listed in the
CHANGELOG's Security sections:

- A key removed from a view's context stops rendering; before, its last
  value stayed in the Rust state.
- A `mark_safe` grant no longer outlives the value it was granted for.
- Password hashes and other sensitive model fields are withheld from
  templates and custom tag handlers on every path, including nested
  containers, the JIT serializer and the Rust queryset serializer.
- A template name can't escape its template directory
  (`{% include "../secret.txt" %}`).
- Component methods that change state (`mount`, `unmount`, `update`,
  `trigger_update`) can't be called from a template.

## Bug fixes

1.2 includes several hundred bug fixes. The main areas:

- **Templates:** inheritance and `{{ block.super }}`, `{% regroup %}` over
  objects, dictionary views and key types, `Decimal`, datetime and time-zone
  handling, and several crashes on unusual input.
- **Client and VDOM:** scoped listeners are rebound or removed when their
  attributes change, `dj-keydown.enter` bindings fire, and patches arriving
  during an event no longer force a full re-render.
- **Runtime:** async work renders under the render lock, `start_async`
  from a `db_notify`-released event runs, and `use_actors` views mount
  their real template.
- **Components:** modal, alert and badge dismiss, table sorting, pagination
  and selection, and buttons inside modals and sheets all reach the server.
- **Admin:** admin pages no longer return a 500 under the recommended
  `TEMPLATES` setup.
- **Scaffolding:** `djust new --with-streaming`, `--from-schema`,
  `--with-auth` and `--with-presence` generate working projects.
- **Packaging:** `collectstatic` with `ManifestStaticFilesStorage` (and
  WhiteNoise) no longer fails on a missing `client.min.js.map`.

The full list is in the `1.2.0rc1` to `1.2.0rc11` sections of the
[CHANGELOG](https://github.com/djust-org/djust/blob/main/CHANGELOG.md).
1.2.0 itself is identical to 1.2.0rc11 apart from the version number.

## Upgrading from 1.1

1. **Upgrade and collect static files.**

   ```bash
   pip install "djust==1.2.0"
   python manage.py collectstatic --noinput
   ```

   `djust.C013` stays until `collectstatic` has run.

2. **Run the checks.** `python manage.py check` reports the new `T018`,
   `C016`, `V014` and `U001` messages. Fix or silence them; for templates
   using `{% extends %}`, also run `python manage.py djust_typecheck`.

3. **Run your test suite**, and expect these test-only changes:
   - add `via_websocket=False` to `LiveViewTestClient.mount()` calls that
     test the HTTP prerender;
   - catch `NoHandlerFoundError` (or pass `raise_on_missing=False`) where a
     test sends an event with no handler.

4. **Load every template.** Template errors that djust 1.1 ignored are now
   raised when a template is loaded or rendered. Rendering each page once, in
   a test or by hand, finds them.

5. **Check what changed on the page:** timestamps (now in local time),
   number formatting, `{{ }}` output of booleans, lists and dicts, and any
   `{% url %}` whose reverse used to fail silently.

6. **Mark component HTML safe.** Wrap markup passed to built-in component
   slots in `mark_safe()` or `format_html()`, and make custom filters and
   tags that produce markup return safe strings.

7. **Review permissions:**
   - grant model permissions to staff who use djust admin;
   - add `"filterable": True` to `DataTable` columns that users filter;
   - give observability clients other than `djust_mcp` the
     `X-Djust-Observability-Token` header.

8. **Update client JavaScript and CSS** that reads booleans, decimals or
   datetimes from state, or targets the modal backdrop, `data-value` or
   `data-row-id`.

9. **Update links and imports** to the theme gallery and component
   catalogue if you used the old paths or `Storybook*` names.

10. **Remove workarounds** that 1.2 makes unnecessary: `unquote()` on URL
    kwargs, `# noqa: V008` on same-module helpers annotated `-> str`, and
    any `template_resolve_lazy` setting.

In-flight resumable uploads restart after the upgrade, and back-navigation
snapshots from 1.1 are ignored once. Neither needs any action.
