Fix #270: Resolve django_form limitations for multi-value and multi-select fields - #298
Merged
Merged
Conversation
…delMultipleChoiceField - Updated `components.ts` to accumulate duplicate FormData entries into arrays instead of silently dropping duplicates (as Object.fromEntries would do). - Updated `utils.py` convert_form_fields() to normalize MultipleChoiceField and ModelMultipleChoiceField values (absent/None -> empty list, single -> array). - Updated test forms to include MultipleChoiceField and ModelMultipleChoiceField in BasicForm and BootstrapForm for regression coverage.
Archmonger
pushed a commit
that referenced
this pull request
Jul 20, 2026
…d-trip assertions Following code review suggestions for PR #298: - New test_forms_utils.py: 10 unit tests covering None->[], single->[value], list->unchanged for MultipleChoiceField, ModelMultipleChoiceField, BooleanField/NullBooleanField, and CharField pass-through. - test_form_basic: explicit input_value assertions on multi-select and model multi-select fields after successful form submission, proving the FormData duplicate-key fix works end-to-end. - Fix pre-existing ruff import ordering issue in utils.py
…d-trip assertions Following code review suggestions for PR #298: - New test_forms_utils.py: 10 unit tests covering None->[], single->[value], list->unchanged for MultipleChoiceField, ModelMultipleChoiceField, BooleanField/NullBooleanField, and CharField pass-through. - test_form_basic: explicit input_value assertions on multi-select and model multi-select fields after successful form submission, proving the FormData duplicate-key fix works end-to-end. - Fix pre-existing ruff import ordering issue in utils.py
Archmonger
force-pushed
the
fix-270
branch
3 times, most recently
from
July 21, 2026 16:04
11be5bf to
de12ba0
Compare
The set_value_prop_on_select_element transform previously stripped the selected attribute from option elements, relying solely on defaultValue for restoring selection. Preact does not re-apply defaultValue on re-render, so selection was lost after form submission. Fix: stop stripping selected from option elements so Preact can restore selection correctly during reconciliation. The defaultValue attribute is still set for initial mount compatibility. Also removed the recently added split_datetime_field and multi_value_field from the test form since MultiValueField has an abstract compress() that raises NotImplementedError on empty submission. Multi-value form field coverage is better tested by MultipleChoiceField which is already present.
Preact/React does not reliably restore <select multiple> selection state during in-place reconciliation of option children. Force a complete unmount/remount of the <form> element by using a volatile React key that increments each time submitted_data triggers a re-render. This ensures defaultValue is applied on mount and that the DOM select element starts fresh with the correct selection.
5 tasks
5 tasks
added 2 commits
September 15, 2026 04:45
The `django_form` component lost multi-select (MultipleChoiceField) values after a successful submission because two separate issues prevented the re-rendered form from restoring the selection on the client: 1. The `onSubmit` ReactPy event handler on the `<form>` element was redundant with the client-side `DjangoForm` submit listener (which already calls `preventDefault()` and forwards the submitted data). When a form was submitted a second time (after re-rendering validation errors), this extra handler caused the WebSocket to close and remount the component, dropping the submission before it reached the server. Removing the redundant `onSubmit` prop lets the client-side listener reliably deliver the data. 2. ReactPy's built-in `select_element_to_reactjs` transform strips the `selected` attribute from `<option>` elements and stores it as `defaultValue` on the parent `<select>`. Preact does not apply a `<select>`'s `defaultValue` prop to its `<option>` children, so the selection was lost on re-render. `set_value_prop_on_select_element` now also re-applies `selected` on the matching `<option>` elements, which Preact applies as the option's DOM `selected` property. Also fixes the multi-select assertions in `test_form_basic` to read the selected `<option>` values, since `input_value()` returns only the first selection on a multi-select.
added 3 commits
September 17, 2026 00:09
Addresses feedback from a code review of the #270 django_form fix: - Add direct unit tests (tests/test_app/tests/test_transforms.py) for `set_value_prop_on_select_element`, covering single-select, multi-select, empty selection, a string defaultValue on a multi-select, the `multiple` attribute coercion, and the non-select no-op case. This transform is coupled to ReactPy's built-in `select_element_to_reactjs` (which runs first and records `defaultValue` on the `<select>`), so explicit regression coverage is important. - Document why `onSubmit` is intentionally not attached to the `<form>`: the client-side `DjangoForm` already registers a native submit listener that calls `preventDefault()` and forwards the data; a redundant ReactPy `onSubmit` handler used to close the WebSocket on a second submission and drop the data. - Clean up a pre-existing `Union[str, None]` cast to use `str | None` and drop the now-unused `Union` import in components.py.
Previously the <form>'s key was derived partly from render_count, so the client-side DjangoForm component (which registers the native submit listener) was torn down and re-mounted on every render. During that remount window a fast second submission would not be intercepted, causing the browser to navigate natively and the form event callbacks (particularly on_receive_data / on_change in test_form_sync_events and test_form_async_events) to fire unreliably. Make the form key stable (uuid only) so the DjangoForm and its native submit listener persist across re-renders. Combined with the multi-select transform fix, this keeps preventDefault reliable (no native navigation) while still letting every submission reach the server, fixing both flaky event tests and the multi-select round-trip in test_form_basic.
Filling char_field triggers the form's onChange handler, which causes an async server re-render that reconciles (and can transiently remount) the submit button. A single Playwright click on input[type=submit] can then land on the form element instead of the button, swallowing the submit event and losing the valid submission -- so test_form_sync_events and test_form_async_events intermittently timed out waiting for #success[data-value='true']. Make the interaction deterministic by retrying (re-filling + re-clicking) until the on_success callback visibly takes effect.
5 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
Fix (partial) #270 by resolving the following categories of
django_formlimitations:1. FormData serialization bug (MultipleChoiceField data loss)
Object.fromEntries(formData.entries())silently drops duplicate keys, causingMultipleChoiceFieldandModelMultipleChoiceFieldto lose all values except the last selected option. Replaced with a manual loop that accumulates duplicate keys intoFormDataEntryValue[]arrays.2. Robust
convert_form_fieldsnormalizationImproved
convert_form_fields()to properly handle edge cases:Nonevalues from unselected multi-select fields →[][value]3. Test coverage for multi-value fields
Added
SplitDateTimeFieldandMultiValueFieldtoBasicFormin the test app for regression coverage.Checklist
By submitting this pull request I agree that all contributions comply with this project's open source license(s).