WTForms 3.1.1__tar.gz → 3.2.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 (187) hide show
  1. {wtforms-3.1.1 → wtforms-3.2.0}/CHANGES.rst +37 -1
  2. {wtforms-3.1.1 → wtforms-3.2.0}/PKG-INFO +10 -10
  3. {wtforms-3.1.1 → wtforms-3.2.0}/README.rst +5 -5
  4. {wtforms-3.1.1 → wtforms-3.2.0}/docs/conf.py +3 -3
  5. {wtforms-3.1.1 → wtforms-3.2.0}/docs/contributing.rst +2 -2
  6. {wtforms-3.1.1 → wtforms-3.2.0}/docs/ext.rst +3 -3
  7. {wtforms-3.1.1 → wtforms-3.2.0}/docs/faq.rst +6 -6
  8. {wtforms-3.1.1 → wtforms-3.2.0}/docs/fields.rst +1 -1
  9. wtforms-3.2.0/docs/requirements.txt +5 -0
  10. {wtforms-3.1.1 → wtforms-3.2.0}/docs/whats_new.rst +2 -2
  11. {wtforms-3.1.1 → wtforms-3.2.0}/pyproject.toml +23 -30
  12. wtforms-3.2.0/src/wtforms/__init__.py +77 -0
  13. {wtforms-3.1.1 → wtforms-3.2.0}/src/wtforms/csrf/session.py +1 -0
  14. wtforms-3.2.0/src/wtforms/fields/__init__.py +69 -0
  15. {wtforms-3.1.1 → wtforms-3.2.0}/src/wtforms/fields/choices.py +9 -6
  16. {wtforms-3.1.1 → wtforms-3.2.0}/src/wtforms/fields/core.py +10 -19
  17. {wtforms-3.1.1 → wtforms-3.2.0}/src/wtforms/fields/datetime.py +2 -1
  18. {wtforms-3.1.1 → wtforms-3.2.0}/src/wtforms/fields/form.py +2 -1
  19. {wtforms-3.1.1 → wtforms-3.2.0}/src/wtforms/fields/list.py +2 -1
  20. {wtforms-3.1.1 → wtforms-3.2.0}/src/wtforms/form.py +3 -2
  21. {wtforms-3.1.1 → wtforms-3.2.0}/src/wtforms/locale/ar/LC_MESSAGES/wtforms.mo +0 -0
  22. {wtforms-3.1.1 → wtforms-3.2.0}/src/wtforms/locale/ar/LC_MESSAGES/wtforms.po +8 -8
  23. {wtforms-3.1.1 → wtforms-3.2.0}/src/wtforms/locale/bg/LC_MESSAGES/wtforms.mo +0 -0
  24. {wtforms-3.1.1 → wtforms-3.2.0}/src/wtforms/locale/bg/LC_MESSAGES/wtforms.po +8 -8
  25. {wtforms-3.1.1 → wtforms-3.2.0}/src/wtforms/locale/ca/LC_MESSAGES/wtforms.mo +0 -0
  26. {wtforms-3.1.1 → wtforms-3.2.0}/src/wtforms/locale/ca/LC_MESSAGES/wtforms.po +8 -8
  27. {wtforms-3.1.1 → wtforms-3.2.0}/src/wtforms/locale/cs_CZ/LC_MESSAGES/wtforms.mo +0 -0
  28. {wtforms-3.1.1 → wtforms-3.2.0}/src/wtforms/locale/cs_CZ/LC_MESSAGES/wtforms.po +8 -8
  29. {wtforms-3.1.1 → wtforms-3.2.0}/src/wtforms/locale/cy/LC_MESSAGES/wtforms.mo +0 -0
  30. {wtforms-3.1.1 → wtforms-3.2.0}/src/wtforms/locale/cy/LC_MESSAGES/wtforms.po +8 -8
  31. {wtforms-3.1.1 → wtforms-3.2.0}/src/wtforms/locale/de/LC_MESSAGES/wtforms.mo +0 -0
  32. {wtforms-3.1.1 → wtforms-3.2.0}/src/wtforms/locale/de/LC_MESSAGES/wtforms.po +8 -8
  33. {wtforms-3.1.1 → wtforms-3.2.0}/src/wtforms/locale/de_CH/LC_MESSAGES/wtforms.mo +0 -0
  34. {wtforms-3.1.1 → wtforms-3.2.0}/src/wtforms/locale/de_CH/LC_MESSAGES/wtforms.po +8 -8
  35. {wtforms-3.1.1 → wtforms-3.2.0}/src/wtforms/locale/el/LC_MESSAGES/wtforms.mo +0 -0
  36. {wtforms-3.1.1 → wtforms-3.2.0}/src/wtforms/locale/el/LC_MESSAGES/wtforms.po +8 -8
  37. {wtforms-3.1.1 → wtforms-3.2.0}/src/wtforms/locale/en/LC_MESSAGES/wtforms.mo +0 -0
  38. {wtforms-3.1.1 → wtforms-3.2.0}/src/wtforms/locale/es/LC_MESSAGES/wtforms.mo +0 -0
  39. {wtforms-3.1.1 → wtforms-3.2.0}/src/wtforms/locale/es/LC_MESSAGES/wtforms.po +12 -12
  40. {wtforms-3.1.1 → wtforms-3.2.0}/src/wtforms/locale/et/LC_MESSAGES/wtforms.mo +0 -0
  41. {wtforms-3.1.1 → wtforms-3.2.0}/src/wtforms/locale/et/LC_MESSAGES/wtforms.po +8 -8
  42. {wtforms-3.1.1 → wtforms-3.2.0}/src/wtforms/locale/fa/LC_MESSAGES/wtforms.mo +0 -0
  43. {wtforms-3.1.1 → wtforms-3.2.0}/src/wtforms/locale/fa/LC_MESSAGES/wtforms.po +8 -8
  44. {wtforms-3.1.1 → wtforms-3.2.0}/src/wtforms/locale/fi/LC_MESSAGES/wtforms.mo +0 -0
  45. {wtforms-3.1.1 → wtforms-3.2.0}/src/wtforms/locale/fi/LC_MESSAGES/wtforms.po +8 -8
  46. {wtforms-3.1.1 → wtforms-3.2.0}/src/wtforms/locale/fr/LC_MESSAGES/wtforms.mo +0 -0
  47. {wtforms-3.1.1 → wtforms-3.2.0}/src/wtforms/locale/fr/LC_MESSAGES/wtforms.po +12 -12
  48. {wtforms-3.1.1 → wtforms-3.2.0}/src/wtforms/locale/he/LC_MESSAGES/wtforms.mo +0 -0
  49. {wtforms-3.1.1 → wtforms-3.2.0}/src/wtforms/locale/he/LC_MESSAGES/wtforms.po +8 -8
  50. {wtforms-3.1.1 → wtforms-3.2.0}/src/wtforms/locale/hu/LC_MESSAGES/wtforms.mo +0 -0
  51. {wtforms-3.1.1 → wtforms-3.2.0}/src/wtforms/locale/hu/LC_MESSAGES/wtforms.po +8 -8
  52. {wtforms-3.1.1 → wtforms-3.2.0}/src/wtforms/locale/it/LC_MESSAGES/wtforms.mo +0 -0
  53. {wtforms-3.1.1 → wtforms-3.2.0}/src/wtforms/locale/it/LC_MESSAGES/wtforms.po +8 -8
  54. {wtforms-3.1.1 → wtforms-3.2.0}/src/wtforms/locale/ja/LC_MESSAGES/wtforms.mo +0 -0
  55. {wtforms-3.1.1 → wtforms-3.2.0}/src/wtforms/locale/ja/LC_MESSAGES/wtforms.po +8 -8
  56. wtforms-3.2.0/src/wtforms/locale/kk/LC_MESSAGES/wtforms.mo +0 -0
  57. wtforms-3.2.0/src/wtforms/locale/kk/LC_MESSAGES/wtforms.po +187 -0
  58. wtforms-3.2.0/src/wtforms/locale/ko/LC_MESSAGES/wtforms.mo +0 -0
  59. {wtforms-3.1.1 → wtforms-3.2.0}/src/wtforms/locale/ko/LC_MESSAGES/wtforms.po +24 -24
  60. {wtforms-3.1.1 → wtforms-3.2.0}/src/wtforms/locale/nb/LC_MESSAGES/wtforms.mo +0 -0
  61. {wtforms-3.1.1 → wtforms-3.2.0}/src/wtforms/locale/nb/LC_MESSAGES/wtforms.po +8 -8
  62. wtforms-3.2.0/src/wtforms/locale/nl/LC_MESSAGES/wtforms.mo +0 -0
  63. {wtforms-3.1.1 → wtforms-3.2.0}/src/wtforms/locale/nl/LC_MESSAGES/wtforms.po +20 -23
  64. {wtforms-3.1.1 → wtforms-3.2.0}/src/wtforms/locale/pl/LC_MESSAGES/wtforms.mo +0 -0
  65. {wtforms-3.1.1 → wtforms-3.2.0}/src/wtforms/locale/pl/LC_MESSAGES/wtforms.po +8 -8
  66. wtforms-3.2.0/src/wtforms/locale/pt/LC_MESSAGES/wtforms.mo +0 -0
  67. {wtforms-3.1.1 → wtforms-3.2.0}/src/wtforms/locale/pt/LC_MESSAGES/wtforms.po +26 -29
  68. {wtforms-3.1.1 → wtforms-3.2.0}/src/wtforms/locale/ro/LC_MESSAGES/wtforms.mo +0 -0
  69. {wtforms-3.1.1 → wtforms-3.2.0}/src/wtforms/locale/ro/LC_MESSAGES/wtforms.po +14 -10
  70. wtforms-3.2.0/src/wtforms/locale/ru/LC_MESSAGES/wtforms.mo +0 -0
  71. {wtforms-3.1.1 → wtforms-3.2.0}/src/wtforms/locale/ru/LC_MESSAGES/wtforms.po +18 -18
  72. {wtforms-3.1.1 → wtforms-3.2.0}/src/wtforms/locale/sk/LC_MESSAGES/wtforms.mo +0 -0
  73. {wtforms-3.1.1 → wtforms-3.2.0}/src/wtforms/locale/sk/LC_MESSAGES/wtforms.po +13 -13
  74. wtforms-3.2.0/src/wtforms/locale/sv/LC_MESSAGES/wtforms.mo +0 -0
  75. {wtforms-3.1.1 → wtforms-3.2.0}/src/wtforms/locale/sv/LC_MESSAGES/wtforms.po +18 -23
  76. wtforms-3.2.0/src/wtforms/locale/tr/LC_MESSAGES/wtforms.mo +0 -0
  77. {wtforms-3.1.1 → wtforms-3.2.0}/src/wtforms/locale/tr/LC_MESSAGES/wtforms.po +37 -40
  78. wtforms-3.2.0/src/wtforms/locale/uk/LC_MESSAGES/wtforms.mo +0 -0
  79. {wtforms-3.1.1 → wtforms-3.2.0}/src/wtforms/locale/uk/LC_MESSAGES/wtforms.po +38 -41
  80. {wtforms-3.1.1 → wtforms-3.2.0}/src/wtforms/locale/wtforms.pot +11 -11
  81. {wtforms-3.1.1 → wtforms-3.2.0}/src/wtforms/locale/zh/LC_MESSAGES/wtforms.mo +0 -0
  82. {wtforms-3.1.1 → wtforms-3.2.0}/src/wtforms/locale/zh/LC_MESSAGES/wtforms.po +8 -8
  83. wtforms-3.2.0/src/wtforms/locale/zh_TW/LC_MESSAGES/wtforms.mo +0 -0
  84. {wtforms-3.1.1 → wtforms-3.2.0}/src/wtforms/locale/zh_TW/LC_MESSAGES/wtforms.po +35 -38
  85. {wtforms-3.1.1 → wtforms-3.2.0}/src/wtforms/utils.py +4 -2
  86. {wtforms-3.1.1 → wtforms-3.2.0}/src/wtforms/validators.py +6 -4
  87. wtforms-3.2.0/src/wtforms/widgets/__init__.py +57 -0
  88. {wtforms-3.1.1 → wtforms-3.2.0}/src/wtforms/widgets/core.py +21 -35
  89. {wtforms-3.1.1 → wtforms-3.2.0}/tests/fields/test_boolean.py +0 -1
  90. {wtforms-3.1.1 → wtforms-3.2.0}/tests/fields/test_custom.py +1 -1
  91. {wtforms-3.1.1 → wtforms-3.2.0}/tests/fields/test_date.py +0 -1
  92. {wtforms-3.1.1 → wtforms-3.2.0}/tests/fields/test_datetime.py +9 -7
  93. {wtforms-3.1.1 → wtforms-3.2.0}/tests/fields/test_datetimelocal.py +4 -2
  94. {wtforms-3.1.1 → wtforms-3.2.0}/tests/fields/test_decimal.py +0 -1
  95. {wtforms-3.1.1 → wtforms-3.2.0}/tests/fields/test_field.py +3 -3
  96. {wtforms-3.1.1 → wtforms-3.2.0}/tests/fields/test_file.py +0 -1
  97. {wtforms-3.1.1 → wtforms-3.2.0}/tests/fields/test_filters.py +0 -1
  98. {wtforms-3.1.1 → wtforms-3.2.0}/tests/fields/test_flags.py +1 -1
  99. {wtforms-3.1.1 → wtforms-3.2.0}/tests/fields/test_float.py +0 -1
  100. {wtforms-3.1.1 → wtforms-3.2.0}/tests/fields/test_html5.py +2 -3
  101. {wtforms-3.1.1 → wtforms-3.2.0}/tests/fields/test_integer.py +0 -1
  102. {wtforms-3.1.1 → wtforms-3.2.0}/tests/fields/test_month.py +0 -1
  103. {wtforms-3.1.1 → wtforms-3.2.0}/tests/fields/test_radio.py +0 -1
  104. {wtforms-3.1.1 → wtforms-3.2.0}/tests/fields/test_selectmultiple.py +17 -2
  105. {wtforms-3.1.1 → wtforms-3.2.0}/tests/fields/test_string.py +0 -1
  106. {wtforms-3.1.1 → wtforms-3.2.0}/tests/fields/test_time.py +0 -1
  107. {wtforms-3.1.1 → wtforms-3.2.0}/tests/fields/test_week.py +0 -1
  108. {wtforms-3.1.1 → wtforms-3.2.0}/tests/test_form.py +6 -6
  109. {wtforms-3.1.1 → wtforms-3.2.0}/tests/test_widgets.py +1 -1
  110. {wtforms-3.1.1 → wtforms-3.2.0}/tests/validators/test_anyof.py +15 -0
  111. {wtforms-3.1.1 → wtforms-3.2.0}/tests/validators/test_disabled.py +0 -1
  112. {wtforms-3.1.1 → wtforms-3.2.0}/tests/validators/test_noneof.py +15 -0
  113. {wtforms-3.1.1 → wtforms-3.2.0}/tests/validators/test_readonly.py +0 -1
  114. {wtforms-3.1.1 → wtforms-3.2.0}/tox.ini +2 -3
  115. wtforms-3.1.1/.hgignore +0 -1
  116. wtforms-3.1.1/docs/requirements.txt +0 -5
  117. wtforms-3.1.1/src/wtforms/__init__.py +0 -7
  118. wtforms-3.1.1/src/wtforms/fields/__init__.py +0 -11
  119. wtforms-3.1.1/src/wtforms/locale/ko/LC_MESSAGES/wtforms.mo +0 -0
  120. wtforms-3.1.1/src/wtforms/locale/nl/LC_MESSAGES/wtforms.mo +0 -0
  121. wtforms-3.1.1/src/wtforms/locale/pt/LC_MESSAGES/wtforms.mo +0 -0
  122. wtforms-3.1.1/src/wtforms/locale/ru/LC_MESSAGES/wtforms.mo +0 -0
  123. wtforms-3.1.1/src/wtforms/locale/sv/LC_MESSAGES/wtforms.mo +0 -0
  124. wtforms-3.1.1/src/wtforms/locale/tr/LC_MESSAGES/wtforms.mo +0 -0
  125. wtforms-3.1.1/src/wtforms/locale/uk/LC_MESSAGES/wtforms.mo +0 -0
  126. wtforms-3.1.1/src/wtforms/locale/zh_TW/LC_MESSAGES/wtforms.mo +0 -0
  127. wtforms-3.1.1/src/wtforms/widgets/__init__.py +0 -3
  128. {wtforms-3.1.1 → wtforms-3.2.0}/.gitignore +0 -0
  129. {wtforms-3.1.1 → wtforms-3.2.0}/LICENSE.rst +0 -0
  130. {wtforms-3.1.1 → wtforms-3.2.0}/docs/Makefile +0 -0
  131. {wtforms-3.1.1 → wtforms-3.2.0}/docs/_static/logo_joined.svg +0 -0
  132. {wtforms-3.1.1 → wtforms-3.2.0}/docs/_static/logo_split.svg +0 -0
  133. {wtforms-3.1.1 → wtforms-3.2.0}/docs/_static/logo_square.svg +0 -0
  134. {wtforms-3.1.1 → wtforms-3.2.0}/docs/changes.rst +0 -0
  135. {wtforms-3.1.1 → wtforms-3.2.0}/docs/crash_course.rst +0 -0
  136. {wtforms-3.1.1 → wtforms-3.2.0}/docs/csrf.rst +0 -0
  137. {wtforms-3.1.1 → wtforms-3.2.0}/docs/forms.rst +0 -0
  138. {wtforms-3.1.1 → wtforms-3.2.0}/docs/i18n.rst +0 -0
  139. {wtforms-3.1.1 → wtforms-3.2.0}/docs/index.rst +0 -0
  140. {wtforms-3.1.1 → wtforms-3.2.0}/docs/license.rst +0 -0
  141. {wtforms-3.1.1 → wtforms-3.2.0}/docs/make.bat +0 -0
  142. {wtforms-3.1.1 → wtforms-3.2.0}/docs/meta.rst +0 -0
  143. {wtforms-3.1.1 → wtforms-3.2.0}/docs/specific_problems.rst +0 -0
  144. {wtforms-3.1.1 → wtforms-3.2.0}/docs/validators.rst +0 -0
  145. {wtforms-3.1.1 → wtforms-3.2.0}/docs/widgets.rst +0 -0
  146. {wtforms-3.1.1 → wtforms-3.2.0}/hatch_build.py +0 -0
  147. {wtforms-3.1.1 → wtforms-3.2.0}/src/wtforms/csrf/__init__.py +0 -0
  148. {wtforms-3.1.1 → wtforms-3.2.0}/src/wtforms/csrf/core.py +0 -0
  149. {wtforms-3.1.1 → wtforms-3.2.0}/src/wtforms/fields/numeric.py +0 -0
  150. {wtforms-3.1.1 → wtforms-3.2.0}/src/wtforms/fields/simple.py +0 -0
  151. {wtforms-3.1.1 → wtforms-3.2.0}/src/wtforms/i18n.py +0 -0
  152. {wtforms-3.1.1 → wtforms-3.2.0}/src/wtforms/locale/README.md +0 -0
  153. {wtforms-3.1.1 → wtforms-3.2.0}/src/wtforms/locale/en/LC_MESSAGES/wtforms.po +0 -0
  154. {wtforms-3.1.1 → wtforms-3.2.0}/src/wtforms/meta.py +0 -0
  155. {wtforms-3.1.1 → wtforms-3.2.0}/tests/__init__.py +0 -0
  156. {wtforms-3.1.1 → wtforms-3.2.0}/tests/common.py +0 -0
  157. {wtforms-3.1.1 → wtforms-3.2.0}/tests/conftest.py +0 -0
  158. {wtforms-3.1.1 → wtforms-3.2.0}/tests/fields/__init__.py +0 -0
  159. {wtforms-3.1.1 → wtforms-3.2.0}/tests/fields/test_color.py +0 -0
  160. {wtforms-3.1.1 → wtforms-3.2.0}/tests/fields/test_defaults.py +0 -0
  161. {wtforms-3.1.1 → wtforms-3.2.0}/tests/fields/test_form.py +1 -1
  162. {wtforms-3.1.1 → wtforms-3.2.0}/tests/fields/test_hidden.py +0 -0
  163. {wtforms-3.1.1 → wtforms-3.2.0}/tests/fields/test_label.py +0 -0
  164. {wtforms-3.1.1 → wtforms-3.2.0}/tests/fields/test_list.py +1 -1
  165. {wtforms-3.1.1 → wtforms-3.2.0}/tests/fields/test_password.py +0 -0
  166. {wtforms-3.1.1 → wtforms-3.2.0}/tests/fields/test_select.py +1 -1
  167. {wtforms-3.1.1 → wtforms-3.2.0}/tests/fields/test_submit.py +0 -0
  168. {wtforms-3.1.1 → wtforms-3.2.0}/tests/fields/test_textarea.py +0 -0
  169. {wtforms-3.1.1 → wtforms-3.2.0}/tests/test_csrf.py +1 -1
  170. {wtforms-3.1.1 → wtforms-3.2.0}/tests/test_i18n.py +0 -0
  171. {wtforms-3.1.1 → wtforms-3.2.0}/tests/test_locale_babel.py +1 -1
  172. {wtforms-3.1.1 → wtforms-3.2.0}/tests/test_webob.py +0 -0
  173. {wtforms-3.1.1 → wtforms-3.2.0}/tests/validators/__init__.py +0 -0
  174. {wtforms-3.1.1 → wtforms-3.2.0}/tests/validators/test_data_required.py +0 -0
  175. {wtforms-3.1.1 → wtforms-3.2.0}/tests/validators/test_email.py +0 -0
  176. {wtforms-3.1.1 → wtforms-3.2.0}/tests/validators/test_equal_to.py +0 -0
  177. {wtforms-3.1.1 → wtforms-3.2.0}/tests/validators/test_input_required.py +0 -0
  178. {wtforms-3.1.1 → wtforms-3.2.0}/tests/validators/test_ipaddress.py +0 -0
  179. {wtforms-3.1.1 → wtforms-3.2.0}/tests/validators/test_lazy_proxy.py +0 -0
  180. {wtforms-3.1.1 → wtforms-3.2.0}/tests/validators/test_length.py +0 -0
  181. {wtforms-3.1.1 → wtforms-3.2.0}/tests/validators/test_mac_address.py +0 -0
  182. {wtforms-3.1.1 → wtforms-3.2.0}/tests/validators/test_number_range.py +0 -0
  183. {wtforms-3.1.1 → wtforms-3.2.0}/tests/validators/test_optional.py +0 -0
  184. {wtforms-3.1.1 → wtforms-3.2.0}/tests/validators/test_regexp.py +0 -0
  185. {wtforms-3.1.1 → wtforms-3.2.0}/tests/validators/test_url.py +0 -0
  186. {wtforms-3.1.1 → wtforms-3.2.0}/tests/validators/test_uuid.py +0 -0
  187. {wtforms-3.1.1 → wtforms-3.2.0}/tests/validators/test_validators.py +0 -0
