Return MultiParams from request.post_vars() to preserve multi-valued form fields

Form submissions with multiple values for the same key (e.g. ``<select
multiple>``) previously had every value but the last silently discarded,
because ``post_vars()`` called ``dict(parse_qsl(...))``. Switch the
implementation to return a ``MultiParams`` object built from
``parse_qs(...)``, mirroring the existing ``request.args`` shape so both
sides of the GET/POST surface behave consistently.

``MultiParams`` gains an ``items()`` method that yields (key, first_value)
pairs, matching ``__getitem__`` semantics, so it works in patterns such as
``dict(post_vars())`` and the existing ``_coerce_execute_write_payload``
dict comprehension.

Internal callers of ``_json_or_form_payload`` (``execute_write`` and
``stored_queries``) had ``isinstance(data, dict)`` guards intended to
validate JSON shape but also rejected the new ``MultiParams`` return on
the form path. Gate those guards on ``is_json`` so the form path passes
through unchanged.

The bundled ``my_plugin`` test fixture wraps ``post_vars()`` in ``dict()``
before passing to ``Response.json``, demonstrating the migration path for
plugins that need a JSON-serialisable mapping.

Closes #2425
This commit is contained in:
August Cayzer 2026-05-29 21:43:39 +01:00
commit a759c47c9e
8 changed files with 62 additions and 21 deletions

View file

@ -43,6 +43,7 @@ Bug fixes
- Fixed a bug where visiting ``/<database>/-/query`` without a ``?sql=`` parameter returned a 500 error. (:issue:`2743`)
- The ``datasette inspect`` command now correctly records row counts for tables with more than 10,000 rows. (:issue:`2712`)
- ``await request.post_vars()`` now returns a :ref:`MultiParams <internals_multiparams>` object instead of a ``dict``, so multiple values for the same form field are preserved. Use ``.getlist(key)`` to retrieve every value. Existing ``post_vars[key]`` access continues to work, but now returns the *first* submitted value rather than the *last* (matching ``request.args`` semantics). (:issue:`2425`)
.. _v1_0_a30:

View file

@ -103,8 +103,8 @@ The object also has the following awaitable methods:
Don't forget to read about :ref:`internals_csrf`!
``await request.post_vars()`` - dictionary
Returns a dictionary of form variables that were submitted in the request body via ``POST`` using ``application/x-www-form-urlencoded`` encoding. For multipart forms or file uploads, use ``request.form()`` instead.
``await request.post_vars()`` - MultiParams
Returns a :ref:`MultiParams <internals_multiparams>` object of form variables that were submitted in the request body via ``POST`` using ``application/x-www-form-urlencoded`` encoding. This has the same shape as ``request.args``, so use ``.getlist(key)`` to retrieve every value submitted for keys with multiple values (such as ``<select multiple>`` fields). For multipart forms or file uploads, use ``request.form()`` instead.
``await request.post_body()`` - bytes
Returns the un-parsed body of a request submitted by ``POST`` - useful for things like incoming JSON data.