Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improved form rendering #11

Open
wants to merge 9 commits into
base: master
Choose a base branch
from
87 changes: 10 additions & 77 deletions docs/macros.rst
Expand Up @@ -67,42 +67,6 @@ API
:param text: The text that will displayed on the item.
:param kwargs: Additional keyword arguments pass to ``url_for()``.

{{ render_field() }}
---------------------

Render a form field create by Flask-WTF/WTForms.

Example
~~~~~~~~
.. code-block:: jinja

{% from 'bootstrap/form.html' import render_field %}

<form method="post">
{{ form.csrf_token() }}
{{ render_field(form.username) }}
{{ render_field(form.password) }}
{{ render_field(form.submit) }}
</form>

API
~~~~

.. py:function:: render_field(field, form_type="basic", horizontal_columns=('lg', 2, 10), button_map={})

Render a single form field.

:param field: The form field (attribute) to render.
:param form_type: One of ``basic``, ``inline`` or ``horizontal``. See the
Bootstrap docs for details on different form layouts.
:param horizontal_columns: When using the horizontal layout, layout forms
like this. Must be a 3-tuple of ``(column-type,
left-column-size, right-column-size)``.
:param button_map: A dictionary, mapping button field names to Bootstrap category names such as
``primary``, ``danger`` or ``success``. Buttons not found
in the ``button_map`` will use the ``secondary`` type of
button.


{{ render_form() }}
---------------------
Expand All @@ -120,51 +84,20 @@ Example
API
~~~~

.. py:function:: quick_form(form,\
action="",\
method="post",\
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why these arguments were removed?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I made them be part of kwargs instead

extra_classes=None,\
role="form",\
form_type="basic",\
horizontal_columns=('lg', 2, 10),\
enctype=None,\
button_map={},\
id="",\
novalidate=False,\
render_kw={})
.. py:function:: render_form(form, inline=false, tooltips=false, custom=false, col_type="md", compact_tables=false, horizontal=false, horizontal_label_width=3, **kwargs)

Outputs Bootstrap-markup for a complete Flask-WTF form.

:param form: The form to output.
:param method: ``<form>`` method attribute.
:param extra_classes: The classes to add to the ``<form>``.
:param role: ``<form>`` role attribute.
:param form_type: One of ``basic``, ``inline`` or ``horizontal``. See the
Bootstrap docs for details on different form layouts.
:param horizontal_columns: When using the horizontal layout, layout forms
like this. Must be a 3-tuple of ``(column-type,
left-column-size, right-column-size)``.
:param enctype: ``<form>`` enctype attribute. If ``None``, will
automatically be set to ``multipart/form-data`` if a
:class:`~wtforms.fields.FileField` is present in the form.
:param button_map: A dictionary, mapping button field names to names such as
``primary``, ``danger`` or ``success``. Buttons not found
in the ``button_map`` will use the ``default`` type of
button.
:param id: The ``<form>`` id attribute.
:param novalidate: Flag that decide whether add ``novalidate`` class in ``<form>``.
:param render_kw: A dictionary, specifying custom attributes for the
``<form>`` tag.

.. py:function:: form_errors(form, hiddens=True)

Renders paragraphs containing form error messages. This is usually only used
to output hidden field form errors, as others are attached to the form
fields.

:param form: Form whose errors should be rendered.
:param hiddens: If ``True``, render errors of hidden fields as well. If
``'only'``, render *only* these.
:param inline: Whether the form should be rendered inline
:param tooltips: Whether the error messages should appear as tooltips
:param custom: Whether the form elements should be converted to Bootstrap custom form elements
:param col_type: Should be one of [none, "xs", "sm", "md", "lg", "xl"]. Specifies the responsive breakpoints
:param compact_tables: Whether to use the special "form-row" class instead of "row" when creating nested forms
:param horizontal: Whether the form should be rendered as a horizontal form
:param horizontal_label_width: Should be a number between 1 and 11, that specifies the label width if the form is horizontal
:param kwargs: Specify custom attributes for the ``<form>`` tag. Sensible defaults are already set,
but you might want to change the "action", "method" or "id" attribute


{{ render_pager() }}
Expand Down
65 changes: 57 additions & 8 deletions examples/app.py
Expand Up @@ -2,8 +2,9 @@
from flask import Flask, render_template, request

from flask_wtf import FlaskForm
from wtforms import StringField, SubmitField, BooleanField, PasswordField
from wtforms.validators import DataRequired, Length
from wtforms.validators import DataRequired, Length, InputRequired
from wtforms.fields import *
from wtforms.fields import html5

from flask_bootstrap import Bootstrap
from flask_sqlalchemy import SQLAlchemy
Expand All @@ -14,12 +15,60 @@
bootstrap = Bootstrap(app)
db = SQLAlchemy(app)