@@ -1,5 +1,42 @@
1
1
  .. currentmodule:: wtforms
2
2
 
3
+ Version 3.2.0
4
+ -------------
5
+
6
+ Released 2024-10-20
7
+
8
+ - Translations update: korean, chinese (traditional), portugese, russian,
9
+ dutch, kazakh, swedish, turkish, slovak, ukranian, spanish, french.
10
+ - Move the repository to the pallets-eco organization. :pr:`854`
11
+ - Stop supporting Python 3.9 and start supporting Python 3.13 :pr:`855`
12
+ - Removed `required` flag support from :class:`~fields.HiddenWidget`,
13
+ :class:`~fields.RangeWidget` and :class:`~fields.SelectWidget` to
14
+ conform to W3C :pr:`810`
15
+ - :class:`~wtforms.validators.NoneOf` and :class:`~wtforms.validators.AnyOf`
16
+ can validate multiple valued fields like :class:`~fields.SelectMultipleField`
17
+ :pr:`538` :pr:`807`
18
+ - Use GHA and pre-commit workflows inspired from Flask. :pr:`856` :pr:`860`
19
+ - ⚠️Breaking change⚠️: Some deprecated code was removed (:pr:`859`):
20
+
21
+ - :class:`~wtforms.Flags` can no longer be tuples. :issue:`467`
22
+ - `iter_choices` needs a tuple of 4 items :issue:`816`
23
+
24
+ - ⚠️Breaking change⚠️: The key for form errors moved from :data:`None` to
25
+ empty string `""`. :issue:`829` :pr:`858`
26
+
27
+ .. note::
28
+ If you need to keep the old behavior you can set the ``_form_error_key``
29
+ parameter of your form to :data:`None`.
30
+
31
+
32
+ Version 3.1.2
33
+ -------------
34
+
35
+ Released 2024-01-06
36
+
37
+ - Fix :class:`~fields.SelectMultipleField` value coercion on validation.
38
+ :issue:`822` :pr:`823`
39
+
3
40
  Version 3.1.1
