WTForms 3.3.0b2__tar.gz → 3.3.0b3__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.3.0b2 → wtforms-3.3.0b3}/CHANGES.rst +11 -0
- {wtforms-3.3.0b2 → wtforms-3.3.0b3}/PKG-INFO +1 -1
- {wtforms-3.3.0b2 → wtforms-3.3.0b3}/docs/fields.rst +40 -19
- {wtforms-3.3.0b2 → wtforms-3.3.0b3}/src/wtforms/__init__.py +3 -1
- {wtforms-3.3.0b2 → wtforms-3.3.0b3}/src/wtforms/datalist.py +16 -11
- {wtforms-3.3.0b2 → wtforms-3.3.0b3}/src/wtforms/fields/__init__.py +4 -0
- {wtforms-3.3.0b2 → wtforms-3.3.0b3}/src/wtforms/fields/choices.py +56 -30
- {wtforms-3.3.0b2 → wtforms-3.3.0b3}/src/wtforms/fields/core.py +4 -1
- {wtforms-3.3.0b2 → wtforms-3.3.0b3}/src/wtforms/fields/form.py +2 -2
- {wtforms-3.3.0b2 → wtforms-3.3.0b3}/src/wtforms/fields/list.py +2 -2
- {wtforms-3.3.0b2 → wtforms-3.3.0b3}/src/wtforms/form.py +10 -5
- {wtforms-3.3.0b2 → wtforms-3.3.0b3}/tests/fields/test_form.py +6 -6
- {wtforms-3.3.0b2 → wtforms-3.3.0b3}/tests/fields/test_select.py +129 -37
- {wtforms-3.3.0b2 → wtforms-3.3.0b3}/tests/fields/test_selectmultiple.py +43 -6
- {wtforms-3.3.0b2 → wtforms-3.3.0b3}/tests/test_datalist.py +20 -0
- {wtforms-3.3.0b2 → wtforms-3.3.0b3}/.gitignore +0 -0
- {wtforms-3.3.0b2 → wtforms-3.3.0b3}/LICENSE.rst +0 -0
- {wtforms-3.3.0b2 → wtforms-3.3.0b3}/README.rst +0 -0
- {wtforms-3.3.0b2 → wtforms-3.3.0b3}/docs/Makefile +0 -0
- {wtforms-3.3.0b2 → wtforms-3.3.0b3}/docs/_static/logo_joined.svg +0 -0
- {wtforms-3.3.0b2 → wtforms-3.3.0b3}/docs/_static/logo_split.svg +0 -0
- {wtforms-3.3.0b2 → wtforms-3.3.0b3}/docs/_static/logo_square.svg +0 -0
- {wtforms-3.3.0b2 → wtforms-3.3.0b3}/docs/changes.rst +0 -0
- {wtforms-3.3.0b2 → wtforms-3.3.0b3}/docs/conf.py +0 -0
- {wtforms-3.3.0b2 → wtforms-3.3.0b3}/docs/contributing.rst +0 -0
- {wtforms-3.3.0b2 → wtforms-3.3.0b3}/docs/crash_course.rst +0 -0
- {wtforms-3.3.0b2 → wtforms-3.3.0b3}/docs/csrf.rst +0 -0
- {wtforms-3.3.0b2 → wtforms-3.3.0b3}/docs/ext.rst +0 -0
- {wtforms-3.3.0b2 → wtforms-3.3.0b3}/docs/faq.rst +0 -0
- {wtforms-3.3.0b2 → wtforms-3.3.0b3}/docs/forms.rst +0 -0
- {wtforms-3.3.0b2 → wtforms-3.3.0b3}/docs/i18n.rst +0 -0
- {wtforms-3.3.0b2 → wtforms-3.3.0b3}/docs/index.rst +0 -0
- {wtforms-3.3.0b2 → wtforms-3.3.0b3}/docs/license.rst +0 -0
- {wtforms-3.3.0b2 → wtforms-3.3.0b3}/docs/make.bat +0 -0
- {wtforms-3.3.0b2 → wtforms-3.3.0b3}/docs/meta.rst +0 -0
- {wtforms-3.3.0b2 → wtforms-3.3.0b3}/docs/specific_problems.rst +0 -0
- {wtforms-3.3.0b2 → wtforms-3.3.0b3}/docs/validators.rst +0 -0
- {wtforms-3.3.0b2 → wtforms-3.3.0b3}/docs/whats_new.rst +0 -0
- {wtforms-3.3.0b2 → wtforms-3.3.0b3}/docs/widgets.rst +0 -0
- {wtforms-3.3.0b2 → wtforms-3.3.0b3}/hatch_build.py +0 -0
- {wtforms-3.3.0b2 → wtforms-3.3.0b3}/pyproject.toml +0 -0
- {wtforms-3.3.0b2 → wtforms-3.3.0b3}/src/wtforms/_compat.py +0 -0
- {wtforms-3.3.0b2 → wtforms-3.3.0b3}/src/wtforms/csrf/__init__.py +0 -0
- {wtforms-3.3.0b2 → wtforms-3.3.0b3}/src/wtforms/csrf/core.py +0 -0
- {wtforms-3.3.0b2 → wtforms-3.3.0b3}/src/wtforms/csrf/session.py +0 -0
- {wtforms-3.3.0b2 → wtforms-3.3.0b3}/src/wtforms/fields/datetime.py +0 -0
- {wtforms-3.3.0b2 → wtforms-3.3.0b3}/src/wtforms/fields/numeric.py +0 -0
- {wtforms-3.3.0b2 → wtforms-3.3.0b3}/src/wtforms/fields/simple.py +0 -0
- {wtforms-3.3.0b2 → wtforms-3.3.0b3}/src/wtforms/i18n.py +0 -0
- {wtforms-3.3.0b2 → wtforms-3.3.0b3}/src/wtforms/locale/README.md +0 -0
- {wtforms-3.3.0b2 → wtforms-3.3.0b3}/src/wtforms/locale/ar/LC_MESSAGES/wtforms.mo +0 -0
- {wtforms-3.3.0b2 → wtforms-3.3.0b3}/src/wtforms/locale/ar/LC_MESSAGES/wtforms.po +0 -0
- {wtforms-3.3.0b2 → wtforms-3.3.0b3}/src/wtforms/locale/bg/LC_MESSAGES/wtforms.mo +0 -0
- {wtforms-3.3.0b2 → wtforms-3.3.0b3}/src/wtforms/locale/bg/LC_MESSAGES/wtforms.po +0 -0
- {wtforms-3.3.0b2 → wtforms-3.3.0b3}/src/wtforms/locale/ca/LC_MESSAGES/wtforms.mo +0 -0
- {wtforms-3.3.0b2 → wtforms-3.3.0b3}/src/wtforms/locale/ca/LC_MESSAGES/wtforms.po +0 -0
- {wtforms-3.3.0b2 → wtforms-3.3.0b3}/src/wtforms/locale/cs_CZ/LC_MESSAGES/wtforms.mo +0 -0
- {wtforms-3.3.0b2 → wtforms-3.3.0b3}/src/wtforms/locale/cs_CZ/LC_MESSAGES/wtforms.po +0 -0
- {wtforms-3.3.0b2 → wtforms-3.3.0b3}/src/wtforms/locale/cy/LC_MESSAGES/wtforms.mo +0 -0
- {wtforms-3.3.0b2 → wtforms-3.3.0b3}/src/wtforms/locale/cy/LC_MESSAGES/wtforms.po +0 -0
- {wtforms-3.3.0b2 → wtforms-3.3.0b3}/src/wtforms/locale/de/LC_MESSAGES/wtforms.mo +0 -0
- {wtforms-3.3.0b2 → wtforms-3.3.0b3}/src/wtforms/locale/de/LC_MESSAGES/wtforms.po +0 -0
- {wtforms-3.3.0b2 → wtforms-3.3.0b3}/src/wtforms/locale/de_CH/LC_MESSAGES/wtforms.mo +0 -0
- {wtforms-3.3.0b2 → wtforms-3.3.0b3}/src/wtforms/locale/de_CH/LC_MESSAGES/wtforms.po +0 -0
- {wtforms-3.3.0b2 → wtforms-3.3.0b3}/src/wtforms/locale/el/LC_MESSAGES/wtforms.mo +0 -0
- {wtforms-3.3.0b2 → wtforms-3.3.0b3}/src/wtforms/locale/el/LC_MESSAGES/wtforms.po +0 -0
- {wtforms-3.3.0b2 → wtforms-3.3.0b3}/src/wtforms/locale/en/LC_MESSAGES/wtforms.mo +0 -0
- {wtforms-3.3.0b2 → wtforms-3.3.0b3}/src/wtforms/locale/en/LC_MESSAGES/wtforms.po +0 -0
- {wtforms-3.3.0b2 → wtforms-3.3.0b3}/src/wtforms/locale/es/LC_MESSAGES/wtforms.mo +0 -0
- {wtforms-3.3.0b2 → wtforms-3.3.0b3}/src/wtforms/locale/es/LC_MESSAGES/wtforms.po +0 -0
- {wtforms-3.3.0b2 → wtforms-3.3.0b3}/src/wtforms/locale/et/LC_MESSAGES/wtforms.mo +0 -0
- {wtforms-3.3.0b2 → wtforms-3.3.0b3}/src/wtforms/locale/et/LC_MESSAGES/wtforms.po +0 -0
- {wtforms-3.3.0b2 → wtforms-3.3.0b3}/src/wtforms/locale/fa/LC_MESSAGES/wtforms.mo +0 -0
- {wtforms-3.3.0b2 → wtforms-3.3.0b3}/src/wtforms/locale/fa/LC_MESSAGES/wtforms.po +0 -0
- {wtforms-3.3.0b2 → wtforms-3.3.0b3}/src/wtforms/locale/fi/LC_MESSAGES/wtforms.mo +0 -0
- {wtforms-3.3.0b2 → wtforms-3.3.0b3}/src/wtforms/locale/fi/LC_MESSAGES/wtforms.po +0 -0
- {wtforms-3.3.0b2 → wtforms-3.3.0b3}/src/wtforms/locale/fr/LC_MESSAGES/wtforms.mo +0 -0
- {wtforms-3.3.0b2 → wtforms-3.3.0b3}/src/wtforms/locale/fr/LC_MESSAGES/wtforms.po +0 -0
- {wtforms-3.3.0b2 → wtforms-3.3.0b3}/src/wtforms/locale/he/LC_MESSAGES/wtforms.mo +0 -0
- {wtforms-3.3.0b2 → wtforms-3.3.0b3}/src/wtforms/locale/he/LC_MESSAGES/wtforms.po +0 -0
- {wtforms-3.3.0b2 → wtforms-3.3.0b3}/src/wtforms/locale/hu/LC_MESSAGES/wtforms.mo +0 -0
- {wtforms-3.3.0b2 → wtforms-3.3.0b3}/src/wtforms/locale/hu/LC_MESSAGES/wtforms.po +0 -0
- {wtforms-3.3.0b2 → wtforms-3.3.0b3}/src/wtforms/locale/it/LC_MESSAGES/wtforms.mo +0 -0
- {wtforms-3.3.0b2 → wtforms-3.3.0b3}/src/wtforms/locale/it/LC_MESSAGES/wtforms.po +0 -0
- {wtforms-3.3.0b2 → wtforms-3.3.0b3}/src/wtforms/locale/ja/LC_MESSAGES/wtforms.mo +0 -0
- {wtforms-3.3.0b2 → wtforms-3.3.0b3}/src/wtforms/locale/ja/LC_MESSAGES/wtforms.po +0 -0
- {wtforms-3.3.0b2 → wtforms-3.3.0b3}/src/wtforms/locale/kk/LC_MESSAGES/wtforms.mo +0 -0
- {wtforms-3.3.0b2 → wtforms-3.3.0b3}/src/wtforms/locale/kk/LC_MESSAGES/wtforms.po +0 -0
- {wtforms-3.3.0b2 → wtforms-3.3.0b3}/src/wtforms/locale/ko/LC_MESSAGES/wtforms.mo +0 -0
- {wtforms-3.3.0b2 → wtforms-3.3.0b3}/src/wtforms/locale/ko/LC_MESSAGES/wtforms.po +0 -0
- {wtforms-3.3.0b2 → wtforms-3.3.0b3}/src/wtforms/locale/nb/LC_MESSAGES/wtforms.mo +0 -0
- {wtforms-3.3.0b2 → wtforms-3.3.0b3}/src/wtforms/locale/nb/LC_MESSAGES/wtforms.po +0 -0
- {wtforms-3.3.0b2 → wtforms-3.3.0b3}/src/wtforms/locale/nl/LC_MESSAGES/wtforms.mo +0 -0
- {wtforms-3.3.0b2 → wtforms-3.3.0b3}/src/wtforms/locale/nl/LC_MESSAGES/wtforms.po +0 -0
- {wtforms-3.3.0b2 → wtforms-3.3.0b3}/src/wtforms/locale/pl/LC_MESSAGES/wtforms.mo +0 -0
- {wtforms-3.3.0b2 → wtforms-3.3.0b3}/src/wtforms/locale/pl/LC_MESSAGES/wtforms.po +0 -0
- {wtforms-3.3.0b2 → wtforms-3.3.0b3}/src/wtforms/locale/pt/LC_MESSAGES/wtforms.mo +0 -0
- {wtforms-3.3.0b2 → wtforms-3.3.0b3}/src/wtforms/locale/pt/LC_MESSAGES/wtforms.po +0 -0
- {wtforms-3.3.0b2 → wtforms-3.3.0b3}/src/wtforms/locale/ro/LC_MESSAGES/wtforms.mo +0 -0
- {wtforms-3.3.0b2 → wtforms-3.3.0b3}/src/wtforms/locale/ro/LC_MESSAGES/wtforms.po +0 -0
- {wtforms-3.3.0b2 → wtforms-3.3.0b3}/src/wtforms/locale/ru/LC_MESSAGES/wtforms.mo +0 -0
- {wtforms-3.3.0b2 → wtforms-3.3.0b3}/src/wtforms/locale/ru/LC_MESSAGES/wtforms.po +0 -0
- {wtforms-3.3.0b2 → wtforms-3.3.0b3}/src/wtforms/locale/sk/LC_MESSAGES/wtforms.mo +0 -0
- {wtforms-3.3.0b2 → wtforms-3.3.0b3}/src/wtforms/locale/sk/LC_MESSAGES/wtforms.po +0 -0
- {wtforms-3.3.0b2 → wtforms-3.3.0b3}/src/wtforms/locale/sr/LC_MESSAGES/wtforms.mo +0 -0
- {wtforms-3.3.0b2 → wtforms-3.3.0b3}/src/wtforms/locale/sr/LC_MESSAGES/wtforms.po +0 -0
- {wtforms-3.3.0b2 → wtforms-3.3.0b3}/src/wtforms/locale/sv/LC_MESSAGES/wtforms.mo +0 -0
- {wtforms-3.3.0b2 → wtforms-3.3.0b3}/src/wtforms/locale/sv/LC_MESSAGES/wtforms.po +0 -0
- {wtforms-3.3.0b2 → wtforms-3.3.0b3}/src/wtforms/locale/ta/LC_MESSAGES/wtforms.mo +0 -0
- {wtforms-3.3.0b2 → wtforms-3.3.0b3}/src/wtforms/locale/ta/LC_MESSAGES/wtforms.po +0 -0
- {wtforms-3.3.0b2 → wtforms-3.3.0b3}/src/wtforms/locale/tr/LC_MESSAGES/wtforms.mo +0 -0
- {wtforms-3.3.0b2 → wtforms-3.3.0b3}/src/wtforms/locale/tr/LC_MESSAGES/wtforms.po +0 -0
- {wtforms-3.3.0b2 → wtforms-3.3.0b3}/src/wtforms/locale/uk/LC_MESSAGES/wtforms.mo +0 -0
- {wtforms-3.3.0b2 → wtforms-3.3.0b3}/src/wtforms/locale/uk/LC_MESSAGES/wtforms.po +0 -0
- {wtforms-3.3.0b2 → wtforms-3.3.0b3}/src/wtforms/locale/wtforms.pot +0 -0
- {wtforms-3.3.0b2 → wtforms-3.3.0b3}/src/wtforms/locale/zh/LC_MESSAGES/wtforms.mo +0 -0
- {wtforms-3.3.0b2 → wtforms-3.3.0b3}/src/wtforms/locale/zh/LC_MESSAGES/wtforms.po +0 -0
- {wtforms-3.3.0b2 → wtforms-3.3.0b3}/src/wtforms/locale/zh_TW/LC_MESSAGES/wtforms.mo +0 -0
- {wtforms-3.3.0b2 → wtforms-3.3.0b3}/src/wtforms/locale/zh_TW/LC_MESSAGES/wtforms.po +0 -0
- {wtforms-3.3.0b2 → wtforms-3.3.0b3}/src/wtforms/meta.py +0 -0
- {wtforms-3.3.0b2 → wtforms-3.3.0b3}/src/wtforms/utils.py +0 -0
- {wtforms-3.3.0b2 → wtforms-3.3.0b3}/src/wtforms/validators.py +0 -0
- {wtforms-3.3.0b2 → wtforms-3.3.0b3}/src/wtforms/widgets/__init__.py +0 -0
- {wtforms-3.3.0b2 → wtforms-3.3.0b3}/src/wtforms/widgets/core.py +0 -0
- {wtforms-3.3.0b2 → wtforms-3.3.0b3}/tests/__init__.py +0 -0
- {wtforms-3.3.0b2 → wtforms-3.3.0b3}/tests/common.py +0 -0
- {wtforms-3.3.0b2 → wtforms-3.3.0b3}/tests/conftest.py +0 -0
- {wtforms-3.3.0b2 → wtforms-3.3.0b3}/tests/fields/__init__.py +0 -0
- {wtforms-3.3.0b2 → wtforms-3.3.0b3}/tests/fields/test_boolean.py +0 -0
- {wtforms-3.3.0b2 → wtforms-3.3.0b3}/tests/fields/test_button.py +0 -0
- {wtforms-3.3.0b2 → wtforms-3.3.0b3}/tests/fields/test_color.py +0 -0
- {wtforms-3.3.0b2 → wtforms-3.3.0b3}/tests/fields/test_custom.py +0 -0
- {wtforms-3.3.0b2 → wtforms-3.3.0b3}/tests/fields/test_date.py +0 -0
- {wtforms-3.3.0b2 → wtforms-3.3.0b3}/tests/fields/test_datetime.py +0 -0
- {wtforms-3.3.0b2 → wtforms-3.3.0b3}/tests/fields/test_datetimelocal.py +0 -0
- {wtforms-3.3.0b2 → wtforms-3.3.0b3}/tests/fields/test_decimal.py +0 -0
- {wtforms-3.3.0b2 → wtforms-3.3.0b3}/tests/fields/test_defaults.py +0 -0
- {wtforms-3.3.0b2 → wtforms-3.3.0b3}/tests/fields/test_field.py +0 -0
- {wtforms-3.3.0b2 → wtforms-3.3.0b3}/tests/fields/test_file.py +0 -0
- {wtforms-3.3.0b2 → wtforms-3.3.0b3}/tests/fields/test_filters.py +0 -0
- {wtforms-3.3.0b2 → wtforms-3.3.0b3}/tests/fields/test_flags.py +0 -0
- {wtforms-3.3.0b2 → wtforms-3.3.0b3}/tests/fields/test_float.py +0 -0
- {wtforms-3.3.0b2 → wtforms-3.3.0b3}/tests/fields/test_hidden.py +0 -0
- {wtforms-3.3.0b2 → wtforms-3.3.0b3}/tests/fields/test_html5.py +0 -0
- {wtforms-3.3.0b2 → wtforms-3.3.0b3}/tests/fields/test_integer.py +0 -0
- {wtforms-3.3.0b2 → wtforms-3.3.0b3}/tests/fields/test_label.py +0 -0
- {wtforms-3.3.0b2 → wtforms-3.3.0b3}/tests/fields/test_list.py +0 -0
- {wtforms-3.3.0b2 → wtforms-3.3.0b3}/tests/fields/test_month.py +0 -0
- {wtforms-3.3.0b2 → wtforms-3.3.0b3}/tests/fields/test_password.py +0 -0
- {wtforms-3.3.0b2 → wtforms-3.3.0b3}/tests/fields/test_radio.py +0 -0
- {wtforms-3.3.0b2 → wtforms-3.3.0b3}/tests/fields/test_string.py +0 -0
- {wtforms-3.3.0b2 → wtforms-3.3.0b3}/tests/fields/test_submit.py +0 -0
- {wtforms-3.3.0b2 → wtforms-3.3.0b3}/tests/fields/test_textarea.py +0 -0
- {wtforms-3.3.0b2 → wtforms-3.3.0b3}/tests/fields/test_time.py +0 -0
- {wtforms-3.3.0b2 → wtforms-3.3.0b3}/tests/fields/test_week.py +0 -0
- {wtforms-3.3.0b2 → wtforms-3.3.0b3}/tests/test_csrf.py +0 -0
- {wtforms-3.3.0b2 → wtforms-3.3.0b3}/tests/test_form.py +0 -0
- {wtforms-3.3.0b2 → wtforms-3.3.0b3}/tests/test_i18n.py +0 -0
- {wtforms-3.3.0b2 → wtforms-3.3.0b3}/tests/test_locale_babel.py +0 -0
- {wtforms-3.3.0b2 → wtforms-3.3.0b3}/tests/test_webob.py +0 -0
- {wtforms-3.3.0b2 → wtforms-3.3.0b3}/tests/test_widgets.py +0 -0
- {wtforms-3.3.0b2 → wtforms-3.3.0b3}/tests/validators/__init__.py +0 -0
- {wtforms-3.3.0b2 → wtforms-3.3.0b3}/tests/validators/test_anyof.py +0 -0
- {wtforms-3.3.0b2 → wtforms-3.3.0b3}/tests/validators/test_data_required.py +0 -0
- {wtforms-3.3.0b2 → wtforms-3.3.0b3}/tests/validators/test_date_range.py +0 -0
- {wtforms-3.3.0b2 → wtforms-3.3.0b3}/tests/validators/test_disabled.py +0 -0
- {wtforms-3.3.0b2 → wtforms-3.3.0b3}/tests/validators/test_email.py +0 -0
- {wtforms-3.3.0b2 → wtforms-3.3.0b3}/tests/validators/test_equal_to.py +0 -0
- {wtforms-3.3.0b2 → wtforms-3.3.0b3}/tests/validators/test_input_required.py +0 -0
- {wtforms-3.3.0b2 → wtforms-3.3.0b3}/tests/validators/test_ipaddress.py +0 -0
- {wtforms-3.3.0b2 → wtforms-3.3.0b3}/tests/validators/test_lazy_proxy.py +0 -0
- {wtforms-3.3.0b2 → wtforms-3.3.0b3}/tests/validators/test_length.py +0 -0
- {wtforms-3.3.0b2 → wtforms-3.3.0b3}/tests/validators/test_mac_address.py +0 -0
- {wtforms-3.3.0b2 → wtforms-3.3.0b3}/tests/validators/test_noneof.py +0 -0
- {wtforms-3.3.0b2 → wtforms-3.3.0b3}/tests/validators/test_number_range.py +0 -0
- {wtforms-3.3.0b2 → wtforms-3.3.0b3}/tests/validators/test_optional.py +0 -0
- {wtforms-3.3.0b2 → wtforms-3.3.0b3}/tests/validators/test_readonly.py +0 -0
- {wtforms-3.3.0b2 → wtforms-3.3.0b3}/tests/validators/test_regexp.py +0 -0
- {wtforms-3.3.0b2 → wtforms-3.3.0b3}/tests/validators/test_url.py +0 -0
- {wtforms-3.3.0b2 → wtforms-3.3.0b3}/tests/validators/test_uuid.py +0 -0
- {wtforms-3.3.0b2 → wtforms-3.3.0b3}/tests/validators/test_validators.py +0 -0
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
.. currentmodule:: wtforms
|
|
2
2
|
|
|
3
|
+
Version 3.3.0b3
|
|
4
|
+
---------------
|
|
5
|
+
|
|
6
|
+
Released 2026-06-12
|
|
7
|
+
|
|
8
|
+
- Add :func:`fields.enum_choices`, :func:`fields.enum_coerce` and
|
|
9
|
+
:func:`~wtforms.datalist.enum_datalist` for Enum-backed choices,
|
|
10
|
+
replacing ``SelectChoice.from_enum`` and friends. :issue:`922`
|
|
11
|
+
- An Enum ``coerce`` is no longer wrapped, and options default to
|
|
12
|
+
``member.value``. :issue:`922`
|
|
13
|
+
|
|
3
14
|
Version 3.3.0b2
|
|
4
15
|
---------------
|
|
5
16
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: WTForms
|
|
3
|
-
Version: 3.3.
|
|
3
|
+
Version: 3.3.0b3
|
|
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
|
|
@@ -337,9 +337,7 @@ Choice Fields
|
|
|
337
337
|
option cannot be applied to your problem you may wish to skip choice
|
|
338
338
|
validation (see below).
|
|
339
339
|
|
|
340
|
-
**Select fields with
|
|
341
|
-
|
|
342
|
-
Use :class:`SelectChoice` to assign an option to an ``<optgroup>``.
|
|
340
|
+
**Select fields with optgroup**::
|
|
343
341
|
|
|
344
342
|
class PastebinEntry(Form):
|
|
345
343
|
language = SelectField('Programming Language', choices=[
|
|
@@ -349,6 +347,8 @@ Choice Fields
|
|
|
349
347
|
SelectChoice('text', 'Plain Text'),
|
|
350
348
|
])
|
|
351
349
|
|
|
350
|
+
Use :class:`SelectChoice` to assign an option to an ``<optgroup>``.
|
|
351
|
+
|
|
352
352
|
**Select fields with dynamic choice values**::
|
|
353
353
|
|
|
354
354
|
def available_groups():
|
|
@@ -400,28 +400,44 @@ Choice Fields
|
|
|
400
400
|
**Select fields backed by an Enum**::
|
|
401
401
|
|
|
402
402
|
from enum import Enum
|
|
403
|
+
from wtforms.fields import enum_choices, enum_coerce
|
|
403
404
|
|
|
404
405
|
class Color(Enum):
|
|
405
|
-
RED =
|
|
406
|
-
GREEN =
|
|
407
|
-
BLUE =
|
|
406
|
+
RED = "red"
|
|
407
|
+
GREEN = "green"
|
|
408
|
+
BLUE = "blue"
|
|
408
409
|
|
|
409
410
|
class PaintForm(Form):
|
|
410
|
-
color = SelectField(
|
|
411
|
+
color = SelectField(
|
|
412
|
+
choices=enum_choices(Color),
|
|
413
|
+
coerce=enum_coerce(Color),
|
|
414
|
+
)
|
|
415
|
+
|
|
416
|
+
:func:`~wtforms.fields.enum_choices` builds the option list from
|
|
417
|
+
the Enum members; by default the HTML ``value`` of each option is the
|
|
418
|
+
member's ``value`` (``"red"``, ``"green"``, ...).
|
|
419
|
+
:func:`~wtforms.fields.enum_coerce` returns the matching ``coerce``
|
|
420
|
+
callable that resolves the submitted string back into a member, so
|
|
421
|
+
``form.color.data`` is a ``Color`` member after submit. Pre-selecting
|
|
422
|
+
works the usual way, with an Enum member: ``PaintForm(color=Color.RED)``.
|
|
411
423
|
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
424
|
+
Use ``by="name"`` on **both** helpers when the Enum value is not a good
|
|
425
|
+
transport identifier — non-unique, non-serialisable, or when you simply
|
|
426
|
+
want ``member.name`` on the wire::
|
|
427
|
+
|
|
428
|
+
class PaintForm(Form):
|
|
429
|
+
color = SelectField(
|
|
430
|
+
choices=enum_choices(Color, by="name"),
|
|
431
|
+
coerce=enum_coerce(Color, by="name"),
|
|
432
|
+
)
|
|
417
433
|
|
|
418
|
-
By default the option label is ``str(
|
|
434
|
+
By default the option label is ``str(member)`` if the Enum defines its
|
|
419
435
|
own ``__str__`` (also the case for :class:`enum.StrEnum`), otherwise
|
|
420
|
-
``
|
|
421
|
-
|
|
436
|
+
``member.name``. To customise, pass a ``label`` callable taking an Enum
|
|
437
|
+
member and returning the label string::
|
|
422
438
|
|
|
423
|
-
|
|
424
|
-
# → [SelectChoice('
|
|
439
|
+
enum_choices(Color, label=lambda member: member.name.title())
|
|
440
|
+
# → [SelectChoice('red', 'Red'), SelectChoice('green', 'Green'), SelectChoice('blue', 'Blue')]
|
|
425
441
|
|
|
426
442
|
**Skipping choice validation**::
|
|
427
443
|
|
|
@@ -454,6 +470,10 @@ Choice Fields
|
|
|
454
470
|
which are not in the given choices list will cause validation on the field
|
|
455
471
|
to fail.
|
|
456
472
|
|
|
473
|
+
.. autofunction:: wtforms.fields.enum_choices
|
|
474
|
+
|
|
475
|
+
.. autofunction:: wtforms.fields.enum_coerce
|
|
476
|
+
|
|
457
477
|
Submit fields
|
|
458
478
|
-------------
|
|
459
479
|
|
|
@@ -580,8 +600,6 @@ Data Lists
|
|
|
580
600
|
|
|
581
601
|
.. currentmodule:: wtforms
|
|
582
602
|
|
|
583
|
-
.. autoclass:: DataListChoice
|
|
584
|
-
|
|
585
603
|
.. class:: DataList(choices=None, *, render_kw=None, widget=None)
|
|
586
604
|
|
|
587
605
|
A :mdn-tag:`datalist` of suggestions. Unlike
|
|
@@ -658,6 +676,9 @@ Data Lists
|
|
|
658
676
|
:class:`~wtforms.fields.TextAreaField` is silently ignored by the
|
|
659
677
|
browser.
|
|
660
678
|
|
|
679
|
+
.. autoclass:: DataListChoice
|
|
680
|
+
|
|
681
|
+
.. autofunction:: wtforms.datalist.enum_datalist
|
|
661
682
|
|
|
662
683
|
.. currentmodule:: wtforms.fields
|
|
663
684
|
|
|
@@ -2,6 +2,7 @@ from wtforms import validators
|
|
|
2
2
|
from wtforms import widgets
|
|
3
3
|
from wtforms.datalist import DataList
|
|
4
4
|
from wtforms.datalist import DataListChoice
|
|
5
|
+
from wtforms.datalist import enum_datalist
|
|
5
6
|
from wtforms.fields.choices import Choice
|
|
6
7
|
from wtforms.fields.choices import RadioField
|
|
7
8
|
from wtforms.fields.choices import SelectChoice
|
|
@@ -41,7 +42,7 @@ from wtforms.fields.simple import URLField
|
|
|
41
42
|
from wtforms.form import Form
|
|
42
43
|
from wtforms.validators import ValidationError
|
|
43
44
|
|
|
44
|
-
__version__ = "3.3.
|
|
45
|
+
__version__ = "3.3.0b3"
|
|
45
46
|
|
|
46
47
|
__all__ = [
|
|
47
48
|
"validators",
|
|
@@ -86,4 +87,5 @@ __all__ = [
|
|
|
86
87
|
"Choice",
|
|
87
88
|
"SelectChoice",
|
|
88
89
|
"DataListChoice",
|
|
90
|
+
"enum_datalist",
|
|
89
91
|
]
|
|
@@ -4,9 +4,10 @@ from dataclasses import field
|
|
|
4
4
|
|
|
5
5
|
from wtforms import widgets
|
|
6
6
|
from wtforms._compat import get_signature
|
|
7
|
+
from wtforms.fields.choices import _enum_options
|
|
7
8
|
from wtforms.fields.choices import Choice
|
|
8
9
|
|
|
9
|
-
__all__ = ("DataList", "DataListChoice")
|
|
10
|
+
__all__ = ("DataList", "DataListChoice", "enum_datalist")
|
|
10
11
|
|
|
11
12
|
|
|
12
13
|
@dataclass
|
|
@@ -35,16 +36,6 @@ class DataListChoice:
|
|
|
35
36
|
def __iter__(self):
|
|
36
37
|
return iter((self.value, self.label, self.render_kw))
|
|
37
38
|
|
|
38
|
-
@classmethod
|
|
39
|
-
def from_enum(cls, enum_cls, *, label=None):
|
|
40
|
-
"""Build a list of choices from an :class:`enum.Enum` class.
|
|
41
|
-
|
|
42
|
-
See :meth:`SelectChoice.from_enum` for details.
|
|
43
|
-
"""
|
|
44
|
-
if label is None:
|
|
45
|
-
label = str if "__str__" in enum_cls.__dict__ else lambda m: m.name
|
|
46
|
-
return [cls(value=m.name, label=label(m)) for m in enum_cls]
|
|
47
|
-
|
|
48
39
|
@classmethod
|
|
49
40
|
def from_input(cls, input):
|
|
50
41
|
"""Coerce a value passed by the user into a :class:`DataListChoice`."""
|
|
@@ -73,6 +64,20 @@ class DataListChoice:
|
|
|
73
64
|
return cls(*input)
|
|
74
65
|
|
|
75
66
|
|
|
67
|
+
def enum_datalist(enum_cls, *, by="value", label=None):
|
|
68
|
+
"""Build a list of :class:`DataListChoice` from an :class:`enum.Enum` class.
|
|
69
|
+
|
|
70
|
+
Same semantics as :func:`~wtforms.fields.enum_choices`: ``by`` selects
|
|
71
|
+
which member attribute becomes the ``<option>`` value (``"value"`` by
|
|
72
|
+
default, ``"name"`` otherwise) and ``label`` defaults to ``str(member)``
|
|
73
|
+
when the Enum defines its own ``__str__``, else ``member.name``.
|
|
74
|
+
|
|
75
|
+
A ``<datalist>`` only suggests values for a free-text field, so there is
|
|
76
|
+
no coercion counterpart — the field stores whatever string is submitted.
|
|
77
|
+
"""
|
|
78
|
+
return _enum_options(enum_cls, by, label, DataListChoice)
|
|
79
|
+
|
|
80
|
+
|
|
76
81
|
class DataList:
|
|
77
82
|
"""A ``<datalist>`` of suggestions attached to a single field.
|
|
78
83
|
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
from wtforms.fields.choices import Choice
|
|
2
|
+
from wtforms.fields.choices import enum_choices
|
|
3
|
+
from wtforms.fields.choices import enum_coerce
|
|
2
4
|
from wtforms.fields.choices import RadioField
|
|
3
5
|
from wtforms.fields.choices import SelectChoice
|
|
4
6
|
from wtforms.fields.choices import SelectField
|
|
@@ -46,6 +48,8 @@ __all__ = [
|
|
|
46
48
|
"SelectMultipleField",
|
|
47
49
|
"SelectFieldBase",
|
|
48
50
|
"RadioField",
|
|
51
|
+
"enum_choices",
|
|
52
|
+
"enum_coerce",
|
|
49
53
|
"DateTimeField",
|
|
50
54
|
"DateField",
|
|
51
55
|
"TimeField",
|
|
@@ -2,7 +2,6 @@ import warnings
|
|
|
2
2
|
from dataclasses import dataclass
|
|
3
3
|
from dataclasses import field
|
|
4
4
|
from dataclasses import replace
|
|
5
|
-
from enum import Enum
|
|
6
5
|
from itertools import groupby
|
|
7
6
|
from operator import attrgetter
|
|
8
7
|
from typing import NamedTuple
|
|
@@ -21,18 +20,6 @@ __all__ = (
|
|
|
21
20
|
)
|
|
22
21
|
|
|
23
22
|
|
|
24
|
-
def _enum_coerce(enum_cls):
|
|
25
|
-
def coerce(v):
|
|
26
|
-
if isinstance(v, enum_cls):
|
|
27
|
-
return v
|
|
28
|
-
try:
|
|
29
|
-
return enum_cls[v]
|
|
30
|
-
except KeyError as e:
|
|
31
|
-
raise ValueError(str(e)) from e
|
|
32
|
-
|
|
33
|
-
return coerce
|
|
34
|
-
|
|
35
|
-
|
|
36
23
|
class Choice(NamedTuple):
|
|
37
24
|
"""
|
|
38
25
|
A rendered option yielded by
|
|
@@ -90,19 +77,6 @@ class SelectChoice:
|
|
|
90
77
|
def __iter__(self):
|
|
91
78
|
return iter((self.value, self.label, self.render_kw, self.optgroup))
|
|
92
79
|
|
|
93
|
-
@classmethod
|
|
94
|
-
def from_enum(cls, enum_cls, *, label=None):
|
|
95
|
-
"""Build a list of choices from an :class:`enum.Enum` class.
|
|
96
|
-
|
|
97
|
-
The HTML value of each option is the item ``name``. The label
|
|
98
|
-
defaults to ``str(item)`` when the Enum defines its own
|
|
99
|
-
``__str__``, otherwise to ``item.name``. Pass ``label=`` (a
|
|
100
|
-
callable taking an item) to override.
|
|
101
|
-
"""
|
|
102
|
-
if label is None:
|
|
103
|
-
label = str if "__str__" in enum_cls.__dict__ else lambda m: m.name
|
|
104
|
-
return [cls(value=m.name, label=label(m)) for m in enum_cls]
|
|
105
|
-
|
|
106
80
|
@classmethod
|
|
107
81
|
def from_input(cls, input, optgroup=None):
|
|
108
82
|
"""Coerce a value passed by the user via ``choices=...`` into a
|
|
@@ -141,6 +115,60 @@ class SelectChoice:
|
|
|
141
115
|
return cls(*input, optgroup=optgroup)
|
|
142
116
|
|
|
143
117
|
|
|
118
|
+
def _enum_options(enum_cls, by, label, factory):
|
|
119
|
+
if by not in ("value", "name"):
|
|
120
|
+
raise ValueError(f"by must be 'value' or 'name', got {by!r}")
|
|
121
|
+
if label is None:
|
|
122
|
+
label = str if "__str__" in enum_cls.__dict__ else lambda m: m.name
|
|
123
|
+
return [factory(value=getattr(m, by), label=label(m)) for m in enum_cls]
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def enum_choices(enum_cls, *, by="value", label=None):
|
|
127
|
+
"""Build a list of :class:`SelectChoice` from an :class:`enum.Enum` class.
|
|
128
|
+
|
|
129
|
+
``by`` selects which member attribute becomes the HTML ``value=``:
|
|
130
|
+
``"value"`` (the default) emits ``member.value``, ``"name"`` emits
|
|
131
|
+
``member.name``. Use ``"name"`` for enums whose values are not good
|
|
132
|
+
transport identifiers (non-string, non-unique or non-serialisable).
|
|
133
|
+
|
|
134
|
+
The label defaults to ``str(member)`` when the Enum defines its own
|
|
135
|
+
``__str__``, otherwise to ``member.name``. Pass ``label=`` (a callable
|
|
136
|
+
taking a member) to override.
|
|
137
|
+
|
|
138
|
+
Pair with :func:`enum_coerce` using the same ``by`` to round-trip the
|
|
139
|
+
submitted string back into a member.
|
|
140
|
+
"""
|
|
141
|
+
return _enum_options(enum_cls, by, label, SelectChoice)
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def enum_coerce(enum_cls, *, by="value"):
|
|
145
|
+
"""Return a ``coerce`` callable mapping a submitted string back to an
|
|
146
|
+
:class:`enum.Enum` member.
|
|
147
|
+
|
|
148
|
+
``by`` must match the one passed to :func:`enum_choices`: ``"value"``
|
|
149
|
+
(the default) resolves ``str(member.value)`` — so ``IntEnum`` values
|
|
150
|
+
round-trip too — and ``"name"`` resolves ``member.name``. Already-coerced
|
|
151
|
+
members pass through unchanged.
|
|
152
|
+
"""
|
|
153
|
+
if by not in ("value", "name"):
|
|
154
|
+
raise ValueError(f"by must be 'value' or 'name', got {by!r}")
|
|
155
|
+
|
|
156
|
+
def coerce(v):
|
|
157
|
+
if isinstance(v, enum_cls):
|
|
158
|
+
return v
|
|
159
|
+
|
|
160
|
+
try:
|
|
161
|
+
if by == "value":
|
|
162
|
+
by_value = {str(m.value): m for m in enum_cls}
|
|
163
|
+
return by_value[str(v)]
|
|
164
|
+
else:
|
|
165
|
+
return enum_cls[v]
|
|
166
|
+
except KeyError as e:
|
|
167
|
+
raise ValueError(str(e)) from e
|
|
168
|
+
|
|
169
|
+
return coerce
|
|
170
|
+
|
|
171
|
+
|
|
144
172
|
def _normalize_iter_choice(choice):
|
|
145
173
|
"""Coerce a value yielded by :meth:`SelectFieldBase.iter_choices` or
|
|
146
174
|
:meth:`SelectFieldBase.iter_groups` into a :class:`Choice`.
|
|
@@ -334,8 +362,6 @@ class SelectField(SelectFieldBase):
|
|
|
334
362
|
**kwargs,
|
|
335
363
|
):
|
|
336
364
|
super().__init__(label, validators, **kwargs)
|
|
337
|
-
if isinstance(coerce, type) and issubclass(coerce, Enum):
|
|
338
|
-
coerce = _enum_coerce(coerce)
|
|
339
365
|
self.coerce = coerce
|
|
340
366
|
if callable(choices):
|
|
341
367
|
self._choices_callable = choices
|
|
@@ -389,8 +415,8 @@ class SelectField(SelectFieldBase):
|
|
|
389
415
|
],
|
|
390
416
|
)
|
|
391
417
|
|
|
392
|
-
def post_process(self):
|
|
393
|
-
super().post_process()
|
|
418
|
+
def post_process(self, formdata=None):
|
|
419
|
+
super().post_process(formdata)
|
|
394
420
|
if self._choices_callable is not None:
|
|
395
421
|
self.choices = self._invoke_choices_callback(self._choices_callable)
|
|
396
422
|
|
|
@@ -350,13 +350,16 @@ class Field:
|
|
|
350
350
|
except ValueError as e:
|
|
351
351
|
self.process_errors.append(e.args[0])
|
|
352
352
|
|
|
353
|
-
def post_process(self):
|
|
353
|
+
def post_process(self, formdata=None):
|
|
354
354
|
"""Hook called after every field in the enclosing form has been processed.
|
|
355
355
|
|
|
356
356
|
Override this when a field needs to read other fields' processed data,
|
|
357
357
|
for example to resolve dynamic choices that depend on the form state.
|
|
358
358
|
The base implementation resolves any inline :class:`~wtforms.DataList`
|
|
359
359
|
attached to the field.
|
|
360
|
+
|
|
361
|
+
:param formdata: The resolved formdata the enclosing form was bound
|
|
362
|
+
with, or ``None`` for non-formdata cycles.
|
|
360
363
|
"""
|
|
361
364
|
if self._datalist is not None and not isinstance(self._datalist, str):
|
|
362
365
|
self._datalist._resolve(self)
|
|
@@ -70,8 +70,8 @@ class FormField(Field):
|
|
|
70
70
|
meta={"_parent_form": self._form},
|
|
71
71
|
)
|
|
72
72
|
|
|
73
|
-
def post_process(self):
|
|
74
|
-
self.form.post_process()
|
|
73
|
+
def post_process(self, formdata=None):
|
|
74
|
+
self.form.post_process(formdata)
|
|
75
75
|
|
|
76
76
|
def validate(self, form, extra_validators=()):
|
|
77
77
|
if extra_validators:
|
|
@@ -114,9 +114,9 @@ class FieldList(Field):
|
|
|
114
114
|
if k.isdigit():
|
|
115
115
|
yield int(k)
|
|
116
116
|
|
|
117
|
-
def post_process(self):
|
|
117
|
+
def post_process(self, formdata=None):
|
|
118
118
|
for entry in self.entries:
|
|
119
|
-
entry.post_process()
|
|
119
|
+
entry.post_process(formdata)
|
|
120
120
|
|
|
121
121
|
def validate(self, form, extra_validators=()):
|
|
122
122
|
"""
|
|
@@ -131,15 +131,20 @@ class BaseForm:
|
|
|
131
131
|
field.process(formdata, data, extra_filters=field_extra_filters)
|
|
132
132
|
|
|
133
133
|
if self._parent_form is None:
|
|
134
|
-
self.post_process()
|
|
134
|
+
self.post_process(formdata)
|
|
135
135
|
|
|
136
|
-
def post_process(self):
|
|
136
|
+
def post_process(self, formdata=None):
|
|
137
137
|
"""Hook called at the end of :meth:`process` on the root form.
|
|
138
138
|
|
|
139
139
|
Runs the :meth:`~fields.Field.post_process` hook on every field, after
|
|
140
140
|
all fields have been processed. Override this on a form subclass to add
|
|
141
|
-
cross-field finalization logic; call ``super().post_process()``
|
|
142
|
-
per-field hooks running.
|
|
141
|
+
cross-field finalization logic; call ``super().post_process(formdata)``
|
|
142
|
+
to keep per-field hooks running.
|
|
143
|
+
|
|
144
|
+
:param formdata: The resolved formdata the form was bound with
|
|
145
|
+
(already passed through :meth:`~meta.DefaultMeta.wrap_formdata`),
|
|
146
|
+
or ``None`` for non-formdata cycles. Forwarded to every nested
|
|
147
|
+
``post_process``.
|
|
143
148
|
|
|
144
149
|
``post_process`` is only triggered automatically on the root form. Forms
|
|
145
150
|
nested inside a :class:`~fields.FormField` (or via :class:`~fields.FieldList`
|
|
@@ -148,7 +153,7 @@ class BaseForm:
|
|
|
148
153
|
``post_process`` runs exactly once per processing cycle.
|
|
149
154
|
"""
|
|
150
155
|
for field in self._fields.values():
|
|
151
|
-
field.post_process()
|
|
156
|
+
field.post_process(formdata)
|
|
152
157
|
|
|
153
158
|
def validate(self, extra_validators=None):
|
|
154
159
|
"""
|
|
@@ -118,15 +118,15 @@ def test_post_process_propagates_through_form_field():
|
|
|
118
118
|
class Inner(Form):
|
|
119
119
|
x = StringField()
|
|
120
120
|
|
|
121
|
-
def post_process(self):
|
|
122
|
-
super().post_process()
|
|
121
|
+
def post_process(self, formdata=None):
|
|
122
|
+
super().post_process(formdata)
|
|
123
123
|
captured.append(("inner", self.x.data))
|
|
124
124
|
|
|
125
125
|
class Outer(Form):
|
|
126
126
|
block = FormField(Inner)
|
|
127
127
|
|
|
128
|
-
def post_process(self):
|
|
129
|
-
super().post_process()
|
|
128
|
+
def post_process(self, formdata=None):
|
|
129
|
+
super().post_process(formdata)
|
|
130
130
|
captured.append(("outer", self.block.form.x.data))
|
|
131
131
|
|
|
132
132
|
Outer(DummyPostData({"block-x": "v"}))
|
|
@@ -182,9 +182,9 @@ def test_post_process_mutation_propagates_top_down():
|
|
|
182
182
|
tenant = StringField()
|
|
183
183
|
block = FormField(Inner)
|
|
184
184
|
|
|
185
|
-
def post_process(self):
|
|
185
|
+
def post_process(self, formdata=None):
|
|
186
186
|
self.tenant.data = (self.tenant.data or "").upper()
|
|
187
|
-
super().post_process()
|
|
187
|
+
super().post_process(formdata)
|
|
188
188
|
|
|
189
189
|
Outer(DummyPostData({"tenant": "acme", "block-item": "a"}))
|
|
190
190
|
assert captured == ["ACME"]
|