WTForms 3.2.2__tar.gz → 3.3.0b1__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 (191) hide show
  1. {wtforms-3.2.2 → wtforms-3.3.0b1}/CHANGES.rst +73 -1
  2. {wtforms-3.2.2 → wtforms-3.3.0b1}/PKG-INFO +1 -1
  3. {wtforms-3.2.2 → wtforms-3.3.0b1}/docs/conf.py +15 -0
  4. {wtforms-3.2.2 → wtforms-3.3.0b1}/docs/crash_course.rst +16 -0
  5. {wtforms-3.2.2 → wtforms-3.3.0b1}/docs/fields.rst +219 -43
  6. {wtforms-3.2.2 → wtforms-3.3.0b1}/docs/forms.rst +3 -3
  7. {wtforms-3.2.2 → wtforms-3.3.0b1}/docs/specific_problems.rst +5 -5
  8. {wtforms-3.2.2 → wtforms-3.3.0b1}/docs/validators.rst +7 -4
  9. wtforms-3.3.0b1/docs/widgets.rst +162 -0
  10. {wtforms-3.2.2 → wtforms-3.3.0b1}/pyproject.toml +0 -1
  11. {wtforms-3.2.2 → wtforms-3.3.0b1}/src/wtforms/__init__.py +9 -1
  12. wtforms-3.3.0b1/src/wtforms/datalist.py +52 -0
  13. {wtforms-3.2.2 → wtforms-3.3.0b1}/src/wtforms/fields/__init__.py +6 -0
  14. wtforms-3.3.0b1/src/wtforms/fields/choices.py +338 -0
  15. {wtforms-3.2.2 → wtforms-3.3.0b1}/src/wtforms/fields/core.py +26 -4
  16. wtforms-3.3.0b1/src/wtforms/fields/datetime.py +293 -0
  17. {wtforms-3.2.2 → wtforms-3.3.0b1}/src/wtforms/fields/list.py +79 -12
  18. {wtforms-3.2.2 → wtforms-3.3.0b1}/src/wtforms/fields/numeric.py +34 -12
  19. {wtforms-3.2.2 → wtforms-3.3.0b1}/src/wtforms/fields/simple.py +68 -16
  20. {wtforms-3.2.2 → wtforms-3.3.0b1}/src/wtforms/form.py +5 -1
  21. {wtforms-3.2.2 → wtforms-3.3.0b1}/src/wtforms/locale/README.md +6 -0
  22. {wtforms-3.2.2 → wtforms-3.3.0b1}/src/wtforms/locale/ar/LC_MESSAGES/wtforms.po +8 -8
  23. {wtforms-3.2.2 → wtforms-3.3.0b1}/src/wtforms/locale/bg/LC_MESSAGES/wtforms.po +7 -7
  24. {wtforms-3.2.2 → wtforms-3.3.0b1}/src/wtforms/locale/ca/LC_MESSAGES/wtforms.po +8 -9
  25. {wtforms-3.2.2 → wtforms-3.3.0b1}/src/wtforms/locale/cs_CZ/LC_MESSAGES/wtforms.po +7 -7
  26. {wtforms-3.2.2 → wtforms-3.3.0b1}/src/wtforms/locale/cy/LC_MESSAGES/wtforms.po +9 -8
  27. {wtforms-3.2.2 → wtforms-3.3.0b1}/src/wtforms/locale/de/LC_MESSAGES/wtforms.po +7 -7
  28. {wtforms-3.2.2 → wtforms-3.3.0b1}/src/wtforms/locale/de_CH/LC_MESSAGES/wtforms.po +7 -7
  29. {wtforms-3.2.2 → wtforms-3.3.0b1}/src/wtforms/locale/el/LC_MESSAGES/wtforms.po +7 -7
  30. wtforms-3.3.0b1/src/wtforms/locale/en/LC_MESSAGES/wtforms.mo +0 -0
  31. {wtforms-3.2.2 → wtforms-3.3.0b1}/src/wtforms/locale/en/LC_MESSAGES/wtforms.po +59 -43
  32. {wtforms-3.2.2 → wtforms-3.3.0b1}/src/wtforms/locale/es/LC_MESSAGES/wtforms.po +5 -6
  33. {wtforms-3.2.2 → wtforms-3.3.0b1}/src/wtforms/locale/et/LC_MESSAGES/wtforms.po +7 -7
  34. {wtforms-3.2.2 → wtforms-3.3.0b1}/src/wtforms/locale/fa/LC_MESSAGES/wtforms.po +6 -6
  35. {wtforms-3.2.2 → wtforms-3.3.0b1}/src/wtforms/locale/fi/LC_MESSAGES/wtforms.po +7 -7
  36. {wtforms-3.2.2 → wtforms-3.3.0b1}/src/wtforms/locale/fr/LC_MESSAGES/wtforms.po +8 -9
  37. {wtforms-3.2.2 → wtforms-3.3.0b1}/src/wtforms/locale/he/LC_MESSAGES/wtforms.po +7 -7
  38. {wtforms-3.2.2 → wtforms-3.3.0b1}/src/wtforms/locale/hu/LC_MESSAGES/wtforms.po +7 -7
  39. {wtforms-3.2.2 → wtforms-3.3.0b1}/src/wtforms/locale/it/LC_MESSAGES/wtforms.po +10 -10
  40. {wtforms-3.2.2 → wtforms-3.3.0b1}/src/wtforms/locale/ja/LC_MESSAGES/wtforms.po +7 -7
  41. {wtforms-3.2.2 → wtforms-3.3.0b1}/src/wtforms/locale/kk/LC_MESSAGES/wtforms.po +7 -8
  42. {wtforms-3.2.2 → wtforms-3.3.0b1}/src/wtforms/locale/ko/LC_MESSAGES/wtforms.po +5 -6
  43. {wtforms-3.2.2 → wtforms-3.3.0b1}/src/wtforms/locale/nb/LC_MESSAGES/wtforms.po +7 -7
  44. {wtforms-3.2.2 → wtforms-3.3.0b1}/src/wtforms/locale/nl/LC_MESSAGES/wtforms.mo +0 -0
  45. {wtforms-3.2.2 → wtforms-3.3.0b1}/src/wtforms/locale/nl/LC_MESSAGES/wtforms.po +9 -11
  46. {wtforms-3.2.2 → wtforms-3.3.0b1}/src/wtforms/locale/pl/LC_MESSAGES/wtforms.po +9 -10
  47. {wtforms-3.2.2 → wtforms-3.3.0b1}/src/wtforms/locale/pt/LC_MESSAGES/wtforms.po +5 -6
  48. {wtforms-3.2.2 → wtforms-3.3.0b1}/src/wtforms/locale/ro/LC_MESSAGES/wtforms.po +11 -11
  49. {wtforms-3.2.2 → wtforms-3.3.0b1}/src/wtforms/locale/ru/LC_MESSAGES/wtforms.po +8 -9
  50. {wtforms-3.2.2 → wtforms-3.3.0b1}/src/wtforms/locale/sk/LC_MESSAGES/wtforms.po +5 -6
  51. wtforms-3.3.0b1/src/wtforms/locale/sr/LC_MESSAGES/wtforms.mo +0 -0
  52. {wtforms-3.2.2 → wtforms-3.3.0b1}/src/wtforms/locale/sr/LC_MESSAGES/wtforms.po +53 -48
  53. {wtforms-3.2.2 → wtforms-3.3.0b1}/src/wtforms/locale/sv/LC_MESSAGES/wtforms.po +5 -6
  54. {wtforms-3.2.2 → wtforms-3.3.0b1}/src/wtforms/locale/ta/LC_MESSAGES/wtforms.po +7 -7
  55. {wtforms-3.2.2 → wtforms-3.3.0b1}/src/wtforms/locale/tr/LC_MESSAGES/wtforms.po +5 -6
  56. {wtforms-3.2.2 → wtforms-3.3.0b1}/src/wtforms/locale/uk/LC_MESSAGES/wtforms.po +6 -7
  57. {wtforms-3.2.2 → wtforms-3.3.0b1}/src/wtforms/locale/zh/LC_MESSAGES/wtforms.po +7 -7
  58. {wtforms-3.2.2 → wtforms-3.3.0b1}/src/wtforms/locale/zh_TW/LC_MESSAGES/wtforms.po +5 -6
  59. {wtforms-3.2.2 → wtforms-3.3.0b1}/src/wtforms/validators.py +223 -30
  60. {wtforms-3.2.2 → wtforms-3.3.0b1}/src/wtforms/widgets/__init__.py +4 -0
  61. {wtforms-3.2.2 → wtforms-3.3.0b1}/src/wtforms/widgets/core.py +147 -80
  62. {wtforms-3.2.2 → wtforms-3.3.0b1}/tests/conftest.py +7 -1
  63. wtforms-3.3.0b1/tests/fields/test_button.py +88 -0
  64. {wtforms-3.2.2 → wtforms-3.3.0b1}/tests/fields/test_date.py +9 -0
  65. wtforms-3.3.0b1/tests/fields/test_datetime.py +116 -0
  66. wtforms-3.3.0b1/tests/fields/test_datetimelocal.py +141 -0
  67. {wtforms-3.2.2 → wtforms-3.3.0b1}/tests/fields/test_decimal.py +7 -0
  68. {wtforms-3.2.2 → wtforms-3.3.0b1}/tests/fields/test_float.py +13 -2
  69. {wtforms-3.2.2 → wtforms-3.3.0b1}/tests/fields/test_html5.py +55 -1
  70. {wtforms-3.2.2 → wtforms-3.3.0b1}/tests/fields/test_integer.py +9 -0
  71. {wtforms-3.2.2 → wtforms-3.3.0b1}/tests/fields/test_list.py +162 -0
  72. {wtforms-3.2.2 → wtforms-3.3.0b1}/tests/fields/test_radio.py +14 -6
  73. wtforms-3.3.0b1/tests/fields/test_select.py +478 -0
  74. {wtforms-3.2.2 → wtforms-3.3.0b1}/tests/fields/test_selectmultiple.py +95 -14
  75. {wtforms-3.2.2 → wtforms-3.3.0b1}/tests/fields/test_time.py +9 -0
  76. {wtforms-3.2.2 → wtforms-3.3.0b1}/tests/fields/test_week.py +9 -0
  77. wtforms-3.3.0b1/tests/test_datalist.py +371 -0
  78. {wtforms-3.2.2 → wtforms-3.3.0b1}/tests/test_widgets.py +89 -5
  79. wtforms-3.3.0b1/tests/validators/test_date_range.py +126 -0
  80. {wtforms-3.2.2 → wtforms-3.3.0b1}/tests/validators/test_email.py +47 -0
  81. {wtforms-3.2.2 → wtforms-3.3.0b1}/tests/validators/test_number_range.py +19 -0
  82. wtforms-3.3.0b1/tests/validators/test_regexp.py +106 -0
  83. wtforms-3.3.0b1/tests/validators/test_url.py +143 -0
  84. {wtforms-3.2.2 → wtforms-3.3.0b1}/tests/validators/test_uuid.py +16 -1
  85. wtforms-3.2.2/docs/widgets.rst +0 -104
  86. wtforms-3.2.2/src/wtforms/fields/choices.py +0 -227
  87. wtforms-3.2.2/src/wtforms/fields/datetime.py +0 -170
  88. wtforms-3.2.2/src/wtforms/locale/en/LC_MESSAGES/wtforms.mo +0 -0
  89. wtforms-3.2.2/src/wtforms/locale/sr/LC_MESSAGES/wtforms.mo +0 -0
  90. wtforms-3.2.2/tests/fields/test_datetime.py +0 -72
  91. wtforms-3.2.2/tests/fields/test_datetimelocal.py +0 -74
  92. wtforms-3.2.2/tests/fields/test_select.py +0 -285
  93. wtforms-3.2.2/tests/validators/test_regexp.py +0 -64
  94. wtforms-3.2.2/tests/validators/test_url.py +0 -76
  95. {wtforms-3.2.2 → wtforms-3.3.0b1}/.gitignore +0 -0
  96. {wtforms-3.2.2 → wtforms-3.3.0b1}/.hgignore +0 -0
  97. {wtforms-3.2.2 → wtforms-3.3.0b1}/LICENSE.rst +0 -0
  98. {wtforms-3.2.2 → wtforms-3.3.0b1}/README.rst +0 -0
  99. {wtforms-3.2.2 → wtforms-3.3.0b1}/docs/Makefile +0 -0
  100. {wtforms-3.2.2 → wtforms-3.3.0b1}/docs/_static/logo_joined.svg +0 -0
  101. {wtforms-3.2.2 → wtforms-3.3.0b1}/docs/_static/logo_split.svg +0 -0
  102. {wtforms-3.2.2 → wtforms-3.3.0b1}/docs/_static/logo_square.svg +0 -0
  103. {wtforms-3.2.2 → wtforms-3.3.0b1}/docs/changes.rst +0 -0
  104. {wtforms-3.2.2 → wtforms-3.3.0b1}/docs/contributing.rst +0 -0
  105. {wtforms-3.2.2 → wtforms-3.3.0b1}/docs/csrf.rst +0 -0
  106. {wtforms-3.2.2 → wtforms-3.3.0b1}/docs/ext.rst +0 -0
  107. {wtforms-3.2.2 → wtforms-3.3.0b1}/docs/faq.rst +0 -0
  108. {wtforms-3.2.2 → wtforms-3.3.0b1}/docs/i18n.rst +0 -0
  109. {wtforms-3.2.2 → wtforms-3.3.0b1}/docs/index.rst +0 -0
  110. {wtforms-3.2.2 → wtforms-3.3.0b1}/docs/license.rst +0 -0
  111. {wtforms-3.2.2 → wtforms-3.3.0b1}/docs/make.bat +0 -0
  112. {wtforms-3.2.2 → wtforms-3.3.0b1}/docs/meta.rst +0 -0
  113. {wtforms-3.2.2 → wtforms-3.3.0b1}/docs/whats_new.rst +0 -0
  114. {wtforms-3.2.2 → wtforms-3.3.0b1}/hatch_build.py +0 -0
  115. {wtforms-3.2.2 → wtforms-3.3.0b1}/src/wtforms/csrf/__init__.py +0 -0
  116. {wtforms-3.2.2 → wtforms-3.3.0b1}/src/wtforms/csrf/core.py +0 -0
  117. {wtforms-3.2.2 → wtforms-3.3.0b1}/src/wtforms/csrf/session.py +0 -0
  118. {wtforms-3.2.2 → wtforms-3.3.0b1}/src/wtforms/fields/form.py +0 -0
  119. {wtforms-3.2.2 → wtforms-3.3.0b1}/src/wtforms/i18n.py +0 -0
  120. {wtforms-3.2.2 → wtforms-3.3.0b1}/src/wtforms/locale/ar/LC_MESSAGES/wtforms.mo +0 -0
  121. {wtforms-3.2.2 → wtforms-3.3.0b1}/src/wtforms/locale/bg/LC_MESSAGES/wtforms.mo +0 -0
  122. {wtforms-3.2.2 → wtforms-3.3.0b1}/src/wtforms/locale/ca/LC_MESSAGES/wtforms.mo +0 -0
  123. {wtforms-3.2.2 → wtforms-3.3.0b1}/src/wtforms/locale/cs_CZ/LC_MESSAGES/wtforms.mo +0 -0
  124. {wtforms-3.2.2 → wtforms-3.3.0b1}/src/wtforms/locale/cy/LC_MESSAGES/wtforms.mo +0 -0
  125. {wtforms-3.2.2 → wtforms-3.3.0b1}/src/wtforms/locale/de/LC_MESSAGES/wtforms.mo +0 -0
  126. {wtforms-3.2.2 → wtforms-3.3.0b1}/src/wtforms/locale/de_CH/LC_MESSAGES/wtforms.mo +0 -0
  127. {wtforms-3.2.2 → wtforms-3.3.0b1}/src/wtforms/locale/el/LC_MESSAGES/wtforms.mo +0 -0
  128. {wtforms-3.2.2 → wtforms-3.3.0b1}/src/wtforms/locale/es/LC_MESSAGES/wtforms.mo +0 -0
  129. {wtforms-3.2.2 → wtforms-3.3.0b1}/src/wtforms/locale/et/LC_MESSAGES/wtforms.mo +0 -0
  130. {wtforms-3.2.2 → wtforms-3.3.0b1}/src/wtforms/locale/fa/LC_MESSAGES/wtforms.mo +0 -0
  131. {wtforms-3.2.2 → wtforms-3.3.0b1}/src/wtforms/locale/fi/LC_MESSAGES/wtforms.mo +0 -0
  132. {wtforms-3.2.2 → wtforms-3.3.0b1}/src/wtforms/locale/fr/LC_MESSAGES/wtforms.mo +0 -0
  133. {wtforms-3.2.2 → wtforms-3.3.0b1}/src/wtforms/locale/he/LC_MESSAGES/wtforms.mo +0 -0
  134. {wtforms-3.2.2 → wtforms-3.3.0b1}/src/wtforms/locale/hu/LC_MESSAGES/wtforms.mo +0 -0
  135. {wtforms-3.2.2 → wtforms-3.3.0b1}/src/wtforms/locale/it/LC_MESSAGES/wtforms.mo +0 -0
  136. {wtforms-3.2.2 → wtforms-3.3.0b1}/src/wtforms/locale/ja/LC_MESSAGES/wtforms.mo +0 -0
  137. {wtforms-3.2.2 → wtforms-3.3.0b1}/src/wtforms/locale/kk/LC_MESSAGES/wtforms.mo +0 -0
  138. {wtforms-3.2.2 → wtforms-3.3.0b1}/src/wtforms/locale/ko/LC_MESSAGES/wtforms.mo +0 -0
  139. {wtforms-3.2.2 → wtforms-3.3.0b1}/src/wtforms/locale/nb/LC_MESSAGES/wtforms.mo +0 -0
  140. {wtforms-3.2.2 → wtforms-3.3.0b1}/src/wtforms/locale/pl/LC_MESSAGES/wtforms.mo +0 -0
  141. {wtforms-3.2.2 → wtforms-3.3.0b1}/src/wtforms/locale/pt/LC_MESSAGES/wtforms.mo +0 -0
  142. {wtforms-3.2.2 → wtforms-3.3.0b1}/src/wtforms/locale/ro/LC_MESSAGES/wtforms.mo +0 -0
  143. {wtforms-3.2.2 → wtforms-3.3.0b1}/src/wtforms/locale/ru/LC_MESSAGES/wtforms.mo +0 -0
  144. {wtforms-3.2.2 → wtforms-3.3.0b1}/src/wtforms/locale/sk/LC_MESSAGES/wtforms.mo +0 -0
  145. {wtforms-3.2.2 → wtforms-3.3.0b1}/src/wtforms/locale/sv/LC_MESSAGES/wtforms.mo +0 -0
  146. {wtforms-3.2.2 → wtforms-3.3.0b1}/src/wtforms/locale/ta/LC_MESSAGES/wtforms.mo +0 -0
  147. {wtforms-3.2.2 → wtforms-3.3.0b1}/src/wtforms/locale/tr/LC_MESSAGES/wtforms.mo +0 -0
  148. {wtforms-3.2.2 → wtforms-3.3.0b1}/src/wtforms/locale/uk/LC_MESSAGES/wtforms.mo +0 -0
  149. {wtforms-3.2.2 → wtforms-3.3.0b1}/src/wtforms/locale/wtforms.pot +0 -0
  150. {wtforms-3.2.2 → wtforms-3.3.0b1}/src/wtforms/locale/zh/LC_MESSAGES/wtforms.mo +0 -0
  151. {wtforms-3.2.2 → wtforms-3.3.0b1}/src/wtforms/locale/zh_TW/LC_MESSAGES/wtforms.mo +0 -0
  152. {wtforms-3.2.2 → wtforms-3.3.0b1}/src/wtforms/meta.py +0 -0
  153. {wtforms-3.2.2 → wtforms-3.3.0b1}/src/wtforms/utils.py +0 -0
  154. {wtforms-3.2.2 → wtforms-3.3.0b1}/tests/__init__.py +0 -0
  155. {wtforms-3.2.2 → wtforms-3.3.0b1}/tests/common.py +0 -0
  156. {wtforms-3.2.2 → wtforms-3.3.0b1}/tests/fields/__init__.py +0 -0
  157. {wtforms-3.2.2 → wtforms-3.3.0b1}/tests/fields/test_boolean.py +0 -0
  158. {wtforms-3.2.2 → wtforms-3.3.0b1}/tests/fields/test_color.py +0 -0
  159. {wtforms-3.2.2 → wtforms-3.3.0b1}/tests/fields/test_custom.py +0 -0
  160. {wtforms-3.2.2 → wtforms-3.3.0b1}/tests/fields/test_defaults.py +0 -0
  161. {wtforms-3.2.2 → wtforms-3.3.0b1}/tests/fields/test_field.py +0 -0
  162. {wtforms-3.2.2 → wtforms-3.3.0b1}/tests/fields/test_file.py +0 -0
  163. {wtforms-3.2.2 → wtforms-3.3.0b1}/tests/fields/test_filters.py +0 -0
  164. {wtforms-3.2.2 → wtforms-3.3.0b1}/tests/fields/test_flags.py +0 -0
  165. {wtforms-3.2.2 → wtforms-3.3.0b1}/tests/fields/test_form.py +0 -0
  166. {wtforms-3.2.2 → wtforms-3.3.0b1}/tests/fields/test_hidden.py +0 -0
  167. {wtforms-3.2.2 → wtforms-3.3.0b1}/tests/fields/test_label.py +0 -0
  168. {wtforms-3.2.2 → wtforms-3.3.0b1}/tests/fields/test_month.py +0 -0
  169. {wtforms-3.2.2 → wtforms-3.3.0b1}/tests/fields/test_password.py +0 -0
  170. {wtforms-3.2.2 → wtforms-3.3.0b1}/tests/fields/test_string.py +0 -0
  171. {wtforms-3.2.2 → wtforms-3.3.0b1}/tests/fields/test_submit.py +0 -0
  172. {wtforms-3.2.2 → wtforms-3.3.0b1}/tests/fields/test_textarea.py +0 -0
  173. {wtforms-3.2.2 → wtforms-3.3.0b1}/tests/test_csrf.py +0 -0
  174. {wtforms-3.2.2 → wtforms-3.3.0b1}/tests/test_form.py +0 -0
  175. {wtforms-3.2.2 → wtforms-3.3.0b1}/tests/test_i18n.py +0 -0
  176. {wtforms-3.2.2 → wtforms-3.3.0b1}/tests/test_locale_babel.py +0 -0
  177. {wtforms-3.2.2 → wtforms-3.3.0b1}/tests/test_webob.py +0 -0
  178. {wtforms-3.2.2 → wtforms-3.3.0b1}/tests/validators/__init__.py +0 -0
  179. {wtforms-3.2.2 → wtforms-3.3.0b1}/tests/validators/test_anyof.py +0 -0
  180. {wtforms-3.2.2 → wtforms-3.3.0b1}/tests/validators/test_data_required.py +0 -0
  181. {wtforms-3.2.2 → wtforms-3.3.0b1}/tests/validators/test_disabled.py +0 -0
  182. {wtforms-3.2.2 → wtforms-3.3.0b1}/tests/validators/test_equal_to.py +0 -0
  183. {wtforms-3.2.2 → wtforms-3.3.0b1}/tests/validators/test_input_required.py +0 -0
  184. {wtforms-3.2.2 → wtforms-3.3.0b1}/tests/validators/test_ipaddress.py +0 -0
  185. {wtforms-3.2.2 → wtforms-3.3.0b1}/tests/validators/test_lazy_proxy.py +0 -0
  186. {wtforms-3.2.2 → wtforms-3.3.0b1}/tests/validators/test_length.py +0 -0
  187. {wtforms-3.2.2 → wtforms-3.3.0b1}/tests/validators/test_mac_address.py +0 -0
  188. {wtforms-3.2.2 → wtforms-3.3.0b1}/tests/validators/test_noneof.py +0 -0
  189. {wtforms-3.2.2 → wtforms-3.3.0b1}/tests/validators/test_optional.py +0 -0
  190. {wtforms-3.2.2 → wtforms-3.3.0b1}/tests/validators/test_readonly.py +0 -0
  191. {wtforms-3.2.2 → wtforms-3.3.0b1}/tests/validators/test_validators.py +0 -0
