Repository: wtforms/wtforms (python)

Issue:
DataRequired and InputRequired do not add required attribute to RadioField objects
  When adding a `DataRequired` or `InputRequired` validator to a `RadioField`, the `required` attribute of the `input` tag.

Here's the function I wrote that reveals the problem:

```python
    @bp.route('/<string:slug>/survey', methods=['GET', 'POST'])
    def survey(slug):
        survey = Survey.query.filter_by(slug=slug).first_or_404()
        questions = survey.questions
        for question in questions:
            if question.category == 'word':
                field = StringField(question.question, validators=[InputRequired()])
            elif question.category == 'likert':
                choices = [('1', 'Strongly Agree'),
                           ('2', 'Agree'),
                           ('3', 'Neutral'),
                           ('4', 'Disagree'),
                           ('5', 'Strongly Disagree')]
                field = RadioField(question.question, choices=choices, validators=[InputRequired()])
            setattr(FlaskForm, str(question.id), field)
        setattr(FlaskForm, 'submit', SubmitField('Submit'))
        form = FlaskForm()
        if form.validate_on_submit():
            response = Response(survey=survey)
            db.session.add(response)
            for question in questions:
                answer = Answer(question=question, response=response)
                field = getattr(form, str(question.id))
                if question.category == 'word':
                    answer.answer = field.data
                elif question.category == 'likert':
                    choices = {'1': 'Strongly Agree',
                               '2': 'Agree',
                               '3': 'Neutral',
                               '4': 'Disagree',
                               '5': 'Strongly Disagree'}
                    answer.answer = choices[field.data]
                db.session.add(answer)
            db.session.commit()
            with open('app/static/ty.txt', 'r') as f:
                ty = [x.strip() for x in f.readlines()]
            return render_template('ty.html', ty=ty)
        return render_template('survey.html', form=form, questions=questions)
```

I understand there might be other issues with the above (such as me being informed I should subclass FlaskForm).

The above function gives the following `input` tag for a `StringField`:

```html
    <input class="form-control" id="4" name="4" required="" type="text" value="">
```

This is the `input` tag for a `RadioField`:

```html
    <input id="1-0" name="1" type="radio" value="1">
```

While in the browser's debugger, if I edit the above to `<input id="1-0" name="1" type="radio" value="1" required="">`, the radio button requires a selection.

Pull request:
Fix RadioField required attribute
Fixes #477 

Add required attribute for RadioFields.

Patch:
diff --git a/src/wtforms/fields/core.py b/src/wtforms/fields/core.py
index 331e5d6..0326545 100644
--- a/src/wtforms/fields/core.py
+++ b/src/wtforms/fields/core.py
@@ -500,7 +500,11 @@ class SelectFieldBase(Field):
 
     def __iter__(self):
         opts = dict(
-            widget=self.option_widget, name=self.name, _form=None, _meta=self.meta
+            widget=self.option_widget,
+            validators=self.validators,
+            name=self.name,
+            _form=None,
+            _meta=self.meta,
         )
         for i, (value, label, checked) in enumerate(self.iter_choices()):
             opt = self._Option(label=label, id="%s-%d" % (self.id, i), **opts)


Test patch:
diff --git a/tests/fields/test_radio.py b/tests/fields/test_radio.py
index 523a71b..863b933 100644
--- a/tests/fields/test_radio.py
+++ b/tests/fields/test_radio.py
@@ -1,3 +1,6 @@
+from tests.common import DummyPostData
+
+from wtforms import validators
 from wtforms.fields import RadioField
 from wtforms.form import Form
 
@@ -9,6 +12,10 @@ def make_form(name="F", **fields):
 class F(Form):
     a = RadioField(choices=[("a", "hello"), ("b", "bye")], default="a")
     b = RadioField(choices=[(1, "Item 1"), (2, "Item 2")], coerce=int)
