Added
- Inline radio buttons via
data-dj-inlineattribute (v0.7.2, #991) — opt-in horizontal layout forforms.RadioSelectfields without writing any new Python. Users addwidget=forms.RadioSelect(attrs={"data-dj-inline": "true"})to aChoiceFieldand load{% static 'djust/djust-forms.css' %}once in their base template; the bundled stylesheet uses the CSS:has()parent selector (Selectors Level 4 — Chromium 105+, Safari 15.4+, Firefox 121+, all stable since 2023) to walk up from each marked<input type="radio">and lay out its containing wrapper asinline-flexwith sensible spacing, full keyboard navigation, and the browser's native focus ring preserved. Composes with anything that renders a DjangoRadioSelect(plainforms.Form,LiveViewForm, ModelForms, Django admin, djust-theming form templates) — the same[data-dj-inline]selector targets both the stock<ul><li>markup and djust-theming's<div>-wrapped variant. Skip-able: don't link the CSS file → the attribute is inert. Override-able: write your own CSS rule keyed on[data-dj-inline]for any visual treatment (segmented controls, CSS Grid columns, etc.). New file:python/djust/static/djust/djust-forms.css. Documented in a new "Inline Radio Buttons" section ofdocs/website/guides/forms.mdwith the API, the why-data-attribute reasoning, and examples for customizing the visual treatment + multi-field forms. Covered by 12 regression tests intests/test_inline_radios_991.py(3 Django-render contract tests + 5 CSS-ships-and-targets-correctly tests + 2 backwards-compat tests + 2 edge cases).
Decisions
- ADR-012:
_FRAMEWORK_INTERNAL_ATTRSfilter is the right tool; do NOT rename framework-internal attrs (v0.7.2, #962, close-without-code) — v0.5.7 #762 added a_FRAMEWORK_INTERNAL_ATTRSfrozenset inpython/djust/live_view.pyto prevent ~25 framework-set attrs (sync_safe,login_required,template_name, ...) from leaking intoget_state()/ reactive-state debug payloads. The v0.5.7 retro filed #962 to decide whether to additionally rename those attrs to_*-prefixed form as defense-in-depth. Decision after a full review: keep the filter, don't rename. Rename would break every user view readingself.login_required/self.template_name(both first-class documented attrs; the latter is Django public API) without net defense-in-depth benefit — the filter is a single centralized gate at the exact leakage point. Mitigation for the filter's maintenance burden: the PR review checklist will remind authors to add new framework-set attrs to the frozenset at introduction time. Seedocs/adr/012-framework-internal-attrs-filter-vs-rename.md.
Infrastructure
- Weekly real-cloud CI matrix for upload writers (v0.7.2, #963) — all v0.5.7 upload-writer tests mock the SDKs. Happy-path end-to-end verification against real AWS S3 / Google Cloud Storage / Azure Blob was missing; silent regressions in credential handling, SDK auth chain changes, or bucket permissions could reach production without detection. New workflow
.github/workflows/weekly-cloud-uploads.ymlruns every Monday at 06:00 UTC (plus manualworkflow_dispatch) against all three providers in parallel (fail-fast: false — each provider's outage is independent). Each matrix slot uploads a 1 MB blob, HEADs it, GETs it, and DELETEs it. Failure opens atech-debt+ newcloud-integrationlabel issue viaactions/github-script@v7with a diagnostic link to the run. Credentials come from GitHub encrypted secrets (CLOUD_INT_AWS_*,CLOUD_INT_GCP_*,CLOUD_INT_AZURE_*) so contributors' PRs never have access. The three provider-specific integration tests live undertests/cloud_integration/and auto-skip whenDJUST_CLOUD_INTEGRATIONisn't set — running the full test suite locally or in PR CI costs nothing. Cost: a few cents per provider per weekly run.
Documentation
key_templateUUID-prefix convention fors3_events(v0.7.2, #964) —djust.contrib.uploads.s3_events.parse_s3_eventextractsupload_idby finding the first UUID-shaped path segment in the S3 object key; apps whosekey_templatedoesn't produce such a segment silently fall back to the full key asupload_id, and hooks registered against the UUID then don't fire. This was the #1 source of "my hook isn't being called" reports from v0.5.7+ users. Fix: (a) the module docstring now documents the convention prominently with two recommendedkey_templateshapes (uploads/<uuid>/<filename>and<tenant>/<uuid>/<filename>); (b) aDEBUGlog entry fires on thedjust.contrib.uploads.s3_eventslogger whenever fallback happens, naming the offending key — so enablingDEBUGlogging once is enough to diagnose a silent hook; (c) a "Key-template convention fors3_events" section has been added todocs/website/guides/uploads.mdwith a debugging recipe and a pointer to the "custom upload-id routing" escape hatch (viax-amz-meta-upload-id/ JWT / DB lookup). Covered by 3 new regression tests intests/test_presigned_s3_820.py(no-UUID fallback + DEBUG log, happy path emits no log, UUID segment position doesn't matter).
Fixed
- Rust renderer honors
__str__key on serialized model dicts (v0.7.2, #968) —djust.serialization._serialize_model_safelysets"__str__": str(obj)on every dict it produces so{{ obj }}in a Rust-engine template can match Django's defaultstr(obj)semantics. The RustValue::ObjectDisplay impl (crates/djust_core/src/lib.rs) previously ignored the key and emitted the literal"[Object]"for any dict. This broke FK display silently in LiveView templates —{{ claim.claimant }}(whereclaimantserializes to a nested dict) rendered as[Object]instead of the claimant's string representation, since the page still returned 200 the only way to notice was visual inspection. Reported by a downstream consumer prototype team who hit six occurrences in a single project. Fix: when the value isValue::Objectand contains a"__str__": Value::String(...)entry, render the string. Non-model dicts (no__str__, or__str__not a string) keep the existing"[Object]"fallback. Plain Python objects with custom__str__were already correct (handled byFromPyObject). Covered by 5 Rust unit tests incrates/djust_core/src/lib.rs::testsand 13 Python integration tests intests/test_rust_renderer_str_key.py(model dict, nested FK, HTML-auto-escape, dotted-access, plain-dict fallback, null/int__str__edge cases, empty-string__str__, backwards-compat for plain Python objects + lists + scalars). djust.dev_serverNameError on module load whenwatchdogis not installed (v0.7.2, #994) — thetry/except ImportErrorblock atdev_server.py:13-19setsWATCHDOG_AVAILABLE = Falsebut the class statementclass DjustFileChangeHandler(FileSystemEventHandler)on line 25 referenced the symbol unconditionally. When watchdog is absent, class definition time crashes withNameError: name 'FileSystemEventHandler' is not defined, which in turn breakspython manage.py checkin any djust install without the[dev]extra (becausedjust.checks.check_hot_view_replacementimportsWATCHDOG_AVAILABLEfromdjust.dev_server). Latent since at least v0.5.4rc1 — the pattern predates the v0.5.x refactor; only surfaces when an install omits watchdog. Fix: theexcept ImportErrorbranch now defines stubFileSystemEventHandler,FileSystemEvent, andObserverclasses purely to satisfy the class statements below at import time.HotReloadServer.start()already short-circuits onWATCHDOG_AVAILABLE = False, so the stubs are never instantiated in a running process. Covered by 3 regression tests intests/test_dev_server_watchdog_missing.pythat block watchdog via asys.meta_pathfinder and verify (a)djust.dev_serverimports cleanly, (b)HotReloadServer.start()no-ops with the documented warning, (c)djust.checks.check_hot_view_replacement's downstream import path survives.