@@ -1,5 +1,78 @@
1
1
  .. currentmodule:: wtforms
2
2
 
3
+ Version 3.3.0b1
4
+ ---------------
5
+
6
+ Released 2026-05-10
7
+
8
+ - Add :class:`~validators.DateRange`. ``min`` and ``max`` may be callables
9
+ to compute dynamic bounds; :class:`~validators.NumberRange` accepts
10
+ callables on the same terms. :pr:`787`
11
+ - Make :class:`~fields.FloatField` render as ``<input type="number">`` with
12
+ ``step="any"``. :issue:`677` :pr:`679`
13
+ - :meth:`~fields.FieldList._add_entry` now routes through :meth:`~meta.DefaultMeta.bind_field`,
14
+ consistent with how the form constructor binds top-level fields. Custom ``Meta.bind_field``
15
+ overrides were silently bypassed for all ``FieldList`` entries.
16
+ - Add `test_environment` support to :class:`~validators.Email`. :issue:`869`
17
+ - Reset :class:`~fields.FieldList` index state when reprocessing entries. :issue:`874`
18
+ - Let :class:`~widgets.RangeInput` accept `min` and `max` at construction time. :issue:`693`
19
+ - Do not render a ``for`` attribute on the main label of :class:`~fields.RadioField`.
20
+ :issue:`775`
21
+ - Deprecate :class:`~fields.DateTimeField`, which will be removed in WTForms 3.4. :issue:`831`
22
+ - Clarify that :class:`~fields.SelectField` ``choices`` is re-evaluated at
23
+ every form instantiation, including callables, in the documentation. :issue:`875`
24
+ - Render ``False`` as ``value="False"`` in :class:`~fields.SelectField` options
25
+ instead of omitting the attribute. :issue:`878`
26
+ - Accept :class:`uuid.UUID` instances in the :class:`~validators.UUID` validator,
27
+ and translate unexpected types into :class:`~validators.ValidationError`
28
+ instead of letting :class:`TypeError` escape. :issue:`549` :pr:`769`
29
+ - Escape double quotes in :class:`~markupsafe.Markup` attribute values
30
+ passed to :func:`~widgets.html_params`, and return a
31
+ :class:`~markupsafe.Markup`. :issue:`801`
32
+ - Clarify documentation about filters. :issue:`859`
33
+ - Let render-time ``checked`` kwargs take precedence over the field's value
34
+ in :class:`~widgets.CheckboxInput` and :class:`~widgets.RadioInput`.
35
+ :issue:`706`
36
+ - Add a ``tz`` parameter to :class:`~fields.DateTimeLocalField` to associate a
37
+ timezone with the input. :issue:`833`
38
+ - Add :class:`~fields.FieldList` ``insert_entry`` and indexed ``pop_entry``.
39
+ :issue:`256`
40
+ - :class:`~fields.FieldList` builds entries in the order keys appear in
41
+ ``formdata``, instead of sorting them by numeric index. :issue:`256`
42
+ - Add :class:`~fields.ButtonField` to render ``<button type="submit">``
43
+ controls with string values. :issue:`784`
44
+ - :class:`~fields.SelectField` refactor. Choices tuples and dicts are
45
+ deprecated in favor of :class:`~fields.Choice`. :pr:`739`
46
+ - ``<option>`` HTML attributes can be passed using
47
+ :class:`~fields.Choice`. :issue:`692` :pr:`739`
48
+ - Add :meth:`fields.Choice.from_enum` to build choices from an
49
+ :class:`enum.Enum` class, and let :class:`~fields.SelectField` accept an
50
+ Enum class as ``coerce``. :issue:`338`
51
+ - Defer to ``email_validator``'s module-level defaults for the
52
+ :class:`~validators.Email` ``test_environment``, ``allow_smtputf8`` and
53
+ ``allow_empty_local`` options. :issue:`915`
54
+ - Add ``invalid_value_message`` and ``invalid_choice_message`` keyword
55
+ arguments for customizing built-in field error messages. On
56
+ ``SelectMultipleField``, ``invalid_choice_message`` may also be a callable
57
+ for custom pluralization. :issue:`39` :issue:`832`
58
+ - :class:`~fields.SelectField` and :class:`~fields.SelectMultipleField`
59
+ ``pre_validate`` now short-circuits when ``process_formdata`` already
60
+ produced an error, so a single coercion failure no longer surfaces as both
61
+ an "invalid value" and an "invalid choice" error.
62
+ - Add a ``matcher`` keyword argument to :class:`~validators.Regexp` to
63
+ override the matching function. Defaults to :func:`re.match`; pass
64
+ :func:`re.search` or :func:`re.fullmatch` to change the anchoring
65
+ behaviour. :issue:`867`
66
+ - Add an ``html_pattern`` keyword argument to :class:`~validators.Regexp`
67
+ to control the HTML ``pattern`` attribute emitted on supporting widgets.
68
+ :issue:`759`
69
+ - :class:`~validators.URL` validator is based on ``urllib.parse.urlparse``
70
+ instead of a regex. New keyword arguments: ``allow_userinfo`` for
71
+ accepting ``user:password@host`` URLs; ``schemes``
72
+ (default ``("http", "https")``) is the allowlist of accepted schemes
73
+ :pr:`847`
74
+ - Add native :class:`DataList` support. :issue:`653`
75
+
3
76
  Version 3.2.2
