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.
Files changed (195) hide show
  1. {wtforms-3.2.2 → wtforms-3.3.0}/CHANGES.rst +111 -1
  2. {wtforms-3.2.2 → wtforms-3.3.0}/PKG-INFO +3 -3
  3. {wtforms-3.2.2 → wtforms-3.3.0}/docs/conf.py +15 -0
  4. {wtforms-3.2.2 → wtforms-3.3.0}/docs/crash_course.rst +16 -0
  5. {wtforms-3.2.2 → wtforms-3.3.0}/docs/fields.rst +260 -40
  6. {wtforms-3.2.2 → wtforms-3.3.0}/docs/forms.rst +5 -3
  7. {wtforms-3.2.2 → wtforms-3.3.0}/docs/specific_problems.rst +5 -5
  8. {wtforms-3.2.2 → wtforms-3.3.0}/docs/validators.rst +7 -4
  9. {wtforms-3.2.2 → wtforms-3.3.0}/docs/whats_new.rst +16 -1
  10. wtforms-3.3.0/docs/widgets.rst +161 -0
  11. {wtforms-3.2.2 → wtforms-3.3.0}/pyproject.toml +4 -5
  12. {wtforms-3.2.2 → wtforms-3.3.0}/src/wtforms/__init__.py +13 -1
  13. wtforms-3.3.0/src/wtforms/_compat.py +15 -0
  14. wtforms-3.3.0/src/wtforms/datalist.py +136 -0
  15. {wtforms-3.2.2 → wtforms-3.3.0}/src/wtforms/fields/__init__.py +10 -0
  16. wtforms-3.3.0/src/wtforms/fields/choices.py +625 -0
  17. {wtforms-3.2.2 → wtforms-3.3.0}/src/wtforms/fields/core.py +46 -5
  18. wtforms-3.3.0/src/wtforms/fields/datetime.py +290 -0
  19. {wtforms-3.2.2 → wtforms-3.3.0}/src/wtforms/fields/form.py +17 -2
  20. {wtforms-3.2.2 → wtforms-3.3.0}/src/wtforms/fields/list.py +96 -13
  21. {wtforms-3.2.2 → wtforms-3.3.0}/src/wtforms/fields/numeric.py +39 -15
  22. {wtforms-3.2.2 → wtforms-3.3.0}/src/wtforms/fields/simple.py +68 -16
  23. {wtforms-3.2.2 → wtforms-3.3.0}/src/wtforms/form.py +40 -1
  24. {wtforms-3.2.2 → wtforms-3.3.0}/src/wtforms/locale/README.md +6 -0
  25. {wtforms-3.2.2 → wtforms-3.3.0}/src/wtforms/locale/ar/LC_MESSAGES/wtforms.po +8 -8
  26. {wtforms-3.2.2 → wtforms-3.3.0}/src/wtforms/locale/bg/LC_MESSAGES/wtforms.po +7 -7
  27. {wtforms-3.2.2 → wtforms-3.3.0}/src/wtforms/locale/ca/LC_MESSAGES/wtforms.po +8 -9
  28. {wtforms-3.2.2 → wtforms-3.3.0}/src/wtforms/locale/cs_CZ/LC_MESSAGES/wtforms.po +7 -7
  29. {wtforms-3.2.2 → wtforms-3.3.0}/src/wtforms/locale/cy/LC_MESSAGES/wtforms.po +9 -8
  30. {wtforms-3.2.2 → wtforms-3.3.0}/src/wtforms/locale/de/LC_MESSAGES/wtforms.po +7 -7
  31. {wtforms-3.2.2 → wtforms-3.3.0}/src/wtforms/locale/de_CH/LC_MESSAGES/wtforms.po +7 -7
  32. {wtforms-3.2.2 → wtforms-3.3.0}/src/wtforms/locale/el/LC_MESSAGES/wtforms.po +7 -7
  33. wtforms-3.3.0/src/wtforms/locale/en/LC_MESSAGES/wtforms.mo +0 -0
  34. {wtforms-3.2.2 → wtforms-3.3.0}/src/wtforms/locale/en/LC_MESSAGES/wtforms.po +59 -43
  35. {wtforms-3.2.2 → wtforms-3.3.0}/src/wtforms/locale/es/LC_MESSAGES/wtforms.po +5 -6
  36. {wtforms-3.2.2 → wtforms-3.3.0}/src/wtforms/locale/et/LC_MESSAGES/wtforms.po +7 -7
  37. {wtforms-3.2.2 → wtforms-3.3.0}/src/wtforms/locale/fa/LC_MESSAGES/wtforms.po +6 -6
  38. {wtforms-3.2.2 → wtforms-3.3.0}/src/wtforms/locale/fi/LC_MESSAGES/wtforms.po +7 -7
  39. {wtforms-3.2.2 → wtforms-3.3.0}/src/wtforms/locale/fr/LC_MESSAGES/wtforms.po +8 -9
  40. {wtforms-3.2.2 → wtforms-3.3.0}/src/wtforms/locale/he/LC_MESSAGES/wtforms.po +7 -7
  41. {wtforms-3.2.2 → wtforms-3.3.0}/src/wtforms/locale/hu/LC_MESSAGES/wtforms.po +7 -7
  42. {wtforms-3.2.2 → wtforms-3.3.0}/src/wtforms/locale/it/LC_MESSAGES/wtforms.po +10 -10
  43. {wtforms-3.2.2 → wtforms-3.3.0}/src/wtforms/locale/ja/LC_MESSAGES/wtforms.po +7 -7
  44. {wtforms-3.2.2 → wtforms-3.3.0}/src/wtforms/locale/kk/LC_MESSAGES/wtforms.po +7 -8
  45. {wtforms-3.2.2 → wtforms-3.3.0}/src/wtforms/locale/ko/LC_MESSAGES/wtforms.po +5 -6
  46. {wtforms-3.2.2 → wtforms-3.3.0}/src/wtforms/locale/nb/LC_MESSAGES/wtforms.po +7 -7
  47. {wtforms-3.2.2 → wtforms-3.3.0}/src/wtforms/locale/nl/LC_MESSAGES/wtforms.mo +0 -0
  48. {wtforms-3.2.2 → wtforms-3.3.0}/src/wtforms/locale/nl/LC_MESSAGES/wtforms.po +9 -11
  49. {wtforms-3.2.2 → wtforms-3.3.0}/src/wtforms/locale/pl/LC_MESSAGES/wtforms.po +9 -10
  50. {wtforms-3.2.2 → wtforms-3.3.0}/src/wtforms/locale/pt/LC_MESSAGES/wtforms.po +5 -6
  51. {wtforms-3.2.2 → wtforms-3.3.0}/src/wtforms/locale/ro/LC_MESSAGES/wtforms.po +11 -11
  52. {wtforms-3.2.2 → wtforms-3.3.0}/src/wtforms/locale/ru/LC_MESSAGES/wtforms.po +8 -9
  53. {wtforms-3.2.2 → wtforms-3.3.0}/src/wtforms/locale/sk/LC_MESSAGES/wtforms.po +5 -6
  54. wtforms-3.3.0/src/wtforms/locale/sr/LC_MESSAGES/wtforms.mo +0 -0
  55. {wtforms-3.2.2 → wtforms-3.3.0}/src/wtforms/locale/sr/LC_MESSAGES/wtforms.po +53 -48
  56. {wtforms-3.2.2 → wtforms-3.3.0}/src/wtforms/locale/sv/LC_MESSAGES/wtforms.po +5 -6
  57. {wtforms-3.2.2 → wtforms-3.3.0}/src/wtforms/locale/ta/LC_MESSAGES/wtforms.po +7 -7
  58. {wtforms-3.2.2 → wtforms-3.3.0}/src/wtforms/locale/tr/LC_MESSAGES/wtforms.po +5 -6
  59. {wtforms-3.2.2 → wtforms-3.3.0}/src/wtforms/locale/uk/LC_MESSAGES/wtforms.po +6 -7
  60. {wtforms-3.2.2 → wtforms-3.3.0}/src/wtforms/locale/wtforms.pot +54 -39
  61. {wtforms-3.2.2 → wtforms-3.3.0}/src/wtforms/locale/zh/LC_MESSAGES/wtforms.po +7 -7
  62. {wtforms-3.2.2 → wtforms-3.3.0}/src/wtforms/locale/zh_TW/LC_MESSAGES/wtforms.po +5 -6
  63. {wtforms-3.2.2 → wtforms-3.3.0}/src/wtforms/validators.py +223 -30
  64. {wtforms-3.2.2 → wtforms-3.3.0}/src/wtforms/widgets/__init__.py +4 -0
  65. {wtforms-3.2.2 → wtforms-3.3.0}/src/wtforms/widgets/core.py +185 -79
  66. {wtforms-3.2.2 → wtforms-3.3.0}/tests/conftest.py +18 -4
  67. wtforms-3.3.0/tests/fields/test_button.py +88 -0
  68. {wtforms-3.2.2 → wtforms-3.3.0}/tests/fields/test_date.py +22 -0
  69. wtforms-3.3.0/tests/fields/test_datetime.py +129 -0
  70. wtforms-3.3.0/tests/fields/test_datetimelocal.py +141 -0
  71. {wtforms-3.2.2 → wtforms-3.3.0}/tests/fields/test_decimal.py +17 -0
  72. {wtforms-3.2.2 → wtforms-3.3.0}/tests/fields/test_field.py +9 -0
  73. wtforms-3.3.0/tests/fields/test_float.py +54 -0
  74. {wtforms-3.2.2 → wtforms-3.3.0}/tests/fields/test_form.py +99 -0
  75. {wtforms-3.2.2 → wtforms-3.3.0}/tests/fields/test_html5.py +55 -1
  76. {wtforms-3.2.2 → wtforms-3.3.0}/tests/fields/test_integer.py +22 -0
  77. wtforms-3.3.0/tests/fields/test_list.py +488 -0
  78. {wtforms-3.2.2 → wtforms-3.3.0}/tests/fields/test_radio.py +35 -6
  79. wtforms-3.3.0/tests/fields/test_select.py +950 -0
  80. wtforms-3.3.0/tests/fields/test_selectmultiple.py +371 -0
  81. {wtforms-3.2.2 → wtforms-3.3.0}/tests/fields/test_time.py +22 -0
  82. {wtforms-3.2.2 → wtforms-3.3.0}/tests/fields/test_week.py +22 -0
  83. wtforms-3.3.0/tests/test_datalist.py +439 -0
  84. {wtforms-3.2.2 → wtforms-3.3.0}/tests/test_form.py +22 -0
  85. {wtforms-3.2.2 → wtforms-3.3.0}/tests/test_widgets.py +141 -5
  86. wtforms-3.3.0/tests/validators/test_date_range.py +126 -0
  87. {wtforms-3.2.2 → wtforms-3.3.0}/tests/validators/test_email.py +47 -0
  88. {wtforms-3.2.2 → wtforms-3.3.0}/tests/validators/test_number_range.py +19 -0
  89. wtforms-3.3.0/tests/validators/test_regexp.py +106 -0
  90. wtforms-3.3.0/tests/validators/test_url.py +143 -0
  91. {wtforms-3.2.2 → wtforms-3.3.0}/tests/validators/test_uuid.py +16 -1
  92. wtforms-3.2.2/.hgignore +0 -1
  93. wtforms-3.2.2/docs/widgets.rst +0 -104
  94. wtforms-3.2.2/src/wtforms/fields/choices.py +0 -227
  95. wtforms-3.2.2/src/wtforms/fields/datetime.py +0 -170
  96. wtforms-3.2.2/src/wtforms/locale/en/LC_MESSAGES/wtforms.mo +0 -0
  97. wtforms-3.2.2/src/wtforms/locale/sr/LC_MESSAGES/wtforms.mo +0 -0
  98. wtforms-3.2.2/tests/fields/test_datetime.py +0 -72
  99. wtforms-3.2.2/tests/fields/test_datetimelocal.py +0 -74
  100. wtforms-3.2.2/tests/fields/test_float.py +0 -30
  101. wtforms-3.2.2/tests/fields/test_list.py +0 -249
  102. wtforms-3.2.2/tests/fields/test_select.py +0 -285
  103. wtforms-3.2.2/tests/fields/test_selectmultiple.py +0 -207
  104. wtforms-3.2.2/tests/validators/test_regexp.py +0 -64
  105. wtforms-3.2.2/tests/validators/test_url.py +0 -76
  106. {wtforms-3.2.2 → wtforms-3.3.0}/.gitignore +0 -0
  107. {wtforms-3.2.2 → wtforms-3.3.0}/LICENSE.rst +0 -0
  108. {wtforms-3.2.2 → wtforms-3.3.0}/README.rst +0 -0
  109. {wtforms-3.2.2 → wtforms-3.3.0}/docs/Makefile +0 -0
  110. {wtforms-3.2.2 → wtforms-3.3.0}/docs/_static/logo_joined.svg +0 -0
  111. {wtforms-3.2.2 → wtforms-3.3.0}/docs/_static/logo_split.svg +0 -0
  112. {wtforms-3.2.2 → wtforms-3.3.0}/docs/_static/logo_square.svg +0 -0
  113. {wtforms-3.2.2 → wtforms-3.3.0}/docs/changes.rst +0 -0
  114. {wtforms-3.2.2 → wtforms-3.3.0}/docs/contributing.rst +0 -0
  115. {wtforms-3.2.2 → wtforms-3.3.0}/docs/csrf.rst +0 -0
  116. {wtforms-3.2.2 → wtforms-3.3.0}/docs/ext.rst +0 -0
  117. {wtforms-3.2.2 → wtforms-3.3.0}/docs/faq.rst +0 -0
  118. {wtforms-3.2.2 → wtforms-3.3.0}/docs/i18n.rst +0 -0
  119. {wtforms-3.2.2 → wtforms-3.3.0}/docs/index.rst +0 -0
  120. {wtforms-3.2.2 → wtforms-3.3.0}/docs/license.rst +0 -0
  121. {wtforms-3.2.2 → wtforms-3.3.0}/docs/make.bat +0 -0
  122. {wtforms-3.2.2 → wtforms-3.3.0}/docs/meta.rst +0 -0
  123. {wtforms-3.2.2 → wtforms-3.3.0}/hatch_build.py +0 -0
  124. {wtforms-3.2.2 → wtforms-3.3.0}/src/wtforms/csrf/__init__.py +0 -0
  125. {wtforms-3.2.2 → wtforms-3.3.0}/src/wtforms/csrf/core.py +0 -0
  126. {wtforms-3.2.2 → wtforms-3.3.0}/src/wtforms/csrf/session.py +0 -0
  127. {wtforms-3.2.2 → wtforms-3.3.0}/src/wtforms/i18n.py +0 -0
  128. {wtforms-3.2.2 → wtforms-3.3.0}/src/wtforms/locale/ar/LC_MESSAGES/wtforms.mo +0 -0
  129. {wtforms-3.2.2 → wtforms-3.3.0}/src/wtforms/locale/bg/LC_MESSAGES/wtforms.mo +0 -0
  130. {wtforms-3.2.2 → wtforms-3.3.0}/src/wtforms/locale/ca/LC_MESSAGES/wtforms.mo +0 -0
  131. {wtforms-3.2.2 → wtforms-3.3.0}/src/wtforms/locale/cs_CZ/LC_MESSAGES/wtforms.mo +0 -0
  132. {wtforms-3.2.2 → wtforms-3.3.0}/src/wtforms/locale/cy/LC_MESSAGES/wtforms.mo +0 -0
  133. {wtforms-3.2.2 → wtforms-3.3.0}/src/wtforms/locale/de/LC_MESSAGES/wtforms.mo +0 -0
  134. {wtforms-3.2.2 → wtforms-3.3.0}/src/wtforms/locale/de_CH/LC_MESSAGES/wtforms.mo +0 -0
  135. {wtforms-3.2.2 → wtforms-3.3.0}/src/wtforms/locale/el/LC_MESSAGES/wtforms.mo +0 -0
  136. {wtforms-3.2.2 → wtforms-3.3.0}/src/wtforms/locale/es/LC_MESSAGES/wtforms.mo +0 -0
  137. {wtforms-3.2.2 → wtforms-3.3.0}/src/wtforms/locale/et/LC_MESSAGES/wtforms.mo +0 -0
  138. {wtforms-3.2.2 → wtforms-3.3.0}/src/wtforms/locale/fa/LC_MESSAGES/wtforms.mo +0 -0
  139. {wtforms-3.2.2 → wtforms-3.3.0}/src/wtforms/locale/fi/LC_MESSAGES/wtforms.mo +0 -0
  140. {wtforms-3.2.2 → wtforms-3.3.0}/src/wtforms/locale/fr/LC_MESSAGES/wtforms.mo +0 -0
  141. {wtforms-3.2.2 → wtforms-3.3.0}/src/wtforms/locale/he/LC_MESSAGES/wtforms.mo +0 -0
  142. {wtforms-3.2.2 → wtforms-3.3.0}/src/wtforms/locale/hu/LC_MESSAGES/wtforms.mo +0 -0
  143. {wtforms-3.2.2 → wtforms-3.3.0}/src/wtforms/locale/it/LC_MESSAGES/wtforms.mo +0 -0
  144. {wtforms-3.2.2 → wtforms-3.3.0}/src/wtforms/locale/ja/LC_MESSAGES/wtforms.mo +0 -0
  145. {wtforms-3.2.2 → wtforms-3.3.0}/src/wtforms/locale/kk/LC_MESSAGES/wtforms.mo +0 -0
  146. {wtforms-3.2.2 → wtforms-3.3.0}/src/wtforms/locale/ko/LC_MESSAGES/wtforms.mo +0 -0
  147. {wtforms-3.2.2 → wtforms-3.3.0}/src/wtforms/locale/nb/LC_MESSAGES/wtforms.mo +0 -0
  148. {wtforms-3.2.2 → wtforms-3.3.0}/src/wtforms/locale/pl/LC_MESSAGES/wtforms.mo +0 -0
  149. {wtforms-3.2.2 → wtforms-3.3.0}/src/wtforms/locale/pt/LC_MESSAGES/wtforms.mo +0 -0
  150. {wtforms-3.2.2 → wtforms-3.3.0}/src/wtforms/locale/ro/LC_MESSAGES/wtforms.mo +0 -0
  151. {wtforms-3.2.2 → wtforms-3.3.0}/src/wtforms/locale/ru/LC_MESSAGES/wtforms.mo +0 -0
  152. {wtforms-3.2.2 → wtforms-3.3.0}/src/wtforms/locale/sk/LC_MESSAGES/wtforms.mo +0 -0
  153. {wtforms-3.2.2 → wtforms-3.3.0}/src/wtforms/locale/sv/LC_MESSAGES/wtforms.mo +0 -0
  154. {wtforms-3.2.2 → wtforms-3.3.0}/src/wtforms/locale/ta/LC_MESSAGES/wtforms.mo +0 -0
  155. {wtforms-3.2.2 → wtforms-3.3.0}/src/wtforms/locale/tr/LC_MESSAGES/wtforms.mo +0 -0
  156. {wtforms-3.2.2 → wtforms-3.3.0}/src/wtforms/locale/uk/LC_MESSAGES/wtforms.mo +0 -0
  157. {wtforms-3.2.2 → wtforms-3.3.0}/src/wtforms/locale/zh/LC_MESSAGES/wtforms.mo +0 -0
  158. {wtforms-3.2.2 → wtforms-3.3.0}/src/wtforms/locale/zh_TW/LC_MESSAGES/wtforms.mo +0 -0
  159. {wtforms-3.2.2 → wtforms-3.3.0}/src/wtforms/meta.py +0 -0
  160. {wtforms-3.2.2 → wtforms-3.3.0}/src/wtforms/utils.py +0 -0
  161. {wtforms-3.2.2 → wtforms-3.3.0}/tests/__init__.py +0 -0
  162. {wtforms-3.2.2 → wtforms-3.3.0}/tests/common.py +0 -0
  163. {wtforms-3.2.2 → wtforms-3.3.0}/tests/fields/__init__.py +0 -0
  164. {wtforms-3.2.2 → wtforms-3.3.0}/tests/fields/test_boolean.py +0 -0
  165. {wtforms-3.2.2 → wtforms-3.3.0}/tests/fields/test_color.py +0 -0
  166. {wtforms-3.2.2 → wtforms-3.3.0}/tests/fields/test_custom.py +0 -0
  167. {wtforms-3.2.2 → wtforms-3.3.0}/tests/fields/test_defaults.py +0 -0
  168. {wtforms-3.2.2 → wtforms-3.3.0}/tests/fields/test_file.py +0 -0
  169. {wtforms-3.2.2 → wtforms-3.3.0}/tests/fields/test_filters.py +0 -0
  170. {wtforms-3.2.2 → wtforms-3.3.0}/tests/fields/test_flags.py +0 -0
  171. {wtforms-3.2.2 → wtforms-3.3.0}/tests/fields/test_hidden.py +0 -0
  172. {wtforms-3.2.2 → wtforms-3.3.0}/tests/fields/test_label.py +0 -0
  173. {wtforms-3.2.2 → wtforms-3.3.0}/tests/fields/test_month.py +0 -0
  174. {wtforms-3.2.2 → wtforms-3.3.0}/tests/fields/test_password.py +0 -0
  175. {wtforms-3.2.2 → wtforms-3.3.0}/tests/fields/test_string.py +0 -0
  176. {wtforms-3.2.2 → wtforms-3.3.0}/tests/fields/test_submit.py +0 -0
  177. {wtforms-3.2.2 → wtforms-3.3.0}/tests/fields/test_textarea.py +0 -0
  178. {wtforms-3.2.2 → wtforms-3.3.0}/tests/test_csrf.py +0 -0
  179. {wtforms-3.2.2 → wtforms-3.3.0}/tests/test_i18n.py +0 -0
  180. {wtforms-3.2.2 → wtforms-3.3.0}/tests/test_locale_babel.py +0 -0
  181. {wtforms-3.2.2 → wtforms-3.3.0}/tests/test_webob.py +0 -0
  182. {wtforms-3.2.2 → wtforms-3.3.0}/tests/validators/__init__.py +0 -0
  183. {wtforms-3.2.2 → wtforms-3.3.0}/tests/validators/test_anyof.py +0 -0
  184. {wtforms-3.2.2 → wtforms-3.3.0}/tests/validators/test_data_required.py +0 -0
  185. {wtforms-3.2.2 → wtforms-3.3.0}/tests/validators/test_disabled.py +0 -0
  186. {wtforms-3.2.2 → wtforms-3.3.0}/tests/validators/test_equal_to.py +0 -0
  187. {wtforms-3.2.2 → wtforms-3.3.0}/tests/validators/test_input_required.py +0 -0
  188. {wtforms-3.2.2 → wtforms-3.3.0}/tests/validators/test_ipaddress.py +0 -0
  189. {wtforms-3.2.2 → wtforms-3.3.0}/tests/validators/test_lazy_proxy.py +0 -0
  190. {wtforms-3.2.2 → wtforms-3.3.0}/tests/validators/test_length.py +0 -0
  191. {wtforms-3.2.2 → wtforms-3.3.0}/tests/validators/test_mac_address.py +0 -0
  192. {wtforms-3.2.2 → wtforms-3.3.0}/tests/validators/test_noneof.py +0 -0
  193. {wtforms-3.2.2 → wtforms-3.3.0}/tests/validators/test_optional.py +0 -0
  194. {wtforms-3.2.2 → wtforms-3.3.0}/tests/validators/test_readonly.py +0 -0
  195. {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.4
1
+ Metadata-Version: 2.5
2
2
  Name: WTForms
3
- Version: 3.2.2
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.10
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 ``<label for="id">`` construct.
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:: RadioField(default field arguments, choices=[], coerce=str)
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 ``<ul>`` list of radio choices.
307
+ a :mdn-tag:`ul` list of radio choices.
308
+
276
309
 
277
- .. class:: SelectField(default field arguments, choices=[], coerce=str, option_widget=None, validate_choice=True)
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 ``(value, label)`` or ``(value, label, render_kw)`` tuples.
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. If set, the ``render_kw`` dictionnary will be rendered as
284
- HTML ``<option>`` parameters. The value can be of any
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 dictionary of ``{label: list}`` pairs defining groupings of options.
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=[('cpp', 'C++'), ('py', 'Python'), ('text', 'Plain Text')])
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
- Note that the `choices` keyword is only evaluated once, so if you want to make
298
- a dynamic drop-down list, you'll want to assign the choices list to the field
299
- after instantiation. Any submitted choices which are not in the given choices
300
- list will cause validation on the field to fail. If this option cannot be
301
- applied to your problem you may wish to skip choice validation (see below).
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 didn't pass a `choices` to the :class:`~wtforms.fields.SelectField`
314
- constructor, but rather created the list in the view function. Also, the
315
- `coerce` keyword arg to :class:`~wtforms.fields.SelectField` says that we
316
- use :func:`int()` to coerce form data. The default coerce is
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=[('1', 'Option 1'), ('2', 'Option 2'), ('None', 'No option')], coerce=coerce_none)
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
- .. autoclass:: SearchField(default field arguments)
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=[], coerce=str, option_widget=None)
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
- .. autoclass:: SubmitField(default field arguments)
362
-
363
- .. autoclass:: StringField(default field arguments)
364
-
365
- .. code-block:: jinja
479
+ .. autofunction:: wtforms.fields.enum_choices
366
480
 
367
- {{ form.username(size=30, maxlength=50) }}
481
+ .. autofunction:: wtforms.fields.enum_coerce
368
482
 
369
- .. autoclass:: TelField(default field arguments)
483
+ Submit fields
484
+ -------------
370
485
 
371
- .. autoclass:: TimeField(default field arguments, format='%H:%M')
486
+ WTForms provides boolean and string-based submit controls.
372
487
 
373
- .. autoclass:: URLField(default field arguments)
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` or :class:`SubmitField` instances.
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` for ways you can
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, field):
155
- return field.strip()
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__