+    c = RadioField(
+        choices=[("a", "Item 1"), ("b", "Item 2")],
+        validators=[validators.InputRequired()],
+    )
 
 
 def test_radio_field():
@@ -74,3 +81,24 @@ def test_callable_choices():
         '<label for="a-1">bye</label></li>'
         "</ul>"
     )
+
+
+def test_required_flag():
+    form = F()
+    assert form.c() == (
+        '<ul id="c">'
+        '<li><input id="c-0" name="c" required type="radio" value="a"> '
+        '<label for="c-0">Item 1</label></li>'
+        '<li><input id="c-1" name="c" required type="radio" value="b"> '
+        '<label for="c-1">Item 2</label></li>'
+        "</ul>"
+    )
+
+
+def test_required_validator():
+    form = F(DummyPostData(b=1, c="a"))
+    assert form.validate()
+    assert form.c.errors == []
+    form = F(DummyPostData(b=1))
+    assert form.validate() is False
+    assert form.c.errors == ["This field is required."]
diff --git a/tests/fields/test_select.py b/tests/fields/test_select.py
index 2e1bc7e..bbfbef4 100644
--- a/tests/fields/test_select.py
+++ b/tests/fields/test_select.py
@@ -1,6 +1,7 @@
 import pytest
 from tests.common import DummyPostData
 
+from wtforms import validators
 from wtforms import widgets
 from wtforms.fields import SelectField
 from wtforms.form import Form
@@ -163,3 +164,34 @@ def test_callable_choices():
         '<option value="foo">foo</option>',
         '<option selected value="bar">bar</option>',
     ]
+
+
+def test_requried_flag():
+    F = make_form(
+        c=SelectField(
+            choices=[("a", "hello"), ("b", "bye")],
+            validators=[validators.InputRequired()],
+        )
+    )
+    form = F(DummyPostData(c="a"))
+    assert form.c() == (
+        '<select id="c" name="c" required>'
+        '<option selected value="a">hello</option>'
+        '<option value="b">bye</option>'
+        "</select>"
+    )
+
+
+def test_required_validator():
+    F = make_form(
+        c=SelectField(
+            choices=[("a", "hello"), ("b", "bye")],
+            validators=[validators.InputRequired()],
+        )
+    )
+    form = F(DummyPostData(c="b"))
+    assert form.validate()
+    assert form.c.errors == []
+    form = F()
+    assert form.validate() is False
+    assert form.c.errors == ["This field is required."]
diff --git a/tests/fields/test_selectmultiple.py b/tests/fields/test_selectmultiple.py
index 4fc5856..1aac0b8 100644
--- a/tests/fields/test_selectmultiple.py
+++ b/tests/fields/test_selectmultiple.py
@@ -1,6 +1,7 @@
 import pytest
 from tests.common import DummyPostData
 
+from wtforms import validators
 from wtforms.fields import SelectField
 from wtforms.fields import SelectMultipleField
 from wtforms.form import Form
@@ -103,3 +104,34 @@ def test_dont_validate_choices():
     assert form.validate()
     assert form.a.data == ["b"]
     assert len(form.a.errors) == 0
+
+
+def test_requried_flag():
+    F = make_form(
+        c=SelectMultipleField(
+            choices=[("a", "hello"), ("b", "bye")],
+            validators=[validators.InputRequired()],
+        )
+    )
+    form = F(DummyPostData(c=["a"]))
+    assert form.c() == (
+        '<select id="c" multiple name="c" required>'
+        '<option selected value="a">hello</option>'
+        '<option value="b">bye</option>'
+        "</select>"
+    )
+
+
+def test_required_validator():
+    F = make_form(
+        c=SelectMultipleField(
+            choices=[("a", "hello"), ("b", "bye")],
+            validators=[validators.InputRequired()],
+        )
+    )
+    form = F(DummyPostData(c=["a"]))
+    assert form.validate()
+    assert form.c.errors == []
+    form = F()
+    assert form.validate() is False
+    assert form.c.errors == ["This field is required."]