4
77
  -------------
5
78
 
@@ -44,7 +117,6 @@ Released 2024-10-20
44
117
  If you need to keep the old behavior you can set the ``_form_error_key``
45
118
  parameter of your form to :data:`None`.
46
119
 
47
-
48
120
  Version 3.1.2
49
121
  -------------
50
122
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: WTForms
3
- Version: 3.2.2
3
+ Version: 3.3.0b1
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
@@ -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
@@ -135,7 +138,7 @@ The Field base class
135
138
  .. attribute:: label
136
139
 
137
140
  This is a :class:`Label` instance which when evaluated as a string
138
- returns an HTML ``<label for="id">`` construct.
141
+ returns an HTML :mdn-tag:`label` construct.
139
142
 
140
143
  .. attribute:: default
141
144
 
@@ -220,6 +223,13 @@ refer to a single input from the form.
220
223
 
221
224
  .. autoclass:: DateTimeField(default field arguments, format='%Y-%m-%d %H:%M:%S')
222
225
 
226
+ .. warning::
227
+ :class:`DateTimeField` renders ``<input type="datetime">``, which is
228
+ obsolete in HTML and not broadly supported by browsers. Use
229
+ :class:`DateTimeLocalField` instead.
230
+
231
+ :class:`DateTimeField` is deprecated and will be removed in WTForms 3.4.
232
+
223
233
  .. autoclass:: DateTimeLocalField(default field arguments, format='%Y-%m-%d %H:%M:%S')
