diff --git a/CHANGES.rst b/CHANGES.rst index edfc3faa..e00fef95 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -22,6 +22,7 @@ Release date: - parameter for ``render_form``, ``render_field``, and ``render_form_row``. - Add ``BOOTSTRAP_FORM_INLINE_CLASSES`` config for Bootstrap 5, defaults to ``row row-cols-lg-auto g-3 align-items-center``. Also add a ``form_inline_classes`` parameter for ``render_form``. +- Add support for WTForms range fields (``DecimalRangeField`` and ``IntegerRangeField``). 1.8.0 diff --git a/examples/bootstrap4/app.py b/examples/bootstrap4/app.py index 68a14021..37c46685 100644 --- a/examples/bootstrap4/app.py +++ b/examples/bootstrap4/app.py @@ -32,12 +32,12 @@ class ExampleForm(FlaskForm): """An example form that contains all the supported bootstrap style form fields.""" date = DateField(description="We'll never share your email with anyone else.") # add help text with `description` datetime = DateTimeField(render_kw={'placeholder': 'this is a placeholder'}) # add HTML attribute with `render_kw` - datetimelocal = DateTimeLocalField() + datetime_local = DateTimeLocalField() time = TimeField() floating = FloatField() integer = IntegerField() - decimalslider = DecimalRangeField() - integerslider = IntegerRangeField(render_kw={'min': '0', 'max': '4'}) + decimal_slider = DecimalRangeField() + integer_slider = IntegerRangeField(render_kw={'min': '0', 'max': '4'}) email = EmailField() url = URLField() search = SearchField() @@ -45,7 +45,7 @@ class ExampleForm(FlaskForm): image = FileField(render_kw={'class': 'my-class'}, validators=[Regexp('.+\.jpg$')]) # add your class option = RadioField(choices=[('dog', 'Dog'), ('cat', 'Cat'), ('bird', 'Bird'), ('alien', 'Alien')]) select = SelectField(choices=[('dog', 'Dog'), ('cat', 'Cat'), ('bird', 'Bird'), ('alien', 'Alien')]) - selectmulti = SelectMultipleField(choices=[('dog', 'Dog'), ('cat', 'Cat'), ('bird', 'Bird'), ('alien', 'Alien')]) + select_multiple = SelectMultipleField(choices=[('dog', 'Dog'), ('cat', 'Cat'), ('bird', 'Bird'), ('alien', 'Alien')]) bio = TextAreaField() title = StringField() secret = PasswordField() diff --git a/examples/bootstrap5/app.py b/examples/bootstrap5/app.py index 96aec6f6..b7422632 100644 --- a/examples/bootstrap5/app.py +++ b/examples/bootstrap5/app.py @@ -32,12 +32,12 @@ class ExampleForm(FlaskForm): """An example form that contains all the supported bootstrap style form fields.""" date = DateField(description="We'll never share your email with anyone else.") # add help text with `description` datetime = DateTimeField(render_kw={'placeholder': 'this is a placeholder'}) # add HTML attribute with `render_kw` - datetimelocal = DateTimeLocalField() + datetime_local = DateTimeLocalField() time = TimeField() floating = FloatField() integer = IntegerField() - decimalslider = DecimalRangeField() - integerslider = IntegerRangeField(render_kw={'min': '0', 'max': '4'}) + decimal_slider = DecimalRangeField() + integer_slider = IntegerRangeField(render_kw={'min': '0', 'max': '4'}) email = EmailField() url = URLField() search = SearchField() @@ -45,7 +45,7 @@ class ExampleForm(FlaskForm): image = FileField(render_kw={'class': 'my-class'}, validators=[Regexp('.+\.jpg$')]) # add your class option = RadioField(choices=[('dog', 'Dog'), ('cat', 'Cat'), ('bird', 'Bird'), ('alien', 'Alien')]) select = SelectField(choices=[('dog', 'Dog'), ('cat', 'Cat'), ('bird', 'Bird'), ('alien', 'Alien')]) - selectmulti = SelectMultipleField(choices=[('dog', 'Dog'), ('cat', 'Cat'), ('bird', 'Bird'), ('alien', 'Alien')]) + select_multiple = SelectMultipleField(choices=[('dog', 'Dog'), ('cat', 'Cat'), ('bird', 'Bird'), ('alien', 'Alien')]) bio = TextAreaField() title = StringField() secret = PasswordField() diff --git a/flask_bootstrap/templates/bootstrap4/form.html b/flask_bootstrap/templates/bootstrap4/form.html index 8295aedb..5e3f1e1b 100644 --- a/flask_bootstrap/templates/bootstrap4/form.html +++ b/flask_bootstrap/templates/bootstrap4/form.html @@ -156,6 +156,12 @@ {% else %} {{ field(class="form-control-file%s" % extra_classes, **kwargs)|safe }} {% endif %} + {%- elif field.type in ['DecimalRangeField', 'IntegerRangeField'] -%} + {% if field.errors %} + {{ field(class="form-control-range is-invalid%s" % extra_classes, **kwargs)|safe }} + {% else %} + {{ field(class="form-control-range%s" % extra_classes, **kwargs)|safe }} + {% endif %} {% else %} {% if field.errors %} {{ field(class="form-control mb-2 mr-sm-2 mb-sm-0 is-invalid%s" % extra_classes, **kwargs)|safe }} @@ -172,6 +178,12 @@ {% else %} {{ field(class="form-control-file%s" % extra_classes, **kwargs)|safe }} {% endif %} + {%- elif field.type in ['DecimalRangeField', 'IntegerRangeField'] -%} + {% if field.errors %} + {{ field(class="form-control-range is-invalid%s" % extra_classes, **kwargs)|safe }} + {% else %} + {{ field(class="form-control-range%s" % extra_classes, **kwargs)|safe }} + {% endif %} {% else %} {% if field.errors %} {{ field(class="form-control is-invalid%s" % extra_classes, **kwargs)|safe }} @@ -199,6 +211,12 @@ {% else %} {{ field(class="form-control-file%s" % extra_classes, **kwargs)|safe }} {% endif %} + {%- elif field.type in ['DecimalRangeField', 'IntegerRangeField'] -%} + {% if field.errors %} + {{ field(class="form-control-range is-invalid%s" % extra_classes, **kwargs)|safe }} + {% else %} + {{ field(class="form-control-range%s" % extra_classes, **kwargs)|safe }} + {% endif %} {% else %} {% if field.errors %} {{ field(class="form-control is-invalid%s" % extra_classes, **kwargs)|safe }} diff --git a/flask_bootstrap/templates/bootstrap5/form.html b/flask_bootstrap/templates/bootstrap5/form.html index feba1310..fce44d26 100644 --- a/flask_bootstrap/templates/bootstrap5/form.html +++ b/flask_bootstrap/templates/bootstrap5/form.html @@ -146,19 +146,35 @@ {%- if field.flags.required %} required{% endif -%}"> {%- if form_type == "inline" %} {{ field.label(class="visually-hidden")|safe }} - {% if field.errors %} - {{ field(class="form-control mb-2 mr-sm-2 mb-sm-0 is-invalid%s" % extra_classes, **kwargs)|safe }} - {% else %} - {{ field(class="form-control mb-2 mr-sm-2 mb-sm-0%s" % extra_classes, **kwargs)|safe }} - {% endif %} + {%- if field.type in ['DecimalRangeField', 'IntegerRangeField'] %} + {% if field.errors %} + {{ field(class="form-range is-invalid%s" % extra_classes, **kwargs)|safe }} + {% else %} + {{ field(class="form-range%s" % extra_classes, **kwargs)|safe }} + {% endif %} + {%- else -%} + {% if field.errors %} + {{ field(class="form-control mb-2 mr-sm-2 mb-sm-0 is-invalid%s" % extra_classes, **kwargs)|safe }} + {% else %} + {{ field(class="form-control mb-2 mr-sm-2 mb-sm-0%s" % extra_classes, **kwargs)|safe }} + {% endif %} + {%- endif %} {% elif form_type == "horizontal" %} {{ field.label(class="col-form-label" + (" col-%s-%s" % horizontal_columns[0:2]))|safe }}
+ {%- if field.type in ['DecimalRangeField', 'IntegerRangeField'] %} + {% if field.errors %} + {{ field(class="form-range is-invalid%s" % extra_classes, **kwargs)|safe }} + {% else %} + {{ field(class="form-range%s" % extra_classes, **kwargs)|safe }} + {% endif %} + {%- else -%} {% if field.errors %} {{ field(class="form-control is-invalid%s" % extra_classes, **kwargs)|safe }} {% else %} {{ field(class="form-control%s" % extra_classes, **kwargs)|safe }} {% endif %} + {%- endif %}
{%- if field.errors %} {%- for error in field.errors %} @@ -173,11 +189,19 @@ {%- endif %} {%- else -%} {{ field.label(class="form-label")|safe }} - {% if field.errors %} - {{ field(class="form-control is-invalid%s" % extra_classes, **kwargs)|safe }} - {% else %} - {{ field(class="form-control%s" % extra_classes, **kwargs)|safe }} - {% endif %} + {%- if field.type in ['DecimalRangeField', 'IntegerRangeField'] %} + {% if field.errors %} + {{ field(class="form-range is-invalid%s" % extra_classes, **kwargs)|safe }} + {% else %} + {{ field(class="form-range%s" % extra_classes, **kwargs)|safe }} + {% endif %} + {%- else -%} + {% if field.errors %} + {{ field(class="form-control is-invalid%s" % extra_classes, **kwargs)|safe }} + {% else %} + {{ field(class="form-control%s" % extra_classes, **kwargs)|safe }} + {% endif %} + {%- endif %} {%- if field.errors %} {%- for error in field.errors %}
{{ error }}
diff --git a/tests/test_bootstrap4/test_render_form.py b/tests/test_bootstrap4/test_render_form.py index 6d67f3db..8ea11bc9 100644 --- a/tests/test_bootstrap4/test_render_form.py +++ b/tests/test_bootstrap4/test_render_form.py @@ -1,7 +1,8 @@ from flask import current_app, render_template_string from flask_wtf import FlaskForm from wtforms import BooleanField, FileField, MultipleFileField,\ - PasswordField, RadioField, StringField, SubmitField + PasswordField, RadioField, StringField, SubmitField, IntegerRangeField,\ + DecimalRangeField from wtforms.validators import DataRequired from flask_bootstrap import SwitchField @@ -42,7 +43,7 @@ def description(): # test render SwitchField -def test_switchfield(app, client): +def test_switch_field(app, client): class TestForm(FlaskForm): remember = SwitchField('Remember me', description='Just check this') @@ -62,6 +63,28 @@ def test_switch(): assert 'Just check this' in data +# test render IntegerRangeField and DecimalRangeField +def test_range_fields(app, client): + + class TestForm(FlaskForm): + decimal_slider = DecimalRangeField() + integer_slider = IntegerRangeField(render_kw={'min': '0', 'max': '4'}) + + @app.route('/range') + def test_range(): + form = TestForm() + return render_template_string(''' + {% from 'bootstrap4/form.html' import render_form %} + {{ render_form(form) }} + ''', form=form) + + response = client.get('/range') + data = response.get_data(as_text=True) + assert 'Decimal Slider' in data + assert 'Integer Slider' in data + assert 'form-control-range' in data + + # test WTForm fields for render_form and render_field def test_render_form_enctype(app, client): class SingleUploadForm(FlaskForm): diff --git a/tests/test_bootstrap5/test_render_form.py b/tests/test_bootstrap5/test_render_form.py index 81010464..973860ba 100644 --- a/tests/test_bootstrap5/test_render_form.py +++ b/tests/test_bootstrap5/test_render_form.py @@ -1,9 +1,10 @@ from flask import render_template_string from flask_wtf import FlaskForm from flask_bootstrap import SwitchField +from wtforms import IntegerRangeField, DecimalRangeField -def test_switchfield(app, client): +def test_switch_field(app, client): class TestForm(FlaskForm): remember = SwitchField('Remember me', description='Just check this') @@ -25,6 +26,28 @@ def test_switch(): assert 'Just check this' in data +# test render IntegerRangeField and DecimalRangeField +def test_range_fields(app, client): + + class TestForm(FlaskForm): + decimal_slider = DecimalRangeField() + integer_slider = IntegerRangeField(render_kw={'min': '0', 'max': '4'}) + + @app.route('/range') + def test_range(): + form = TestForm() + return render_template_string(''' + {% from 'bootstrap5/form.html' import render_form %} + {{ render_form(form) }} + ''', form=form) + + response = client.get('/range') + data = response.get_data(as_text=True) + assert 'Decimal Slider' in data + assert 'Integer Slider' in data + assert 'form-range' in data + + def test_form_group_class(app, client, hello_form): @app.route('/default') def test_default():