4
41
  -------------
5
42
 
@@ -14,7 +51,6 @@ Released 2023-11-01
14
51
  - Restored support for 3-items tuple return value from `iter_choices`
15
52
  :pr:`816`
16
53
 
17
-
18
54
  Version 3.1.0
19
55
  -------------
20
56
 
@@ -1,11 +1,11 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.3
2
2
  Name: WTForms
3
- Version: 3.1.1
3
+ Version: 3.2.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
7
- Project-URL: Source Code, https://github.com/wtforms/wtforms/
8
- Project-URL: Issue Tracker, https://github.com/wtforms/wtforms/issues
7
+ Project-URL: Source Code, https://github.com/pallets-eco/wtforms/
8
+ Project-URL: Issue Tracker, https://github.com/pallets-eco/wtforms/issues
9
9
  Project-URL: Chat, https://discord.gg/pallets
10
10
  Maintainer: WTForms
11
11
  License: Copyright 2008 WTForms
@@ -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.8
47
+ Requires-Python: >=3.9
48
48
  Requires-Dist: markupsafe
49
49
  Provides-Extra: email
50
50
  Requires-Dist: email-validator; extra == 'email'
@@ -100,9 +100,9 @@ with frameworks even better.
100
100
 
101
101
  .. _Flask-WTF: https://flask-wtf.readthedocs.io/