224
234
 
225
235
  .. autoclass:: DecimalField(default field arguments, places=2, rounding=None, use_locale=False, number_format=None)
@@ -233,7 +243,7 @@ refer to a single input from the form.
233
243
  Example usage::
234
244
 
235
245
  class UploadForm(Form):
236
- image = FileField('Image File', [validators.regexp('^[^/\\]\.jpg$')])
246
+ image = FileField('Image File', [validators.regexp(r'^[^/\\]\.jpg$', matcher=re.fullmatch)])
237
247
  description = TextAreaField('Image Description')
238
248
 
239
249
  def validate_image(form, field):
@@ -260,7 +270,28 @@ refer to a single input from the form.
260
270
 
261
271
  .. autoclass:: MonthField(default field arguments, format='%Y-%m')
262
272
 
263
- .. autoclass:: RadioField(default field arguments, choices=[], coerce=str)
273
+ .. autoclass:: SearchField(default field arguments)
274
+
275
+ .. autoclass:: StringField(default field arguments)
276
+
277
+ .. code-block:: jinja
278
+
279
+ {{ form.username(size=30, maxlength=50) }}
280
+
281
+ .. autoclass:: TelField(default field arguments)
282
+
283
+ .. autoclass:: TimeField(default field arguments, format='%H:%M')
284
+
285
+ .. autoclass:: URLField(default field arguments)
286
+
287
+ Choice Fields
288
+ -------------
289
+
290
+ .. autoclass:: Choice
291
+
292
+ .. autoclass:: SelectChoice
293
+
294
+ .. autoclass:: RadioField(default field arguments, choices=None, coerce=str)
264
295
 
