--- title: "1.34.0 — H.265 video on upload, one rendition job at a time, per-account code limits, sign-out-everywhere" description: "Video uploads now produce a full H.265 mp4 automatically, and every job engine runs at most one rendition job at a time so uploads can never starve ordinary work. Rendition sizes, formats and codecs are admin-configurable through three settings plus a read-only options endpoint. One-time codes are r…" date: "2026-10-10" tags: ["release"] canonical: "https://django-mojo.sitesmojo.com/changelog/1-34-0/" --- Video uploads now produce a full H.265 mp4 automatically, and every job engine runs at most one rendition job at a time so uploads can never starve ordinary work. Rendition sizes, formats and codecs are admin-configurable through three settings plus a read-only options endpoint. One-time codes are rate-limited per account with 429s, a password reset or change signs the account out everywhere, and the own-password-change response returns new tokens the client must adopt. Framework no-save fields now apply to every model. ### Breaking - Changing your own password through the account save now returns top-level `tokens` and invalidates the old ones. Clients must adopt the new tokens or the user is logged out. - `auth/sms/login` now requires a `login` bouncer token where bouncer enforcement is on. Custom clients must fetch one first. - `id`, `pk`, `created` and `uuid` are refused on save for every model, even those that declare their own `NO_SAVE_FIELDS`. Declare `ALLOW_SAVE_FIELDS` to hand back `created` or `uuid`. A posted `id` is ignored silently, not 400. - `mojo_secrets`, `secret` and `setting` are no longer accepted in a FileManager request body. - A setting's group is fixed at creation. Changing it returns 400. A create through the active group that names another group returns 403. - A FileManager's AWS role on a store that uses platform credentials can be set only by a superuser, whichever key carries it. Roles already stored are not undone. - Setting a group's link address or moving a group between trees needs signed-in global `manage_groups` or `groups`. Group API keys and sub-group managers get 403. - Moving a group within a tree needs member save rights on both the old and new parent. - Assistant tools refuse a caller-supplied `graph`. `describe_model` now returns a single `serialization` block. Models wanting a richer assistant view declare `GRAPHS["ai"]`. - The assistant context endpoint opens a row only for a caller who may read it. Anything else is a 404. - `BaseRenderer.get_rendition_options` and `get_automatic_rendition_roles` are now instance methods. The old classmethods are `default_rendition_options` and `default_automatic_roles`. Renderer subclasses set `config_category` to be overridable. - A file's `metadata.expires_at` without a timezone is refused with 400 over the API. - A webhook `dispatch()` with an idempotency key over 255 characters raises `ValueError`. - A confined credential naming an unknown, inactive or foreign group in `?group=` gets 403 instead of falling through. - A settings-file `APIKEY_PERMS_PROTECTION` map is a floor a settings row cannot remove or loosen. A malformed map blocks API-key permission grants until repaired. ### Added - `FILEMAN_RENDITIONS_IMAGE`, `_VIDEO` and `_DOCUMENT` settings, global or per group, override any rendition's size, format, quality or codec and choose which roles run on upload. Values are validated on save with messages naming the field, and numeric options are capped. - `GET /api/fileman/renditions/options` describes every rendition role, its defaults, allowed values and limits for an admin UI. - `JOBS_CHANNEL_LIMITS` caps how many jobs a channel may hold on one engine. The jobs CLI takes `--channel-limits`, and heartbeats report the caps. - `JOBS_ENGINE_RESERVED_WORKERS` holds back worker slots that only the `priority` and box-direct channels may claim, so deploys start while workers are busy. - Per-account one-time-code limits: `CODE_ATTEMPT_LIMIT`, `CODE_ATTEMPT_WINDOW`, `CODE_SEND_LIMIT`, `CODE_SEND_WINDOW`, `TOTP_ATTEMPT_DAILY_LIMIT`. Over the limit returns 429 with `Retry-After`. - `SMS_OTP_LENGTH` sets the SMS code length, 6 to 10. - `AUTH_HANDOFF_REQUIRE_PKCE` and PKCE on `POST /api/auth/handoff`. With the setting at `native`, handoffs to app destinations must carry a code challenge. - `User.end_sessions` signs an account out everywhere: rotates the auth key, revokes OAuth grants, drops websockets. - `auth/manage/throttle` accepts every per-account bucket, and `clear_rate_limit` for an account clears all of them. - `reconcile_fileman_public_access --groups` and `--manager` re-check group-scoped stores. - `RestMeta.ALLOW_SAVE_FIELDS` and `Model.get_no_save_fields()`. - Edge nginx serves `.webmanifest` as `application/manifest+json`. ### Changed - A video upload builds its thumbnails, the 10-second preview and a full `video_mp4` again. The mp4 is H.265 at CRF 28; Chrome and Safari play it, Firefox does not. Set `{"video_mp4": {"codec": "h264", "bitrate": "2000k"}}` for H.264, or add `video_webm` to `_automatic` for a fallback. - Every engine consuming `renditions` runs one rendition job at a time by default. Set `JOBS_CHANNEL_LIMITS` to `{"renditions": 0}` to lift it. An explicit dict replaces the default rather than merging with it. - Engines with 8 or more workers reserve 2 for priority work by default, 4 to 7 reserve 1. Set `JOBS_ENGINE_RESERVED_WORKERS=0` for the old behaviour. - Video thumbnails and transcodes scale into a bounding box, keeping aspect ratio and never upscaling. Output dimensions may differ from before. - A password reset or change, an admin temporary password, or a session revoke signs the account out everywhere. - Re-sending a login, reset or sign-up code inside its lifetime re-sends the same code. Sends are capped at 5 per 15 minutes, and a refused send returns the normal success answer. - `ALLOW_PHONE_CHANGE=False` also refuses clearing a phone number. Removing a verified number sends the `phone_removed_notify` email. - A deploy whose coordination lease is lost is reported as `failed` with reason `lease_expired_before_start` or `lease_expired_mid_canary`. `superseded` is used only when a successor is seen. - `WS_MAX_CONNECTIONS` counts live connections only. The server ping is the presence heartbeat; do not disable it in production. - Bouncer no longer learns or enforces automatic user-agent, fingerprint or `/24` subnet signatures. Manual signatures still apply. `BOUNCER_LEARN_UA_THRESHOLD`, `BOUNCER_LEARN_UA_TTL` and `BOUNCER_LEARN_FP_THRESHOLD` are no longer read. - Assistant searches on a model whose search fields include a sensitive field are refused entirely. - Webhook fan-out keys longer than 64 characters are hashed. The fan-out job's real outcome is `metadata.result`, not its job status. - Document preview `max_pages` is now applied. Image `quality` applies to WEBP. - Releases are published as a wheel only. No source archive is built. ### Fixed - A model declaring `NO_SAVE_FIELDS` silently made `id` writable, so a posted `id` could re-target a save at another row. - A file admin could set an assume-role on a platform-credential store by posting it inside `secrets` or `settings`. - Image format `jpg` raised inside Pillow and silently dropped the rendition. - A file's metadata save over the API hit a two-argument signature and errored. - Expired-file cleanup reads a timezone-less expiry as UTC and survives one bad file. - A dead websocket no longer counts against the connection cap, and a disconnect of any kind cleans up its Redis state. - A stale password-reset answer no longer reveals whether an account exists. ### Upgrade notes - Workers need ffmpeg with `libx265`, or every `video_mp4` rendition lands as a failed row. Expect minutes of CPU per uploaded video, one at a time per engine. - Load the new `phone_removed_notify` email template with `seed_email_templates`, or the removal notice fails silently. - Re-render edge nginx config to pick up the `.webmanifest` type. - Existing automatic bouncer signatures stop being enforced on upgrade. Their rows remain. - A `{}` settings row that used to wipe the file's `APIKEY_PERMS_PROTECTION` no longer does. - The packaged Admin bundle and its pinned manifest hash ship together. A mismatch stops Django at import. - No migrations in this release.