102
102
  .. _WTForms-Alchemy: https://wtforms-alchemy.readthedocs.io/
103
- .. _WTForms-SQLAlchemy: https://github.com/wtforms/wtforms-sqlalchemy
104
- .. _WTForms-AppEngine: https://github.com/wtforms/wtforms-appengine
105
- .. _WTForms-Django: https://github.com/wtforms/wtforms-django
103
+ .. _WTForms-SQLAlchemy: https://github.com/pallets-eco/wtforms-sqlalchemy
104
+ .. _WTForms-AppEngine: https://github.com/pallets-eco/wtforms-appengine
105
+ .. _WTForms-Django: https://github.com/pallets-eco/wtforms-django
106
106
  .. _WTForms-Bootstrap5: https://github.com/LaunchPlatform/wtforms-bootstrap5
107
107
  .. _Starlette-WTF: https://github.com/muicss/starlette-wtf
108
108
  .. _Bootstrap-Flask: https://github.com/helloflask/bootstrap-flask
@@ -113,7 +113,7 @@ Links
113
113
 
114
114
  - Documentation: https://wtforms.readthedocs.io/
115
115
  - Releases: https://pypi.org/project/WTForms/
116
- - Code: https://github.com/wtforms/wtforms
117
- - Issue tracker: https://github.com/wtforms/wtforms/issues
116
+ - Code: https://github.com/pallets-eco/wtforms
117
+ - Issue tracker: https://github.com/pallets-eco/wtforms/issues
118
118
  - Discord Chat: https://discord.gg/F65P7Z9