265
296
  .. code-block:: jinja
266
297
 
@@ -272,49 +303,70 @@ refer to a single input from the form.
272
303
  {% endfor %}
273
304
 
274
305
  Simply outputting the field without iterating its subfields will result in
275
- a ``<ul>`` list of radio choices.
306
+ a :mdn-tag:`ul` list of radio choices.
307
+
276
308
 
277
- .. class:: SelectField(default field arguments, choices=[], coerce=str, option_widget=None, validate_choice=True)
309
+ .. class:: SelectField(default field arguments, choices=None, coerce=str, option_widget=None, validate_choice=True, invalid_value_message=None, invalid_choice_message=None)
278
310
 
279
311
  Select fields take a ``choices`` parameter which is either:
280
312
 
281
- * a list of ``(value, label)`` or ``(value, label, render_kw)`` tuples.
313
+ * a list of :class:`Choice`.
282
314
  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
315
+ as the label. The :class:`Choice` ``render_kw`` mapping is rendered as
316
+ HTML :mdn-tag:`option` parameters. The value can be of any
285
317
  type, but because form data is sent to the browser as strings, you
286
318
  will need to provide a ``coerce`` function that converts a string
287
319
  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.
320
+ * a function taking no argument, and returning a list of :class:`Choice`.
290
321
 
