Dedupe the submit button against list formdata in FormRequest.from_response

When formdata is a list of (key, value) tuples rather than a dict,
from_response() failed to suppress the form's own submit button: the
clickable de-dup tested `clickable[0] not in formdata`, which for a list
compares the name string against tuples and is always true, so the form's
default submit value was emitted in addition to the caller's override.
Dedup against `formdata_keys` instead -- it already normalizes both dict and
list-of-tuples and is what the regular-input de-dup on the line above uses,
so both container types now behave the same.
This commit is contained in:
winklemad 2026-07-11 04:13:13 +05:30
parent c9446931a8
commit 6f44e92dc9
2 changed files with 34 additions and 1 deletions

View File

@ -245,7 +245,7 @@ def _get_inputs(
if not dont_click:
clickable = _get_clickable(clickdata, form)
if clickable and clickable[0] not in formdata and clickable[0] is not None:
if clickable and clickable[0] not in formdata_keys and clickable[0] is not None:
values.append(clickable)
formdata_items = formdata.items() if isinstance(formdata, dict) else formdata

View File

@ -499,6 +499,39 @@ class TestFormRequest(TestRequest):
fs = _qs(req)
assert fs[b"clickme"] == [b"two"]
def test_from_response_override_clickable_formdata_list(self):
# formdata as a list of (key, value) tuples (documented as equivalent to
# a dict) must dedupe the submit button the same way; otherwise the form's
# default submit value leaks in alongside the override.
response = _buildresponse(
"""<form><input type="submit" name="clickme" value="one"> </form>"""
)
req = self.request_class.from_response(
response, formdata=[("clickme", "two")], clickdata={"name": "clickme"}
)
assert _qs(req)[b"clickme"] == [b"two"]
def test_from_response_override_auto_clicked_submit_formdata_list(self):
# Same class of bug without clickdata: the auto-selected submit must be
# overridden by a list formdata entry, not duplicated.
response = _buildresponse(
"""<form method="post">"""
"""<input type="submit" name="clickme" value="one"></form>"""
)
req = self.request_class.from_response(response, formdata=[("clickme", "two")])
assert _qs(req)[b"clickme"] == [b"two"]
def test_from_response_submit_kept_with_unrelated_formdata_list(self):
# A list formdata that does not name the submit must not suppress it.
response = _buildresponse(
"""<form method="post">"""
"""<input type="submit" name="clickme" value="one"></form>"""
)
req = self.request_class.from_response(response, formdata=[("extra", "x")])
fs = _qs(req)
assert fs[b"clickme"] == [b"one"]
assert fs[b"extra"] == [b"x"]
def test_from_response_dont_click(self):
response = _buildresponse(
"""<form action="get.php" method="GET">