119
119
  - Translation: https://hosted.weblate.org/projects/wtforms/wtforms/
@@ -48,9 +48,9 @@ with frameworks even better.
48
48
 
49
49
  .. _Flask-WTF: https://flask-wtf.readthedocs.io/
50
50
  .. _WTForms-Alchemy: https://wtforms-alchemy.readthedocs.io/
51
- .. _WTForms-SQLAlchemy: https://github.com/wtforms/wtforms-sqlalchemy
52
- .. _WTForms-AppEngine: https://github.com/wtforms/wtforms-appengine
53
- .. _WTForms-Django: https://github.com/wtforms/wtforms-django
51
+ .. _WTForms-SQLAlchemy: https://github.com/pallets-eco/wtforms-sqlalchemy
52
+ .. _WTForms-AppEngine: https://github.com/pallets-eco/wtforms-appengine
53
+ .. _WTForms-Django: https://github.com/pallets-eco/wtforms-django
54
54
  .. _WTForms-Bootstrap5: https://github.com/LaunchPlatform/wtforms-bootstrap5
55
55
  .. _Starlette-WTF: https://github.com/muicss/starlette-wtf
56
56
  .. _Bootstrap-Flask: https://github.com/helloflask/bootstrap-flask
@@ -61,7 +61,7 @@ Links
61
61
 
62
62
  - Documentation: https://wtforms.readthedocs.io/
63
63
  - Releases: https://pypi.org/project/WTForms/
64
- - Code: https://github.com/wtforms/wtforms
65
- - Issue tracker: https://github.com/wtforms/wtforms/issues
64
+ - Code: https://github.com/pallets-eco/wtforms
65
+ - Issue tracker: https://github.com/pallets-eco/wtforms/issues
66
66
  - Discord Chat: https://discord.gg/F65P7Z9
67
67
  - Translation: https://hosted.weblate.org/projects/wtforms/wtforms/