291
322
 
292
323
  **Select fields with static choice values**::
293
324
 
294
325
  class PastebinEntry(Form):
295
- language = SelectField('Programming Language', choices=[('cpp', 'C++'), ('py', 'Python'), ('text', 'Plain Text')])
326
+ language = SelectField('Programming Language', choices=[
327
+ Choice('cpp', 'C++'),
328
+ Choice('py', 'Python'),
329
+ Choice('text', 'Plain Text'),
330
+ ])
331
+
332
+ Note that the `choices` keyword is evaluated each time the form is
333
+ instantiated, so callables passed as `choices` are re-evaluated per instance.
334
+ For dynamic options, either pass a callable to `choices` or assign
335
+ `field.choices` after instantiation. Any submitted choices which are not in
336
+ the given choices list will cause validation on the field to fail. If this
337
+ option cannot be applied to your problem you may wish to skip choice
338
+ validation (see below).
339
+
340
+ **Select fields with ``<optgroup>``**::
296
341
 
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).
342
+ Use :class:`SelectChoice` to assign an option to an ``<optgroup>``.
343
+
344
+ class PastebinEntry(Form):
345
+ language = SelectField('Programming Language', choices=[
346
+ SelectChoice('cpp', 'C++', optgroup='Compiled'),
347
+ SelectChoice('rs', 'Rust', optgroup='Compiled'),
348
+ SelectChoice('py', 'Python', optgroup='Interpreted'),
349
+ SelectChoice('text', 'Plain Text'),
350
+ ])
302
351
 
303
352
  **Select fields with dynamic choice values**::
304
353
 
354
+ def available_groups():
355
+ return [Choice(g.id, g.name) for g in Group.query.order_by('name')]
356
+
305
357
  class UserDetails(Form):
306
- group_id = SelectField('Group', coerce=int)
358
+ group_id = SelectField('Group', coerce=int, choices=available_groups)
307
359
 
308
360
  def edit_user(request, id):
309
361
  user = User.query.get(id)
310
362
  form = UserDetails(request.POST, obj=user)
311
- form.group_id.choices = [(g.id, g.name) for g in Group.query.order_by('name')]
312
363
 
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
317
- :func:`str()`.
364
+ Note we passed a callable to `choices` rather than a static list; it is
365
+ invoked each time the form is instantiated, producing fresh options per
366
+ request. If the choices depend on data only available in the view, you can
367
+ instead assign `form.group_id.choices` after instantiation. The `coerce`
368
+ keyword arg to :class:`~wtforms.fields.SelectField` says that we use
369
+ :func:`int()` to coerce form data. The default coerce is :func:`str()`.
318
370
 
319
371
  **Coerce function example**::
320
372
 
@@ -324,11 +376,41 @@ refer to a single input from the form.
324
376
  return value
325
377
 
326
378
  class NonePossible(Form):
327
- my_select_field = SelectField('Select an option', choices=[('1', 'Option 1'), ('2', 'Option 2'), ('None', 'No option')], coerce=coerce_none)
379
+ my_select_field = SelectField('Select an option', choices=[
380
+ Choice('1', 'Option 1'),
381
+ Choice('2', 'Option 2'),
382
+ Choice('None', 'No option'),
383
+ ], coerce=coerce_none)
328
384
 
