From f1a3dc1e235e2df7deb89c9166f5751e08b418c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89loi=20Rivard?= Date: Fri, 29 Dec 2023 20:31:43 +0100 Subject: [PATCH] fix: :class:`~fields.SelectMultipleField` value coercion on validation. Co-authored-by: Andy Caine --- CHANGES.rst | 6 ++++++ src/wtforms/fields/choices.py | 15 +++++++++------ tests/fields/test_selectmultiple.py | 13 +++++++++++++ 3 files changed, 28 insertions(+), 6 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index 186501bcb..18129d459 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,5 +1,11 @@ .. currentmodule:: wtforms +Unreleased +---------- + +- Fix :class:`~fields.SelectMultipleField` value coercion on validation. + :issue:`822` :pr:`823` + Version 3.1.1 ------------- diff --git a/src/wtforms/fields/choices.py b/src/wtforms/fields/choices.py index 2b1fd2e08..62ed49763 100644 --- a/src/wtforms/fields/choices.py +++ b/src/wtforms/fields/choices.py @@ -121,8 +121,9 @@ def _choices_generator(self, choices): _choices = zip(choices, choices) for value, label, *other_args in _choices: + selected = self.coerce(value) == self.data render_kw = other_args[0] if len(other_args) else {} - yield (value, label, self.coerce(value) == self.data, render_kw) + yield (value, label, selected, render_kw) def process_data(self, value): try: @@ -173,9 +174,9 @@ def _choices_generator(self, choices): else: _choices = zip(choices, choices) - for value, label, *args in _choices: + for value, label, *other_args in _choices: selected = self.data is not None and self.coerce(value) in self.data - render_kw = args[0] if len(args) else {} + render_kw = other_args[0] if len(other_args) else {} yield (value, label, selected, render_kw) def process_data(self, value): @@ -201,9 +202,11 @@ def pre_validate(self, form): if self.choices is None: raise TypeError(self.gettext("Choices cannot be None.")) - acceptable = {c[0] for c in self.iter_choices()} - if any(d not in acceptable for d in self.data): - unacceptable = [str(d) for d in set(self.data) - acceptable] + acceptable = [self.coerce(choice[0]) for choice in self.iter_choices()] + if any(data not in acceptable for data in self.data): + unacceptable = [ + str(data) for data in set(self.data) if data not in acceptable + ] raise ValidationError( self.ngettext( "'%(value)s' is not a valid choice for this field.", diff --git a/tests/fields/test_selectmultiple.py b/tests/fields/test_selectmultiple.py index 5fd2a99b7..127fe45ab 100644 --- a/tests/fields/test_selectmultiple.py +++ b/tests/fields/test_selectmultiple.py @@ -190,3 +190,16 @@ def test_optgroup_option_render_kw(): assert list(form.a.iter_choices()) == [ ("a", "Foo", True, {"title": "foobar", "data-foo": "bar"}) ] + + +def test_can_supply_coercable_values_as_options(): + F = make_form( + a=SelectMultipleField( + choices=[("1", "One"), ("2", "Two")], + coerce=int, + ) + ) + post_data = DummyPostData(a=["1", "2"]) + form = F(post_data) + assert form.validate() + assert form.a.data == [1, 2]