WTForms 3.2.2__tar.gz → 3.3.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {wtforms-3.2.2 → wtforms-3.3.0}/CHANGES.rst +111 -1
- {wtforms-3.2.2 → wtforms-3.3.0}/PKG-INFO +3 -3
- {wtforms-3.2.2 → wtforms-3.3.0}/docs/conf.py +15 -0
- {wtforms-3.2.2 → wtforms-3.3.0}/docs/crash_course.rst +16 -0
- {wtforms-3.2.2 → wtforms-3.3.0}/docs/fields.rst +260 -40
- {wtforms-3.2.2 → wtforms-3.3.0}/docs/forms.rst +5 -3
- {wtforms-3.2.2 → wtforms-3.3.0}/docs/specific_problems.rst +5 -5
- {wtforms-3.2.2 → wtforms-3.3.0}/docs/validators.rst +7 -4
- {wtforms-3.2.2 → wtforms-3.3.0}/docs/whats_new.rst +16 -1
- wtforms-3.3.0/docs/widgets.rst +161 -0
- {wtforms-3.2.2 → wtforms-3.3.0}/pyproject.toml +4 -5
- {wtforms-3.2.2 → wtforms-3.3.0}/src/wtforms/__init__.py +13 -1
- wtforms-3.3.0/src/wtforms/_compat.py +15 -0
- wtforms-3.3.0/src/wtforms/datalist.py +136 -0
- {wtforms-3.2.2 → wtforms-3.3.0}/src/wtforms/fields/__init__.py +10 -0
- wtforms-3.3.0/src/wtforms/fields/choices.py +625 -0
- {wtforms-3.2.2 → wtforms-3.3.0}/src/wtforms/fields/core.py +46 -5
- wtforms-3.3.0/src/wtforms/fields/datetime.py +290 -0
- {wtforms-3.2.2 → wtforms-3.3.0}/src/wtforms/fields/form.py +17 -2
- {wtforms-3.2.2 → wtforms-3.3.0}/src/wtforms/fields/list.py +96 -13
- {wtforms-3.2.2 → wtforms-3.3.0}/src/wtforms/fields/numeric.py +39 -15
- {wtforms-3.2.2 → wtforms-3.3.0}/src/wtforms/fields/simple.py +68 -16
- {wtforms-3.2.2 → wtforms-3.3.0}/src/wtforms/form.py +40 -1
- {wtforms-3.2.2 → wtforms-3.3.0}/src/wtforms/locale/README.md +6 -0
- {wtforms-3.2.2 → wtforms-3.3.0}/src/wtforms/locale/ar/LC_MESSAGES/wtforms.po +8 -8
- {wtforms-3.2.2 → wtforms-3.3.0}/src/wtforms/locale/bg/LC_MESSAGES/wtforms.po +7 -7
- {wtforms-3.2.2 → wtforms-3.3.0}/src/wtforms/locale/ca/LC_MESSAGES/wtforms.po +8 -9
- {wtforms-3.2.2 → wtforms-3.3.0}/src/wtforms/locale/cs_CZ/LC_MESSAGES/wtforms.po +7 -7
- {wtforms-3.2.2 → wtforms-3.3.0}/src/wtforms/locale/cy/LC_MESSAGES/wtforms.po +9 -8
- {wtforms-3.2.2 → wtforms-3.3.0}/src/wtforms/locale/de/LC_MESSAGES/wtforms.po +7 -7
- {wtforms-3.2.2 → wtforms-3.3.0}/src/wtforms/locale/de_CH/LC_MESSAGES/wtforms.po +7 -7
- {wtforms-3.2.2 → wtforms-3.3.0}/src/wtforms/locale/el/LC_MESSAGES/wtforms.po +7 -7
- wtforms-3.3.0/src/wtforms/locale/en/LC_MESSAGES/wtforms.mo +0 -0
- {wtforms-3.2.2 → wtforms-3.3.0}/src/wtforms/locale/en/LC_MESSAGES/wtforms.po +59 -43
- {wtforms-3.2.2 → wtforms-3.3.0}/src/wtforms/locale/es/LC_MESSAGES/wtforms.po +5 -6
- {wtforms-3.2.2 → wtforms-3.3.0}/src/wtforms/locale/et/LC_MESSAGES/wtforms.po +7 -7
- {wtforms-3.2.2 → wtforms-3.3.0}/src/wtforms/locale/fa/LC_MESSAGES/wtforms.po +6 -6
- {wtforms-3.2.2 → wtforms-3.3.0}/src/wtforms/locale/fi/LC_MESSAGES/wtforms.po +7 -7
- {wtforms-3.2.2 → wtforms-3.3.0}/src/wtforms/locale/fr/LC_MESSAGES/wtforms.po +8 -9
- {wtforms-3.2.2 → wtforms-3.3.0}/src/wtforms/locale/he/LC_MESSAGES/wtforms.po +7 -7
- {wtforms-3.2.2 → wtforms-3.3.0}/src/wtforms/locale/hu/LC_MESSAGES/wtforms.po +7 -7
- {wtforms-3.2.2 → wtforms-3.3.0}/src/wtforms/locale/it/LC_MESSAGES/wtforms.po +10 -10
- {wtforms-3.2.2 → wtforms-3.3.0}/src/wtforms/locale/ja/LC_MESSAGES/wtforms.po +7 -7
- {wtforms-3.2.2 → wtforms-3.3.0}/src/wtforms/locale/kk/LC_MESSAGES/wtforms.po +7 -8
- {wtforms-3.2.2 → wtforms-3.3.0}/src/wtforms/locale/ko/LC_MESSAGES/wtforms.po +5 -6
- {wtforms-3.2.2 → wtforms-3.3.0}/src/wtforms/locale/nb/LC_MESSAGES/wtforms.po +7 -7
- {wtforms-3.2.2 → wtforms-3.3.0}/src/wtforms/locale/nl/LC_MESSAGES/wtforms.mo +0 -0
- {wtforms-3.2.2 → wtforms-3.3.0}/src/wtforms/locale/nl/LC_MESSAGES/wtforms.po +9 -11
- {wtforms-3.2.2 → wtforms-3.3.0}/src/wtforms/locale/pl/LC_MESSAGES/wtforms.po +9 -10
- {wtforms-3.2.2 → wtforms-3.3.0}/src/wtforms/locale/pt/LC_MESSAGES/wtforms.po +5 -6
- {wtforms-3.2.2 → wtforms-3.3.0}/src/wtforms/locale/ro/LC_MESSAGES/wtforms.po +11 -11
- {wtforms-3.2.2 → wtforms-3.3.0}/src/wtforms/locale/ru/LC_MESSAGES/wtforms.po +8 -9
- {wtforms-3.2.2 → wtforms-3.3.0}/src/wtforms/locale/sk/LC_MESSAGES/wtforms.po +5 -6
- wtforms-3.3.0/src/wtforms/locale/sr/LC_MESSAGES/wtforms.mo +0 -0
- {wtforms-3.2.2 → wtforms-3.3.0}/src/wtforms/locale/sr/LC_MESSAGES/wtforms.po +53 -48
- {wtforms-3.2.2 → wtforms-3.3.0}/src/wtforms/locale/sv/LC_MESSAGES/wtforms.po +5 -6
- {wtforms-3.2.2 → wtforms-3.3.0}/src/wtforms/locale/ta/LC_MESSAGES/wtforms.po +7 -7
- {wtforms-3.2.2 → wtforms-3.3.0}/src/wtforms/locale/tr/LC_MESSAGES/wtforms.po +5 -6
- {wtforms-3.2.2 → wtforms-3.3.0}/src/wtforms/locale/uk/LC_MESSAGES/wtforms.po +6 -7
- {wtforms-3.2.2 → wtforms-3.3.0}/src/wtforms/locale/wtforms.pot +54 -39
- {wtforms-3.2.2 → wtforms-3.3.0}/src/wtforms/locale/zh/LC_MESSAGES/wtforms.po +7 -7
- {wtforms-3.2.2 → wtforms-3.3.0}/src/wtforms/locale/zh_TW/LC_MESSAGES/wtforms.po +5 -6
- {wtforms-3.2.2 → wtforms-3.3.0}/src/wtforms/validators.py +223 -30
- {wtforms-3.2.2 → wtforms-3.3.0}/src/wtforms/widgets/__init__.py +4 -0
- {wtforms-3.2.2 → wtforms-3.3.0}/src/wtforms/widgets/core.py +185 -79
- {wtforms-3.2.2 → wtforms-3.3.0}/tests/conftest.py +18 -4
- wtforms-3.3.0/tests/fields/test_button.py +88 -0
- {wtforms-3.2.2 → wtforms-3.3.0}/tests/fields/test_date.py +22 -0
- wtforms-3.3.0/tests/fields/test_datetime.py +129 -0
- wtforms-3.3.0/tests/fields/test_datetimelocal.py +141 -0
- {wtforms-3.2.2 → wtforms-3.3.0}/tests/fields/test_decimal.py +17 -0
- {wtforms-3.2.2 → wtforms-3.3.0}/tests/fields/test_field.py +9 -0
- wtforms-3.3.0/tests/fields/test_float.py +54 -0
- {wtforms-3.2.2 → wtforms-3.3.0}/tests/fields/test_form.py +99 -0
- {wtforms-3.2.2 → wtforms-3.3.0}/tests/fields/test_html5.py +55 -1
- {wtforms-3.2.2 → wtforms-3.3.0}/tests/fields/test_integer.py +22 -0
- wtforms-3.3.0/tests/fields/test_list.py +488 -0
- {wtforms-3.2.2 → wtforms-3.3.0}/tests/fields/test_radio.py +35 -6
- wtforms-3.3.0/tests/fields/test_select.py +950 -0
- wtforms-3.3.0/tests/fields/test_selectmultiple.py +371 -0
- {wtforms-3.2.2 → wtforms-3.3.0}/tests/fields/test_time.py +22 -0
- {wtforms-3.2.2 → wtforms-3.3.0}/tests/fields/test_week.py +22 -0
- wtforms-3.3.0/tests/test_datalist.py +439 -0
- {wtforms-3.2.2 → wtforms-3.3.0}/tests/test_form.py +22 -0
- {wtforms-3.2.2 → wtforms-3.3.0}/tests/test_widgets.py +141 -5
- wtforms-3.3.0/tests/validators/test_date_range.py +126 -0
- {wtforms-3.2.2 → wtforms-3.3.0}/tests/validators/test_email.py +47 -0
- {wtforms-3.2.2 → wtforms-3.3.0}/tests/validators/test_number_range.py +19 -0
- wtforms-3.3.0/tests/validators/test_regexp.py +106 -0
- wtforms-3.3.0/tests/validators/test_url.py +143 -0
- {wtforms-3.2.2 → wtforms-3.3.0}/tests/validators/test_uuid.py +16 -1
- wtforms-3.2.2/.hgignore +0 -1
- wtforms-3.2.2/docs/widgets.rst +0 -104
- wtforms-3.2.2/src/wtforms/fields/choices.py +0 -227
- wtforms-3.2.2/src/wtforms/fields/datetime.py +0 -170
- wtforms-3.2.2/src/wtforms/locale/en/LC_MESSAGES/wtforms.mo +0 -0
- wtforms-3.2.2/src/wtforms/locale/sr/LC_MESSAGES/wtforms.mo +0 -0
- wtforms-3.2.2/tests/fields/test_datetime.py +0 -72
- wtforms-3.2.2/tests/fields/test_datetimelocal.py +0 -74
- wtforms-3.2.2/tests/fields/test_float.py +0 -30
- wtforms-3.2.2/tests/fields/test_list.py +0 -249
- wtforms-3.2.2/tests/fields/test_select.py +0 -285
- wtforms-3.2.2/tests/fields/test_selectmultiple.py +0 -207
- wtforms-3.2.2/tests/validators/test_regexp.py +0 -64
- wtforms-3.2.2/tests/validators/test_url.py +0 -76
- {wtforms-3.2.2 → wtforms-3.3.0}/.gitignore +0 -0
- {wtforms-3.2.2 → wtforms-3.3.0}/LICENSE.rst +0 -0
- {wtforms-3.2.2 → wtforms-3.3.0}/README.rst +0 -0
- {wtforms-3.2.2 → wtforms-3.3.0}/docs/Makefile +0 -0
- {wtforms-3.2.2 → wtforms-3.3.0}/docs/_static/logo_joined.svg +0 -0
- {wtforms-3.2.2 → wtforms-3.3.0}/docs/_static/logo_split.svg +0 -0
- {wtforms-3.2.2 → wtforms-3.3.0}/docs/_static/logo_square.svg +0 -0
- {wtforms-3.2.2 → wtforms-3.3.0}/docs/changes.rst +0 -0
- {wtforms-3.2.2 → wtforms-3.3.0}/docs/contributing.rst +0 -0
- {wtforms-3.2.2 → wtforms-3.3.0}/docs/csrf.rst +0 -0
- {wtforms-3.2.2 → wtforms-3.3.0}/docs/ext.rst +0 -0
- {wtforms-3.2.2 → wtforms-3.3.0}/docs/faq.rst +0 -0
- {wtforms-3.2.2 → wtforms-3.3.0}/docs/i18n.rst +0 -0
- {wtforms-3.2.2 → wtforms-3.3.0}/docs/index.rst +0 -0
- {wtforms-3.2.2 → wtforms-3.3.0}/docs/license.rst +0 -0
- {wtforms-3.2.2 → wtforms-3.3.0}/docs/make.bat +0 -0
- {wtforms-3.2.2 → wtforms-3.3.0}/docs/meta.rst +0 -0
- {wtforms-3.2.2 → wtforms-3.3.0}/hatch_build.py +0 -0
- {wtforms-3.2.2 → wtforms-3.3.0}/src/wtforms/csrf/__init__.py +0 -0
- {wtforms-3.2.2 → wtforms-3.3.0}/src/wtforms/csrf/core.py +0 -0
- {wtforms-3.2.2 → wtforms-3.3.0}/src/wtforms/csrf/session.py +0 -0
- {wtforms-3.2.2 → wtforms-3.3.0}/src/wtforms/i18n.py +0 -0
- {wtforms-3.2.2 → wtforms-3.3.0}/src/wtforms/locale/ar/LC_MESSAGES/wtforms.mo +0 -0
- {wtforms-3.2.2 → wtforms-3.3.0}/src/wtforms/locale/bg/LC_MESSAGES/wtforms.mo +0 -0
- {wtforms-3.2.2 → wtforms-3.3.0}/src/wtforms/locale/ca/LC_MESSAGES/wtforms.mo +0 -0
- {wtforms-3.2.2 → wtforms-3.3.0}/src/wtforms/locale/cs_CZ/LC_MESSAGES/wtforms.mo +0 -0
- {wtforms-3.2.2 → wtforms-3.3.0}/src/wtforms/locale/cy/LC_MESSAGES/wtforms.mo +0 -0
- {wtforms-3.2.2 → wtforms-3.3.0}/src/wtforms/locale/de/LC_MESSAGES/wtforms.mo +0 -0
- {wtforms-3.2.2 → wtforms-3.3.0}/src/wtforms/locale/de_CH/LC_MESSAGES/wtforms.mo +0 -0
- {wtforms-3.2.2 → wtforms-3.3.0}/src/wtforms/locale/el/LC_MESSAGES/wtforms.mo +0 -0
- {wtforms-3.2.2 → wtforms-3.3.0}/src/wtforms/locale/es/LC_MESSAGES/wtforms.mo +0 -0
- {wtforms-3.2.2 → wtforms-3.3.0}/src/wtforms/locale/et/LC_MESSAGES/wtforms.mo +0 -0
- {wtforms-3.2.2 → wtforms-3.3.0}/src/wtforms/locale/fa/LC_MESSAGES/wtforms.mo +0 -0
- {wtforms-3.2.2 → wtforms-3.3.0}/src/wtforms/locale/fi/LC_MESSAGES/wtforms.mo +0 -0
- {wtforms-3.2.2 → wtforms-3.3.0}/src/wtforms/locale/fr/LC_MESSAGES/wtforms.mo +0 -0
- {wtforms-3.2.2 → wtforms-3.3.0}/src/wtforms/locale/he/LC_MESSAGES/wtforms.mo +0 -0
- {wtforms-3.2.2 → wtforms-3.3.0}/src/wtforms/locale/hu/LC_MESSAGES/wtforms.mo +0 -0
- {wtforms-3.2.2 → wtforms-3.3.0}/src/wtforms/locale/it/LC_MESSAGES/wtforms.mo +0 -0
- {wtforms-3.2.2 → wtforms-3.3.0}/src/wtforms/locale/ja/LC_MESSAGES/wtforms.mo +0 -0
- {wtforms-3.2.2 → wtforms-3.3.0}/src/wtforms/locale/kk/LC_MESSAGES/wtforms.mo +0 -0
- {wtforms-3.2.2 → wtforms-3.3.0}/src/wtforms/locale/ko/LC_MESSAGES/wtforms.mo +0 -0
- {wtforms-3.2.2 → wtforms-3.3.0}/src/wtforms/locale/nb/LC_MESSAGES/wtforms.mo +0 -0
- {wtforms-3.2.2 → wtforms-3.3.0}/src/wtforms/locale/pl/LC_MESSAGES/wtforms.mo +0 -0
- {wtforms-3.2.2 → wtforms-3.3.0}/src/wtforms/locale/pt/LC_MESSAGES/wtforms.mo +0 -0
- {wtforms-3.2.2 → wtforms-3.3.0}/src/wtforms/locale/ro/LC_MESSAGES/wtforms.mo +0 -0
- {wtforms-3.2.2 → wtforms-3.3.0}/src/wtforms/locale/ru/LC_MESSAGES/wtforms.mo +0 -0
- {wtforms-3.2.2 → wtforms-3.3.0}/src/wtforms/locale/sk/LC_MESSAGES/wtforms.mo +0 -0
- {wtforms-3.2.2 → wtforms-3.3.0}/src/wtforms/locale/sv/LC_MESSAGES/wtforms.mo +0 -0
- {wtforms-3.2.2 → wtforms-3.3.0}/src/wtforms/locale/ta/LC_MESSAGES/wtforms.mo +0 -0
- {wtforms-3.2.2 → wtforms-3.3.0}/src/wtforms/locale/tr/LC_MESSAGES/wtforms.mo +0 -0
- {wtforms-3.2.2 → wtforms-3.3.0}/src/wtforms/locale/uk/LC_MESSAGES/wtforms.mo +0 -0
- {wtforms-3.2.2 → wtforms-3.3.0}/src/wtforms/locale/zh/LC_MESSAGES/wtforms.mo +0 -0
- {wtforms-3.2.2 → wtforms-3.3.0}/src/wtforms/locale/zh_TW/LC_MESSAGES/wtforms.mo +0 -0
- {wtforms-3.2.2 → wtforms-3.3.0}/src/wtforms/meta.py +0 -0
- {wtforms-3.2.2 → wtforms-3.3.0}/src/wtforms/utils.py +0 -0
- {wtforms-3.2.2 → wtforms-3.3.0}/tests/__init__.py +0 -0
- {wtforms-3.2.2 → wtforms-3.3.0}/tests/common.py +0 -0
- {wtforms-3.2.2 → wtforms-3.3.0}/tests/fields/__init__.py +0 -0
- {wtforms-3.2.2 → wtforms-3.3.0}/tests/fields/test_boolean.py +0 -0
- {wtforms-3.2.2 → wtforms-3.3.0}/tests/fields/test_color.py +0 -0
- {wtforms-3.2.2 → wtforms-3.3.0}/tests/fields/test_custom.py +0 -0
- {wtforms-3.2.2 → wtforms-3.3.0}/tests/fields/test_defaults.py +0 -0
- {wtforms-3.2.2 → wtforms-3.3.0}/tests/fields/test_file.py +0 -0
- {wtforms-3.2.2 → wtforms-3.3.0}/tests/fields/test_filters.py +0 -0
- {wtforms-3.2.2 → wtforms-3.3.0}/tests/fields/test_flags.py +0 -0
- {wtforms-3.2.2 → wtforms-3.3.0}/tests/fields/test_hidden.py +0 -0
- {wtforms-3.2.2 → wtforms-3.3.0}/tests/fields/test_label.py +0 -0
- {wtforms-3.2.2 → wtforms-3.3.0}/tests/fields/test_month.py +0 -0
- {wtforms-3.2.2 → wtforms-3.3.0}/tests/fields/test_password.py +0 -0
- {wtforms-3.2.2 → wtforms-3.3.0}/tests/fields/test_string.py +0 -0
- {wtforms-3.2.2 → wtforms-3.3.0}/tests/fields/test_submit.py +0 -0
- {wtforms-3.2.2 → wtforms-3.3.0}/tests/fields/test_textarea.py +0 -0
- {wtforms-3.2.2 → wtforms-3.3.0}/tests/test_csrf.py +0 -0
- {wtforms-3.2.2 → wtforms-3.3.0}/tests/test_i18n.py +0 -0
- {wtforms-3.2.2 → wtforms-3.3.0}/tests/test_locale_babel.py +0 -0
- {wtforms-3.2.2 → wtforms-3.3.0}/tests/test_webob.py +0 -0
- {wtforms-3.2.2 → wtforms-3.3.0}/tests/validators/__init__.py +0 -0
- {wtforms-3.2.2 → wtforms-3.3.0}/tests/validators/test_anyof.py +0 -0
- {wtforms-3.2.2 → wtforms-3.3.0}/tests/validators/test_data_required.py +0 -0
- {wtforms-3.2.2 → wtforms-3.3.0}/tests/validators/test_disabled.py +0 -0
- {wtforms-3.2.2 → wtforms-3.3.0}/tests/validators/test_equal_to.py +0 -0
- {wtforms-3.2.2 → wtforms-3.3.0}/tests/validators/test_input_required.py +0 -0
- {wtforms-3.2.2 → wtforms-3.3.0}/tests/validators/test_ipaddress.py +0 -0
- {wtforms-3.2.2 → wtforms-3.3.0}/tests/validators/test_lazy_proxy.py +0 -0
- {wtforms-3.2.2 → wtforms-3.3.0}/tests/validators/test_length.py +0 -0
- {wtforms-3.2.2 → wtforms-3.3.0}/tests/validators/test_mac_address.py +0 -0
- {wtforms-3.2.2 → wtforms-3.3.0}/tests/validators/test_noneof.py +0 -0
- {wtforms-3.2.2 → wtforms-3.3.0}/tests/validators/test_optional.py +0 -0
- {wtforms-3.2.2 → wtforms-3.3.0}/tests/validators/test_readonly.py +0 -0
- {wtforms-3.2.2 → wtforms-3.3.0}/tests/validators/test_validators.py +0 -0
|
@@ -1,5 +1,116 @@
|
|
|
1
1
|
.. currentmodule:: wtforms
|
|
2
2
|
|
|
3
|
+
Version 3.3.0
|
|
4
|
+
-------------
|
|
5
|
+
|
|
6
|
+
Released 2026-10-23
|
|
7
|
+
|
|
8
|
+
- Drop support for Python 3.10. Add support for Python 3.15.
|
|
9
|
+
- Add native :class:`DataList` support. Options are declared with
|
|
10
|
+
:class:`DataListChoice`, or with the shorthand ``{value: label}``
|
|
11
|
+
dict syntax. :issue:`653` :issue:`886` :pr:`921`
|
|
12
|
+
- Add :class:`~fields.ButtonField` to render ``<button type="submit">``
|
|
13
|
+
controls with string values. :issue:`784` :pr:`892`
|
|
14
|
+
- Add :class:`~validators.DateRange`. ``min`` and ``max`` may be callables
|
|
15
|
+
to compute dynamic bounds; :class:`~validators.NumberRange` accepts
|
|
16
|
+
callables on the same terms. :pr:`787`
|
|
17
|
+
- Add :class:`~fields.FieldList` ``insert_entry`` and indexed ``pop_entry``.
|
|
18
|
+
The following entries are renumbered, with their ids and labels.
|
|
19
|
+
:issue:`256` :pr:`901` :pr:`912`
|
|
20
|
+
- :class:`~fields.SelectField` refactor. Options are declared with
|
|
21
|
+
:class:`~fields.SelectChoice`, which takes HTML attributes in
|
|
22
|
+
``render_kw`` and an ``optgroup``. ``iter_choices``
|
|
23
|
+
and ``iter_groups`` yield
|
|
24
|
+
:class:`~fields.Choice` objects. ``choices`` also accepts the shorthand
|
|
25
|
+
dict syntax: ``{value: label}`` for flat options and
|
|
26
|
+
``{label: {value: label}}`` for optgroups. :issue:`692` :issue:`886`
|
|
27
|
+
:issue:`922` :pr:`739` :pr:`889`
|
|
28
|
+
- ``choices`` callables on :class:`~fields.SelectField` and
|
|
29
|
+
:class:`DataList` may accept ``(form, field)``. They run on the
|
|
30
|
+
first read and their result is cached. Calling
|
|
31
|
+
:meth:`~form.BaseForm.post_process` again refreshes them, also for the
|
|
32
|
+
entries added with :meth:`~fields.FieldList.append_entry` and
|
|
33
|
+
:meth:`~fields.FieldList.insert_entry`. :issue:`922`
|
|
34
|
+
- Add :func:`fields.enum_choices`, :func:`fields.enum_coerce` and
|
|
35
|
+
:func:`~datalist.enum_datalist` for Enum-backed choices. :issue:`338`
|
|
36
|
+
:issue:`922` :pr:`916`
|
|
37
|
+
- Add :meth:`fields.Field.post_process` and
|
|
38
|
+
:meth:`form.BaseForm.post_process` hooks, propagated through
|
|
39
|
+
:class:`~fields.FormField` and :class:`~fields.FieldList`, for
|
|
40
|
+
cross-field finalization. :issue:`922`
|
|
41
|
+
- Add ``Field._form`` and ``BaseForm._parent_form`` so fields and nested
|
|
42
|
+
forms can reach their enclosing form. :class:`~fields.FieldList` entries
|
|
43
|
+
and :class:`~fields.SelectField` option subfields no longer get
|
|
44
|
+
``form=None``. :issue:`922`
|
|
45
|
+
- Add ``invalid_value_message`` and ``invalid_choice_message`` keyword
|
|
46
|
+
arguments for customizing built-in field error messages. On
|
|
47
|
+
:class:`~fields.SelectMultipleField`, ``invalid_choice_message`` may also
|
|
48
|
+
be a callable for custom pluralization. :issue:`39` :issue:`832` :pr:`902`
|
|
49
|
+
- :class:`~fields.SelectField` and :class:`~fields.SelectMultipleField`
|
|
50
|
+
``pre_validate`` short-circuits when ``process_formdata`` already produced
|
|
51
|
+
an error, so a single coercion failure no longer surfaces as both an
|
|
52
|
+
"invalid value" and an "invalid choice" error. :pr:`902`
|
|
53
|
+
- Numeric, date, time and select fields report a validation error instead of
|
|
54
|
+
raising a :class:`TypeError` when the input is not a string. :pr:`936`
|
|
55
|
+
- Each bound field gets its own copy of ``render_kw``, and so does its
|
|
56
|
+
:class:`DataList`. Changing one of them on a form instance no
|
|
57
|
+
longer changes it on the other ones. :issue:`942`
|
|
58
|
+
- Make :class:`~fields.FloatField` render as ``<input type="number">`` with
|
|
59
|
+
``step="any"``. :issue:`677` :pr:`679`
|
|
60
|
+
- Add a ``tz`` parameter to :class:`~fields.DateTimeLocalField` to associate a
|
|
61
|
+
timezone with the input. :issue:`833` :pr:`905`
|
|
62
|
+
- Let :class:`~widgets.RangeInput` accept ``min`` and ``max`` at construction
|
|
63
|
+
time. :issue:`693` :pr:`898`
|
|
64
|
+
- ``FieldList._add_entry`` routes through
|
|
65
|
+
:meth:`~meta.DefaultMeta.bind_field`, like the top-level fields. Custom
|
|
66
|
+
``Meta.bind_field`` overrides were silently bypassed for
|
|
67
|
+
:class:`~fields.FieldList` entries. :pr:`891`
|
|
68
|
+
- Reset :class:`~fields.FieldList` index state when reprocessing entries.
|
|
69
|
+
:issue:`874` :pr:`895`
|
|
70
|
+
- Do not render a ``for`` attribute on the main label of
|
|
71
|
+
:class:`~fields.RadioField`. :issue:`775` :pr:`900`
|
|
72
|
+
- Render ``False`` as ``value="False"`` in :class:`~fields.SelectField`
|
|
73
|
+
options instead of omitting the attribute. :issue:`878` :pr:`908`
|
|
74
|
+
- Let render-time ``checked`` kwargs take precedence over the field's value
|
|
75
|
+
in :class:`~widgets.CheckboxInput` and :class:`~widgets.RadioInput`.
|
|
76
|
+
:issue:`706` :pr:`910`
|
|
77
|
+
- Escape double quotes in ``Markup`` attribute values
|
|
78
|
+
passed to :func:`~widgets.html_params`, and return a
|
|
79
|
+
``Markup``. :issue:`801` :pr:`906` :pr:`907`
|
|
80
|
+
- Add ``test_environment`` support to :class:`~validators.Email`, and defer
|
|
81
|
+
to ``email_validator``'s module-level defaults for its
|
|
82
|
+
``test_environment``, ``allow_smtputf8`` and ``allow_empty_local``
|
|
83
|
+
options. :issue:`869` :issue:`915` :pr:`894` :pr:`917`
|
|
84
|
+
- Add ``matcher`` and ``html_pattern`` keyword arguments to
|
|
85
|
+
:class:`~validators.Regexp`. ``matcher`` overrides the matching function,
|
|
86
|
+
:func:`re.match` by default: pass :func:`re.search` or :func:`re.fullmatch`
|
|
87
|
+
to change the anchoring. ``html_pattern`` controls the HTML ``pattern``
|
|
88
|
+
attribute emitted on supporting widgets. :issue:`867` :pr:`759` :pr:`918`
|
|
89
|
+
- :class:`~validators.URL` validator is based on ``urllib.parse.urlparse``
|
|
90
|
+
instead of a regex. New keyword arguments: ``allow_userinfo`` for
|
|
91
|
+
accepting ``user:password@host`` URLs; ``schemes``
|
|
92
|
+
(default ``("http", "https")``) is the allowlist of accepted schemes.
|
|
93
|
+
:pr:`847`
|
|
94
|
+
- Accept :class:`uuid.UUID` instances in the :class:`~validators.UUID`
|
|
95
|
+
validator, and translate unexpected types into
|
|
96
|
+
:class:`~validators.ValidationError` instead of letting
|
|
97
|
+
:class:`TypeError` escape. :issue:`549` :pr:`769`
|
|
98
|
+
- Clarify the documentation of filters, and of :class:`~fields.SelectField`
|
|
99
|
+
``choices``. :issue:`859` :issue:`875` :pr:`896` :pr:`909`
|
|
100
|
+
- Update the Dutch and Serbian translations.
|
|
101
|
+
- Deprecate :class:`~fields.DateTimeField`, which will be removed in WTForms
|
|
102
|
+
3.4. :issue:`831` :pr:`897`
|
|
103
|
+
- Passing :class:`~fields.SelectField` ``choices`` as tuples, or as a dict
|
|
104
|
+
that is not the shorthand syntax, is deprecated and will be removed in
|
|
105
|
+
WTForms 3.4. Use :class:`~fields.SelectChoice` instead. :issue:`922`
|
|
106
|
+
- Passing a callable to ``SelectFieldBase._choices_from_input`` is
|
|
107
|
+
deprecated and will be removed in WTForms 3.4.
|
|
108
|
+
- For :class:`~fields.SelectField` subclasses, yielding raw tuples from
|
|
109
|
+
``iter_choices`` or
|
|
110
|
+
``iter_groups``, and overriding
|
|
111
|
+
``Select.render_option`` with the pre-3.3 signature, are
|
|
112
|
+
deprecated and will be removed in WTForms 4.0. :issue:`922`
|
|
113
|
+
|
|
3
114
|
Version 3.2.2
|
|
4
115
|
-------------
|
|
5
116
|
|
|
@@ -44,7 +155,6 @@ Released 2024-10-20
|
|
|
44
155
|
If you need to keep the old behavior you can set the ``_form_error_key``
|
|
45
156
|
parameter of your form to :data:`None`.
|
|
46
157
|
|
|
47
|
-
|
|
48
158
|
Version 3.1.2
|
|
49
159
|
-------------
|
|
50
160
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
2
|
Name: WTForms
|
|
3
|
-
Version: 3.
|
|
3
|
+
Version: 3.3.0
|
|
4
4
|
Summary: Form validation and rendering for Python web development.
|
|
5
5
|
Project-URL: Documentation, https://wtforms.readthedocs.io
|
|
6
6
|
Project-URL: Changes, https://wtforms.readthedocs.io/changes
|
|
@@ -44,7 +44,7 @@ Classifier: License :: OSI Approved :: BSD License
|
|
|
44
44
|
Classifier: Operating System :: OS Independent
|
|
45
45
|
Classifier: Programming Language :: Python
|
|
46
46
|
Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content
|
|
47
|
-
Requires-Python: >=3.
|
|
47
|
+
Requires-Python: >=3.11
|
|
48
48
|
Requires-Dist: markupsafe>=1.1.1
|
|
49
49
|
Provides-Extra: email
|
|
50
50
|
Requires-Dist: email-validator>=2.0; extra == 'email'
|
|
@@ -13,6 +13,7 @@ release, version = get_version("WTForms")
|
|
|
13
13
|
master_doc = "index"
|
|
14
14
|
extensions = [
|
|
15
15
|
"sphinx.ext.autodoc",
|
|
16
|
+
"sphinx.ext.extlinks",
|
|
16
17
|
"sphinx.ext.intersphinx",
|
|
17
18
|
"sphinx.ext.viewcode",
|
|
18
19
|
"pallets_sphinx_themes",
|
|
@@ -22,6 +23,20 @@ extensions = [
|
|
|
22
23
|
intersphinx_mapping = {
|
|
23
24
|
"python": ("https://docs.python.org/3/", None),
|
|
24
25
|
}
|
|
26
|
+
extlinks = {
|
|
27
|
+
"mdn-tag": (
|
|
28
|
+
"https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/%s",
|
|
29
|
+
"<%s>",
|
|
30
|
+
),
|
|
31
|
+
"mdn-attr": (
|
|
32
|
+
"https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Attributes/%s",
|
|
33
|
+
"%s",
|
|
34
|
+
),
|
|
35
|
+
"mdn-input": (
|
|
36
|
+
"https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/input/%s",
|
|
37
|
+
'<input type="%s">',
|
|
38
|
+
),
|
|
39
|
+
}
|
|
25
40
|
issues_github_path = "pallets-eco/wtforms"
|
|
26
41
|
|
|
27
42
|
# HTML -----------------------------------------------------------------
|
|
@@ -133,6 +133,22 @@ providing with data. Typically, you will want to assign the values manually, but
|
|
|
133
133
|
for this simple case it's perfect. It can also be useful for CRUD and admin
|
|
134
134
|
forms.
|
|
135
135
|
|
|
136
|
+
The same pattern extends to a collection of objects: a
|
|
137
|
+
:class:`~wtforms.fields.FormField` describes one item, a
|
|
138
|
+
:class:`~wtforms.fields.FieldList` holds the list, and ``obj`` /
|
|
139
|
+
``populate_obj`` thread through both ends::
|
|
140
|
+
|
|
141
|
+
class ItemForm(Form):
|
|
142
|
+
name = StringField()
|
|
143
|
+
quantity = IntegerField()
|
|
144
|
+
|
|
145
|
+
class CartForm(Form):
|
|
146
|
+
items = FieldList(FormField(ItemForm))
|
|
147
|
+
|
|
148
|
+
form = CartForm(request.form, obj=cart) # cart.items == [item1, item2, ...]
|
|
149
|
+
if form.validate():
|
|
150
|
+
form.populate_obj(cart)
|
|
151
|
+
|
|
136
152
|
|
|
137
153
|
Exploring in the console
|
|
138
154
|
^^^^^^^^^^^^^^^^^^^^^^^^
|
|
@@ -42,6 +42,9 @@ The Field base class
|
|
|
42
42
|
|
|
43
43
|
.. automethod:: __init__
|
|
44
44
|
|
|
45
|
+
Built-in field processing messages can be customized per field with
|
|
46
|
+
``invalid_value_message=``.
|
|
47
|
+
|
|
45
48
|
**Validation**
|
|
46
49
|
|
|
47
50
|
To validate the field, call its `validate` method, providing a form and any
|
|
@@ -64,6 +67,7 @@ The Field base class
|
|
|
64
67
|
.. automethod:: process(formdata [, data])
|
|
65
68
|
.. automethod:: process_data
|
|
66
69
|
.. automethod:: process_formdata
|
|
70
|
+
.. automethod:: post_process
|
|
67
71
|
.. attribute:: data
|
|
68
72
|
|
|
69
73
|
Contains the resulting (sanitized) value of calling either of the
|
|
@@ -135,7 +139,7 @@ The Field base class
|
|
|
135
139
|
.. attribute:: label
|
|
136
140
|
|
|
137
141
|
This is a :class:`Label` instance which when evaluated as a string
|
|
138
|
-
returns an HTML
|
|
142
|
+
returns an HTML :mdn-tag:`label` construct.
|
|
139
143
|
|
|
140
144
|
.. attribute:: default
|
|
141
145
|
|
|
@@ -220,6 +224,13 @@ refer to a single input from the form.
|
|
|
220
224
|
|
|
221
225
|
.. autoclass:: DateTimeField(default field arguments, format='%Y-%m-%d %H:%M:%S')
|
|
222
226
|
|
|
227
|
+
.. warning::
|
|
228
|
+
:class:`DateTimeField` renders ``<input type="datetime">``, which is
|
|
229
|
+
obsolete in HTML and not broadly supported by browsers. Use
|
|
230
|
+
:class:`DateTimeLocalField` instead.
|
|
231
|
+
|
|
232
|
+
:class:`DateTimeField` is deprecated and will be removed in WTForms 3.4.
|
|
233
|
+
|
|
223
234
|
.. autoclass:: DateTimeLocalField(default field arguments, format='%Y-%m-%d %H:%M:%S')
|
|
224
235
|
|
|
225
236
|
.. autoclass:: DecimalField(default field arguments, places=2, rounding=None, use_locale=False, number_format=None)
|
|
@@ -233,7 +244,7 @@ refer to a single input from the form.
|
|
|
233
244
|
Example usage::
|
|
234
245
|
|
|
235
246
|
class UploadForm(Form):
|
|
236
|
-
image = FileField('Image File', [validators.regexp('^[^/\\]\.jpg$')])
|
|
247
|
+
image = FileField('Image File', [validators.regexp(r'^[^/\\]\.jpg$', matcher=re.fullmatch)])
|
|
237
248
|
description = TextAreaField('Image Description')
|
|
238
249
|
|
|
239
250
|
def validate_image(form, field):
|
|
@@ -260,7 +271,28 @@ refer to a single input from the form.
|
|
|
260
271
|
|
|
261
272
|
.. autoclass:: MonthField(default field arguments, format='%Y-%m')
|
|
262
273
|
|
|
263
|
-
.. autoclass::
|
|
274
|
+
.. autoclass:: SearchField(default field arguments)
|
|
275
|
+
|
|
276
|
+
.. autoclass:: StringField(default field arguments)
|
|
277
|
+
|
|
278
|
+
.. code-block:: jinja
|
|
279
|
+
|
|
280
|
+
{{ form.username(size=30, maxlength=50) }}
|
|
281
|
+
|
|
282
|
+
.. autoclass:: TelField(default field arguments)
|
|
283
|
+
|
|
284
|
+
.. autoclass:: TimeField(default field arguments, format='%H:%M')
|
|
285
|
+
|
|
286
|
+
.. autoclass:: URLField(default field arguments)
|
|
287
|
+
|
|
288
|
+
Choice Fields
|
|
289
|
+
-------------
|
|
290
|
+
|
|
291
|
+
.. autoclass:: SelectChoice
|
|
292
|
+
|
|
293
|
+
.. autoclass:: Choice
|
|
294
|
+
|
|
295
|
+
.. autoclass:: RadioField(default field arguments, choices=None, coerce=str)
|
|
264
296
|
|
|
265
297
|
.. code-block:: jinja
|
|
266
298
|
|
|
@@ -272,50 +304,88 @@ refer to a single input from the form.
|
|
|
272
304
|
{% endfor %}
|
|
273
305
|
|
|
274
306
|
Simply outputting the field without iterating its subfields will result in
|
|
275
|
-
a
|
|
307
|
+
a :mdn-tag:`ul` list of radio choices.
|
|
308
|
+
|
|
276
309
|
|
|
277
|
-
.. class:: SelectField(default field arguments, choices=
|
|
310
|
+
.. class:: SelectField(default field arguments, choices=None, coerce=str, option_widget=None, validate_choice=True, invalid_value_message=None, invalid_choice_message=None)
|
|
278
311
|
|
|
279
312
|
Select fields take a ``choices`` parameter which is either:
|
|
280
313
|
|
|
281
|
-
* a list of
|
|
314
|
+
* a list of :class:`SelectChoice`.
|
|
282
315
|
It can also be a list of only values, in which case the value is used
|
|
283
|
-
as the label.
|
|
284
|
-
HTML
|
|
316
|
+
as the label. The :class:`SelectChoice` ``render_kw`` mapping is rendered as
|
|
317
|
+
HTML :mdn-tag:`option` parameters. The value can be of any
|
|
285
318
|
type, but because form data is sent to the browser as strings, you
|
|
286
319
|
will need to provide a ``coerce`` function that converts a string
|
|
287
320
|
back to the expected type.
|
|
288
|
-
* a
|
|
289
|
-
* a function taking no argument, and returning either a list or a dictionary.
|
|
321
|
+
* a function taking no argument, and returning a list of :class:`SelectChoice`.
|
|
290
322
|
|
|
291
323
|
|
|
292
324
|
**Select fields with static choice values**::
|
|
293
325
|
|
|
294
326
|
class PastebinEntry(Form):
|
|
295
|
-
language = SelectField('Programming Language', choices=[
|
|
327
|
+
language = SelectField('Programming Language', choices=[
|
|
328
|
+
SelectChoice('cpp', 'C++'),
|
|
329
|
+
SelectChoice('py', 'Python'),
|
|
330
|
+
SelectChoice('text', 'Plain Text'),
|
|
331
|
+
])
|
|
332
|
+
|
|
333
|
+
A callable passed as `choices` is resolved separately for each form
|
|
334
|
+
instance, the first time its choices are read.
|
|
335
|
+
For dynamic options, either pass a callable to `choices` or assign
|
|
336
|
+
`field.choices` after instantiation. Any submitted choices which are not in
|
|
337
|
+
the given choices list will cause validation on the field to fail. If this
|
|
338
|
+
option cannot be applied to your problem you may wish to skip choice
|
|
339
|
+
validation (see below).
|
|
340
|
+
|
|
341
|
+
**Select fields with optgroup**::
|
|
296
342
|
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
343
|
+
class PastebinEntry(Form):
|
|
344
|
+
language = SelectField('Programming Language', choices=[
|
|
345
|
+
SelectChoice('cpp', 'C++', optgroup='Compiled'),
|
|
346
|
+
SelectChoice('rs', 'Rust', optgroup='Compiled'),
|
|
347
|
+
SelectChoice('py', 'Python', optgroup='Interpreted'),
|
|
348
|
+
SelectChoice('text', 'Plain Text'),
|
|
349
|
+
])
|
|
350
|
+
|
|
351
|
+
Use :class:`SelectChoice` to assign an option to an ``<optgroup>``.
|
|
302
352
|
|
|
303
353
|
**Select fields with dynamic choice values**::
|
|
304
354
|
|
|
355
|
+
def available_groups():
|
|
356
|
+
return [SelectChoice(g.id, g.name) for g in Group.query.order_by('name')]
|
|
357
|
+
|
|
305
358
|
class UserDetails(Form):
|
|
306
|
-
group_id = SelectField('Group', coerce=int)
|
|
359
|
+
group_id = SelectField('Group', coerce=int, choices=available_groups)
|
|
307
360
|
|
|
308
361
|
def edit_user(request, id):
|
|
309
362
|
user = User.query.get(id)
|
|
310
363
|
form = UserDetails(request.POST, obj=user)
|
|
311
|
-
form.group_id.choices = [(g.id, g.name) for g in Group.query.order_by('name')]
|
|
312
364
|
|
|
313
|
-
Note we
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
365
|
+
Note we passed a callable to `choices` rather than a static list; it
|
|
366
|
+
produces fresh options per request. Its result is cached. Calling
|
|
367
|
+
:meth:`~wtforms.form.BaseForm.post_process` again refreshes it.
|
|
368
|
+
|
|
369
|
+
If the choices depend on data only available in the view, you can instead
|
|
370
|
+
assign `form.group_id.choices` after instantiation. A list assigned this
|
|
371
|
+
way replaces the callable.
|
|
372
|
+
|
|
373
|
+
The `coerce` keyword arg to :class:`~wtforms.fields.SelectField` says
|
|
374
|
+
that we use :func:`int()` to coerce form data. The default coerce is
|
|
317
375
|
:func:`str()`.
|
|
318
376
|
|
|
377
|
+
The callable may optionally accept ``(form, field)`` as positional
|
|
378
|
+
arguments. The callable is invoked when the choices are read, normally
|
|
379
|
+
after the whole form has been processed, so it can read ``field.data`` and
|
|
380
|
+
the data of any other field on the form::
|
|
381
|
+
|
|
382
|
+
def available_groups(form, field):
|
|
383
|
+
return [SelectChoice(g.id, g.name) for g in form.tenant.data.groups]
|
|
384
|
+
|
|
385
|
+
class UserDetails(Form):
|
|
386
|
+
tenant = QuerySelectField('Tenant', ...)
|
|
387
|
+
group_id = SelectField('Group', coerce=int, choices=available_groups)
|
|
388
|
+
|
|
319
389
|
**Coerce function example**::
|
|
320
390
|
|
|
321
391
|
def coerce_none(value):
|
|
@@ -324,11 +394,57 @@ refer to a single input from the form.
|
|
|
324
394
|
return value
|
|
325
395
|
|
|
326
396
|
class NonePossible(Form):
|
|
327
|
-
my_select_field = SelectField('Select an option', choices=[
|
|
397
|
+
my_select_field = SelectField('Select an option', choices=[
|
|
398
|
+
SelectChoice('1', 'Option 1'),
|
|
399
|
+
SelectChoice('2', 'Option 2'),
|
|
400
|
+
SelectChoice('None', 'No option'),
|
|
401
|
+
], coerce=coerce_none)
|
|
328
402
|
|
|
329
403
|
Note when the option None is selected a 'None' str will be passed. By using a coerce
|
|
330
404
|
function the 'None' str will be converted to None.
|
|
331
405
|
|
|
406
|
+
**Select fields backed by an Enum**::
|
|
407
|
+
|
|
408
|
+
from enum import Enum
|
|
409
|
+
from wtforms.fields import enum_choices, enum_coerce
|
|
410
|
+
|
|
411
|
+
class Color(Enum):
|
|
412
|
+
RED = "red"
|
|
413
|
+
GREEN = "green"
|
|
414
|
+
BLUE = "blue"
|
|
415
|
+
|
|
416
|
+
class PaintForm(Form):
|
|
417
|
+
color = SelectField(
|
|
418
|
+
choices=enum_choices(Color),
|
|
419
|
+
coerce=enum_coerce(Color),
|
|
420
|
+
)
|
|
421
|
+
|
|
422
|
+
:func:`~wtforms.fields.enum_choices` builds the option list from
|
|
423
|
+
the Enum members; by default the HTML ``value`` of each option is the
|
|
424
|
+
member's ``value`` (``"red"``, ``"green"``, ...).
|
|
425
|
+
:func:`~wtforms.fields.enum_coerce` returns the matching ``coerce``
|
|
426
|
+
callable that resolves the submitted string back into a member, so
|
|
427
|
+
``form.color.data`` is a ``Color`` member after submit. Pre-selecting
|
|
428
|
+
works the usual way, with an Enum member: ``PaintForm(color=Color.RED)``.
|
|
429
|
+
|
|
430
|
+
Use ``by="name"`` on **both** helpers when the Enum value is not a good
|
|
431
|
+
transport identifier — non-unique, non-serialisable, or when you simply
|
|
432
|
+
want ``member.name`` on the wire::
|
|
433
|
+
|
|
434
|
+
class PaintForm(Form):
|
|
435
|
+
color = SelectField(
|
|
436
|
+
choices=enum_choices(Color, by="name"),
|
|
437
|
+
coerce=enum_coerce(Color, by="name"),
|
|
438
|
+
)
|
|
439
|
+
|
|
440
|
+
By default the option label is ``str(member)`` if the Enum defines its
|
|
441
|
+
own ``__str__`` (also the case for :class:`enum.StrEnum`), otherwise
|
|
442
|
+
``member.name``. To customise, pass a ``label`` callable taking an Enum
|
|
443
|
+
member and returning the label string::
|
|
444
|
+
|
|
445
|
+
enum_choices(Color, label=lambda member: member.name.title())
|
|
446
|
+
# → [SelectChoice('red', 'Red'), SelectChoice('green', 'Green'), SelectChoice('blue', 'Blue')]
|
|
447
|
+
|
|
332
448
|
**Skipping choice validation**::
|
|
333
449
|
|
|
334
450
|
class DynamicSelectForm(Form):
|
|
@@ -349,33 +465,35 @@ refer to a single input from the form.
|
|
|
349
465
|
a list of fields each representing an option. The rendering of this can be
|
|
350
466
|
further controlled by specifying `option_widget=`.
|
|
351
467
|
|
|
352
|
-
|
|
468
|
+
Use ``invalid_value_message=`` to customize errors raised when submitted
|
|
469
|
+
data cannot be coerced, and ``invalid_choice_message=`` to customize errors
|
|
470
|
+
raised when the value is not present in ``choices``.
|
|
353
471
|
|
|
354
|
-
.. autoclass:: SelectMultipleField(default field arguments, choices=
|
|
472
|
+
.. autoclass:: SelectMultipleField(default field arguments, choices=None, coerce=str, option_widget=None, invalid_value_message=None, invalid_choice_message=None)
|
|
355
473
|
|
|
356
474
|
The data on the SelectMultipleField is stored as a list of objects, each of
|
|
357
475
|
which is checked and coerced from the form input. Any submitted choices
|
|
358
476
|
which are not in the given choices list will cause validation on the field
|
|
359
477
|
to fail.
|
|
360
478
|
|
|
361
|
-
..
|
|
362
|
-
|
|
363
|
-
.. autoclass:: StringField(default field arguments)
|
|
364
|
-
|
|
365
|
-
.. code-block:: jinja
|
|
479
|
+
.. autofunction:: wtforms.fields.enum_choices
|
|
366
480
|
|
|
367
|
-
|
|
481
|
+
.. autofunction:: wtforms.fields.enum_coerce
|
|
368
482
|
|
|
369
|
-
|
|
483
|
+
Submit fields
|
|
484
|
+
-------------
|
|
370
485
|
|
|
371
|
-
|
|
486
|
+
WTForms provides boolean and string-based submit controls.
|
|
372
487
|
|
|
373
|
-
.. autoclass::
|
|
488
|
+
.. autoclass:: ButtonField(default field arguments)
|
|
374
489
|
|
|
490
|
+
.. autoclass:: SubmitField(default field arguments)
|
|
375
491
|
|
|
376
492
|
Convenience Fields
|
|
377
493
|
------------------
|
|
378
494
|
|
|
495
|
+
.. currentmodule:: wtforms.fields
|
|
496
|
+
|
|
379
497
|
.. autoclass:: HiddenField(default field arguments)
|
|
380
498
|
|
|
381
499
|
HiddenField is useful for providing data from a model or the application to
|
|
@@ -436,11 +554,24 @@ complex data structures such as lists and nested objects can be represented.
|
|
|
436
554
|
.. autoclass:: FieldList(unbound_field, default field arguments, min_entries=0, max_entries=None, separator='-')
|
|
437
555
|
|
|
438
556
|
**Note**: Due to a limitation in how HTML sends values, FieldList cannot enclose
|
|
439
|
-
:class:`BooleanField
|
|
557
|
+
:class:`BooleanField`, :class:`ButtonField`, or :class:`SubmitField`
|
|
558
|
+
instances.
|
|
440
559
|
|
|
441
560
|
.. automethod:: append_entry([data])
|
|
561
|
+
.. automethod:: insert_entry(index[, data])
|
|
442
562
|
.. automethod:: pop_entry
|
|
443
563
|
|
|
564
|
+
``append_entry`` and ``insert_entry`` accept Python object data for the new
|
|
565
|
+
entry, not submitted formdata. For simple enclosed fields this is usually a
|
|
566
|
+
scalar value. For ``FieldList(FormField(...))``, this may be a dict or an
|
|
567
|
+
object that the enclosed form can process, similar to passing ``data`` or
|
|
568
|
+
``obj`` when constructing that form.
|
|
569
|
+
|
|
570
|
+
Entries are kept with consecutive indices: ``insert_entry``,
|
|
571
|
+
``pop_entry`` and rebuilds from formdata renumber entries so that each
|
|
572
|
+
entry's ``index``, ``name`` and ``id`` reflect its position in
|
|
573
|
+
:attr:`entries`.
|
|
574
|
+
|
|
444
575
|
.. attribute:: entries
|
|
445
576
|
|
|
446
577
|
Each entry in a FieldList is actually an instance of the field you
|
|
@@ -448,8 +579,8 @@ complex data structures such as lists and nested objects can be represented.
|
|
|
448
579
|
FieldList works as expected, and proxies to the enclosed entries list.
|
|
449
580
|
|
|
450
581
|
**Do not** resize the entries list directly, this will result in
|
|
451
|
-
undefined behavior. See `append_entry` and `pop_entry`
|
|
452
|
-
manipulate the list.
|
|
582
|
+
undefined behavior. See `append_entry`, `insert_entry`, and `pop_entry`
|
|
583
|
+
for ways you can manipulate the list.
|
|
453
584
|
|
|
454
585
|
.. automethod:: __iter__
|
|
455
586
|
.. automethod:: __len__
|
|
@@ -461,7 +592,7 @@ complex data structures such as lists and nested objects can be represented.
|
|
|
461
592
|
FormField::
|
|
462
593
|
|
|
463
594
|
class IMForm(Form):
|
|
464
|
-
protocol = SelectField(choices=[('aim', 'AIM'), ('msn', 'MSN')])
|
|
595
|
+
protocol = SelectField(choices=[SelectChoice('aim', 'AIM'), SelectChoice('msn', 'MSN')])
|
|
465
596
|
username = StringField()
|
|
466
597
|
|
|
467
598
|
class ContactForm(Form):
|
|
@@ -470,6 +601,96 @@ complex data structures such as lists and nested objects can be represented.
|
|
|
470
601
|
im_accounts = FieldList(FormField(IMForm))
|
|
471
602
|
|
|
472
603
|
|
|
604
|
+
Data Lists
|
|
605
|
+
----------
|
|
606
|
+
|
|
607
|
+
.. currentmodule:: wtforms
|
|
608
|
+
|
|
609
|
+
.. class:: DataList(choices=None, *, render_kw=None, widget=None)
|
|
610
|
+
|
|
611
|
+
A :mdn-tag:`datalist` of suggestions. Unlike
|
|
612
|
+
:class:`~wtforms.fields.SelectField`, the
|
|
613
|
+
input stays a free-form text input: the browser displays the
|
|
614
|
+
suggestions but the user may type any value. Combine with
|
|
615
|
+
:class:`~wtforms.validators.AnyOf` if you need a closed set.
|
|
616
|
+
|
|
617
|
+
``choices`` is either a list of :class:`~wtforms.DataListChoice`
|
|
618
|
+
(or plain strings, in which case the string is used as both value
|
|
619
|
+
and label), a shorthand ``{value: label}`` dict, or a callable. The
|
|
620
|
+
callable runs when the datalist is rendered, after the whole form has been
|
|
621
|
+
processed. Its result is cached until the next
|
|
622
|
+
:meth:`~wtforms.form.BaseForm.post_process`. The callable may
|
|
623
|
+
take no argument (``fn()``) for a static list, or ``(form, field)``
|
|
624
|
+
to adapt the suggestions to the form data, for example the current
|
|
625
|
+
field value. This is convenient for server-side filtering::
|
|
626
|
+
|
|
627
|
+
DataList(lambda form, field: search(field.data))
|
|
628
|
+
|
|
629
|
+
**Inline DataList owned by a field**
|
|
630
|
+
|
|
631
|
+
Pass a :class:`DataList` instance directly as ``datalist=`` to get
|
|
632
|
+
a field-owned datalist with a unique id derived from the field's
|
|
633
|
+
id::
|
|
634
|
+
|
|
635
|
+
class F(Form):
|
|
636
|
+
country = StringField(datalist=DataList(["FR", "US"]))
|
|
637
|
+
|
|
638
|
+
{{ form.country() }}
|
|
639
|
+
{{ form.country.datalist() }}
|
|
640
|
+
|
|
641
|
+
``{{ form.country() }}`` emits the ``<input list="...">`` and
|
|
642
|
+
``{{ form.country.datalist() }}`` emits the matching
|
|
643
|
+
:mdn-tag:`datalist` element. Both calls are explicit, like
|
|
644
|
+
:attr:`Field.errors` or :attr:`Field.label` — you decide where each
|
|
645
|
+
piece appears in the DOM.
|
|
646
|
+
|
|
647
|
+
Inside a :class:`~wtforms.fields.FieldList`, each entry binds its
|
|
648
|
+
own clone of the inline :class:`DataList`, so a callable
|
|
649
|
+
``choices`` sees each entry's own value::
|
|
650
|
+
|
|
651
|
+
class F(Form):
|
|
652
|
+
items = FieldList(
|
|
653
|
+
StringField(
|
|
654
|
+
datalist=DataList(lambda form, field: suggest(field.data))
|
|
655
|
+
),
|
|
656
|
+
min_entries=3,
|
|
657
|
+
)
|
|
658
|
+
|
|
659
|
+
{% for entry in form.items %}
|
|
660
|
+
{{ entry() }}
|
|
661
|
+
{{ entry.datalist() }}
|
|
662
|
+
{% endfor %}
|
|
663
|
+
|
|
664
|
+
**String references**
|
|
665
|
+
|
|
666
|
+
A field may also reference an existing datalist id directly by
|
|
667
|
+
string::
|
|
668
|
+
|
|
669
|
+
class F(Form):
|
|
670
|
+
field = StringField(datalist="external")
|
|
671
|
+
|
|
672
|
+
The ``list="external"`` attribute is emitted as-is — the user is
|
|
673
|
+
expected to render the :mdn-tag:`datalist` element themselves (in
|
|
674
|
+
a shared layout, a static template, or another form on the same
|
|
675
|
+
page). ``field.datalist()`` returns empty markup in this case, so
|
|
676
|
+
calling it unconditionally in templates is safe.
|
|
677
|
+
|
|
678
|
+
**Compatible fields**
|
|
679
|
+
|
|
680
|
+
Any field accepts a ``datalist=`` parameter, but only text-based
|
|
681
|
+
HTML inputs (``text``, ``search``, ``email``, ``tel``, ``url``,
|
|
682
|
+
``number``, ``range``, ``color``, date/time inputs) honor the
|
|
683
|
+
``list=`` attribute. Passing ``datalist=`` to a
|
|
684
|
+
:class:`~wtforms.fields.SelectField` or
|
|
685
|
+
:class:`~wtforms.fields.TextAreaField` is silently ignored by the
|
|
686
|
+
browser.
|
|
687
|
+
|
|
688
|
+
.. autoclass:: DataListChoice
|
|
689
|
+
|
|
690
|
+
.. autofunction:: wtforms.datalist.enum_datalist
|
|
691
|
+
|
|
692
|
+
.. currentmodule:: wtforms.fields
|
|
693
|
+
|
|
473
694
|
Custom Fields
|
|
474
695
|
-------------
|
|
475
696
|
|
|
@@ -583,8 +804,7 @@ Additional Helper Classes
|
|
|
583
804
|
.. class:: Label
|
|
584
805
|
|
|
585
806
|
On all fields, the `label` property is an instance of this class.
|
|
586
|
-
Labels can be printed to yield a
|
|
587
|
-
``<label for="field_id">Label Text</label>``
|
|
807
|
+
Labels can be printed to yield a :mdn-tag:`label`
|
|
588
808
|
HTML tag enclosure. Similar to fields, you can also call the label with
|
|
589
809
|
additional html params.
|
|
590
810
|
|
|
@@ -151,11 +151,11 @@ The same principle applies for filters with the convention `filter_fieldname`::
|
|
|
151
151
|
class SignupForm(Form):
|
|
152
152
|
name = StringField('name')
|
|
153
153
|
|
|
154
|
-
def filter_name(form,
|
|
155
|
-
return
|
|
154
|
+
def filter_name(form, value):
|
|
155
|
+
return value.strip() if value is not None else value
|
|
156
156
|
|
|
157
157
|
Note that filters are applied after processing the default and incoming data,
|
|
158
|
-
but before validation.
|
|
158
|
+
but before validation. Filters should handle empty values such as `None`.
|
|
159
159
|
|
|
160
160
|
.. _using-forms:
|
|
161
161
|
|
|
@@ -294,6 +294,8 @@ fields.
|
|
|
294
294
|
this to provide form data to the enclosed fields. Accessing the field's
|
|
295
295
|
data before calling process is not recommended.
|
|
296
296
|
|
|
297
|
+
.. automethod:: post_process
|
|
298
|
+
|
|
297
299
|
.. automethod:: validate
|
|
298
300
|
|
|
299
301
|
.. automethod:: __iter__
|