329
385
  Note when the option None is selected a 'None' str will be passed. By using a coerce
330
386
  function the 'None' str will be converted to None.
331
387
 
388
+ **Select fields backed by an Enum**::
389
+
390
+ from enum import Enum
391
+
392
+ class Color(Enum):
393
+ RED = 1
394
+ GREEN = 2
395
+ BLUE = 3
396
+
397
+ class PaintForm(Form):
398
+ color = SelectField(choices=Choice.from_enum(Color), coerce=Color)
399
+
400
+ :meth:`Choice.from_enum` builds the option list from the Enum items;
401
+ the HTML ``value`` of each option is the item's ``name``. Passing the
402
+ Enum class itself as ``coerce`` installs the matching coercion, so
403
+ ``form.color.data`` is a ``Color`` item after submit. Pre-selecting
404
+ works the usual way, with an Enum item: ``PaintForm(color=Color.RED)``.
405
+
406
+ By default the option label is ``str(item)`` if the Enum defines its
407
+ own ``__str__`` (also the case for :class:`enum.StrEnum`), otherwise
408
+ ``item.name``. To customise, pass a ``label`` callable taking an Enum
409
+ item and returning the label string::
410
+
411
+ Choice.from_enum(Color, label=lambda item: item.name.title())
412
+ # → [Choice('RED', 'Red'), Choice('GREEN', 'Green'), Choice('BLUE', 'Blue')]
413
+
332
414
  **Skipping choice validation**::
333
415
 
334
416
  class DynamicSelectForm(Form):
@@ -349,33 +431,31 @@ refer to a single input from the form.
349
431
  a list of fields each representing an option. The rendering of this can be
350
432
  further controlled by specifying `option_widget=`.
351
433
 
352
- .. autoclass:: SearchField(default field arguments)
434
+ Use ``invalid_value_message=`` to customize errors raised when submitted
435
+ data cannot be coerced, and ``invalid_choice_message=`` to customize errors
436
+ raised when the value is not present in ``choices``.
353
437
 
354
- .. autoclass:: SelectMultipleField(default field arguments, choices=[], coerce=str, option_widget=None)
438
+ .. autoclass:: SelectMultipleField(default field arguments, choices=None, coerce=str, option_widget=None, invalid_value_message=None, invalid_choice_message=None)
355
439
 
356
440
  The data on the SelectMultipleField is stored as a list of objects, each of
357
441
  which is checked and coerced from the form input. Any submitted choices
358
442
  which are not in the given choices list will cause validation on the field
359
443
  to fail.
360
444
 
361
- .. autoclass:: SubmitField(default field arguments)
362
-
363
- .. autoclass:: StringField(default field arguments)
364
-
365
- .. code-block:: jinja
366
-
367
- {{ form.username(size=30, maxlength=50) }}
445
+ Submit fields
446
+ -------------
368
447
 
369
- .. autoclass:: TelField(default field arguments)
448
+ WTForms provides boolean and string-based submit controls.
370
449
 
371
- .. autoclass:: TimeField(default field arguments, format='%H:%M')
372
-
373
- .. autoclass:: URLField(default field arguments)
450
+ .. autoclass:: ButtonField(default field arguments)
374
451
 
452
+ .. autoclass:: SubmitField(default field arguments)
375
453
 
376
454
  Convenience Fields
377
455
  ------------------
378
456
 
457
+ .. currentmodule:: wtforms.fields
458
+
379
459
  .. autoclass:: HiddenField(default field arguments)
380
460
 
381
461
  HiddenField is useful for providing data from a model or the application to
@@ -436,11 +516,24 @@ complex data structures such as lists and nested objects can be represented.
436
516
  .. autoclass:: FieldList(unbound_field, default field arguments, min_entries=0, max_entries=None, separator='-')
437
517
 
438
518
  **Note**: Due to a limitation in how HTML sends values, FieldList cannot enclose
439
- :class:`BooleanField` or :class:`SubmitField` instances.
519
+ :class:`BooleanField`, :class:`ButtonField`, or :class:`SubmitField`
520
+ instances.
440
521
 
441
522
  .. automethod:: append_entry([data])
523
+ .. automethod:: insert_entry(index[, data])
442
524
  .. automethod:: pop_entry
443
525
 
526
+ ``append_entry`` and ``insert_entry`` accept Python object data for the new
527
+ entry, not submitted formdata. For simple enclosed fields this is usually a
528
+ scalar value. For ``FieldList(FormField(...))``, this may be a dict or an
529
+ object that the enclosed form can process, similar to passing ``data`` or
530
+ ``obj`` when constructing that form.
531
+
532
+ Entries are kept with consecutive indices: ``insert_entry``,
533
+ ``pop_entry`` and rebuilds from formdata renumber entries so that each
534
+ entry's ``index``, ``name`` and ``id`` reflect its position in
535
+ :attr:`entries`.
536
+
444
537
  .. attribute:: entries
445
538
 
446
539
  Each entry in a FieldList is actually an instance of the field you
@@ -448,8 +541,8 @@ complex data structures such as lists and nested objects can be represented.
448
541
  FieldList works as expected, and proxies to the enclosed entries list.
449
542
 
450
543
  **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.
544
+ undefined behavior. See `append_entry`, `insert_entry`, and `pop_entry`
545
+ for ways you can manipulate the list.
453
546
 
454
547
  .. automethod:: __iter__
455
548
  .. automethod:: __len__
@@ -461,7 +554,7 @@ complex data structures such as lists and nested objects can be represented.
461
554
  FormField::
462
555
 
463
556
  class IMForm(Form):
464
- protocol = SelectField(choices=[('aim', 'AIM'), ('msn', 'MSN')])
557
+ protocol = SelectField(choices=[Choice('aim', 'AIM'), Choice('msn', 'MSN')])
465
558
  username = StringField()
466
559
 
467
560
  class ContactForm(Form):
@@ -470,6 +563,90 @@ complex data structures such as lists and nested objects can be represented.
470
563
  im_accounts = FieldList(FormField(IMForm))
471
564
 
472
565
 
