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

@ -237,7 +237,8 @@ def register_routes():
if request.method == "GET":
return Response.html(request.scope["csrftoken"]())
else:
return Response.json(await request.post_vars())
# post_vars() returns a MultiParams; convert to a plain dict for JSON
return Response.json(dict(await request.post_vars()))
async def csrftoken_form(request, datasette):
return Response.html(