All notable changes to this project will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
- Polish
.pono longer overrides Django core translation ofmsgid "Log in"with the abbreviated"Zaloguj". The package's PO file was inadvertently carrying msgids it doesn't itself use (collected fromexample_project/demo/), and Django merges per-app translations on top of core — so onceformdefaultswas installed, the Django admin login page lost the" się"suffix.Log inandLog outremoved from the package PO/MO; Django core's "Zaloguj się" / "Wyloguj się" now wins. - Translation catalogs split cleanly: the published package
catalog (
src/formdefaults/locale/) now only contains msgids used insidesrc/formdefaults/, and the demo catalog (example_project/demo/locale/) holds demo-only strings. Source#:references in the package PO are restored so futuremakemessagesruns are reproducible. Two harvest scopes are enforced bycd-ing into the right directory.
- Top-level
Makefilewithmessages/messages-pkg/messages-example/compilemessages*targets. Each runsmakemessagesfrom the correct cwd so demo strings can never leak into the published catalog. Parameterized withLOCALE=…(named to avoid clashing with the shell's$LANG).
[example]extra: bumpdjango-dev-helpers>=0.1.2→django-dev-helpers>=0.1.3.run-site>=0.4is kept as-is — the 0.5 line, renamed todjango-run-site, is not yet on PyPI, and PyPI rejects direct git URLs in extras. Devs who need 0.5+ features can installdjango-run-sitefromgit+https://github.com/iplweb/django-run-site.git@v0.5.0manually. Core package (django-formdefaultsitself) is unchanged.
BooleanFieldis now rendered as a 2-option radio in the popup ("Default: checked" / "Default: unchecked"), not a single checkbox. The previous checkbox-as-value plus checkbox-as-override layout was ambiguous — an unchecked value cell could mean either "no override" or "override = False", and the override toggle was the wrong place to disambiguate. The radio makes the saved default explicit while the override toggle stays single-purpose.NullBooleanFieldis left alone (its own 3-state widget already covers the case). Existing rows are not migrated —value=True/Falsecontinues to round-trip correctly.- System-wide warning split into two sentences with a
<br>between them so the callout reads as two short clauses instead of one long wrapped paragraph.
- Radio and checkbox inputs in the modal no longer stretch to 100% of the row width (which used to push their text label onto the next line). Single-input fields keep their full-width look.
- Optional
[example]extra pullingrun-siteanddjango-dev-helpers. With it installed,manage.py run_sitespins up the example project with auto-createdadmin/adminuser and an autologin endpoint — matches our real-project dev experience. The bundledexample_project/runsite.tomland a.run-site-config-awaresettings.pymake Postgres swap-in transparent. - Polish translations for the new radio labels.
- System-wide defaults from the popup. A second "System defaults"
button next to the personal one opens the same modal bound to
FormFieldDefaultValuerows withuser=NULL. Modal is visibly flagged (amber accent, "applies to ALL users" notice) to avoid scope mistakes. formdefaults.views.SystemFormDefaultsView(formdefaults:system-editURL) — backs the new popup; reusesbuild_user_defaults_form(user=None)and validates via the permission hook.formdefaults.permissions.can_edit_system_wide_defaults(user, form_repr=None, form_class=None)— pluggable permission hook. Resolution order: per-form class attributeformdefaults_can_edit_system_wide(user, form_repr) -> bool→ settingsFORMDEFAULTS_CAN_EDIT_SYSTEM_WIDE(dotted path) → default (user.is_superuser).- Demo:
SearchForminexample_projectopts out of system-wide editing via the per-form attribute, exercising the hook end-to-end. - README "Screenshots" section embedding three rendered shots (regular
user, admin both-buttons, admin system modal) under
docs/screenshots/. - Polish translations for the new strings ("System defaults", system-wide warning text, modal help).
{% formdefaults_button form %}now renders a second button only when the permission hook returns True for the request user — otherwise the output is unchanged.- Popup closes automatically on a successful save (HTTP 200). Errors (HTTP 400) still re-render in place with field-level error messages.
- HTML
titletooltip on every override checkbox in the popup — hovering explains the toggle. - Help text in the popup modal explaining the checkbox-vs-value contract, so users discover the UX without reading docs.
- Polish translation shipped in
src/formdefaults/locale/pl/LC_MESSAGES/django.{po,mo}.
- Package templates (
_button.html,_modal_fragment.html) now use English source strings wrapped in{% trans %}. Polish-speaking installs see the same text viaLANGUAGE_CODE='pl'. - example_project is now fully internationalised: English source plus
a Polish
.po/.mo, language switcher in nav,LocaleMiddlewarewired up. - README "Per-user popup" section: removed obsolete "Empty input clears the override" sentence (superseded by v0.4.0's override checkbox).
- Per-field override toggle in the popup edit form. Each value field
has a companion
_override_<name>checkbox rendered to its left. Unchecked → the field is not written as an override on save (and any existing override is deleted). Checked → the value is upserted as the user's override, regardless of what the value is. _user_edit_form.htmltemplate uses a newform.field_pairs()helper that yields(override_checkbox, value_field)tuples infields_setorder.modal.jsauto-checks the override checkbox when the user edits a field's value (input/change events). The user can manually uncheck it to revert.- Default initial value for the popup field is now the currently-effective default (user override if present, else system-wide), so the user can see what they're getting and edit from there.
build_user_defaults_form().save()no longer treats empty input as "delete this override". The override checkbox controls write/delete exclusively. POST data must include_override_<name>=onfor any field the caller wants to upsert.- The popup template
_user_edit_form.htmlnow expects the form to exposefield_pairs(). Custom templates overriding it must adapt.
FormFieldDefaultValue.is_auto_snapshot: bool— flag identifying rows that came fromForm.initialautomatically and should be kept in sync with the code. Cleared on every UI edit (popup or Django admin), making the value "sticky".update_form_db_reprnow refreshes value of rows whereis_auto_snapshot=Trueand value drifted from currentform_field.initial. Sticky rows are never touched.- Data migration
0007_backfill_is_auto_snapshotbackfills the flag for existing rows: True ifvalue == form_field.initial, False otherwise. formdefaults._autosnap_backfill.resolve_initial— helper used by the backfill migration; importable from tests.
- Popup save (
build_user_defaults_form().save()) and admin save (FormRepresentationAdmin.save_formset) both clearis_auto_snapshoton every write so user/admin overrides become sticky immediately.
register_formdecorator andFORMDEFAULTS_FORMSsetting for pre-registration of forms; snapshot is taken onpost_migrate.pre_registeredflag onFormRepresentation.- Per-user popup edit UX:
UserFormDefaultsView(GET fragment / POST save) andformdefaults.urls.{% formdefaults_button %}template tag.- Vanilla-JS modal in
static/formdefaults/modal.js(no jQuery/HTMX dependency).
build_user_defaults_formfor programmatic per-user override editing.dedupe_formdefaultsmanagement command for migrating older databases.example_project/demonstrating all three registration paths.- Unique constraint on
FormFieldDefaultValue(field, user)for non-NULL users + partial unique on(field) WHERE user IS NULLfor system-wide rows + index on(parent, user).
FormDefaultsMixin.get_initialnow passesrequest.userso per-user overrides apply via the mixin (the previous version always returned system-wide values).- Race condition on first render of an unsnapshotted form when two requests
hit at once (
update_form_db_reprnow wraps the body intransaction.atomicand swallowsIntegrityErrorfrom concurrent inserts). FormRepresentationManager.get_or_create_for_instancenow seedslabelwithfull_nameso admin never sees an empty label.
- Per-process freshness cache in
core.get_form_defaultsskips redundantupdate_form_db_reprcalls within a 60-second window. - Test suite now runs against PostgreSQL via testcontainers (Docker required) instead of in-memory SQLite, matching the production target.
- Initial public release. Extracted from
iplweb/bpp where it lived as the
in-tree
formdefaultsDjango app. FormRepresentation,FormFieldRepresentation,FormFieldDefaultValuemodels: store default values per form, per field, optionally per user.FormDefaultsMixinfor class-based views: drop-inget_initial()that pulls saved defaults for the current user.core.get_form_defaults(form_instance, label=None, user=None)— programmatic access for non-CBV use.- Two pre-existing migrations preserved verbatim from the original in-tree app (initial schema + Django 3.2 JSONField alter).