566
+ Data Lists
567
+ ----------
568
+
569
+ .. currentmodule:: wtforms
570
+
571
+ .. class:: DataList(choices=None, *, render_kw=None, widget=None)
572
+
573
+ A :mdn-tag:`datalist` of suggestions. Unlike
574
+ :class:`~wtforms.fields.SelectField`, the
575
+ input stays a free-form text input: the browser displays the
576
+ suggestions but the user may type any value. Combine with
577
+ :class:`~wtforms.validators.AnyOf` if you need a closed set.
578
+
579
+ ``choices`` is either a list of :class:`~wtforms.fields.Choice`
580
+ (or plain strings, in which case the string is used as both value
581
+ and label), or a callable invoked at render time. The callable
582
+ may take no argument (``fn()``) for a static list, or ``(field)``
583
+ to adapt the suggestions to the current field value at each
584
+ render — convenient for server-side filtering::
585
+
586
+ DataList(lambda field: search(field.data))
587
+
588
+ **Inline DataList owned by a field**
589
+
590
+ Pass a :class:`DataList` instance directly as ``datalist=`` to get
591
+ a field-owned datalist with a unique id derived from the field's
592
+ id::
593
+
594
+ class F(Form):
595
+ country = StringField(datalist=DataList(["FR", "US"]))
596
+
597
+ {{ form.country() }}
598
+ {{ form.country.datalist() }}
599
+
600
+ ``{{ form.country() }}`` emits the ``<input list="...">`` and
601
+ ``{{ form.country.datalist() }}`` emits the matching
602
+ :mdn-tag:`datalist` element. Both calls are explicit, like
603
+ :attr:`Field.errors` or :attr:`Field.label` — you decide where each
604
+ piece appears in the DOM.
605
+
606
+ Inside a :class:`~wtforms.fields.FieldList`, each entry binds its
607
+ own clone of the inline :class:`DataList`, so a callable
608
+ ``choices`` sees each entry's own value::
609
+
610
+ class F(Form):
611
+ items = FieldList(
612
+ StringField(
613
+ datalist=DataList(lambda field: suggest(field.data))
614
+ ),
615
+ min_entries=3,
616
+ )
617
+
618
+ {% for entry in form.items %}
619
+ {{ entry() }}
620
+ {{ entry.datalist() }}
621
+ {% endfor %}
622
+
623
+ **String references**
624
+
625
+ A field may also reference an existing datalist id directly by
626
+ string::
627
+
628
+ class F(Form):
629
+ field = StringField(datalist="external")
630
+
631
+ The ``list="external"`` attribute is emitted as-is — the user is
632
+ expected to render the :mdn-tag:`datalist` element themselves (in
633
+ a shared layout, a static template, or another form on the same
634
+ page). ``field.datalist()`` returns empty markup in this case, so
635
+ calling it unconditionally in templates is safe.
636
+
637
+ **Compatible fields**
638
+
639
+ Any field accepts a ``datalist=`` parameter, but only text-based
640
+ HTML inputs (``text``, ``search``, ``email``, ``tel``, ``url``,
641
+ ``number``, ``range``, ``color``, date/time inputs) honor the
642
+ ``list=`` attribute. Passing ``datalist=`` to a
643
+ :class:`~wtforms.fields.SelectField` or
644
+ :class:`~wtforms.fields.TextAreaField` is silently ignored by the
645
+ browser.
646
+
647
+
648
+ .. currentmodule:: wtforms.fields
649
+
473
650
  Custom Fields
474
651
  -------------
475
652
 
@@ -583,8 +760,7 @@ Additional Helper Classes
583
760
  .. class:: Label
584
761
 
585
762
  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>``
763
+ Labels can be printed to yield a :mdn-tag:`label`
588
764
  HTML tag enclosure. Similar to fields, you can also call the label with
589
765
  additional html params.
590
766
 
@@ -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
 
@@ -124,10 +124,10 @@ Alternatively, you can create a field which does this by subclassing::
124
124
  class HiddenInteger(IntegerField):
125
125
  widget = HiddenInput()
126
126
 
127
- Some fields support even more sophisticated customization.For example, what if
128
- a multiple-select was desired where instead of using a multi-row ``<select>``,
129
- a series of checkboxes was used? By using widgets, one can get that behavior
130
- very easily::
127
+ Some fields support even more sophisticated customization. For example, what if
128
+ a multiple-select was desired where instead of using a multi-row
129
+ :mdn-tag:`select`, a series of :mdn-input:`checkbox` controls was used? By
130
+ using widgets, one can get that behavior very easily::
131
131
 
132
132
  class MultiCheckboxField(SelectMultipleField):
133
133
  """
@@ -140,4 +140,4 @@ very easily::
140
140
  option_widget = widgets.CheckboxInput()
141
141
 
142
142
  By overriding `option_widget`, our new multiple-select when iterated will now
143
- produce fields that render as checkboxes.
143
+ produce fields that render as :mdn-input:`checkbox` controls.
@@ -15,15 +15,15 @@ Built-in validators
15
15
 
16
16
  .. autoclass:: wtforms.validators.DataRequired
17
17
 
18
- This also sets the ``required`` :attr:`flag <wtforms.fields.Field.flags>` on
19
- fields it is used on. This flag causes the ``required`` attribute to be
18
+ This also sets the :mdn-attr:`required` :attr:`flag <wtforms.fields.Field.flags>` on
19
+ fields it is used on. This flag causes the :mdn-attr:`required` attribute to be
20
20
  rendered in the tag, which prevents a request/response cycle for validation.
21
21
  This behavior can be overridden in the following ways:
22
22
 
23
23
  - Specifying ``required=False`` when rendering in the template.
24
24
  - Making a custom a widget that doesn't set it.
25
- - Rendering the ``novalidate`` attribute" on the ``form`` tag, or the
26
- ``formnovalidate`` attribute on a submit button.
25
+ - Rendering the :mdn-attr:`novalidate` attribute on the :mdn-tag:`form`
26
+ tag, or the :mdn-attr:`formnovalidate` attribute on a submit button.
27
27
 
28
28
  The ``required`` flag behavior also applies to the :class:`InputRequired` class.
29
29
 
@@ -57,6 +57,9 @@ Built-in validators
57
57
 
58
58
  .. autoclass:: wtforms.validators.NumberRange
59
59
 
60
+ .. autoclass:: wtforms.validators.DateRange
61
+
62
+
60
63
  .. autoclass:: wtforms.validators.Optional
61
64
 
62
65
  This also sets the ``optional`` :attr:`flag <wtforms.fields.Field.flags>` on