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)