djust 0.7.2rc1

Pre-releaseReleased

Added

  • Inline radio buttons via data-dj-inline attribute (v0.7.2, #991) — opt-in horizontal layout for forms.RadioSelect fields without writing any new Python. Users add widget=forms.RadioSelect(attrs={"data-dj-inline": "true"}) to a ChoiceField and 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 as inline-flex with sensible spacing, full keyboard navigation, and the browser's native focus ring preserved. Composes with anything that renders a Django RadioSelect (plain forms.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 of docs/website/guides/forms.md with the API, the why-data-attribute reasoning, and examples for customizing the visual treatment + multi-field forms. Covered by 12 regression tests in tests/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_ATTRS filter 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_ATTRS frozenset in python/djust/live_view.py to prevent ~25 framework-set attrs (sync_safe, login_required, template_name, ...) from leaking into get_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 reading self.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. See docs/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.yml runs every Monday at 06:00 UTC (plus manual workflow_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 a tech-debt + new cloud-integration label issue via actions/github-script@v7 with 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 under tests/cloud_integration/ and auto-skip when DJUST_CLOUD_INTEGRATION isn'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_template UUID-prefix convention for s3_events (v0.7.2, #964)djust.contrib.uploads.s3_events.parse_s3_event extracts upload_id by finding the first UUID-shaped path segment in the S3 object key; apps whose key_template doesn't produce such a segment silently fall back to the full key as upload_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 recommended key_template shapes (uploads/<uuid>/<filename> and <tenant>/<uuid>/<filename>); (b) a DEBUG log entry fires on the djust.contrib.uploads.s3_events logger whenever fallback happens, naming the offending key — so enabling DEBUG logging once is enough to diagnose a silent hook; (c) a "Key-template convention for s3_events" section has been added to docs/website/guides/uploads.md with a debugging recipe and a pointer to the "custom upload-id routing" escape hatch (via x-amz-meta-upload-id / JWT / DB lookup). Covered by 3 new regression tests in tests/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_safely sets "__str__": str(obj) on every dict it produces so {{ obj }} in a Rust-engine template can match Django's default str(obj) semantics. The Rust Value::Object Display 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 }} (where claimant serializes 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 is Value::Object and 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 by FromPyObject). Covered by 5 Rust unit tests in crates/djust_core/src/lib.rs::tests and 13 Python integration tests in tests/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_server NameError on module load when watchdog is not installed (v0.7.2, #994) — the try/except ImportError block at dev_server.py:13-19 sets WATCHDOG_AVAILABLE = False but the class statement class DjustFileChangeHandler(FileSystemEventHandler) on line 25 referenced the symbol unconditionally. When watchdog is absent, class definition time crashes with NameError: name 'FileSystemEventHandler' is not defined, which in turn breaks python manage.py check in any djust install without the [dev] extra (because djust.checks.check_hot_view_replacement imports WATCHDOG_AVAILABLE from djust.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: the except ImportError branch now defines stub FileSystemEventHandler, FileSystemEvent, and Observer classes purely to satisfy the class statements below at import time. HotReloadServer.start() already short-circuits on WATCHDOG_AVAILABLE = False, so the stubs are never instantiated in a running process. Covered by 3 regression tests in tests/test_dev_server_watchdog_missing.py that block watchdog via a sys.meta_path finder and verify (a) djust.dev_server imports cleanly, (b) HotReloadServer.start() no-ops with the documented warning, (c) djust.checks.check_hot_view_replacement's downstream import path survives.

All releases · Atom feed