diff --git a/CHANGES.rst b/CHANGES.rst index 5a4f49890..23d86fc43 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -26,6 +26,8 @@ Unreleased - Choices shortcut for :class:`~fields.core.SelectMultipleField`. :issue:`603` :pr:`605` - Forms can have form-level errors. :issue:`55` :pr:`595` +- Fixed a bug related to `for` and `class` html parameter rendering. + :issue:`449` :pr:`596` Version 2.3.1 diff --git a/src/wtforms/meta.py b/src/wtforms/meta.py index bea8df9a6..710ab58be 100644 --- a/src/wtforms/meta.py +++ b/src/wtforms/meta.py @@ -1,5 +1,6 @@ from wtforms import i18n from wtforms.utils import WebobInputWrapper +from wtforms.widgets.core import clean_key class DefaultMeta: @@ -53,8 +54,12 @@ def render_field(self, field, render_kw): The default implementation calls ``field.widget(field, **render_kw)`` """ + + render_kw = {clean_key(k): v for k, v in render_kw.items()} + other_kw = getattr(field, "render_kw", None) if other_kw is not None: + other_kw = {clean_key(k): v for k, v in other_kw.items()} render_kw = dict(other_kw, **render_kw) return field.widget(field, **render_kw) diff --git a/src/wtforms/widgets/core.py b/src/wtforms/widgets/core.py index 5be3f4041..3e2a2ebef 100644 --- a/src/wtforms/widgets/core.py +++ b/src/wtforms/widgets/core.py @@ -17,6 +17,13 @@ ) +def clean_key(key): + key = key.rstrip("_") + if key.startswith("data_") or key.startswith("aria_"): + key = key.replace("_", "-") + return key + + def html_params(**kwargs): """ Generate HTML attribute syntax from inputted keyword arguments. @@ -53,10 +60,7 @@ def html_params(**kwargs): """ params = [] for k, v in sorted(kwargs.items()): - if k in ("class_", "class__", "for_"): - k = k[:-1] - elif k.startswith("data_") or k.startswith("aria_"): - k = k.replace("_", "-") + k = clean_key(k) if v is True: params.append(k) elif v is False: diff --git a/tests/test_fields.py b/tests/test_fields.py index cde794501..186bcead6 100644 --- a/tests/test_fields.py +++ b/tests/test_fields.py @@ -233,6 +233,37 @@ def test_render_kw(self): 'type="text" value="hello">' ) + def test_render_special(self): + class F(Form): + s = StringField(render_kw={"class_": "foo"}) + + assert '' == F().s() + assert '' == F().s( + **{"class": "bar"} + ) + assert '' == F().s( + **{"class_": "bar"} + ) + + class G(Form): + s = StringField(render_kw={"class__": "foo"}) + + assert '' == G().s() + assert '' == G().s( + **{"class__": "bar"} + ) + + class H(Form): + s = StringField(render_kw={"for_": "foo"}) + + assert '' == H().s() + assert '' == H().s( + **{"for": "bar"} + ) + assert '' == H().s( + **{"for_": "bar"} + ) + def test_select_field_copies_choices(self): class F(Form): items = SelectField(choices=[]) diff --git a/tests/test_widgets.py b/tests/test_widgets.py index 11d724c7b..3bcb70be9 100644 --- a/tests/test_widgets.py +++ b/tests/test_widgets.py @@ -20,9 +20,6 @@ class TestHTMLParams: def test_basic(self): assert html_params(foo=9, k="wuuu") == 'foo="9" k="wuuu"' - assert html_params(class_="foo") == 'class="foo"' - assert html_params(class__="foo") == 'class_="foo"' - assert html_params(for_="foo") == 'for="foo"' assert html_params(readonly=False, foo=9) == 'foo="9"' assert ( html_params(accept="image/png, image/jpeg", required=True)