@@ -22,7 +22,7 @@ extensions = [
22
22
  intersphinx_mapping = {
23
23
  "python": ("https://docs.python.org/3/", None),
24
24
  }
25
- issues_github_path = "wtforms/wtforms"
25
+ issues_github_path = "pallets-eco/wtforms"
26
26
 
27
27
  # HTML -----------------------------------------------------------------
28
28
 
@@ -30,12 +30,12 @@ html_theme = "werkzeug"
30
30
  html_context = {
31
31
  "project_links": [
32
32
  ProjectLink("PyPI Releases", "https://pypi.org/project/WTForms/"),
33
- ProjectLink("Source Code", "https://github.com/wtforms/wtforms/"),
33
+ ProjectLink("Source Code", "https://github.com/pallets-eco/wtforms/"),
34
34
  ProjectLink(
35
35
  "Discord Chat",
36
36
  "https://discord.gg/F65P7Z9",
37
37
  ),
38
- ProjectLink("Issue Tracker", "https://github.com/wtforms/wtforms/issues/"),
38
+ ProjectLink("Issue Tracker", "https://github.com/pallets-eco/wtforms/issues/"),
39
39
  ]
40
40
  }
41
41
  html_sidebars = {
@@ -12,7 +12,7 @@ How to Contribute
12
12
  WTForms is now on `GitHub`_, so all contributions should be either associated
13
13
  with a pull request or with a ticket & patch.
14
14
 
15
- .. _GitHub: https://github.com/wtforms/wtforms
15
+ .. _GitHub: https://github.com/pallets-eco/wtforms
16
16
 
17
17
 
18
18
  Contribution Guidelines
@@ -38,7 +38,7 @@ Code submitted should:
38
38
  advantage of `Github Actions`_ for running tests on all supported Python
39
39
  versions.
40
40
 
41
- .. _Github Actions: https://github.com/wtforms/wtforms/actions
41
+ .. _Github Actions: https://github.com/pallets-eco/wtforms/actions
42
42
  .. _PEP8: https://www.python.org/dev/peps/pep-0008/
43
43
 
44
44
 
@@ -5,8 +5,8 @@ Extensions
5
5
 
6
6
  All WTForms extensions were removed in WTForms 3.0.
7
7
 
8
- * ``wtforms.ext.appengine`` was extracted to `WTForms-Appengine <https://github.com/wtforms/wtforms-appengine>`_
8
+ * ``wtforms.ext.appengine`` was extracted to `WTForms-Appengine <https://github.com/pallets-eco/wtforms-appengine>`_
9
9
  * ``wtforms.ext.csrf`` was integrated into WTForms core. See :doc:`CSRF Docs <csrf>`
10
- * ``wtforms.ext.django`` was extracted to `WTForms-Django <https://github.com/wtforms/wtforms-django>`_
10
+ * ``wtforms.ext.django`` was extracted to `WTForms-Django <https://github.com/pallets-eco/wtforms-django>`_
11
11
  * ``wtforms.ext.i18n`` was integrated into WTForms core. See :doc:`i18n`
12
- * ``wtforms.ext.sqlalchemy`` was extracted to `WTForms-SQLAlchemy <https://github.com/wtforms/wtforms-sqlalchemy>`_
12
+ * ``wtforms.ext.sqlalchemy`` was extracted to `WTForms-SQLAlchemy <https://github.com/pallets-eco/wtforms-sqlalchemy>`_
@@ -37,10 +37,10 @@ but if it's not listed, it doesn't mean it won't work.
37
37
  Special support is there for `SQLAlchemy`_, `Google App Engine`_, and `Django`_
38
38
  collections via companion packages.
39
39
 
40
- .. _SQLAlchemy: https://github.com/wtforms/wtforms-sqlalchemy
41
- .. _Google App Engine: https://github.com/wtforms/wtforms-appengine
42
- .. _WTForms-Django: https://github.com/wtforms/wtforms-django
43
- .. _Django: https://github.com/wtforms/wtforms-django
40
+ .. _SQLAlchemy: https://github.com/pallets-eco/wtforms-sqlalchemy
41
+ .. _Google App Engine: https://github.com/pallets-eco/wtforms-appengine
42
+ .. _WTForms-Django: https://github.com/pallets-eco/wtforms-django
43
+ .. _Django: https://github.com/pallets-eco/wtforms-django
44
44
 
45
45
  Does WTForms support unicode?
46
46
  -----------------------------
@@ -57,7 +57,7 @@ you should have no unicode issues.
57
57
  What versions of Python are supported?
58
58
  --------------------------------------
59
59
 
60
- WTForms supports Python 3.8+
60
+ WTForms supports Python 3.9+
61
61
 
62
62
 
63
63
  How can I contribute to WTForms?
@@ -67,7 +67,7 @@ WTForms is not that scary. Really. We try to keep it as succinct and readable as
67
67
  possible. For bugs and feature requests, you can file a
68
68
  ticket on the `project page`_.
69
69
 
70
- .. _project page: https://github.com/wtforms/wtforms
70
+ .. _project page: https://github.com/pallets-eco/wtforms
71
71
 
72
72
 
73
73
  How do I mark in a template when a field is required?
@@ -258,7 +258,7 @@ refer to a single input from the form.
258
258
 
259
259
  .. autoclass:: IntegerRangeField(default field arguments)
260
260
 
261
- .. autoclass:: MonthField(default field arguments, format='%Y:%m')
261
+ .. autoclass:: MonthField(default field arguments, format='%Y-%m')
262
262
 
263
263
  .. autoclass:: RadioField(default field arguments, choices=[], coerce=str)
264
264
 
@@ -0,0 +1,5 @@
1
+ Sphinx~=7.0.0
2
+ Pallets-Sphinx-Themes~=2.2.0
3
+ sphinx-issues~=4.0.0
4
+ sphinxcontrib-log-cabinet~=1.0.1
5
+ python-dateutil~=2.8.2
@@ -68,8 +68,8 @@ the new API's unless it needs to work across both WTForms 1.x and 2.x
68
68
 
69
69
  .. _WTForms-Alchemy: https://pypi.org/project/WTForms-Alchemy/
70
70
  .. _WTForms-Alchemy-docs: https://wtforms-alchemy.readthedocs.io/
71
- .. _WTForms-Appengine: https://github.com/wtforms/wtforms-appengine
72
- .. _WTForms-Django: https://github.com/wtforms/wtforms-django
71
+ .. _WTForms-Appengine: https://github.com/pallets-eco/wtforms-appengine
72
+ .. _WTForms-Django: https://github.com/pallets-eco/wtforms-django
73
73
 
74
74
 
75
75
  Low-level Changes
@@ -13,7 +13,7 @@ classifiers = [
13
13
  "Programming Language :: Python",
14
14
  "Topic :: Internet :: WWW/HTTP :: Dynamic Content",
15
15
  ]
16
- requires-python = ">=3.8"
16
+ requires-python = ">=3.9"
17
17
  dependencies = [
18
18
  "MarkupSafe",
19
19
  ]
@@ -22,8 +22,8 @@ dynamic = ["version"]
22
22
  [project.urls]
23
23
  Documentation = "https://wtforms.readthedocs.io"
24
24
  Changes = "https://wtforms.readthedocs.io/changes"
25
- "Source Code" = "https://github.com/wtforms/wtforms/"
26
- "Issue Tracker" = "https://github.com/wtforms/wtforms/issues"
25
+ "Source Code" = "https://github.com/pallets-eco/wtforms/"
26
+ "Issue Tracker" = "https://github.com/pallets-eco/wtforms/issues"
27
27
  Chat = "https://discord.gg/pallets"
28
28
 
29
29
  [project.optional-dependencies]
@@ -54,7 +54,8 @@ artifacts = ["src/wtforms/locale/**/*.mo"]
54
54
 
55
55
  [tool.hatch.build.hooks.custom]
56
56
  dependencies = [
57
- "Babel>=2.6.0"
57
+ "Babel>=2.6.0",
58
+ "setuptools; python_version>='3.12'",
58
59
  ]
59
60
 
60
61
  [tool.pytest.ini_options]
@@ -77,30 +78,22 @@ exclude_lines = [
77
78
  "pass",
78
79
  ]
79
80
 
80
- [tool.flake8]
81
- # B = bugbear
82
- # E = pycodestyle errors
83
- # F = flake8 pyflakes
84
- # W = pycodestyle warnings
85
- # B9 = bugbear opinions
86
- # ISC = implicit-str-concat
87
- select = ["B", "E", "F", "W", "B9", "ISC"]
88
- ignore = [
89
- # slice notation whitespace, invalid
90
- "E203",
91
- # line length, handled by bugbear B950
92
- "E501",
93
- # bare except, handled by bugbear B001
94
- "E722",
95
- # bin op line break, invalid
96
- "W503",
97
- # requires 'strict' argument for 'zip'
98
- # that needs python >= 3.10
99
- "B905",
100
- ]
101
- # up to 88 allowed by bugbear B950
102
- max-line-length = 80
103
- per-file-ignores = [
104
- # __init__ modules export names
105
- "**/__init__.py: F401, F403",
81
+ [tool.ruff]
82
+ src = ["src"]
83
+ fix = true
84
+ show-fixes = true
85
+ output-format = "full"
86
+
87
+ [tool.ruff.lint]
88
+ select = [
89
+ "B", # flake8-bugbear
90
+ "E", # pycodestyle error
91
+ "F", # pyflakes
92
+ "I", # isort
93
+ "UP", # pyupgrade
94
+ "W", # pycodestyle warning
106
95
  ]
96
+
97
+ [tool.ruff.lint.isort]
98
+ force-single-line = true
99
+ order-by-type = false
@@ -0,0 +1,77 @@
1
+ from wtforms import validators
2
+ from wtforms import widgets
3
+ from wtforms.fields.choices import RadioField
4
+ from wtforms.fields.choices import SelectField
5
+ from wtforms.fields.choices import SelectMultipleField
6
+ from wtforms.fields.core import Field
7
+ from wtforms.fields.core import Flags
8
+ from wtforms.fields.core import Label
9
+ from wtforms.fields.datetime import DateField
10
+ from wtforms.fields.datetime import DateTimeField
11
+ from wtforms.fields.datetime import DateTimeLocalField
12
+ from wtforms.fields.datetime import MonthField
13
+ from wtforms.fields.datetime import TimeField
14
+ from wtforms.fields.datetime import WeekField
15
+ from wtforms.fields.form import FormField
16
+ from wtforms.fields.list import FieldList
17
+ from wtforms.fields.numeric import DecimalField
18
+ from wtforms.fields.numeric import DecimalRangeField
19
+ from wtforms.fields.numeric import FloatField
20
+ from wtforms.fields.numeric import IntegerField
21
+ from wtforms.fields.numeric import IntegerRangeField
22
+ from wtforms.fields.simple import BooleanField
23
+ from wtforms.fields.simple import ColorField
24
+ from wtforms.fields.simple import EmailField
25
+ from wtforms.fields.simple import FileField
26
+ from wtforms.fields.simple import HiddenField
27
+ from wtforms.fields.simple import MultipleFileField
28
+ from wtforms.fields.simple import PasswordField
29
+ from wtforms.fields.simple import SearchField
30
+ from wtforms.fields.simple import StringField
31
+ from wtforms.fields.simple import SubmitField
32
+ from wtforms.fields.simple import TelField
33
+ from wtforms.fields.simple import TextAreaField
34
+ from wtforms.fields.simple import URLField
35
+ from wtforms.form import Form
36
+ from wtforms.validators import ValidationError
37
+
38
+ __version__ = "3.2.0"
39
+
40
+ __all__ = [
41
+ "validators",
42
+ "widgets",
43
+ "Form",
44
+ "ValidationError",
45
+ "SelectField",
46
+ "SelectMultipleField",
47
+ "RadioField",
48
+ "Field",
49
+ "Flags",
50
+ "Label",
51
+ "DateTimeField",
52
+ "DateField",
53
+ "TimeField",
54
+ "MonthField",
55
+ "DateTimeLocalField",
56
+ "WeekField",
57
+ "FormField",
58
+ "FieldList",
59
+ "IntegerField",
60
+ "DecimalField",
61
+ "FloatField",
62
+ "IntegerRangeField",
63
+ "DecimalRangeField",
64
+ "BooleanField",
65
+ "TextAreaField",
66
+ "PasswordField",
67
+ "FileField",
68
+ "MultipleFileField",
69
+ "HiddenField",
70
+ "SearchField",
71
+ "SubmitField",
72
+ "StringField",
73
+ "TelField",
74
+ "URLField",
75
+ "EmailField",
76
+ "ColorField",
77
+ ]
@@ -12,6 +12,7 @@ for extra security) is used as the value of the csrf_token. If this token
12
12
  validates with the hmac of the random value + expiration time, and the
13
13
  expiration time is not passed, the CSRF validation will pass.
14
14
  """
15
+
15
16
  import hmac
16
17
  import os
17
18
  from datetime import datetime
@@ -0,0 +1,69 @@
1
+ from wtforms.fields.choices import RadioField
2
+ from wtforms.fields.choices import SelectField
3
+ from wtforms.fields.choices import SelectMultipleField
4
+ from wtforms.fields.core import Field
5
+ from wtforms.fields.core import Flags
6
+ from wtforms.fields.core import Label
7
+ from wtforms.fields.datetime import DateField
8
+ from wtforms.fields.datetime import DateTimeField
9
+ from wtforms.fields.datetime import DateTimeLocalField
10
+ from wtforms.fields.datetime import MonthField
11
+ from wtforms.fields.datetime import TimeField
12
+ from wtforms.fields.datetime import WeekField
13
+ from wtforms.fields.form import FormField
14
+ from wtforms.fields.list import FieldList
15
+ from wtforms.fields.numeric import DecimalField
16
+ from wtforms.fields.numeric import DecimalRangeField
17
+ from wtforms.fields.numeric import FloatField
18
+ from wtforms.fields.numeric import IntegerField
19
+ from wtforms.fields.numeric import IntegerRangeField
20
+ from wtforms.fields.simple import BooleanField
21
+ from wtforms.fields.simple import ColorField
22
+ from wtforms.fields.simple import EmailField
23
+ from wtforms.fields.simple import FileField
24
+ from wtforms.fields.simple import HiddenField
25
+ from wtforms.fields.simple import MultipleFileField
26
+ from wtforms.fields.simple import PasswordField
27
+ from wtforms.fields.simple import SearchField
28
+ from wtforms.fields.simple import StringField
29
+ from wtforms.fields.simple import SubmitField
30
+ from wtforms.fields.simple import TelField
31
+ from wtforms.fields.simple import TextAreaField
32
+ from wtforms.fields.simple import URLField
33
+ from wtforms.utils import unset_value as _unset_value
34
+
35
+ __all__ = [
36
+ "Field",
37
+ "Flags",
38
+ "Label",
39
+ "SelectField",
40
+ "SelectMultipleField",
41
+ "RadioField",
42
+ "DateTimeField",
43
+ "DateField",
44
+ "TimeField",
45
+ "MonthField",
46
+ "DateTimeLocalField",
47
+ "WeekField",
48
+ "FormField",
49
+ "IntegerField",
50
+ "DecimalField",
51
+ "FloatField",
52
+ "IntegerRangeField",
53
+ "DecimalRangeField",
54
+ "BooleanField",
55
+ "TextAreaField",
56
+ "PasswordField",
57
+ "FileField",
58
+ "MultipleFileField",
59
+ "HiddenField",
60
+ "SearchField",
61
+ "SubmitField",
62
+ "StringField",
63
+ "TelField",
64
+ "URLField",
65
+ "EmailField",
66
+ "ColorField",
67
+ "FieldList",
68
+ "_unset_value",
69
+ ]
@@ -121,8 +121,9 @@ class SelectField(SelectFieldBase):
121
121
  _choices = zip(choices, choices)
122
122
 
123
123
  for value, label, *other_args in _choices:
124
+ selected = self.coerce(value) == self.data
124
125
  render_kw = other_args[0] if len(other_args) else {}
125
- yield (value, label, self.coerce(value) == self.data, render_kw)
126
+ yield (value, label, selected, render_kw)
126
127
 
127
128
  def process_data(self, value):
128
129
  try:
@@ -173,9 +174,9 @@ class SelectMultipleField(SelectField):
173
174
  else:
174
175
  _choices = zip(choices, choices)
175
176
 
176
- for value, label, *args in _choices:
177
+ for value, label, *other_args in _choices:
177
178
  selected = self.data is not None and self.coerce(value) in self.data
178
- render_kw = args[0] if len(args) else {}
179
+ render_kw = other_args[0] if len(other_args) else {}
179
180
  yield (value, label, selected, render_kw)
180
181
 
181
182
  def process_data(self, value):
@@ -201,9 +202,11 @@ class SelectMultipleField(SelectField):
201
202
  if self.choices is None:
202
203
  raise TypeError(self.gettext("Choices cannot be None."))
203
204
 
204
- acceptable = {c[0] for c in self.iter_choices()}
205
- if any(d not in acceptable for d in self.data):
206
- unacceptable = [str(d) for d in set(self.data) - acceptable]
205
+ acceptable = [self.coerce(choice[0]) for choice in self.iter_choices()]
206
+ if any(data not in acceptable for data in self.data):
207
+ unacceptable = [
208
+ str(data) for data in set(self.data) if data not in acceptable
209
+ ]
207
210
  raise ValidationError(
208
211
  self.ngettext(
209
212
  "'%(value)s' is not a valid choice for this field.",
@@ -1,6 +1,5 @@
1
1
  import inspect
2
2
  import itertools
3
- import warnings
4
3
 
5
4
  from markupsafe import escape
6
5
  from markupsafe import Markup
@@ -131,17 +130,6 @@ class Field:
131
130
  for v in itertools.chain(self.validators, [self.widget]):
132
131
  flags = getattr(v, "field_flags", {})
133
132
 
134
- # check for legacy format, remove eventually
135
- if isinstance(flags, tuple): # pragma: no cover
136
- warnings.warn(
137
- "Flags should be stored in dicts and not in tuples. "
138
- "The next version of WTForms will abandon support "
139
- "for flags in tuples.",
140
- DeprecationWarning,
141
- stacklevel=2,
142
- )
143
- flags = {flag_name: True for flag_name in flags}
144
-
145
133
  for k, v in flags.items():
146
134
  setattr(self.flags, k, v)
147
135
 
@@ -181,14 +169,14 @@ class Field:
181
169
  for validator in validators:
182
170
  if not callable(validator):
183
171
  raise TypeError(
184
- "{} is not a valid validator because it is not "
185
- "callable".format(validator)
172
+ f"{validator} is not a valid validator because it is not "
173
+ "callable"
186
174
  )
187
175
 
188
176
  if inspect.isclass(validator):
189
177
  raise TypeError(
190
- "{} is not a valid validator because it is a class, "
191
- "it should be an instance".format(validator)
178
+ f"{validator} is not a valid validator because it is a class, "
179
+ "it should be an instance"
192
180
  )
193
181
 
194
182
  def gettext(self, string):
@@ -399,8 +387,10 @@ class UnboundField:
399
387
  return self.field_class(*self.args, **kw)
400
388
 
401
389
  def __repr__(self):
402
- return "<UnboundField({}, {!r}, {!r})>".format(
403
- self.field_class.__name__, self.args, self.kwargs
390
+ return (
391
+ "<UnboundField("
392
+ f"{self.field_class.__name__}, {self.args!r}, {self.kwargs!r}"
393
+ ")>"
404
394
  )
405
395
 
406
396
 
@@ -425,7 +415,8 @@ class Flags:
425
415
  for name in dir(self)
426
416
  if not name.startswith("_")
427
417
  )
428
- return "<wtforms.fields.Flags: {%s}>" % ", ".join(flags)
418
+ flags = ", ".join(flags)
419
+ return f"<wtforms.fields.Flags: {{{flags}}}>"
429
420
 
430
421
 
431
422
  class Label:
@@ -34,7 +34,8 @@ class DateTimeField(Field):
34
34
  def _value(self):
35
35
  if self.raw_data:
36
36
  return " ".join(self.raw_data)
37
- return self.data and self.data.strftime(self.format[0]) or ""
37
+ format = self.format[0]
38
+ return self.data and self.data.strftime(format) or ""
38
39
 
39
40
  def process_formdata(self, valuelist):
40
41
  if not valuelist:
@@ -1,6 +1,7 @@
1
+ from wtforms.utils import unset_value
2
+
1
3
  from .. import widgets
2
4
  from .core import Field
3
- from wtforms.utils import unset_value
4
5
 
5
6
  __all__ = ("FormField",)
6
7
 
@@ -1,9 +1,10 @@
1
1
  import itertools
2
2
 
3
+ from wtforms.utils import unset_value
4
+
3
5
  from .. import widgets
4
6
  from .core import Field
5
7
  from .core import UnboundField
6
- from wtforms.utils import unset_value
7
8
 
8
9
  __all__ = ("FieldList",)
9
10
 
@@ -30,6 +30,7 @@ class BaseForm:
30
30
  prefix += "-"
31
31
 
32
32
  self.meta = meta
33
+ self._form_error_key = ""
33
34
  self._prefix = prefix
34
35
  self._fields = OrderedDict()
35
36
 
@@ -113,7 +114,7 @@ class BaseForm:
113
114
  for name, field in self._fields.items():
114
115
  field_extra_filters = filters.get(name, [])
115
116
 
116
- inline_filter = getattr(self, "filter_%s" % name, None)
117
+ inline_filter = getattr(self, f"filter_{name}", None)
117
118
  if inline_filter is not None:
118
119
  field_extra_filters.append(inline_filter)
119
120
 
@@ -155,7 +156,7 @@ class BaseForm:
155
156
  def errors(self):
156
157
  errors = {name: f.errors for name, f in self._fields.items() if f.errors}
157
158
  if self.form_errors:
158
- errors[None] = self.form_errors
159
+ errors[self._form_error_key] = self.form_errors
159
160
  return errors
160
161
 
161
162