class HelloForm(FlaskForm):
username = StringField('Username', validators=[DataRequired(), Length(1, 20)])
password = PasswordField('Password', validators=[DataRequired(), Length(8, 150)])
remember = BooleanField('Remember me')
submit = SubmitField()
choices = list({"choice_0": "First Choice", "choice_1": "Second choice", "choice_3": "Third choice"}.items())


class CoreForm(FlaskForm):
boolean = BooleanField("Boolean", validators=[InputRequired()])
decimal = DecimalField("Decimal", description="Some Description!")
date = DateField("Date")
date_time = DateTimeField("DateTime", render_kw={"placeholder": "Test"})
fieldlist = FieldList(StringField("FieldList (String)"), label="FieldList", min_entries=3)
float = FloatField("Float")
integer = IntegerField("Integer", validators=[DataRequired()])
radio = RadioField("Radio", choices=choices)
select = SelectField("Select", choices=choices, validators=[InputRequired()])
select_multiple = SelectMultipleField("SelectMultiple", choices=choices)
string = StringField("String")
time = TimeField("Time")


class SimpleForm(FlaskForm):
text_area = TextAreaField("TextAreaField")
password = PasswordField("Password", validators=[DataRequired()])
file = FileField("File")
multiple_file = MultipleFileField("MultipleFile")
hidden = HiddenField("Hidden")
submit = SubmitField("Submit")


class HTML5Form(FlaskForm):
html5_date = html5.DateField("DateField")
html5_date_time = html5.DateTimeField("DateTimeField")
html5_date_time_local = html5.DateTimeLocalField("DateTimeLocalField")
html5_decimal = html5.DecimalField("DecimalField")
html5_decimal_range = html5.DecimalRangeField("DecimalRangeField")
html5_email = html5.EmailField("EmailField")
html5_integer = html5.IntegerField("IntegerField")
html5_integer_range = html5.IntegerRangeField("IntegerRangeField")
html5_search = html5.SearchField("SearchField")
html5_tel = html5.TelField("TelField")
html5_time = html5.TimeField("TimeField")
html5_url = html5.URLField("URLField")

class RecaptchaForm(FlaskForm):
# RECAPTCHA_PUBLIC_KEY and RECAPTCHA_PRIVATE_KEY must be set in the config
# flask_wtf_recaptcha = RecaptchaField("RecaptchaField")
pass

class InnerForm(FlaskForm):
email = html5.EmailField("Email")
password = PasswordField("Password")
boolean = BooleanField("Remember me?")

class HelloForm(CoreForm, SimpleForm, HTML5Form):
formfield = FormField(InnerForm, label="FormField")
fieldlist = FieldList(FormField(InnerForm), label="FormField inside FieldList", min_entries=2)


class Message(db.Model):
Expand Down
2 changes: 1 addition & 1 deletion examples/templates/base.html
Expand Up @@ -36,4 +36,4 @@

{{ bootstrap.load_js() }}
</body>
</html>
</html>
16 changes: 7 additions & 9 deletions examples/templates/form.html
Expand Up @@ -2,15 +2,13 @@
{% from 'bootstrap/form.html' import render_form, render_field %}

{% block content %}
<h1>render_form</h1>
<h1>Standard Form</h1>
{{ render_form(form) }}

<h1>render_field</h1>
<form method="post">
{{ form.csrf_token }}
{{ render_field(form.username) }}
{{ render_field(form.password) }}
{{ render_field(form.remember) }}
{{ render_field(form.submit) }}
</form>
madsmtm marked this conversation as resolved.
Show resolved Hide resolved
<br>
<br>
<br>

<h1>Horizontal Form, with bootstrap custom labels</h1>
{{ render_form(form, custom=true, horizontal=true) }}
{% endblock %}
9 changes: 5 additions & 4 deletions flask_bootstrap/__init__.py
Expand Up @@ -11,11 +11,11 @@
from wtforms.fields import HiddenField
except ImportError:

def is_hidden_field_filter(field):
def is_hidden_field_test(field):
raise RuntimeError('WTForms is not installed.')
else:

def is_hidden_field_filter(field):
def is_hidden_field_test(field):
return isinstance(field, HiddenField)


Expand All @@ -34,9 +34,10 @@ def init_app(self, app):
static_folder='static', static_url_path='/bootstrap' + app.static_url_path)
app.register_blueprint(blueprint)

# `bootstrap_is_hidden_field` is deprecated in favor of the `is_hidden` test
app.jinja_env.globals['bootstrap_is_hidden_field'] = is_hidden_field_test
app.jinja_env.globals['bootstrap'] = self
app.jinja_env.globals['bootstrap_is_hidden_field'] = \
is_hidden_field_filter
app.jinja_env.tests['is_hidden'] = is_hidden_field_test
app.jinja_env.add_extension('jinja2.ext.do')
# default settings
app.config.setdefault('BOOTSTRAP_SERVE_LOCAL', False)
Expand Down