From 8b07a40ca5b602e4c0d5fcce3dc92476d369f7b7 Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Fri, 7 Sep 2018 12:02:42 +0200 Subject: [PATCH 1/9] Improved form rendering --- flask_bootstrap/templates/bootstrap/form.html | 206 ++++++++++++++++-- .../templates/bootstrap/utils.html | 8 +- setup.py | 5 +- 3 files changed, 201 insertions(+), 18 deletions(-) diff --git a/flask_bootstrap/templates/bootstrap/form.html b/flask_bootstrap/templates/bootstrap/form.html index 0a404946..443dc348 100644 --- a/flask_bootstrap/templates/bootstrap/form.html +++ b/flask_bootstrap/templates/bootstrap/form.html @@ -1,5 +1,178 @@ {# This file was part of Flask-Bootstrap and was modified under the terms of - its BSD License. Copyright (c) 2013, Marc Brinkmann. All rights reserved. #} + its BSD License. Copyright (c) 2013, Marc Brinkmann. All rights reserved. #} +{% from 'bootstrap/utils.html' import typename, modulename, class %} + + +{% macro help(field, opts={}) %} + {%- if field.description %} + {{ field.description }} + {%- endif -%} +{% endmacro %} + +{% macro error(field, opts={}) %} + {%- for error in field.errors %} +
{{ error }}
+ {%- endfor -%} +{% endmacro %} + + +{% macro render(field, include_label=true, prefix_label=true, label_class=none, field_class=none, opts={}) -%} + {%- set render_kw = dict() -%} + {%- set _ = render_kw.setdefault("placeholder", field.label.text) if label_class == "sr-only" -%} + {%- set _ = render_kw.setdefault("aria-describedby",'%s-desc' % field.id) if field.description -%} + + {{ field.label(class=class(label_class)) if prefix_label and include_label }} + {{ field(class=class(field_class|default("form-control", true), "is-invalid" if field.errors), **render_kw) }} + {{ field.label(class=class(label_class)) if not prefix_label and include_label }} + {{ help(field, opts=opts) }} + {{ error(field, opts=opts) if not opts.inline }} +{%- endmacro %} + + +{% macro form_group(type=none, col=none) %} +
+ {{ caller() }} +
+{%- endmacro %} + + +{% macro form_fieldset(field, type=none, col=none) %} +
+ {{ field.label.text }} + {{ caller() }} +
+{%- endmacro %} + + +{% macro render_in_group(field, group_type=none, col=none, opts={}) -%} + {% set outer_kwargs = kwargs %} {# kwargs is overwritten by the `call` block #} + {% call form_group(group_type, col=col) %} + {{ render(field, opts=opts, **outer_kwargs) }} + {% endcall %} +{%- endmacro %} + + +{% macro form_row() -%} +
+ {{- caller() -}} +
+{%- endmacro %} + + +{%- macro render_field(field, hide_label=none, opts={}) -%} +{% set outer_kwargs = kwargs %} +{% set hide_label = hide_label|default(opts.inline) %} +{%- block render_field scoped -%} + {%- set widget_type = typename(field.widget) -%} + {%- set widget_module = modulename(field.widget) -%} + + {%- set wtforms_widgets_hmtl5_mapping = { + "RangeInput": { + "field_class": "custom-range" if opts.custom, + "label_class": "sr-only" if hide_label, + }, + } + -%} + + {%- set wtforms_widgets_core_mapping = { + "TextInput": { + "label_class": "sr-only" if hide_label, + }, + "PasswordInput": { + "label_class": "sr-only" if hide_label, + }, + "Input": { + "label_class": "sr-only" if hide_label, + }, + "TextArea": { + "label_class": "sr-only" if hide_label, + }, + "Select": { + "field_class": "custom-select" if opts.custom, + }, + "FileInput": { + "group_type": "custom-file" if opts.custom, + "field_class": "custom-file-input" if opts.custom else "form-control-file", + "label_class": "custom-file-label" if opts.custom, + }, + "CheckboxInput": { + "group_type": class("custom-control custom-checkbox", "custom-control-inline" if opts.inline) + if opts.custom else class("form-check", "form-check-inline" if opts.inline), + "field_class": "custom-control-input" if opts.custom else "form-check-input", + "label_class": "custom-control-label" if opts.custom else "form-check-label", + "prefix_label": false, + }, + "RadioInput": { + "group_type": class("custom-control custom-radio", "custom-control-inline" if opts.inline) + if opts.custom else class("form-check", "form-check-inline" if opts.inline), + "field_class": "custom-control-input" if opts.custom else "form-check-input", + "label_class": "custom-control-label" if opts.custom else "form-check-label", + "prefix_label": false, + }, + "SubmitInput": { + "field_class": "btn btn-primary", + "include_label": false, + }, + } + -%} + + {%- if widget_module == 'wtforms.widgets.html5' -%} + {{ render_in_group(field, label_class=("sr-only" if hide_label), opts=opts, **dict(outer_kwargs, **wtforms_widgets_hmtl5_mapping.get(widget_type, {}))) }} + + {%- elif widget_module == 'wtforms.widgets.core' -%} + {%- if widget_type in wtforms_widgets_core_mapping -%} + {{ render_in_group(field, opts=opts, **dict(outer_kwargs, **wtforms_widgets_core_mapping.get(widget_type, {}))) }} + {%- elif widget_type in ['Option', 'HiddenInput'] -%} + {{ field() }} {# `Option` is a pseudowidget, we don't wanna to do anything fancy here #} + + {%- elif widget_type == 'ListWidget' -%} + {# Removes support for actual list elements like `ul` & `li` #} + {% call form_fieldset(field, col=outer_kwargs.get('col')) %} + {%- for subfield in field %} + {{ render_field(subfield, prefix_label=field.widget.prefix_label, opts=opts) }} + {% endfor -%} + {% endcall %} + {%- elif widget_type == 'TableWidget' -%} +
+ {%- for subfield in field %} + {{ render_field(subfield, col="col-md-6", hide_label=true, opts=opts) }} + {% endfor -%} +
+ {%- else -%} + {{ field }} + {{ field|pprint }} + {{ error(field, opts=opts) }} + {%- endif -%} + + {%- else -%} + {{ field }} + {{ field|pprint }} + {%- block render_field_unknown scoped %} + {# We can try to render the unknown element, but this block is intended to be subclassed #} + {{ render_in_group(field, label_class=("sr-only" if hide_label), opts=opts, **outer_kwargs) }} + {% endblock -%} + {%- endif -%} +{%- endblock -%} +{%- endmacro %} + + +{% macro render_form(form) -%} + {%- set _ = kwargs.setdefault("inline", false) -%} + {%- set _ = kwargs.setdefault("tooltips", false) -%} + {%- set _ = kwargs.setdefault("custom", false) -%} + {%- set _ = kwargs.setdefault("button_map", {}) -%} +
+ {%- for field in form -%} + {{ render_field(field, opts=kwargs) }} + {%- endfor %} +
+{%- endmacro %} + + + + + + {% macro form_errors(form, hiddens=True) %} {%- if form.errors %} @@ -17,20 +190,20 @@ {% macro _hz_form_wrap(horizontal_columns, form_type, add_group=False, required=False) %} {% if form_type == "horizontal" %} {% if add_group %} -
{% endif %} +
+ {% endif %}
{% endif %} -{{ caller() }} - -{% if form_type == "horizontal" %} - {% if add_group %}
{% endif %} -
-{% endif %} + {{ caller() }} + {% if form_type == "horizontal" %} + {% if add_group %}
{% endif %} + + {% endif %} {% endmacro %} -{% macro render_field(field, +{% macro render_field2(field, form_type="basic", horizontal_columns=('lg', 2, 10), button_map={}) %} @@ -79,7 +252,7 @@ {{ field.label }} {%- for subfield in field %} {% if not bootstrap_is_hidden_field(subfield) -%} - {{ render_field(subfield, + {{ render_field2(subfield, form_type=form_type, horizontal_columns=horizontal_columns, button_map=button_map) }} @@ -160,7 +333,7 @@ {% endmacro %} {# valid form types are "basic", "inline" and "horizontal" #} -{% macro render_form(form, +{% macro render_form2(form, action="", method="post", extra_classes=None, @@ -172,7 +345,8 @@ id="", novalidate=False, render_kw={}) %} - {#- + +{#- action="" is what we want, from http://www.ietf.org/rfc/rfc2396.txt: 4.2. Same-document References @@ -189,8 +363,8 @@ current document and should be replaced by that URI when transformed into a request. - -#} - {#- if any file fields are inside the form and enctype is automatic, adjust +-#} +{#- if any file fields are inside the form and enctype is automatic, adjust if file fields are found. could really use the equalto test of jinja2 here, but latter is not available until 2.8 @@ -221,11 +395,11 @@ {{ form_errors(form, hiddens='only') }} {%- for field in form %} {% if not bootstrap_is_hidden_field(field) -%} - {{ render_field(field, + {{ render_field2(field, form_type=form_type, horizontal_columns=horizontal_columns, button_map=button_map) }} {%- endif %} {%- endfor %} -{%- endmacro %} \ No newline at end of file +{%- endmacro %} diff --git a/flask_bootstrap/templates/bootstrap/utils.html b/flask_bootstrap/templates/bootstrap/utils.html index c454694f..9aeaef2a 100644 --- a/flask_bootstrap/templates/bootstrap/utils.html +++ b/flask_bootstrap/templates/bootstrap/utils.html @@ -1,5 +1,5 @@ {% macro render_static(type, filename_or_url, local=True) %} - {% if local -%}{% set filename_or_url = url_for('static', filename=filename_or_url) %}{%- endif %} + {% set filename_or_url = url_for('static', filename=filename_or_url) if local %} {% if type == 'css' -%} {%- elif type == 'js' -%} @@ -8,3 +8,9 @@ {%- endif %} {% endmacro %} + +{% macro typename(obj) %}{{ obj.__class__.__name__ }}{% endmacro %} + +{% macro modulename(obj) %}{{ obj.__module__ }}{% endmacro %} + +{% macro class() -%}{{- varargs|select|unique|join(' ') -}}{%- endmacro %} diff --git a/setup.py b/setup.py index c6f18448..147fdf49 100644 --- a/setup.py +++ b/setup.py @@ -32,8 +32,11 @@ include_package_data=True, test_suite='test_bootstrap_flask', install_requires=[ - 'Flask' + 'Flask>=1' ], + extras_require={ + 'forms': ['WTForms>=2.2.0', 'Flask-WTF'] + }, keywords='flask extension development', classifiers=[ 'Development Status :: 3 - Alpha', From 4a2d4d881297a6f105dd55be5197a3afb7ad41cd Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Mon, 10 Sep 2018 10:35:02 +0200 Subject: [PATCH 2/9] Added support for horizontal forms + more --- flask_bootstrap/__init__.py | 7 +- flask_bootstrap/templates/bootstrap/form.html | 554 ++++++------------ .../templates/bootstrap/utils.html | 20 +- 3 files changed, 207 insertions(+), 374 deletions(-) diff --git a/flask_bootstrap/__init__.py b/flask_bootstrap/__init__.py index 27fb75c8..78c502bd 100644 --- a/flask_bootstrap/__init__.py +++ b/flask_bootstrap/__init__.py @@ -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) @@ -35,8 +35,7 @@ def init_app(self, app): app.register_blueprint(blueprint) 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) diff --git a/flask_bootstrap/templates/bootstrap/form.html b/flask_bootstrap/templates/bootstrap/form.html index 443dc348..622444b2 100644 --- a/flask_bootstrap/templates/bootstrap/form.html +++ b/flask_bootstrap/templates/bootstrap/form.html @@ -1,405 +1,223 @@ {# This file was part of Flask-Bootstrap and was modified under the terms of its BSD License. Copyright (c) 2013, Marc Brinkmann. All rights reserved. #} -{% from 'bootstrap/utils.html' import typename, modulename, class %} +{%- from 'bootstrap/utils.html' import typename, modulename, class, get_col_from_count, get_col, tag -%} + + +{%- macro css_horizontal_fix() -%} +{%- block css_horizontal_fix scoped -%} + +{%- endblock -%} +{%- endmacro -%} -{% macro help(field, opts={}) %} - {%- if field.description %} - {{ field.description }} - {%- endif -%} -{% endmacro %} - -{% macro error(field, opts={}) %} - {%- for error in field.errors %} -
{{ error }}
+{%- macro hidden_errors(field) -%} +{%- block hidden_errors scoped -%} + {# Doesn't actually work, has no element which makes it "display: block" #} + {%- for subfield in field|select('is_hidden') -%} + {{ error(subfield) }} {%- endfor -%} -{% endmacro %} +{%- endblock -%} +{%- endmacro -%} -{% macro render(field, include_label=true, prefix_label=true, label_class=none, field_class=none, opts={}) -%} - {%- set render_kw = dict() -%} - {%- set _ = render_kw.setdefault("placeholder", field.label.text) if label_class == "sr-only" -%} - {%- set _ = render_kw.setdefault("aria-describedby",'%s-desc' % field.id) if field.description -%} +{%- macro input_container(container_type=none, opts={}) -%}{%- set outer_caller = caller -%} +{%- block input_container scoped -%} + {%- call tag("div", container_type, get_col(12 - opts.horizontal_label_width, opts.col_type) if opts.horizontal) -%} + {{ outer_caller() }} + {%- endcall -%} +{%- endblock -%} +{%- endmacro -%} - {{ field.label(class=class(label_class)) if prefix_label and include_label }} - {{ field(class=class(field_class|default("form-control", true), "is-invalid" if field.errors), **render_kw) }} - {{ field.label(class=class(label_class)) if not prefix_label and include_label }} - {{ help(field, opts=opts) }} - {{ error(field, opts=opts) if not opts.inline }} -{%- endmacro %} +{%- macro group(type, col=none, extra=none, opts={}) -%}{%- set outer_caller = caller -%}{%- set outer_kwargs = kwargs -%} +{%- block group scoped -%} + {%- call tag(type, col, 'mb-2 mr-sm-2' if opts.inline and not col, ('form-row' if opts.compact_tables else 'row') if opts.horizontal, extra, **outer_kwargs) -%} + {{ outer_caller() }} + {%- endcall -%} +{%- endblock -%} +{%- endmacro -%} -{% macro form_group(type=none, col=none) %} -
- {{ caller() }} -
-{%- endmacro %} +{%- macro help(field, opts={}) -%} +{%- block help scoped -%} + {%- if field.description -%} + {{ field.description }} + {%- endif -%} +{%- endblock -%} +{%- endmacro -%} -{% macro form_fieldset(field, type=none, col=none) %} -
- {{ field.label.text }} - {{ caller() }} -
-{%- endmacro %} +{%- macro error(field, opts={}) -%} +{%- block error scoped -%} + {%- for error in field.errors -%} + {{ error }} + {%- endfor -%} +{%- endblock -%} +{%- endmacro -%} -{% macro render_in_group(field, group_type=none, col=none, opts={}) -%} - {% set outer_kwargs = kwargs %} {# kwargs is overwritten by the `call` block #} - {% call form_group(group_type, col=col) %} - {{ render(field, opts=opts, **outer_kwargs) }} - {% endcall %} -{%- endmacro %} + +{%- macro label(field, is_before=true, legend=false, label_class=none, hide_label=false, opts={}) -%} +{%- block label scoped -%} + {%- set label_class = class(label_class, 'sr-only' if hide_label) -%} + {%- set outer_class = class('col-form-label', get_col(opts.horizontal_label_width, opts.col_type)) if opts.horizontal -%} + {%- if legend -%} + {%- call tag("div", outer_class) -%} + {{ field.label.text }} + {%- endcall -%} + {%- else -%} + {{ field.label(class=class(label_class, outer_class if is_before)) }} + {%- endif -%} +{%- endblock -%} +{%- endmacro -%} -{% macro form_row() -%} -
- {{- caller() -}} -
-{%- endmacro %} +{%- macro render(field, field_class=none, hide_label=false, opts={}) -%} +{%- block render scoped -%} + {%- set render_kw = dict() -%} + {%- set _ = render_kw.setdefault("placeholder", field.label.text) if hide_label -%} + {%- set _ = render_kw.setdefault("aria-describedby",'%s-desc' % field.id) if field.description -%} + {{ field(class=class(field_class|default("form-control", true), "is-invalid" if field.errors), **render_kw) }} +{%- endblock -%} +{%- endmacro -%} + + +{%- macro render_single(field, hide_label=false, col=none, prefix_label=true, container_type=none, field_class=none, label_class=none, double_label=false, opts={}) -%} +{%- block render_single scoped -%} + {%- call group("div", col, "form-group", opts=opts) -%} + {{ label(field, is_before=true, label_class=(("d-%s-inline-block d-none" % opts.col_type if opts.col_type else "d-inline-block") if double_label else label_class), hide_label=hide_label, opts=opts) if prefix_label or double_label }} + {%- call input_container(container_type=container_type, opts=opts) -%} + {{ render(field, field_class=field_class, hide_label=hide_label, opts=opts) }} + {{ label(field, is_before=false, label_class=label_class, hide_label=hide_label, opts=opts) if not prefix_label or double_label }} + {{ help(field, opts=opts) }} + {{ error(field, opts=opts) if not opts.inline }} + {%- endcall -%} + {%- endcall -%} +{%- endblock -%} +{%- endmacro -%} + + +{%- macro render_nested(field, hide_label=false, container_type=none, opts={}) -%}{%- set outer_kwargs = kwargs -%} +{%- block render_nested scoped -%} + {%- call group("fieldset", opts=opts, id=field.id) -%} + {{ label(field, legend=true, hide_label=hide_label, opts=opts) }} + {{ hidden_errors(field) }} + {%- call input_container(container_type=container_type, opts=opts) -%} + {%- for subfield in field -%} + {{ render_field(subfield, in_block=true, opts=opts, **outer_kwargs) }} + {%- endfor -%} + {%- endcall -%} + {%- endcall -%} +{%- endblock -%} +{%- endmacro -%} -{%- macro render_field(field, hide_label=none, opts={}) -%} -{% set outer_kwargs = kwargs %} -{% set hide_label = hide_label|default(opts.inline) %} +{%- macro render_field(field, in_block=false, col=none, prefix_label=true, opts=opts) -%} {%- block render_field scoped -%} {%- set widget_type = typename(field.widget) -%} {%- set widget_module = modulename(field.widget) -%} - - {%- set wtforms_widgets_hmtl5_mapping = { - "RangeInput": { - "field_class": "custom-range" if opts.custom, - "label_class": "sr-only" if hide_label, - }, - } - -%} - - {%- set wtforms_widgets_core_mapping = { - "TextInput": { - "label_class": "sr-only" if hide_label, - }, - "PasswordInput": { - "label_class": "sr-only" if hide_label, - }, - "Input": { - "label_class": "sr-only" if hide_label, - }, - "TextArea": { - "label_class": "sr-only" if hide_label, - }, - "Select": { - "field_class": "custom-select" if opts.custom, - }, - "FileInput": { - "group_type": "custom-file" if opts.custom, - "field_class": "custom-file-input" if opts.custom else "form-control-file", - "label_class": "custom-file-label" if opts.custom, - }, - "CheckboxInput": { - "group_type": class("custom-control custom-checkbox", "custom-control-inline" if opts.inline) - if opts.custom else class("form-check", "form-check-inline" if opts.inline), - "field_class": "custom-control-input" if opts.custom else "form-check-input", - "label_class": "custom-control-label" if opts.custom else "form-check-label", - "prefix_label": false, - }, - "RadioInput": { - "group_type": class("custom-control custom-radio", "custom-control-inline" if opts.inline) - if opts.custom else class("form-check", "form-check-inline" if opts.inline), - "field_class": "custom-control-input" if opts.custom else "form-check-input", - "label_class": "custom-control-label" if opts.custom else "form-check-label", - "prefix_label": false, - }, - "SubmitInput": { - "field_class": "btn btn-primary", - "include_label": false, - }, - } - -%} + {%- set opts.horizontal = opts.horizontal and not in_block -%} + {%- set prefix_label = prefix_label or opts.horizontal -%} + {%- set hide_label = in_block or opts.inline -%} {%- if widget_module == 'wtforms.widgets.html5' -%} - {{ render_in_group(field, label_class=("sr-only" if hide_label), opts=opts, **dict(outer_kwargs, **wtforms_widgets_hmtl5_mapping.get(widget_type, {}))) }} + {%- if widget_type == "RangeInput" -%} + {%- set field_class = "custom-range" if opts.custom else "form-control-range" -%} + {%- endif -%} + {{ render_single(field, hide_label=hide_label, col=col, prefix_label=prefix_label, field_class=field_class, opts=opts) }} {%- elif widget_module == 'wtforms.widgets.core' -%} - {%- if widget_type in wtforms_widgets_core_mapping -%} - {{ render_in_group(field, opts=opts, **dict(outer_kwargs, **wtforms_widgets_core_mapping.get(widget_type, {}))) }} - {%- elif widget_type in ['Option', 'HiddenInput'] -%} + {%- if widget_type in ('Option', 'HiddenInput') -%} {{ field() }} {# `Option` is a pseudowidget, we don't wanna to do anything fancy here #} + {%- elif widget_type == 'FileInput' -%} + {{ render_single( + field, + hide_label=hide_label and not opts.custom, + col=col, + prefix_label=prefix_label and not opts.custom, + container_type="custom-file" if opts.custom, + field_class="custom-file-input" if opts.custom else "form-control-file", + label_class="custom-file-label" if opts.custom, + double_label=opts.horizontal and opts.custom, + opts=opts, + ) }} + + {%- elif widget_type in ("CheckboxInput", "RadioInput") -%} + {{ render_single( + field, + hide_label=false, + col=col, + prefix_label=prefix_label and opts.horizontal, + container_type=class("custom-control", "custom-checkbox" if widget_type == "CheckboxInput" else "custom-radio", "custom-control-inline" if opts.inline) if opts.custom else class("form-check", "form-check-inline" if opts.inline), + field_class="custom-control-input" if opts.custom else "form-check-input", + label_class="custom-control-label" if opts.custom else "form-check-label", + double_label=opts.horizontal and opts.custom, + opts=opts, + ) }} + + {%- elif widget_type == 'Select' -%} + {{ render_single(field, hide_label=hide_label, col=col, prefix_label=prefix_label, field_class="custom-select" if opts.custom, opts=opts) }} + + {%- elif widget_type == 'SubmitInput' -%} + {{ render_single(field, hide_label=true, col=col, prefix_label=prefix_label, field_class="btn btn-primary", opts=opts) }} + {%- elif widget_type == 'ListWidget' -%} {# Removes support for actual list elements like `ul` & `li` #} - {% call form_fieldset(field, col=outer_kwargs.get('col')) %} - {%- for subfield in field %} - {{ render_field(subfield, prefix_label=field.widget.prefix_label, opts=opts) }} - {% endfor -%} - {% endcall %} + {{ render_nested(field, hide_label=hide_label, opts=opts, col=col, prefix_label=field.widget.prefix_label) }} + {%- elif widget_type == 'TableWidget' -%} -
- {%- for subfield in field %} - {{ render_field(subfield, col="col-md-6", hide_label=true, opts=opts) }} - {% endfor -%} -
+ {{ render_nested(field, hide_label=hide_label, container_type=('form-row' if opts.compact_tables else 'row'), opts=opts, col=get_col_from_count(field|reject('is_hidden')|list|length, opts.col_type)) }} + {%- else -%} - {{ field }} - {{ field|pprint }} - {{ error(field, opts=opts) }} + {{ render_single(field, hide_label=hide_label, col=col, prefix_label=prefix_label, opts=opts) }} {%- endif -%} {%- else -%} - {{ field }} - {{ field|pprint }} - {%- block render_field_unknown scoped %} - {# We can try to render the unknown element, but this block is intended to be subclassed #} - {{ render_in_group(field, label_class=("sr-only" if hide_label), opts=opts, **outer_kwargs) }} - {% endblock -%} + {%- block render_field_unknown scoped -%} + {# We can try to render the unknown element, but this block is intended to be extended #} + {{ render_single(field, hide_label=hide_label, col=col, prefix_label=prefix_label, opts=opts) }} + {%- endblock -%} {%- endif -%} {%- endblock -%} -{%- endmacro %} - - -{% macro render_form(form) -%} - {%- set _ = kwargs.setdefault("inline", false) -%} - {%- set _ = kwargs.setdefault("tooltips", false) -%} - {%- set _ = kwargs.setdefault("custom", false) -%} - {%- set _ = kwargs.setdefault("button_map", {}) -%} -
+{%- endmacro -%} + + +{%- macro render_form(form, inline=false, tooltips=false, custom=false, col_type="md", compact_tables=false, horizontal=false, horizontal_label_width=3) -%}{% set outer_kwargs = kwargs %} +{%- block render_form scoped -%} + {# `col_type` should be one of [none, "sm", "md", "lg", "xl"] #} + {# `horizontal_label_width` should be a number between 1 and 11, that specifies the label width if the form is horizontal #} + {%- set _ = outer_kwargs.setdefault("method", "post") -%} + {%- set _ = outer_kwargs.setdefault("action", "") -%} + {%- set _ = outer_kwargs.setdefault("enctype", "multipart/form-data") if form|selectattr("type", "equalto", "FileField") -%} + {%- call tag("form", 'form-inline' if inline, **outer_kwargs) -%} + {{ css_horizontal_fix() }} + {{ hidden_errors(field) }} {%- for field in form -%} - {{ render_field(field, opts=kwargs) }} - {%- endfor %} -
-{%- endmacro %} - - - - - - - -{% macro form_errors(form, hiddens=True) %} - {%- if form.errors %} - {%- for fieldname, errors in form.errors.items() %} - {%- if bootstrap_is_hidden_field(form[fieldname]) and hiddens or - not bootstrap_is_hidden_field(form[fieldname]) and hiddens != 'only' %} - {%- for error in errors %} -
{{ error }}
- {%- endfor %} - {%- endif %} - {%- endfor %} - {%- endif %} -{%- endmacro %} - -{% macro _hz_form_wrap(horizontal_columns, form_type, add_group=False, required=False) %} - {% if form_type == "horizontal" %} - {% if add_group %} -
- {% endif %} -
- {% endif %} - {{ caller() }} - {% if form_type == "horizontal" %} - {% if add_group %}
{% endif %} -
- {% endif %} -{% endmacro %} - -{% macro render_field2(field, - form_type="basic", - horizontal_columns=('lg', 2, 10), - button_map={}) %} - - {# this is a workaround hack for the more straightforward-code of just passing required=required parameter. older versions of wtforms do not have -the necessary fix for required=False attributes, but will also not set the required flag in the first place. we skirt the issue using the code below #} - {% if field.flags.required and not required in kwargs %} - {% set kwargs = dict(required=True, **kwargs) %} - {% endif %} - - {% if field.widget.input_type == 'checkbox' %} - {% call _hz_form_wrap(horizontal_columns, form_type, True, required=required) %} -
- -
- {% endcall %} - {%- elif field.type == 'RadioField' -%} - {# note: A cleaner solution would be rendering depending on the widget, - this is just a hack for now, until I can think of something better #} - {% call _hz_form_wrap(horizontal_columns, form_type, True, required=required) %} - {% for item in field -%} -
- -
- {% endfor %} - {% endcall %} - {%- elif field.type == 'SubmitField' -%} - {# deal with jinja scoping issues? #} - {% set field_kwargs = kwargs %} - - {# note: same issue as above - should check widget, not field type #} - {% call _hz_form_wrap(horizontal_columns, form_type, True, required=required) %} - {{ field(class='btn btn-%s' % button_map.get(field.name, 'secondary'), - **field_kwargs) }} - {% endcall %} - {%- elif field.type == 'FormField' -%} - {# note: FormFields are tricky to get right and complex setups requiring - these are probably beyond the scope of what this macro tries to do. - the code below ensures that things don't break horribly if we run into - one, but does not try too hard to get things pretty. #} -
- {{ field.label }} - {%- for subfield in field %} - {% if not bootstrap_is_hidden_field(subfield) -%} - {{ render_field2(subfield, - form_type=form_type, - horizontal_columns=horizontal_columns, - button_map=button_map) }} - {%- endif %} - {%- endfor %} -
- {% else -%} -
- {%- if form_type == "inline" %} - {{ field.label(class="sr-only")|safe }} - {% if field.type == 'FileField' %} - {% if field.errors %} - {{ field(class="form-control-file is-invalid", **kwargs)|safe }} - {% else %} - {{ field(class="form-control-file", **kwargs)|safe }} - {% endif %} - {% else %} - {% if field.errors %} - {{ field(class="form-control mb-2 mr-sm-2 mb-sm-0 is-invalid", **kwargs)|safe }} - {% else %} - {{ field(class="form-control mb-2 mr-sm-2 mb-sm-0", **kwargs)|safe }} - {% endif %} - {% endif %} - {% elif form_type == "horizontal" %} - {{ field.label(class="form-control-label " + (" col-%s-%s" % horizontal_columns[0:2]))|safe }} -
- {% if field.type == 'FileField' %} - {% if field.errors %} - {{ field(class="form-control-file is-invalid", **kwargs)|safe }} - {% else %} - {{ field(class="form-control-file", **kwargs)|safe }} - {% endif %} - {% else %} - {% if field.errors %} - {{ field(class="form-control is-invalid", **kwargs)|safe }} - {% else %} - {{ field(class="form-control", **kwargs)|safe }} - {% endif %} - {% endif %} -
- {%- if field.errors %} - {%- for error in field.errors %} - {% call _hz_form_wrap(horizontal_columns, form_type, required=required) %} -
{{ error }}
- {% endcall %} - {%- endfor %} - {%- elif field.description -%} - {% call _hz_form_wrap(horizontal_columns, form_type, required=required) %} - {{ field.description|safe }} - {% endcall %} - {%- endif %} - {%- else -%} - {{ field.label(class="form-control-label")|safe }} - {% if field.type == 'FileField' %} - {% if field.errors %} - {{ field(class="form-control-file is-invalid", **kwargs)|safe }} - {% else %} - {{ field(class="form-control-file", **kwargs)|safe }} - {% endif %} - {% else %} - {% if field.errors %} - {{ field(class="form-control is-invalid", **kwargs)|safe }} - {% else %} - {{ field(class="form-control", **kwargs)|safe }} - {% endif %} - {% endif %} - {%- if field.errors %} - {%- for error in field.errors %} -
{{ error }}
- {%- endfor %} - {%- elif field.description -%} - {{ field.description|safe }} - {%- endif %} - {%- endif %} -
- {% endif %} -{% endmacro %} - -{# valid form types are "basic", "inline" and "horizontal" #} -{% macro render_form2(form, - action="", - method="post", - extra_classes=None, - role="form", - form_type="basic", - horizontal_columns=('lg', 2, 10), - enctype=None, - button_map={}, - id="", - novalidate=False, - render_kw={}) %} - -{#- -action="" is what we want, from http://www.ietf.org/rfc/rfc2396.txt: - -4.2. Same-document References - - A URI reference that does not contain a URI is a reference to the - current document. In other words, an empty URI reference within a - document is interpreted as a reference to the start of that document, - and a reference containing only a fragment identifier is a reference - to the identified fragment of that document. Traversal of such a - reference should not result in an additional retrieval action. - However, if the URI reference occurs in a context that is always - intended to result in a new request, as in the case of HTML's FORM - element, then an empty URI reference represents the base URI of the - current document and should be replaced by that URI when transformed - into a request. - --#} -{#- if any file fields are inside the form and enctype is automatic, adjust - if file fields are found. could really use the equalto test of jinja2 - here, but latter is not available until 2.8 - - warning: the code below is guaranteed to make you cry =( -#} - {%- set _enctype = [] %} - {%- if enctype is none -%} - {%- for field in form %} - {%- if field.type == 'FileField' %} - {#- for loops come with a fairly watertight scope, so this list-hack is - used to be able to set values outside of it #} - {%- set _ = _enctype.append('multipart/form-data') -%} - {%- endif %} - {%- endfor %} - {%- else %} - {% set _ = _enctype.append(enctype) %} - {%- endif %} - - {{ form.hidden_tag() }} - {{ form_errors(form, hiddens='only') }} - {%- for field in form %} - {% if not bootstrap_is_hidden_field(field) -%} - {{ render_field2(field, - form_type=form_type, - horizontal_columns=horizontal_columns, - button_map=button_map) }} - {%- endif %} - {%- endfor %} - -{%- endmacro %} + {{ render_field(field, opts=namespace(inline=inline, tooltips=tooltips, custom=custom, col_type=col_type, compact_tables=compact_tables, horizontal=horizontal, horizontal_label_width=horizontal_label_width)) }} + {%- endfor -%} + {%- endcall -%} +{%- endblock -%} +{%- endmacro -%} diff --git a/flask_bootstrap/templates/bootstrap/utils.html b/flask_bootstrap/templates/bootstrap/utils.html index 9aeaef2a..d0952366 100644 --- a/flask_bootstrap/templates/bootstrap/utils.html +++ b/flask_bootstrap/templates/bootstrap/utils.html @@ -9,8 +9,24 @@ {%- endif %} {% endmacro %} -{% macro typename(obj) %}{{ obj.__class__.__name__ }}{% endmacro %} +{% macro typename(obj) -%}{{- obj.__class__.__name__ -}}{%- endmacro %} -{% macro modulename(obj) %}{{ obj.__module__ }}{% endmacro %} +{% macro modulename(obj) -%}{{- obj.__module__ -}}{%- endmacro %} {% macro class() -%}{{- varargs|select|unique|join(' ') -}}{%- endmacro %} + +{%- macro get_col(size=none, type=none) -%} + {{- ['col', type, size]|select|join('-') -}} +{%- endmacro -%} + +{%- macro get_col_from_count(count, type=none) -%} + {%- set size = (12 // count) if count in [1, 2, 3, 4, 6] else "auto" -%} + {{- get_col(size, type) -}} +{%- endmacro -%} + +{%- macro tag(tag_type) -%} + {%- set _ = kwargs.setdefault("class", class(*varargs) or none) -%} + <{{ tag_type }}{{ kwargs|xmlattr }}> + {{ caller() }} + +{%- endmacro -%} From 7ea5caad330d10f22deee97c9c2af0981ff4c7cc Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Mon, 10 Sep 2018 11:22:15 +0200 Subject: [PATCH 3/9] Fixed failing test, and added (official) support for `flask_wtf/RecaptchaWidget` --- flask_bootstrap/templates/bootstrap/form.html | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/flask_bootstrap/templates/bootstrap/form.html b/flask_bootstrap/templates/bootstrap/form.html index 622444b2..fd1adeb3 100644 --- a/flask_bootstrap/templates/bootstrap/form.html +++ b/flask_bootstrap/templates/bootstrap/form.html @@ -134,7 +134,7 @@ {%- endmacro -%} -{%- macro render_field(field, in_block=false, col=none, prefix_label=true, opts=opts) -%} +{%- macro render_field(field, in_block=false, col=none, prefix_label=true, opts=namespace()) -%} {%- block render_field scoped -%} {%- set widget_type = typename(field.widget) -%} {%- set widget_module = modulename(field.widget) -%} @@ -195,6 +195,10 @@ {{ render_single(field, hide_label=hide_label, col=col, prefix_label=prefix_label, opts=opts) }} {%- endif -%} + {%- elif widget_module == 'flask_wtf.recaptcha.widgets' -%} + {# widget_type == 'RecaptchaWidget' #} + {{ render_single(field, hide_label=hide_label, col=col, prefix_label=prefix_label, opts=opts) }} + {%- else -%} {%- block render_field_unknown scoped -%} {# We can try to render the unknown element, but this block is intended to be extended #} From bb7dd600d0d748882fe5d0d783cd233f601b3b3c Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Mon, 17 Sep 2018 13:25:59 +0200 Subject: [PATCH 4/9] Fixed `hidden_errors` macro --- flask_bootstrap/templates/bootstrap/form.html | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/flask_bootstrap/templates/bootstrap/form.html b/flask_bootstrap/templates/bootstrap/form.html index fd1adeb3..a15d1669 100644 --- a/flask_bootstrap/templates/bootstrap/form.html +++ b/flask_bootstrap/templates/bootstrap/form.html @@ -35,9 +35,10 @@ {%- macro hidden_errors(field) -%} {%- block hidden_errors scoped -%} - {# Doesn't actually work, has no element which makes it "display: block" #} {%- for subfield in field|select('is_hidden') -%} - {{ error(subfield) }} + {%- for error in subfield.errors -%} +
{{ error }}
+ {%- endfor -%} {%- endfor -%} {%- endblock -%} {%- endmacro -%} From a3caa8a728b47182a088d404f379804e29ddecaf Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Mon, 17 Sep 2018 15:39:27 +0200 Subject: [PATCH 5/9] Updated for backwards compatibility, added Jinja>=2.8 as requirement --- flask_bootstrap/__init__.py | 2 ++ flask_bootstrap/templates/bootstrap/form.html | 4 ++- .../templates/bootstrap/form_compat.html | 34 +++++++++++++++++++ setup.py | 3 +- 4 files changed, 41 insertions(+), 2 deletions(-) create mode 100644 flask_bootstrap/templates/bootstrap/form_compat.html diff --git a/flask_bootstrap/__init__.py b/flask_bootstrap/__init__.py index 78c502bd..d6f1ac06 100644 --- a/flask_bootstrap/__init__.py +++ b/flask_bootstrap/__init__.py @@ -34,6 +34,8 @@ 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_filter app.jinja_env.globals['bootstrap'] = self app.jinja_env.tests['is_hidden'] = is_hidden_field_test app.jinja_env.add_extension('jinja2.ext.do') diff --git a/flask_bootstrap/templates/bootstrap/form.html b/flask_bootstrap/templates/bootstrap/form.html index a15d1669..c8d9cbe1 100644 --- a/flask_bootstrap/templates/bootstrap/form.html +++ b/flask_bootstrap/templates/bootstrap/form.html @@ -1,6 +1,7 @@ {# This file was part of Flask-Bootstrap and was modified under the terms of its BSD License. Copyright (c) 2013, Marc Brinkmann. All rights reserved. #} {%- from 'bootstrap/utils.html' import typename, modulename, class, get_col_from_count, get_col, tag -%} +{%- extends 'bootstrap/form_compat.html' -%} {%- macro css_horizontal_fix() -%} @@ -217,7 +218,8 @@ {%- set _ = outer_kwargs.setdefault("method", "post") -%} {%- set _ = outer_kwargs.setdefault("action", "") -%} {%- set _ = outer_kwargs.setdefault("enctype", "multipart/form-data") if form|selectattr("type", "equalto", "FileField") -%} - {%- call tag("form", 'form-inline' if inline, **outer_kwargs) -%} + {%- set extra_classes = outer_kwargs.pop("class", none) -%} + {%- call tag("form", 'form-inline' if inline, extra_classes, **outer_kwargs) -%} {{ css_horizontal_fix() }} {{ hidden_errors(field) }} {%- for field in form -%} diff --git a/flask_bootstrap/templates/bootstrap/form_compat.html b/flask_bootstrap/templates/bootstrap/form_compat.html new file mode 100644 index 00000000..5b5896b0 --- /dev/null +++ b/flask_bootstrap/templates/bootstrap/form_compat.html @@ -0,0 +1,34 @@ +{%- from 'bootstrap/utils.html' import typename, modulename -%} +{%- set global_button_map = {} -%} + +{%- set new_render_field = render_field -%} +{%- macro render_field(field, form_type="basic", horizontal_columns=('md', 3, 9), button_map={}, col=none, prefix_label=true, opts=namespace()) -%} + {%- if not opts -%} + {%- set opts.inline = form_type == 'inline' -%} + {%- set opts.horizontal = form_type == 'horizontal' -%} + {%- set opts.col_type = horizontal_columns[0] -%} + {%- set opts.horizontal_label_width = horizontal_columns[1] -%} + {%- endif -%} + {%- set _ = global_button_map.update(button_map) -%} + {%- if global_button_map and typename(field.widget) == 'SubmitInput' and modulename(field.widget) == 'wtforms.widgets.core' -%} + {{- render_single(field, hide_label=true, col=col, prefix_label=prefix_label, field_class="btn btn-%s" % global_button_map.get(field.name, 'secondary'), opts=opts) -}} + {%- else -%} + {{- new_render_field(field, col=none, prefix_label=true, opts=opts, **kwargs) -}} + {%- endif -%} +{%- endmacro -%} + +{%- macro form_errors(form, hiddens=True) -%} + {# Intentionally left blank, since the previous version used `invalid-feedback`, which wouldn't actualy render the error #} +{%- endmacro -%} + +{%- macro render_form(form, inline=false, tooltips=false, custom=false, col_type=none, compact_tables=false, horizontal=false, horizontal_label_width=none, extra_classes=None, form_type="basic", horizontal_columns=('md', 3, 9), button_map={}, render_kw={}) -%} + {%- set _ = kwargs.setdefault('class', extra_classes) -%} + {%- set inline = form_type == 'inline' if not inline else inline -%} + {%- set horizontal = form_type == 'horizontal' if not horizontal else horizontal -%} + {%- set col_type = horizontal_columns[0] if not col_type else col_type -%} + {%- set horizontal_label_width = horizontal_columns[1] if not horizontal_label_width else horizontal_label_width -%} + {%- set _ = global_button_map.update(button_map) -%} + {%- set _ = kwargs.update(render_kw) -%} + {%- set outer_kwargs = kwargs -%} + {%- block render_form scoped -%}{%- endblock -%} +{%- endmacro -%} diff --git a/setup.py b/setup.py index 147fdf49..3961c0db 100644 --- a/setup.py +++ b/setup.py @@ -32,7 +32,8 @@ include_package_data=True, test_suite='test_bootstrap_flask', install_requires=[ - 'Flask>=1' + 'Flask' + 'Jinja2>=2.8' ], extras_require={ 'forms': ['WTForms>=2.2.0', 'Flask-WTF'] From eaaaec5fd72fd5c01f975e24c8c6d39dff2362f6 Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Mon, 17 Sep 2018 15:44:23 +0200 Subject: [PATCH 6/9] Fixed copying mistake, and changed required jinja version to 2.10 --- flask_bootstrap/__init__.py | 2 +- setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/flask_bootstrap/__init__.py b/flask_bootstrap/__init__.py index d6f1ac06..0bc95c1e 100644 --- a/flask_bootstrap/__init__.py +++ b/flask_bootstrap/__init__.py @@ -35,7 +35,7 @@ def init_app(self, app): 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_filter + app.jinja_env.globals['bootstrap_is_hidden_field'] = is_hidden_field_test app.jinja_env.globals['bootstrap'] = self app.jinja_env.tests['is_hidden'] = is_hidden_field_test app.jinja_env.add_extension('jinja2.ext.do') diff --git a/setup.py b/setup.py index 3961c0db..2a039220 100644 --- a/setup.py +++ b/setup.py @@ -33,7 +33,7 @@ test_suite='test_bootstrap_flask', install_requires=[ 'Flask' - 'Jinja2>=2.8' + 'Jinja2>=2.10' ], extras_require={ 'forms': ['WTForms>=2.2.0', 'Flask-WTF'] From d231989bd6c23d5bc7acc6a52457051b08a4618d Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Mon, 17 Sep 2018 15:52:46 +0200 Subject: [PATCH 7/9] Updated example app to use new form features --- examples/app.py | 65 +++++++++++++++++++++++++++++++----- examples/templates/base.html | 2 +- examples/templates/form.html | 16 ++++----- 3 files changed, 65 insertions(+), 18 deletions(-) diff --git a/examples/app.py b/examples/app.py index fe459a68..633fdb64 100644 --- a/examples/app.py +++ b/examples/app.py @@ -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 @@ -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): diff --git a/examples/templates/base.html b/examples/templates/base.html index 315f5372..90c49c96 100644 --- a/examples/templates/base.html +++ b/examples/templates/base.html @@ -36,4 +36,4 @@ {{ bootstrap.load_js() }} - \ No newline at end of file + diff --git a/examples/templates/form.html b/examples/templates/form.html index ba329130..0ad09751 100644 --- a/examples/templates/form.html +++ b/examples/templates/form.html @@ -2,15 +2,13 @@ {% from 'bootstrap/form.html' import render_form, render_field %} {% block content %} -

render_form

+

Standard Form

{{ render_form(form) }} -

render_field

-
- {{ form.csrf_token }} - {{ render_field(form.username) }} - {{ render_field(form.password) }} - {{ render_field(form.remember) }} - {{ render_field(form.submit) }} -
+
+
+
+ +

Horizontal Form, with bootstrap custom labels

+ {{ render_form(form, custom=true, horizontal=true) }} {% endblock %} From 7c6c99d721a70b582c6115a52454d8bf28b94fb4 Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Mon, 17 Sep 2018 16:12:35 +0200 Subject: [PATCH 8/9] Initial work on documenting `form.html` --- docs/macros.rst | 87 ++++++------------------------------------------- 1 file changed, 10 insertions(+), 77 deletions(-) diff --git a/docs/macros.rst b/docs/macros.rst index 40a3cdd0..171de907 100644 --- a/docs/macros.rst +++ b/docs/macros.rst @@ -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.csrf_token() }} - {{ render_field(form.username) }} - {{ render_field(form.password) }} - {{ render_field(form.submit) }} -
- -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() }} --------------------- @@ -120,51 +84,20 @@ Example API ~~~~ -.. py:function:: quick_form(form,\ - action="",\ - method="post",\ - 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: ``
`` method attribute. - :param extra_classes: The classes to add to the ````. - :param role: ```` 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: ```` 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 ```` id attribute. - :param novalidate: Flag that decide whether add ``novalidate`` class in ````. - :param render_kw: A dictionary, specifying custom attributes for the - ```` 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 ```` tag. Sensible defaults are already set, + but you might want to change the "action", "method" or "id" attribute {{ render_pager() }} From 6e2070b5f5d1f3e961df6c0f4e026bc324706f11 Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Mon, 17 Sep 2018 16:17:05 +0200 Subject: [PATCH 9/9] Fixed setup.py, added missing commas --- setup.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index 2a039220..6704beb8 100644 --- a/setup.py +++ b/setup.py @@ -32,8 +32,8 @@ include_package_data=True, test_suite='test_bootstrap_flask', install_requires=[ - 'Flask' - 'Jinja2>=2.10' + 'Flask', + 'Jinja2>=2.10', ], extras_require={ 'forms': ['WTForms>=2.2.0', 'Flask-WTF']