WTForms 3.3.0b3__tar.gz → 3.3.0b4__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.0b3 → wtforms-3.3.0b4}/CHANGES.rst +28 -0
- {wtforms-3.3.0b3 → wtforms-3.3.0b4}/PKG-INFO +3 -3
- {wtforms-3.3.0b3 → wtforms-3.3.0b4}/docs/fields.rst +26 -17
- {wtforms-3.3.0b3 → wtforms-3.3.0b4}/docs/forms.rst +2 -0
- {wtforms-3.3.0b3 → wtforms-3.3.0b4}/pyproject.toml +4 -4
- {wtforms-3.3.0b3 → wtforms-3.3.0b4}/src/wtforms/__init__.py +1 -1
- {wtforms-3.3.0b3 → wtforms-3.3.0b4}/src/wtforms/datalist.py +18 -13
- {wtforms-3.3.0b3 → wtforms-3.3.0b4}/src/wtforms/fields/choices.py +79 -30
- {wtforms-3.3.0b3 → wtforms-3.3.0b4}/src/wtforms/fields/core.py +5 -2
- {wtforms-3.3.0b3 → wtforms-3.3.0b4}/src/wtforms/fields/datetime.py +22 -25
- {wtforms-3.3.0b3 → wtforms-3.3.0b4}/src/wtforms/fields/list.py +13 -1
- {wtforms-3.3.0b3 → wtforms-3.3.0b4}/src/wtforms/fields/numeric.py +18 -16
- {wtforms-3.3.0b3 → wtforms-3.3.0b4}/src/wtforms/form.py +19 -13
- {wtforms-3.3.0b3 → wtforms-3.3.0b4}/tests/fields/test_date.py +13 -0
- {wtforms-3.3.0b3 → wtforms-3.3.0b4}/tests/fields/test_datetime.py +13 -0
- {wtforms-3.3.0b3 → wtforms-3.3.0b4}/tests/fields/test_datetimelocal.py +4 -4
- {wtforms-3.3.0b3 → wtforms-3.3.0b4}/tests/fields/test_decimal.py +10 -0
- {wtforms-3.3.0b3 → wtforms-3.3.0b4}/tests/fields/test_float.py +13 -0
- {wtforms-3.3.0b3 → wtforms-3.3.0b4}/tests/fields/test_form.py +12 -11
- {wtforms-3.3.0b3 → wtforms-3.3.0b4}/tests/fields/test_integer.py +13 -0
- {wtforms-3.3.0b3 → wtforms-3.3.0b4}/tests/fields/test_list.py +77 -0
- {wtforms-3.3.0b3 → wtforms-3.3.0b4}/tests/fields/test_radio.py +17 -0
- {wtforms-3.3.0b3 → wtforms-3.3.0b4}/tests/fields/test_select.py +147 -7
- {wtforms-3.3.0b3 → wtforms-3.3.0b4}/tests/fields/test_selectmultiple.py +29 -0
- {wtforms-3.3.0b3 → wtforms-3.3.0b4}/tests/fields/test_time.py +13 -0
- {wtforms-3.3.0b3 → wtforms-3.3.0b4}/tests/fields/test_week.py +13 -0
- {wtforms-3.3.0b3 → wtforms-3.3.0b4}/tests/test_datalist.py +56 -0
- {wtforms-3.3.0b3 → wtforms-3.3.0b4}/tests/test_form.py +22 -0
- {wtforms-3.3.0b3 → wtforms-3.3.0b4}/.gitignore +0 -0
- {wtforms-3.3.0b3 → wtforms-3.3.0b4}/LICENSE.rst +0 -0
- {wtforms-3.3.0b3 → wtforms-3.3.0b4}/README.rst +0 -0
- {wtforms-3.3.0b3 → wtforms-3.3.0b4}/docs/Makefile +0 -0
- {wtforms-3.3.0b3 → wtforms-3.3.0b4}/docs/_static/logo_joined.svg +0 -0
- {wtforms-3.3.0b3 → wtforms-3.3.0b4}/docs/_static/logo_split.svg +0 -0
- {wtforms-3.3.0b3 → wtforms-3.3.0b4}/docs/_static/logo_square.svg +0 -0
- {wtforms-3.3.0b3 → wtforms-3.3.0b4}/docs/changes.rst +0 -0
- {wtforms-3.3.0b3 → wtforms-3.3.0b4}/docs/conf.py +0 -0
- {wtforms-3.3.0b3 → wtforms-3.3.0b4}/docs/contributing.rst +0 -0
- {wtforms-3.3.0b3 → wtforms-3.3.0b4}/docs/crash_course.rst +0 -0
- {wtforms-3.3.0b3 → wtforms-3.3.0b4}/docs/csrf.rst +0 -0
- {wtforms-3.3.0b3 → wtforms-3.3.0b4}/docs/ext.rst +0 -0
- {wtforms-3.3.0b3 → wtforms-3.3.0b4}/docs/faq.rst +0 -0
- {wtforms-3.3.0b3 → wtforms-3.3.0b4}/docs/i18n.rst +0 -0
- {wtforms-3.3.0b3 → wtforms-3.3.0b4}/docs/index.rst +0 -0
- {wtforms-3.3.0b3 → wtforms-3.3.0b4}/docs/license.rst +0 -0
- {wtforms-3.3.0b3 → wtforms-3.3.0b4}/docs/make.bat +0 -0
- {wtforms-3.3.0b3 → wtforms-3.3.0b4}/docs/meta.rst +0 -0
- {wtforms-3.3.0b3 → wtforms-3.3.0b4}/docs/specific_problems.rst +0 -0
- {wtforms-3.3.0b3 → wtforms-3.3.0b4}/docs/validators.rst +0 -0
- {wtforms-3.3.0b3 → wtforms-3.3.0b4}/docs/whats_new.rst +0 -0
- {wtforms-3.3.0b3 → wtforms-3.3.0b4}/docs/widgets.rst +0 -0
- {wtforms-3.3.0b3 → wtforms-3.3.0b4}/hatch_build.py +0 -0
- {wtforms-3.3.0b3 → wtforms-3.3.0b4}/src/wtforms/_compat.py +0 -0
- {wtforms-3.3.0b3 → wtforms-3.3.0b4}/src/wtforms/csrf/__init__.py +0 -0
- {wtforms-3.3.0b3 → wtforms-3.3.0b4}/src/wtforms/csrf/core.py +0 -0
- {wtforms-3.3.0b3 → wtforms-3.3.0b4}/src/wtforms/csrf/session.py +0 -0
- {wtforms-3.3.0b3 → wtforms-3.3.0b4}/src/wtforms/fields/__init__.py +0 -0
- {wtforms-3.3.0b3 → wtforms-3.3.0b4}/src/wtforms/fields/form.py +0 -0
- {wtforms-3.3.0b3 → wtforms-3.3.0b4}/src/wtforms/fields/simple.py +0 -0
- {wtforms-3.3.0b3 → wtforms-3.3.0b4}/src/wtforms/i18n.py +0 -0
- {wtforms-3.3.0b3 → wtforms-3.3.0b4}/src/wtforms/locale/README.md +0 -0
- {wtforms-3.3.0b3 → wtforms-3.3.0b4}/src/wtforms/locale/ar/LC_MESSAGES/wtforms.mo +0 -0
- {wtforms-3.3.0b3 → wtforms-3.3.0b4}/src/wtforms/locale/ar/LC_MESSAGES/wtforms.po +0 -0
- {wtforms-3.3.0b3 → wtforms-3.3.0b4}/src/wtforms/locale/bg/LC_MESSAGES/wtforms.mo +0 -0
- {wtforms-3.3.0b3 → wtforms-3.3.0b4}/src/wtforms/locale/bg/LC_MESSAGES/wtforms.po +0 -0
- {wtforms-3.3.0b3 → wtforms-3.3.0b4}/src/wtforms/locale/ca/LC_MESSAGES/wtforms.mo +0 -0
- {wtforms-3.3.0b3 → wtforms-3.3.0b4}/src/wtforms/locale/ca/LC_MESSAGES/wtforms.po +0 -0
- {wtforms-3.3.0b3 → wtforms-3.3.0b4}/src/wtforms/locale/cs_CZ/LC_MESSAGES/wtforms.mo +0 -0
- {wtforms-3.3.0b3 → wtforms-3.3.0b4}/src/wtforms/locale/cs_CZ/LC_MESSAGES/wtforms.po +0 -0
- {wtforms-3.3.0b3 → wtforms-3.3.0b4}/src/wtforms/locale/cy/LC_MESSAGES/wtforms.mo +0 -0
- {wtforms-3.3.0b3 → wtforms-3.3.0b4}/src/wtforms/locale/cy/LC_MESSAGES/wtforms.po +0 -0
- {wtforms-3.3.0b3 → wtforms-3.3.0b4}/src/wtforms/locale/de/LC_MESSAGES/wtforms.mo +0 -0
- {wtforms-3.3.0b3 → wtforms-3.3.0b4}/src/wtforms/locale/de/LC_MESSAGES/wtforms.po +0 -0
- {wtforms-3.3.0b3 → wtforms-3.3.0b4}/src/wtforms/locale/de_CH/LC_MESSAGES/wtforms.mo +0 -0
- {wtforms-3.3.0b3 → wtforms-3.3.0b4}/src/wtforms/locale/de_CH/LC_MESSAGES/wtforms.po +0 -0
- {wtforms-3.3.0b3 → wtforms-3.3.0b4}/src/wtforms/locale/el/LC_MESSAGES/wtforms.mo +0 -0
- {wtforms-3.3.0b3 → wtforms-3.3.0b4}/src/wtforms/locale/el/LC_MESSAGES/wtforms.po +0 -0
- {wtforms-3.3.0b3 → wtforms-3.3.0b4}/src/wtforms/locale/en/LC_MESSAGES/wtforms.mo +0 -0
- {wtforms-3.3.0b3 → wtforms-3.3.0b4}/src/wtforms/locale/en/LC_MESSAGES/wtforms.po +0 -0
- {wtforms-3.3.0b3 → wtforms-3.3.0b4}/src/wtforms/locale/es/LC_MESSAGES/wtforms.mo +0 -0
- {wtforms-3.3.0b3 → wtforms-3.3.0b4}/src/wtforms/locale/es/LC_MESSAGES/wtforms.po +0 -0
- {wtforms-3.3.0b3 → wtforms-3.3.0b4}/src/wtforms/locale/et/LC_MESSAGES/wtforms.mo +0 -0
- {wtforms-3.3.0b3 → wtforms-3.3.0b4}/src/wtforms/locale/et/LC_MESSAGES/wtforms.po +0 -0
- {wtforms-3.3.0b3 → wtforms-3.3.0b4}/src/wtforms/locale/fa/LC_MESSAGES/wtforms.mo +0 -0
- {wtforms-3.3.0b3 → wtforms-3.3.0b4}/src/wtforms/locale/fa/LC_MESSAGES/wtforms.po +0 -0
- {wtforms-3.3.0b3 → wtforms-3.3.0b4}/src/wtforms/locale/fi/LC_MESSAGES/wtforms.mo +0 -0
- {wtforms-3.3.0b3 → wtforms-3.3.0b4}/src/wtforms/locale/fi/LC_MESSAGES/wtforms.po +0 -0
- {wtforms-3.3.0b3 → wtforms-3.3.0b4}/src/wtforms/locale/fr/LC_MESSAGES/wtforms.mo +0 -0
- {wtforms-3.3.0b3 → wtforms-3.3.0b4}/src/wtforms/locale/fr/LC_MESSAGES/wtforms.po +0 -0
- {wtforms-3.3.0b3 → wtforms-3.3.0b4}/src/wtforms/locale/he/LC_MESSAGES/wtforms.mo +0 -0
- {wtforms-3.3.0b3 → wtforms-3.3.0b4}/src/wtforms/locale/he/LC_MESSAGES/wtforms.po +0 -0
- {wtforms-3.3.0b3 → wtforms-3.3.0b4}/src/wtforms/locale/hu/LC_MESSAGES/wtforms.mo +0 -0
- {wtforms-3.3.0b3 → wtforms-3.3.0b4}/src/wtforms/locale/hu/LC_MESSAGES/wtforms.po +0 -0
- {wtforms-3.3.0b3 → wtforms-3.3.0b4}/src/wtforms/locale/it/LC_MESSAGES/wtforms.mo +0 -0
- {wtforms-3.3.0b3 → wtforms-3.3.0b4}/src/wtforms/locale/it/LC_MESSAGES/wtforms.po +0 -0
- {wtforms-3.3.0b3 → wtforms-3.3.0b4}/src/wtforms/locale/ja/LC_MESSAGES/wtforms.mo +0 -0
- {wtforms-3.3.0b3 → wtforms-3.3.0b4}/src/wtforms/locale/ja/LC_MESSAGES/wtforms.po +0 -0
- {wtforms-3.3.0b3 → wtforms-3.3.0b4}/src/wtforms/locale/kk/LC_MESSAGES/wtforms.mo +0 -0
- {wtforms-3.3.0b3 → wtforms-3.3.0b4}/src/wtforms/locale/kk/LC_MESSAGES/wtforms.po +0 -0
- {wtforms-3.3.0b3 → wtforms-3.3.0b4}/src/wtforms/locale/ko/LC_MESSAGES/wtforms.mo +0 -0
- {wtforms-3.3.0b3 → wtforms-3.3.0b4}/src/wtforms/locale/ko/LC_MESSAGES/wtforms.po +0 -0
- {wtforms-3.3.0b3 → wtforms-3.3.0b4}/src/wtforms/locale/nb/LC_MESSAGES/wtforms.mo +0 -0
- {wtforms-3.3.0b3 → wtforms-3.3.0b4}/src/wtforms/locale/nb/LC_MESSAGES/wtforms.po +0 -0
- {wtforms-3.3.0b3 → wtforms-3.3.0b4}/src/wtforms/locale/nl/LC_MESSAGES/wtforms.mo +0 -0
- {wtforms-3.3.0b3 → wtforms-3.3.0b4}/src/wtforms/locale/nl/LC_MESSAGES/wtforms.po +0 -0
- {wtforms-3.3.0b3 → wtforms-3.3.0b4}/src/wtforms/locale/pl/LC_MESSAGES/wtforms.mo +0 -0
- {wtforms-3.3.0b3 → wtforms-3.3.0b4}/src/wtforms/locale/pl/LC_MESSAGES/wtforms.po +0 -0
- {wtforms-3.3.0b3 → wtforms-3.3.0b4}/src/wtforms/locale/pt/LC_MESSAGES/wtforms.mo +0 -0
- {wtforms-3.3.0b3 → wtforms-3.3.0b4}/src/wtforms/locale/pt/LC_MESSAGES/wtforms.po +0 -0
- {wtforms-3.3.0b3 → wtforms-3.3.0b4}/src/wtforms/locale/ro/LC_MESSAGES/wtforms.mo +0 -0
- {wtforms-3.3.0b3 → wtforms-3.3.0b4}/src/wtforms/locale/ro/LC_MESSAGES/wtforms.po +0 -0
- {wtforms-3.3.0b3 → wtforms-3.3.0b4}/src/wtforms/locale/ru/LC_MESSAGES/wtforms.mo +0 -0
- {wtforms-3.3.0b3 → wtforms-3.3.0b4}/src/wtforms/locale/ru/LC_MESSAGES/wtforms.po +0 -0
- {wtforms-3.3.0b3 → wtforms-3.3.0b4}/src/wtforms/locale/sk/LC_MESSAGES/wtforms.mo +0 -0
- {wtforms-3.3.0b3 → wtforms-3.3.0b4}/src/wtforms/locale/sk/LC_MESSAGES/wtforms.po +0 -0
- {wtforms-3.3.0b3 → wtforms-3.3.0b4}/src/wtforms/locale/sr/LC_MESSAGES/wtforms.mo +0 -0
- {wtforms-3.3.0b3 → wtforms-3.3.0b4}/src/wtforms/locale/sr/LC_MESSAGES/wtforms.po +0 -0
- {wtforms-3.3.0b3 → wtforms-3.3.0b4}/src/wtforms/locale/sv/LC_MESSAGES/wtforms.mo +0 -0
- {wtforms-3.3.0b3 → wtforms-3.3.0b4}/src/wtforms/locale/sv/LC_MESSAGES/wtforms.po +0 -0
- {wtforms-3.3.0b3 → wtforms-3.3.0b4}/src/wtforms/locale/ta/LC_MESSAGES/wtforms.mo +0 -0
- {wtforms-3.3.0b3 → wtforms-3.3.0b4}/src/wtforms/locale/ta/LC_MESSAGES/wtforms.po +0 -0
- {wtforms-3.3.0b3 → wtforms-3.3.0b4}/src/wtforms/locale/tr/LC_MESSAGES/wtforms.mo +0 -0
- {wtforms-3.3.0b3 → wtforms-3.3.0b4}/src/wtforms/locale/tr/LC_MESSAGES/wtforms.po +0 -0
- {wtforms-3.3.0b3 → wtforms-3.3.0b4}/src/wtforms/locale/uk/LC_MESSAGES/wtforms.mo +0 -0
- {wtforms-3.3.0b3 → wtforms-3.3.0b4}/src/wtforms/locale/uk/LC_MESSAGES/wtforms.po +0 -0
- {wtforms-3.3.0b3 → wtforms-3.3.0b4}/src/wtforms/locale/wtforms.pot +0 -0
- {wtforms-3.3.0b3 → wtforms-3.3.0b4}/src/wtforms/locale/zh/LC_MESSAGES/wtforms.mo +0 -0
- {wtforms-3.3.0b3 → wtforms-3.3.0b4}/src/wtforms/locale/zh/LC_MESSAGES/wtforms.po +0 -0
- {wtforms-3.3.0b3 → wtforms-3.3.0b4}/src/wtforms/locale/zh_TW/LC_MESSAGES/wtforms.mo +0 -0
- {wtforms-3.3.0b3 → wtforms-3.3.0b4}/src/wtforms/locale/zh_TW/LC_MESSAGES/wtforms.po +0 -0
- {wtforms-3.3.0b3 → wtforms-3.3.0b4}/src/wtforms/meta.py +0 -0
- {wtforms-3.3.0b3 → wtforms-3.3.0b4}/src/wtforms/utils.py +0 -0
- {wtforms-3.3.0b3 → wtforms-3.3.0b4}/src/wtforms/validators.py +0 -0
- {wtforms-3.3.0b3 → wtforms-3.3.0b4}/src/wtforms/widgets/__init__.py +0 -0
- {wtforms-3.3.0b3 → wtforms-3.3.0b4}/src/wtforms/widgets/core.py +0 -0
- {wtforms-3.3.0b3 → wtforms-3.3.0b4}/tests/__init__.py +0 -0
- {wtforms-3.3.0b3 → wtforms-3.3.0b4}/tests/common.py +0 -0
- {wtforms-3.3.0b3 → wtforms-3.3.0b4}/tests/conftest.py +0 -0
- {wtforms-3.3.0b3 → wtforms-3.3.0b4}/tests/fields/__init__.py +0 -0
- {wtforms-3.3.0b3 → wtforms-3.3.0b4}/tests/fields/test_boolean.py +0 -0
- {wtforms-3.3.0b3 → wtforms-3.3.0b4}/tests/fields/test_button.py +0 -0
- {wtforms-3.3.0b3 → wtforms-3.3.0b4}/tests/fields/test_color.py +0 -0
- {wtforms-3.3.0b3 → wtforms-3.3.0b4}/tests/fields/test_custom.py +0 -0
- {wtforms-3.3.0b3 → wtforms-3.3.0b4}/tests/fields/test_defaults.py +0 -0
- {wtforms-3.3.0b3 → wtforms-3.3.0b4}/tests/fields/test_field.py +0 -0
- {wtforms-3.3.0b3 → wtforms-3.3.0b4}/tests/fields/test_file.py +0 -0
- {wtforms-3.3.0b3 → wtforms-3.3.0b4}/tests/fields/test_filters.py +0 -0
- {wtforms-3.3.0b3 → wtforms-3.3.0b4}/tests/fields/test_flags.py +0 -0
- {wtforms-3.3.0b3 → wtforms-3.3.0b4}/tests/fields/test_hidden.py +0 -0
- {wtforms-3.3.0b3 → wtforms-3.3.0b4}/tests/fields/test_html5.py +0 -0
- {wtforms-3.3.0b3 → wtforms-3.3.0b4}/tests/fields/test_label.py +0 -0
- {wtforms-3.3.0b3 → wtforms-3.3.0b4}/tests/fields/test_month.py +0 -0
- {wtforms-3.3.0b3 → wtforms-3.3.0b4}/tests/fields/test_password.py +0 -0
- {wtforms-3.3.0b3 → wtforms-3.3.0b4}/tests/fields/test_string.py +0 -0
- {wtforms-3.3.0b3 → wtforms-3.3.0b4}/tests/fields/test_submit.py +0 -0
- {wtforms-3.3.0b3 → wtforms-3.3.0b4}/tests/fields/test_textarea.py +0 -0
- {wtforms-3.3.0b3 → wtforms-3.3.0b4}/tests/test_csrf.py +0 -0
- {wtforms-3.3.0b3 → wtforms-3.3.0b4}/tests/test_i18n.py +0 -0
- {wtforms-3.3.0b3 → wtforms-3.3.0b4}/tests/test_locale_babel.py +0 -0
- {wtforms-3.3.0b3 → wtforms-3.3.0b4}/tests/test_webob.py +0 -0
- {wtforms-3.3.0b3 → wtforms-3.3.0b4}/tests/test_widgets.py +0 -0
- {wtforms-3.3.0b3 → wtforms-3.3.0b4}/tests/validators/__init__.py +0 -0
- {wtforms-3.3.0b3 → wtforms-3.3.0b4}/tests/validators/test_anyof.py +0 -0
- {wtforms-3.3.0b3 → wtforms-3.3.0b4}/tests/validators/test_data_required.py +0 -0
- {wtforms-3.3.0b3 → wtforms-3.3.0b4}/tests/validators/test_date_range.py +0 -0
- {wtforms-3.3.0b3 → wtforms-3.3.0b4}/tests/validators/test_disabled.py +0 -0
- {wtforms-3.3.0b3 → wtforms-3.3.0b4}/tests/validators/test_email.py +0 -0
- {wtforms-3.3.0b3 → wtforms-3.3.0b4}/tests/validators/test_equal_to.py +0 -0
- {wtforms-3.3.0b3 → wtforms-3.3.0b4}/tests/validators/test_input_required.py +0 -0
- {wtforms-3.3.0b3 → wtforms-3.3.0b4}/tests/validators/test_ipaddress.py +0 -0
- {wtforms-3.3.0b3 → wtforms-3.3.0b4}/tests/validators/test_lazy_proxy.py +0 -0
- {wtforms-3.3.0b3 → wtforms-3.3.0b4}/tests/validators/test_length.py +0 -0
- {wtforms-3.3.0b3 → wtforms-3.3.0b4}/tests/validators/test_mac_address.py +0 -0
- {wtforms-3.3.0b3 → wtforms-3.3.0b4}/tests/validators/test_noneof.py +0 -0
- {wtforms-3.3.0b3 → wtforms-3.3.0b4}/tests/validators/test_number_range.py +0 -0
- {wtforms-3.3.0b3 → wtforms-3.3.0b4}/tests/validators/test_optional.py +0 -0
- {wtforms-3.3.0b3 → wtforms-3.3.0b4}/tests/validators/test_readonly.py +0 -0
- {wtforms-3.3.0b3 → wtforms-3.3.0b4}/tests/validators/test_regexp.py +0 -0
- {wtforms-3.3.0b3 → wtforms-3.3.0b4}/tests/validators/test_url.py +0 -0
- {wtforms-3.3.0b3 → wtforms-3.3.0b4}/tests/validators/test_uuid.py +0 -0
- {wtforms-3.3.0b3 → wtforms-3.3.0b4}/tests/validators/test_validators.py +0 -0
|
@@ -1,5 +1,33 @@
|
|
|
1
1
|
.. currentmodule:: wtforms
|
|
2
2
|
|
|
3
|
+
Version 3.3.0b4
|
|
4
|
+
---------------
|
|
5
|
+
|
|
6
|
+
Released 2026-10-23
|
|
7
|
+
|
|
8
|
+
- Drop support for Python 3.10. Add support for Python 3.15.
|
|
9
|
+
- :func:`fields.enum_choices` and :func:`~wtforms.datalist.enum_datalist`
|
|
10
|
+
label :class:`enum.IntEnum` members with their name on PyPy too, instead of
|
|
11
|
+
their value.
|
|
12
|
+
- :class:`~datalist.DataList` ``choices`` accepts the shorthand
|
|
13
|
+
``{value: label}`` dict syntax. :issue:`886`
|
|
14
|
+
- Default ``invalid_value_message`` and ``invalid_choice_message`` are
|
|
15
|
+
translated when the error is raised, not when the field is built. Forms can
|
|
16
|
+
be built again outside of a translation context.
|
|
17
|
+
- ``choices`` callables on :class:`~fields.SelectField` and
|
|
18
|
+
:class:`~datalist.DataList` run on the first read and their result is
|
|
19
|
+
cached. Calling :meth:`~form.BaseForm.post_process` again refreshes them.
|
|
20
|
+
This also applies to entries added with
|
|
21
|
+
:meth:`~fields.FieldList.append_entry` and
|
|
22
|
+
:meth:`~fields.FieldList.insert_entry`.
|
|
23
|
+
- Passing a callable to ``SelectFieldBase._choices_from_input`` is
|
|
24
|
+
deprecated.
|
|
25
|
+
- Numeric, date, time and select fields report a validation error instead of
|
|
26
|
+
raising a :class:`TypeError` when the input is not a string.
|
|
27
|
+
- The labels of :class:`~fields.FieldList` entries point to the right field
|
|
28
|
+
after :meth:`~fields.FieldList.insert_entry` or
|
|
29
|
+
:meth:`~fields.FieldList.pop_entry` renumbers the entries.
|
|
30
|
+
|
|
3
31
|
Version 3.3.0b3
|
|
4
32
|
---------------
|
|
5
33
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
2
|
Name: WTForms
|
|
3
|
-
Version: 3.3.
|
|
3
|
+
Version: 3.3.0b4
|
|
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'
|
|
@@ -67,6 +67,7 @@ The Field base class
|
|
|
67
67
|
.. automethod:: process(formdata [, data])
|
|
68
68
|
.. automethod:: process_data
|
|
69
69
|
.. automethod:: process_formdata
|
|
70
|
+
.. automethod:: post_process
|
|
70
71
|
.. attribute:: data
|
|
71
72
|
|
|
72
73
|
Contains the resulting (sanitized) value of calling either of the
|
|
@@ -329,8 +330,8 @@ Choice Fields
|
|
|
329
330
|
SelectChoice('text', 'Plain Text'),
|
|
330
331
|
])
|
|
331
332
|
|
|
332
|
-
|
|
333
|
-
|
|
333
|
+
A callable passed as `choices` is resolved separately for each form
|
|
334
|
+
instance, the first time its choices are read.
|
|
334
335
|
For dynamic options, either pass a callable to `choices` or assign
|
|
335
336
|
`field.choices` after instantiation. Any submitted choices which are not in
|
|
336
337
|
the given choices list will cause validation on the field to fail. If this
|
|
@@ -361,17 +362,22 @@ Choice Fields
|
|
|
361
362
|
user = User.query.get(id)
|
|
362
363
|
form = UserDetails(request.POST, obj=user)
|
|
363
364
|
|
|
364
|
-
Note we passed a callable to `choices` rather than a static list; it
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
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
|
|
375
|
+
:func:`str()`.
|
|
370
376
|
|
|
371
377
|
The callable may optionally accept ``(form, field)`` as positional
|
|
372
|
-
arguments.
|
|
373
|
-
whole form has been processed, so it can read ``field.data`` and
|
|
374
|
-
of any other field on the form::
|
|
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::
|
|
375
381
|
|
|
376
382
|
def available_groups(form, field):
|
|
377
383
|
return [SelectChoice(g.id, g.name) for g in form.tenant.data.groups]
|
|
@@ -610,12 +616,15 @@ Data Lists
|
|
|
610
616
|
|
|
611
617
|
``choices`` is either a list of :class:`~wtforms.DataListChoice`
|
|
612
618
|
(or plain strings, in which case the string is used as both value
|
|
613
|
-
and label),
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
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::
|
|
617
626
|
|
|
618
|
-
DataList(lambda field: search(field.data))
|
|
627
|
+
DataList(lambda form, field: search(field.data))
|
|
619
628
|
|
|
620
629
|
**Inline DataList owned by a field**
|
|
621
630
|
|
|
@@ -642,7 +651,7 @@ Data Lists
|
|
|
642
651
|
class F(Form):
|
|
643
652
|
items = FieldList(
|
|
644
653
|
StringField(
|
|
645
|
-
datalist=DataList(lambda field: suggest(field.data))
|
|
654
|
+
datalist=DataList(lambda form, field: suggest(field.data))
|
|
646
655
|
),
|
|
647
656
|
min_entries=3,
|
|
648
657
|
)
|
|
@@ -13,7 +13,7 @@ classifiers = [
|
|
|
13
13
|
"Programming Language :: Python",
|
|
14
14
|
"Topic :: Internet :: WWW/HTTP :: Dynamic Content",
|
|
15
15
|
]
|
|
16
|
-
requires-python = ">=3.
|
|
16
|
+
requires-python = ">=3.11"
|
|
17
17
|
dependencies = [
|
|
18
18
|
"MarkupSafe>=1.1.1",
|
|
19
19
|
]
|
|
@@ -49,12 +49,12 @@ dev = [
|
|
|
49
49
|
env_list = [
|
|
50
50
|
"style",
|
|
51
51
|
"minversions",
|
|
52
|
+
"py315",
|
|
52
53
|
"py314",
|
|
53
54
|
"py313",
|
|
54
55
|
"py312",
|
|
55
56
|
"py311",
|
|
56
|
-
"
|
|
57
|
-
"pypy310",
|
|
57
|
+
"pypy311",
|
|
58
58
|
"docs",
|
|
59
59
|
]
|
|
60
60
|
skip_missing_interpreters = true
|
|
@@ -86,7 +86,7 @@ dependency_groups = ["docs"]
|
|
|
86
86
|
commands = [["sphinx-build", "-W", "-b", "html", "-d", "{envtmpdir}/doctrees", "docs", "{envtmpdir}/html"]]
|
|
87
87
|
|
|
88
88
|
[tool.tox.env.minversions]
|
|
89
|
-
base_python = ["py3.
|
|
89
|
+
base_python = ["py3.11"]
|
|
90
90
|
uv_resolution = "lowest-direct"
|
|
91
91
|
dependency_groups = ["dev"]
|
|
92
92
|
commands = [
|
|
@@ -3,8 +3,8 @@ from dataclasses import dataclass
|
|
|
3
3
|
from dataclasses import field
|
|
4
4
|
|
|
5
5
|
from wtforms import widgets
|
|
6
|
-
from wtforms._compat import get_signature
|
|
7
6
|
from wtforms.fields.choices import _enum_options
|
|
7
|
+
from wtforms.fields.choices import _invoke_choices_callable
|
|
8
8
|
from wtforms.fields.choices import Choice
|
|
9
9
|
|
|
10
10
|
__all__ = ("DataList", "DataListChoice", "enum_datalist")
|
|
@@ -70,7 +70,8 @@ def enum_datalist(enum_cls, *, by="value", label=None):
|
|
|
70
70
|
Same semantics as :func:`~wtforms.fields.enum_choices`: ``by`` selects
|
|
71
71
|
which member attribute becomes the ``<option>`` value (``"value"`` by
|
|
72
72
|
default, ``"name"`` otherwise) and ``label`` defaults to ``str(member)``
|
|
73
|
-
when the Enum defines its own ``__str__
|
|
73
|
+
when the Enum defines its own ``__str__`` or is a :class:`enum.StrEnum`,
|
|
74
|
+
else ``member.name``.
|
|
74
75
|
|
|
75
76
|
A ``<datalist>`` only suggests values for a free-text field, so there is
|
|
76
77
|
no coercion counterpart — the field stores whatever string is submitted.
|
|
@@ -91,6 +92,7 @@ class DataList:
|
|
|
91
92
|
def __init__(self, choices=None, *, render_kw=None, widget=None):
|
|
92
93
|
self._raw_choices = choices
|
|
93
94
|
self._choices = None if callable(choices) else choices
|
|
95
|
+
self._field = None
|
|
94
96
|
self.render_kw = render_kw or {}
|
|
95
97
|
if widget is not None:
|
|
96
98
|
self.widget = widget
|
|
@@ -100,27 +102,30 @@ class DataList:
|
|
|
100
102
|
clone = DataList.__new__(DataList)
|
|
101
103
|
clone._raw_choices = self._raw_choices
|
|
102
104
|
clone._choices = None if callable(self._raw_choices) else self._raw_choices
|
|
105
|
+
clone._field = None
|
|
103
106
|
clone.render_kw = self.render_kw
|
|
104
107
|
clone.widget = self.widget
|
|
105
108
|
clone.id = id
|
|
106
109
|
return clone
|
|
107
110
|
|
|
108
|
-
def
|
|
109
|
-
|
|
110
|
-
if
|
|
111
|
-
|
|
112
|
-
try:
|
|
113
|
-
sig = get_signature(raw)
|
|
114
|
-
sig.bind(field._form, field)
|
|
115
|
-
except TypeError:
|
|
116
|
-
self._choices = raw()
|
|
117
|
-
return
|
|
118
|
-
self._choices = raw(field._form, field)
|
|
111
|
+
def _invalidate(self, field):
|
|
112
|
+
"""Mark the choices for a lazy refresh on the next :meth:`iter_choices`."""
|
|
113
|
+
if callable(self._raw_choices):
|
|
114
|
+
self._field = field
|
|
119
115
|
|
|
120
116
|
def iter_choices(self, field=None):
|
|
117
|
+
if (bound := self._field) is not None:
|
|
118
|
+
self._field = None
|
|
119
|
+
self._choices = _invoke_choices_callable(
|
|
120
|
+
self._raw_choices, bound._form, bound
|
|
121
|
+
)
|
|
121
122
|
raw = self._choices
|
|
122
123
|
if raw is None:
|
|
123
124
|
return []
|
|
125
|
+
if isinstance(raw, dict):
|
|
126
|
+
return [
|
|
127
|
+
DataListChoice(value=value, label=label) for value, label in raw.items()
|
|
128
|
+
]
|
|
124
129
|
return [DataListChoice.from_input(item) for item in raw]
|
|
125
130
|
|
|
126
131
|
def __call__(self, field=None, **kwargs):
|
|
@@ -2,6 +2,7 @@ import warnings
|
|
|
2
2
|
from dataclasses import dataclass
|
|
3
3
|
from dataclasses import field
|
|
4
4
|
from dataclasses import replace
|
|
5
|
+
from enum import StrEnum
|
|
5
6
|
from itertools import groupby
|
|
6
7
|
from operator import attrgetter
|
|
7
8
|
from typing import NamedTuple
|
|
@@ -115,11 +116,35 @@ class SelectChoice:
|
|
|
115
116
|
return cls(*input, optgroup=optgroup)
|
|
116
117
|
|
|
117
118
|
|
|
119
|
+
def _has_custom_str(enum_cls):
|
|
120
|
+
"""Tell whether the enum or one of its user bases defines ``__str__``."""
|
|
121
|
+
stdlib = [
|
|
122
|
+
cls.__str__
|
|
123
|
+
for cls in enum_cls.__mro__
|
|
124
|
+
if cls.__module__ in ("enum", "builtins")
|
|
125
|
+
]
|
|
126
|
+
return enum_cls.__str__ not in stdlib
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def _invoke_choices_callable(cb, form, field):
|
|
130
|
+
"""Call a ``choices`` callable with ``(form, field)``, or with no argument."""
|
|
131
|
+
try:
|
|
132
|
+
sig = get_signature(cb)
|
|
133
|
+
except (ValueError, TypeError):
|
|
134
|
+
return cb()
|
|
135
|
+
try:
|
|
136
|
+
sig.bind(form, field)
|
|
137
|
+
except TypeError:
|
|
138
|
+
return cb()
|
|
139
|
+
return cb(form, field)
|
|
140
|
+
|
|
141
|
+
|
|
118
142
|
def _enum_options(enum_cls, by, label, factory):
|
|
119
143
|
if by not in ("value", "name"):
|
|
120
144
|
raise ValueError(f"by must be 'value' or 'name', got {by!r}")
|
|
121
145
|
if label is None:
|
|
122
|
-
|
|
146
|
+
use_str = issubclass(enum_cls, StrEnum) or _has_custom_str(enum_cls)
|
|
147
|
+
label = str if use_str else lambda m: m.name
|
|
123
148
|
return [factory(value=getattr(m, by), label=label(m)) for m in enum_cls]
|
|
124
149
|
|
|
125
150
|
|
|
@@ -132,8 +157,8 @@ def enum_choices(enum_cls, *, by="value", label=None):
|
|
|
132
157
|
transport identifiers (non-string, non-unique or non-serialisable).
|
|
133
158
|
|
|
134
159
|
The label defaults to ``str(member)`` when the Enum defines its own
|
|
135
|
-
``__str__
|
|
136
|
-
taking a member) to override.
|
|
160
|
+
``__str__`` or is a :class:`enum.StrEnum`, otherwise to ``member.name``.
|
|
161
|
+
Pass ``label=`` (a callable taking a member) to override.
|
|
137
162
|
|
|
138
163
|
Pair with :func:`enum_coerce` using the same ``by`` to round-trip the
|
|
139
164
|
submitted string back into a member.
|
|
@@ -264,6 +289,13 @@ class SelectFieldBase(Field):
|
|
|
264
289
|
def _choices_from_input(self, choices):
|
|
265
290
|
"""Parse the user-supplied ``choices`` into a list of :class:`SelectChoice`."""
|
|
266
291
|
if callable(choices):
|
|
292
|
+
warnings.warn(
|
|
293
|
+
"Passing a callable to _choices_from_input() is deprecated; "
|
|
294
|
+
"resolve the callable before the call. Will be removed in "
|
|
295
|
+
"WTForms 3.4.",
|
|
296
|
+
DeprecationWarning,
|
|
297
|
+
stacklevel=2,
|
|
298
|
+
)
|
|
267
299
|
choices = self._invoke_choices_callback(choices)
|
|
268
300
|
|
|
269
301
|
if choices is None:
|
|
@@ -332,15 +364,7 @@ class SelectFieldBase(Field):
|
|
|
332
364
|
return
|
|
333
365
|
|
|
334
366
|
def _invoke_choices_callback(self, cb):
|
|
335
|
-
|
|
336
|
-
sig = get_signature(cb)
|
|
337
|
-
except (ValueError, TypeError):
|
|
338
|
-
return cb()
|
|
339
|
-
try:
|
|
340
|
-
sig.bind(self._form, self)
|
|
341
|
-
except TypeError:
|
|
342
|
-
return cb()
|
|
343
|
-
return cb(self._form, self)
|
|
367
|
+
return _invoke_choices_callable(cb, self._form, self)
|
|
344
368
|
|
|
345
369
|
class _Option(Field):
|
|
346
370
|
def _value(self):
|
|
@@ -365,23 +389,40 @@ class SelectField(SelectFieldBase):
|
|
|
365
389
|
self.coerce = coerce
|
|
366
390
|
if callable(choices):
|
|
367
391
|
self._choices_callable = choices
|
|
368
|
-
self.
|
|
392
|
+
self._choices_stale = True
|
|
393
|
+
self._choices = None
|
|
369
394
|
else:
|
|
370
395
|
self._choices_callable = None
|
|
396
|
+
self._choices_stale = False
|
|
371
397
|
if choices is None:
|
|
372
|
-
self.
|
|
398
|
+
self._choices = None
|
|
373
399
|
else:
|
|
374
400
|
self._warn_legacy_choices(choices)
|
|
375
|
-
self.
|
|
401
|
+
self._choices = (
|
|
376
402
|
dict(choices) if isinstance(choices, dict) else list(choices)
|
|
377
403
|
)
|
|
378
404
|
self.validate_choice = validate_choice
|
|
379
|
-
self.invalid_value_message = invalid_value_message
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
405
|
+
self.invalid_value_message = invalid_value_message
|
|
406
|
+
self.invalid_choice_message = invalid_choice_message
|
|
407
|
+
|
|
408
|
+
@property
|
|
409
|
+
def choices(self):
|
|
410
|
+
"""The field's choices, resolved from the callable on first read."""
|
|
411
|
+
if self._choices_stale:
|
|
412
|
+
self._choices_stale = False
|
|
413
|
+
self._choices = self._invoke_choices_callback(self._choices_callable)
|
|
414
|
+
return self._choices
|
|
415
|
+
|
|
416
|
+
@choices.setter
|
|
417
|
+
def choices(self, value):
|
|
418
|
+
if callable(value):
|
|
419
|
+
self._choices_callable = value
|
|
420
|
+
self._choices_stale = True
|
|
421
|
+
self._choices = None
|
|
422
|
+
else:
|
|
423
|
+
self._choices_callable = None
|
|
424
|
+
self._choices_stale = False
|
|
425
|
+
self._choices = value
|
|
385
426
|
|
|
386
427
|
def iter_choices(self):
|
|
387
428
|
choices = self._choices_from_input(self.choices) or []
|
|
@@ -418,7 +459,7 @@ class SelectField(SelectFieldBase):
|
|
|
418
459
|
def post_process(self, formdata=None):
|
|
419
460
|
super().post_process(formdata)
|
|
420
461
|
if self._choices_callable is not None:
|
|
421
|
-
self.
|
|
462
|
+
self._choices_stale = True
|
|
422
463
|
|
|
423
464
|
def process_data(self, value):
|
|
424
465
|
try:
|
|
@@ -433,8 +474,11 @@ class SelectField(SelectFieldBase):
|
|
|
433
474
|
|
|
434
475
|
try:
|
|
435
476
|
self.data = self.coerce(valuelist[0])
|
|
436
|
-
except ValueError as exc:
|
|
437
|
-
raise ValueError(
|
|
477
|
+
except (ValueError, TypeError) as exc:
|
|
478
|
+
raise ValueError(
|
|
479
|
+
self.invalid_value_message
|
|
480
|
+
or self.gettext("Invalid Choice: could not coerce.")
|
|
481
|
+
) from exc
|
|
438
482
|
|
|
439
483
|
def pre_validate(self, form):
|
|
440
484
|
if self.process_errors:
|
|
@@ -447,7 +491,9 @@ class SelectField(SelectFieldBase):
|
|
|
447
491
|
raise TypeError(self.gettext("Choices cannot be None."))
|
|
448
492
|
|
|
449
493
|
if not any(choice.selected for choice in self._iter_choices_normalized()):
|
|
450
|
-
raise ValidationError(
|
|
494
|
+
raise ValidationError(
|
|
495
|
+
self.invalid_choice_message or self.gettext("Not a valid choice.")
|
|
496
|
+
)
|
|
451
497
|
|
|
452
498
|
|
|
453
499
|
class SelectMultipleField(SelectField):
|
|
@@ -483,9 +529,7 @@ class SelectMultipleField(SelectField):
|
|
|
483
529
|
validate_choice=validate_choice,
|
|
484
530
|
**kwargs,
|
|
485
531
|
)
|
|
486
|
-
self.invalid_value_message = invalid_value_message
|
|
487
|
-
"Invalid choice(s): one or more data inputs could not be coerced."
|
|
488
|
-
)
|
|
532
|
+
self.invalid_value_message = invalid_value_message
|
|
489
533
|
self.invalid_choice_message = invalid_choice_message
|
|
490
534
|
|
|
491
535
|
def iter_choices(self):
|
|
@@ -527,8 +571,13 @@ class SelectMultipleField(SelectField):
|
|
|
527
571
|
def process_formdata(self, valuelist):
|
|
528
572
|
try:
|
|
529
573
|
self.data = list(self.coerce(x) for x in valuelist)
|
|
530
|
-
except ValueError as exc:
|
|
531
|
-
raise ValueError(
|
|
574
|
+
except (ValueError, TypeError) as exc:
|
|
575
|
+
raise ValueError(
|
|
576
|
+
self.invalid_value_message
|
|
577
|
+
or self.gettext(
|
|
578
|
+
"Invalid choice(s): one or more data inputs could not be coerced."
|
|
579
|
+
)
|
|
580
|
+
) from exc
|
|
532
581
|
|
|
533
582
|
def pre_validate(self, form):
|
|
534
583
|
if self.process_errors:
|
|
@@ -355,14 +355,17 @@ class Field:
|
|
|
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
|
-
The base implementation
|
|
358
|
+
The base implementation refreshes any inline :class:`~wtforms.DataList`
|
|
359
359
|
attached to the field.
|
|
360
360
|
|
|
361
|
+
It may run again after the form data changed. See
|
|
362
|
+
:meth:`~wtforms.form.BaseForm.post_process`.
|
|
363
|
+
|
|
361
364
|
:param formdata: The resolved formdata the enclosing form was bound
|
|
362
365
|
with, or ``None`` for non-formdata cycles.
|
|
363
366
|
"""
|
|
364
367
|
if self._datalist is not None and not isinstance(self._datalist, str):
|
|
365
|
-
self._datalist.
|
|
368
|
+
self._datalist._invalidate(self)
|
|
366
369
|
|
|
367
370
|
def process_data(self, value):
|
|
368
371
|
"""
|
|
@@ -23,7 +23,7 @@ class DateTimeField(Field):
|
|
|
23
23
|
format will be accepted, and the first format in the list will be used
|
|
24
24
|
to produce HTML values.
|
|
25
25
|
|
|
26
|
-
.. deprecated:: 3.
|
|
26
|
+
.. deprecated:: 3.3.0
|
|
27
27
|
``DateTimeField`` renders ``<input type="datetime">``, which is
|
|
28
28
|
obsolete. Use :class:`DateTimeLocalField` instead. ``DateTimeField``
|
|
29
29
|
will be removed in WTForms 3.4.
|
|
@@ -50,9 +50,7 @@ class DateTimeField(Field):
|
|
|
50
50
|
)
|
|
51
51
|
self.format = format if isinstance(format, list) else [format]
|
|
52
52
|
self.strptime_format = clean_datetime_format_for_strptime(self.format)
|
|
53
|
-
self.invalid_value_message = invalid_value_message
|
|
54
|
-
"Not a valid datetime value."
|
|
55
|
-
)
|
|
53
|
+
self.invalid_value_message = invalid_value_message
|
|
56
54
|
|
|
57
55
|
def _value(self):
|
|
58
56
|
if self.raw_data:
|
|
@@ -64,15 +62,17 @@ class DateTimeField(Field):
|
|
|
64
62
|
if not valuelist:
|
|
65
63
|
return
|
|
66
64
|
|
|
67
|
-
date_str = " ".join(valuelist)
|
|
68
65
|
for format in self.strptime_format:
|
|
69
66
|
try:
|
|
67
|
+
date_str = " ".join(valuelist)
|
|
70
68
|
self.data = datetime.datetime.strptime(date_str, format)
|
|
71
69
|
return
|
|
72
|
-
except ValueError:
|
|
70
|
+
except (ValueError, TypeError):
|
|
73
71
|
self.data = None
|
|
74
72
|
|
|
75
|
-
raise ValueError(
|
|
73
|
+
raise ValueError(
|
|
74
|
+
self.invalid_value_message or self.gettext("Not a valid datetime value.")
|
|
75
|
+
)
|
|
76
76
|
|
|
77
77
|
|
|
78
78
|
class DateField(DateTimeField):
|
|
@@ -98,23 +98,22 @@ class DateField(DateTimeField):
|
|
|
98
98
|
invalid_value_message=invalid_value_message,
|
|
99
99
|
**kwargs,
|
|
100
100
|
)
|
|
101
|
-
self.invalid_value_message = invalid_value_message or self.gettext(
|
|
102
|
-
"Not a valid date value."
|
|
103
|
-
)
|
|
104
101
|
|
|
105
102
|
def process_formdata(self, valuelist):
|
|
106
103
|
if not valuelist:
|
|
107
104
|
return
|
|
108
105
|
|
|
109
|
-
date_str = " ".join(valuelist)
|
|
110
106
|
for format in self.strptime_format:
|
|
111
107
|
try:
|
|
108
|
+
date_str = " ".join(valuelist)
|
|
112
109
|
self.data = datetime.datetime.strptime(date_str, format).date()
|
|
113
110
|
return
|
|
114
|
-
except ValueError:
|
|
111
|
+
except (ValueError, TypeError):
|
|
115
112
|
self.data = None
|
|
116
113
|
|
|
117
|
-
raise ValueError(
|
|
114
|
+
raise ValueError(
|
|
115
|
+
self.invalid_value_message or self.gettext("Not a valid date value.")
|
|
116
|
+
)
|
|
118
117
|
|
|
119
118
|
|
|
120
119
|
class TimeField(DateTimeField):
|
|
@@ -140,23 +139,22 @@ class TimeField(DateTimeField):
|
|
|
140
139
|
invalid_value_message=invalid_value_message,
|
|
141
140
|
**kwargs,
|
|
142
141
|
)
|
|
143
|
-
self.invalid_value_message = invalid_value_message or self.gettext(
|
|
144
|
-
"Not a valid time value."
|
|
145
|
-
)
|
|
146
142
|
|
|
147
143
|
def process_formdata(self, valuelist):
|
|
148
144
|
if not valuelist:
|
|
149
145
|
return
|
|
150
146
|
|
|
151
|
-
time_str = " ".join(valuelist)
|
|
152
147
|
for format in self.strptime_format:
|
|
153
148
|
try:
|
|
149
|
+
time_str = " ".join(valuelist)
|
|
154
150
|
self.data = datetime.datetime.strptime(time_str, format).time()
|
|
155
151
|
return
|
|
156
|
-
except ValueError:
|
|
152
|
+
except (ValueError, TypeError):
|
|
157
153
|
self.data = None
|
|
158
154
|
|
|
159
|
-
raise ValueError(
|
|
155
|
+
raise ValueError(
|
|
156
|
+
self.invalid_value_message or self.gettext("Not a valid time value.")
|
|
157
|
+
)
|
|
160
158
|
|
|
161
159
|
|
|
162
160
|
class MonthField(DateField):
|
|
@@ -196,17 +194,14 @@ class WeekField(DateField):
|
|
|
196
194
|
invalid_value_message=invalid_value_message,
|
|
197
195
|
**kwargs,
|
|
198
196
|
)
|
|
199
|
-
self.invalid_value_message = invalid_value_message or self.gettext(
|
|
200
|
-
"Not a valid week value."
|
|
201
|
-
)
|
|
202
197
|
|
|
203
198
|
def process_formdata(self, valuelist):
|
|
204
199
|
if not valuelist:
|
|
205
200
|
return
|
|
206
201
|
|
|
207
|
-
time_str = " ".join(valuelist)
|
|
208
202
|
for format in self.strptime_format:
|
|
209
203
|
try:
|
|
204
|
+
time_str = " ".join(valuelist)
|
|
210
205
|
if "%w" not in format:
|
|
211
206
|
# The '%w' week starting day is needed. This defaults it to monday
|
|
212
207
|
# like ISO 8601 indicates.
|
|
@@ -216,10 +211,12 @@ class WeekField(DateField):
|
|
|
216
211
|
else:
|
|
217
212
|
self.data = datetime.datetime.strptime(time_str, format).date()
|
|
218
213
|
return
|
|
219
|
-
except ValueError:
|
|
214
|
+
except (ValueError, TypeError):
|
|
220
215
|
self.data = None
|
|
221
216
|
|
|
222
|
-
raise ValueError(
|
|
217
|
+
raise ValueError(
|
|
218
|
+
self.invalid_value_message or self.gettext("Not a valid week value.")
|
|
219
|
+
)
|
|
223
220
|
|
|
224
221
|
|
|
225
222
|
class DateTimeLocalField(DateTimeField):
|
|
@@ -205,12 +205,21 @@ class FieldList(Field):
|
|
|
205
205
|
entry.name = new_name
|
|
206
206
|
entry.short_name = str(new_index)
|
|
207
207
|
entry.id = new_id
|
|
208
|
+
self._sync_label(entry, old_id)
|
|
208
209
|
|
|
209
210
|
for descendant in self._iter_descendants(entry):
|
|
210
211
|
if descendant.name and descendant.name.startswith(old_name):
|
|
211
212
|
descendant.name = new_name + descendant.name[len(old_name) :]
|
|
212
213
|
if descendant.id and descendant.id.startswith(old_id):
|
|
214
|
+
old_descendant_id = descendant.id
|
|
213
215
|
descendant.id = new_id + descendant.id[len(old_id) :]
|
|
216
|
+
self._sync_label(descendant, old_descendant_id)
|
|
217
|
+
|
|
218
|
+
@staticmethod
|
|
219
|
+
def _sync_label(field, old_id):
|
|
220
|
+
label = getattr(field, "label", None)
|
|
221
|
+
if label is not None and label.field_id == old_id:
|
|
222
|
+
label.field_id = field.id
|
|
214
223
|
|
|
215
224
|
def _iter_descendants(self, field):
|
|
216
225
|
"""Yield all fields below ``field`` recursively (form sub-fields and
|
|
@@ -231,7 +240,9 @@ class FieldList(Field):
|
|
|
231
240
|
Entries added in this way will *not* receive formdata however, and can
|
|
232
241
|
only receive object data.
|
|
233
242
|
"""
|
|
234
|
-
|
|
243
|
+
field = self._add_entry(data=data)
|
|
244
|
+
field.post_process()
|
|
245
|
+
return field
|
|
235
246
|
|
|
236
247
|
def insert_entry(self, index, data=unset_value):
|
|
237
248
|
"""
|
|
@@ -247,6 +258,7 @@ class FieldList(Field):
|
|
|
247
258
|
field = self._add_entry(data=data)
|
|
248
259
|
self.entries.insert(index, self.entries.pop())
|
|
249
260
|
self._compact_indices()
|
|
261
|
+
field.post_process()
|
|
250
262
|
return field
|
|
251
263
|
|
|
252
264
|
def pop_entry(self, index=-1):
|