WuttaWeb 0.19.1__tar.gz → 0.19.3__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 (202) hide show
  1. {wuttaweb-0.19.1 → wuttaweb-0.19.3}/CHANGELOG.md +18 -0
  2. {wuttaweb-0.19.1 → wuttaweb-0.19.3}/PKG-INFO +2 -3
  3. {wuttaweb-0.19.1 → wuttaweb-0.19.3}/pyproject.toml +2 -3
  4. {wuttaweb-0.19.1 → wuttaweb-0.19.3}/src/wuttaweb/forms/schema.py +47 -4
  5. {wuttaweb-0.19.1 → wuttaweb-0.19.3}/src/wuttaweb/forms/widgets.py +5 -36
  6. {wuttaweb-0.19.1 → wuttaweb-0.19.3}/src/wuttaweb/grids/base.py +23 -11
  7. {wuttaweb-0.19.1 → wuttaweb-0.19.3}/src/wuttaweb/templates/base.mako +1 -1
  8. {wuttaweb-0.19.1 → wuttaweb-0.19.3}/src/wuttaweb/templates/configure.mako +3 -3
  9. {wuttaweb-0.19.1 → wuttaweb-0.19.3}/src/wuttaweb/templates/wutta-components.mako +145 -0
  10. {wuttaweb-0.19.1 → wuttaweb-0.19.3}/src/wuttaweb/views/master.py +4 -1
  11. {wuttaweb-0.19.1 → wuttaweb-0.19.3}/tests/forms/test_schema.py +49 -6
  12. {wuttaweb-0.19.1 → wuttaweb-0.19.3}/tests/forms/test_widgets.py +0 -30
  13. {wuttaweb-0.19.1 → wuttaweb-0.19.3}/tests/views/test_master.py +32 -0
  14. wuttaweb-0.19.1/tests/views/__init__.py +0 -0
  15. {wuttaweb-0.19.1 → wuttaweb-0.19.3}/.gitignore +0 -0
  16. {wuttaweb-0.19.1 → wuttaweb-0.19.3}/COPYING.txt +0 -0
  17. {wuttaweb-0.19.1 → wuttaweb-0.19.3}/README.md +0 -0
  18. {wuttaweb-0.19.1 → wuttaweb-0.19.3}/docs/Makefile +0 -0
  19. {wuttaweb-0.19.1 → wuttaweb-0.19.3}/docs/_static/.keepme +0 -0
  20. {wuttaweb-0.19.1 → wuttaweb-0.19.3}/docs/api/wuttaweb.app.rst +0 -0
  21. {wuttaweb-0.19.1 → wuttaweb-0.19.3}/docs/api/wuttaweb.auth.rst +0 -0
  22. {wuttaweb-0.19.1 → wuttaweb-0.19.3}/docs/api/wuttaweb.cli.rst +0 -0
  23. {wuttaweb-0.19.1 → wuttaweb-0.19.3}/docs/api/wuttaweb.cli.webapp.rst +0 -0
  24. {wuttaweb-0.19.1 → wuttaweb-0.19.3}/docs/api/wuttaweb.conf.rst +0 -0
  25. {wuttaweb-0.19.1 → wuttaweb-0.19.3}/docs/api/wuttaweb.db.continuum.rst +0 -0
  26. {wuttaweb-0.19.1 → wuttaweb-0.19.3}/docs/api/wuttaweb.db.rst +0 -0
  27. {wuttaweb-0.19.1 → wuttaweb-0.19.3}/docs/api/wuttaweb.db.sess.rst +0 -0
  28. {wuttaweb-0.19.1 → wuttaweb-0.19.3}/docs/api/wuttaweb.emails.rst +0 -0
  29. {wuttaweb-0.19.1 → wuttaweb-0.19.3}/docs/api/wuttaweb.forms.base.rst +0 -0
  30. {wuttaweb-0.19.1 → wuttaweb-0.19.3}/docs/api/wuttaweb.forms.rst +0 -0
  31. {wuttaweb-0.19.1 → wuttaweb-0.19.3}/docs/api/wuttaweb.forms.schema.rst +0 -0
  32. {wuttaweb-0.19.1 → wuttaweb-0.19.3}/docs/api/wuttaweb.forms.widgets.rst +0 -0
  33. {wuttaweb-0.19.1 → wuttaweb-0.19.3}/docs/api/wuttaweb.grids.base.rst +0 -0
  34. {wuttaweb-0.19.1 → wuttaweb-0.19.3}/docs/api/wuttaweb.grids.filters.rst +0 -0
  35. {wuttaweb-0.19.1 → wuttaweb-0.19.3}/docs/api/wuttaweb.grids.rst +0 -0
  36. {wuttaweb-0.19.1 → wuttaweb-0.19.3}/docs/api/wuttaweb.handler.rst +0 -0
  37. {wuttaweb-0.19.1 → wuttaweb-0.19.3}/docs/api/wuttaweb.helpers.rst +0 -0
  38. {wuttaweb-0.19.1 → wuttaweb-0.19.3}/docs/api/wuttaweb.menus.rst +0 -0
  39. {wuttaweb-0.19.1 → wuttaweb-0.19.3}/docs/api/wuttaweb.progress.rst +0 -0
  40. {wuttaweb-0.19.1 → wuttaweb-0.19.3}/docs/api/wuttaweb.rst +0 -0
  41. {wuttaweb-0.19.1 → wuttaweb-0.19.3}/docs/api/wuttaweb.static.rst +0 -0
  42. {wuttaweb-0.19.1 → wuttaweb-0.19.3}/docs/api/wuttaweb.subscribers.rst +0 -0
  43. {wuttaweb-0.19.1 → wuttaweb-0.19.3}/docs/api/wuttaweb.util.rst +0 -0
  44. {wuttaweb-0.19.1 → wuttaweb-0.19.3}/docs/api/wuttaweb.views.auth.rst +0 -0
  45. {wuttaweb-0.19.1 → wuttaweb-0.19.3}/docs/api/wuttaweb.views.base.rst +0 -0
  46. {wuttaweb-0.19.1 → wuttaweb-0.19.3}/docs/api/wuttaweb.views.batch.rst +0 -0
  47. {wuttaweb-0.19.1 → wuttaweb-0.19.3}/docs/api/wuttaweb.views.common.rst +0 -0
  48. {wuttaweb-0.19.1 → wuttaweb-0.19.3}/docs/api/wuttaweb.views.email.rst +0 -0
  49. {wuttaweb-0.19.1 → wuttaweb-0.19.3}/docs/api/wuttaweb.views.essential.rst +0 -0
  50. {wuttaweb-0.19.1 → wuttaweb-0.19.3}/docs/api/wuttaweb.views.master.rst +0 -0
  51. {wuttaweb-0.19.1 → wuttaweb-0.19.3}/docs/api/wuttaweb.views.people.rst +0 -0
  52. {wuttaweb-0.19.1 → wuttaweb-0.19.3}/docs/api/wuttaweb.views.progress.rst +0 -0
  53. {wuttaweb-0.19.1 → wuttaweb-0.19.3}/docs/api/wuttaweb.views.roles.rst +0 -0
  54. {wuttaweb-0.19.1 → wuttaweb-0.19.3}/docs/api/wuttaweb.views.rst +0 -0
  55. {wuttaweb-0.19.1 → wuttaweb-0.19.3}/docs/api/wuttaweb.views.settings.rst +0 -0
  56. {wuttaweb-0.19.1 → wuttaweb-0.19.3}/docs/api/wuttaweb.views.upgrades.rst +0 -0
  57. {wuttaweb-0.19.1 → wuttaweb-0.19.3}/docs/api/wuttaweb.views.users.rst +0 -0
  58. {wuttaweb-0.19.1 → wuttaweb-0.19.3}/docs/conf.py +0 -0
  59. {wuttaweb-0.19.1 → wuttaweb-0.19.3}/docs/glossary.rst +0 -0
  60. {wuttaweb-0.19.1 → wuttaweb-0.19.3}/docs/index.rst +0 -0
  61. {wuttaweb-0.19.1 → wuttaweb-0.19.3}/docs/make.bat +0 -0
  62. {wuttaweb-0.19.1 → wuttaweb-0.19.3}/docs/narr/cli/builtin.rst +0 -0
  63. {wuttaweb-0.19.1 → wuttaweb-0.19.3}/docs/narr/cli/index.rst +0 -0
  64. {wuttaweb-0.19.1 → wuttaweb-0.19.3}/docs/narr/templates/base.rst +0 -0
  65. {wuttaweb-0.19.1 → wuttaweb-0.19.3}/docs/narr/templates/index.rst +0 -0
  66. {wuttaweb-0.19.1 → wuttaweb-0.19.3}/docs/narr/templates/lookup.rst +0 -0
  67. {wuttaweb-0.19.1 → wuttaweb-0.19.3}/docs/narr/templates/overview.rst +0 -0
  68. {wuttaweb-0.19.1 → wuttaweb-0.19.3}/src/wuttaweb/__init__.py +0 -0
  69. {wuttaweb-0.19.1 → wuttaweb-0.19.3}/src/wuttaweb/_version.py +0 -0
  70. {wuttaweb-0.19.1 → wuttaweb-0.19.3}/src/wuttaweb/app.py +0 -0
  71. {wuttaweb-0.19.1 → wuttaweb-0.19.3}/src/wuttaweb/auth.py +0 -0
  72. {wuttaweb-0.19.1 → wuttaweb-0.19.3}/src/wuttaweb/cli/__init__.py +0 -0
  73. {wuttaweb-0.19.1 → wuttaweb-0.19.3}/src/wuttaweb/cli/webapp.py +0 -0
  74. {wuttaweb-0.19.1 → wuttaweb-0.19.3}/src/wuttaweb/conf.py +0 -0
  75. {wuttaweb-0.19.1 → wuttaweb-0.19.3}/src/wuttaweb/db/__init__.py +0 -0
  76. {wuttaweb-0.19.1 → wuttaweb-0.19.3}/src/wuttaweb/db/continuum.py +0 -0
  77. {wuttaweb-0.19.1 → wuttaweb-0.19.3}/src/wuttaweb/db/sess.py +0 -0
  78. {wuttaweb-0.19.1 → wuttaweb-0.19.3}/src/wuttaweb/email-templates/feedback.html.mako +0 -0
  79. {wuttaweb-0.19.1 → wuttaweb-0.19.3}/src/wuttaweb/email-templates/feedback.txt.mako +0 -0
  80. {wuttaweb-0.19.1 → wuttaweb-0.19.3}/src/wuttaweb/emails.py +0 -0
  81. {wuttaweb-0.19.1 → wuttaweb-0.19.3}/src/wuttaweb/forms/__init__.py +0 -0
  82. {wuttaweb-0.19.1 → wuttaweb-0.19.3}/src/wuttaweb/forms/base.py +0 -0
  83. {wuttaweb-0.19.1 → wuttaweb-0.19.3}/src/wuttaweb/grids/__init__.py +0 -0
  84. {wuttaweb-0.19.1 → wuttaweb-0.19.3}/src/wuttaweb/grids/filters.py +0 -0
  85. {wuttaweb-0.19.1 → wuttaweb-0.19.3}/src/wuttaweb/handler.py +0 -0
  86. {wuttaweb-0.19.1 → wuttaweb-0.19.3}/src/wuttaweb/helpers.py +0 -0
  87. {wuttaweb-0.19.1 → wuttaweb-0.19.3}/src/wuttaweb/menus.py +0 -0
  88. {wuttaweb-0.19.1 → wuttaweb-0.19.3}/src/wuttaweb/progress.py +0 -0
  89. {wuttaweb-0.19.1 → wuttaweb-0.19.3}/src/wuttaweb/static/__init__.py +0 -0
  90. {wuttaweb-0.19.1 → wuttaweb-0.19.3}/src/wuttaweb/static/img/favicon.ico +0 -0
  91. {wuttaweb-0.19.1 → wuttaweb-0.19.3}/src/wuttaweb/static/img/logo.png +0 -0
  92. {wuttaweb-0.19.1 → wuttaweb-0.19.3}/src/wuttaweb/static/img/testing.png +0 -0
  93. {wuttaweb-0.19.1 → wuttaweb-0.19.3}/src/wuttaweb/subscribers.py +0 -0
  94. {wuttaweb-0.19.1 → wuttaweb-0.19.3}/src/wuttaweb/templates/appinfo/configure.mako +0 -0
  95. {wuttaweb-0.19.1 → wuttaweb-0.19.3}/src/wuttaweb/templates/appinfo/index.mako +0 -0
  96. {wuttaweb-0.19.1 → wuttaweb-0.19.3}/src/wuttaweb/templates/auth/change_password.mako +0 -0
  97. {wuttaweb-0.19.1 → wuttaweb-0.19.3}/src/wuttaweb/templates/auth/login.mako +0 -0
  98. {wuttaweb-0.19.1 → wuttaweb-0.19.3}/src/wuttaweb/templates/base_meta.mako +0 -0
  99. {wuttaweb-0.19.1 → wuttaweb-0.19.3}/src/wuttaweb/templates/batch/view.mako +0 -0
  100. {wuttaweb-0.19.1 → wuttaweb-0.19.3}/src/wuttaweb/templates/deform/checkbox.pt +0 -0
  101. {wuttaweb-0.19.1 → wuttaweb-0.19.3}/src/wuttaweb/templates/deform/checkbox_choice.pt +0 -0
  102. {wuttaweb-0.19.1 → wuttaweb-0.19.3}/src/wuttaweb/templates/deform/checked_password.pt +0 -0
  103. {wuttaweb-0.19.1 → wuttaweb-0.19.3}/src/wuttaweb/templates/deform/dateinput.pt +0 -0
  104. {wuttaweb-0.19.1 → wuttaweb-0.19.3}/src/wuttaweb/templates/deform/datetimeinput.pt +0 -0
  105. {wuttaweb-0.19.1 → wuttaweb-0.19.3}/src/wuttaweb/templates/deform/moneyinput.pt +0 -0
  106. {wuttaweb-0.19.1 → wuttaweb-0.19.3}/src/wuttaweb/templates/deform/password.pt +0 -0
  107. {wuttaweb-0.19.1 → wuttaweb-0.19.3}/src/wuttaweb/templates/deform/permissions.pt +0 -0
  108. {wuttaweb-0.19.1 → wuttaweb-0.19.3}/src/wuttaweb/templates/deform/readonly/checkbox.pt +0 -0
  109. {wuttaweb-0.19.1 → wuttaweb-0.19.3}/src/wuttaweb/templates/deform/readonly/email_recips.pt +0 -0
  110. {wuttaweb-0.19.1 → wuttaweb-0.19.3}/src/wuttaweb/templates/deform/readonly/filedownload.pt +0 -0
  111. {wuttaweb-0.19.1 → wuttaweb-0.19.3}/src/wuttaweb/templates/deform/readonly/notes.pt +0 -0
  112. {wuttaweb-0.19.1 → wuttaweb-0.19.3}/src/wuttaweb/templates/deform/readonly/objectref.pt +0 -0
  113. {wuttaweb-0.19.1 → wuttaweb-0.19.3}/src/wuttaweb/templates/deform/readonly/permissions.pt +0 -0
  114. {wuttaweb-0.19.1 → wuttaweb-0.19.3}/src/wuttaweb/templates/deform/readonly/rolerefs.pt +0 -0
  115. {wuttaweb-0.19.1 → wuttaweb-0.19.3}/src/wuttaweb/templates/deform/select.pt +0 -0
  116. {wuttaweb-0.19.1 → wuttaweb-0.19.3}/src/wuttaweb/templates/deform/textarea.pt +0 -0
  117. {wuttaweb-0.19.1 → wuttaweb-0.19.3}/src/wuttaweb/templates/deform/textinput.pt +0 -0
  118. {wuttaweb-0.19.1 → wuttaweb-0.19.3}/src/wuttaweb/templates/email/settings/view.mako +0 -0
  119. {wuttaweb-0.19.1 → wuttaweb-0.19.3}/src/wuttaweb/templates/forbidden.mako +0 -0
  120. {wuttaweb-0.19.1 → wuttaweb-0.19.3}/src/wuttaweb/templates/form.mako +0 -0
  121. {wuttaweb-0.19.1 → wuttaweb-0.19.3}/src/wuttaweb/templates/forms/vue_template.mako +0 -0
  122. {wuttaweb-0.19.1 → wuttaweb-0.19.3}/src/wuttaweb/templates/grids/table_element.mako +0 -0
  123. {wuttaweb-0.19.1 → wuttaweb-0.19.3}/src/wuttaweb/templates/grids/vue_template.mako +0 -0
  124. {wuttaweb-0.19.1 → wuttaweb-0.19.3}/src/wuttaweb/templates/home.mako +0 -0
  125. {wuttaweb-0.19.1 → wuttaweb-0.19.3}/src/wuttaweb/templates/master/configure.mako +0 -0
  126. {wuttaweb-0.19.1 → wuttaweb-0.19.3}/src/wuttaweb/templates/master/create.mako +0 -0
  127. {wuttaweb-0.19.1 → wuttaweb-0.19.3}/src/wuttaweb/templates/master/delete.mako +0 -0
  128. {wuttaweb-0.19.1 → wuttaweb-0.19.3}/src/wuttaweb/templates/master/edit.mako +0 -0
  129. {wuttaweb-0.19.1 → wuttaweb-0.19.3}/src/wuttaweb/templates/master/form.mako +0 -0
  130. {wuttaweb-0.19.1 → wuttaweb-0.19.3}/src/wuttaweb/templates/master/index.mako +0 -0
  131. {wuttaweb-0.19.1 → wuttaweb-0.19.3}/src/wuttaweb/templates/master/view.mako +0 -0
  132. {wuttaweb-0.19.1 → wuttaweb-0.19.3}/src/wuttaweb/templates/notfound.mako +0 -0
  133. {wuttaweb-0.19.1 → wuttaweb-0.19.3}/src/wuttaweb/templates/page.mako +0 -0
  134. {wuttaweb-0.19.1 → wuttaweb-0.19.3}/src/wuttaweb/templates/people/view_profile.mako +0 -0
  135. {wuttaweb-0.19.1 → wuttaweb-0.19.3}/src/wuttaweb/templates/progress.mako +0 -0
  136. {wuttaweb-0.19.1 → wuttaweb-0.19.3}/src/wuttaweb/templates/setup.mako +0 -0
  137. {wuttaweb-0.19.1 → wuttaweb-0.19.3}/src/wuttaweb/templates/upgrade.mako +0 -0
  138. {wuttaweb-0.19.1 → wuttaweb-0.19.3}/src/wuttaweb/templates/upgrades/configure.mako +0 -0
  139. {wuttaweb-0.19.1 → wuttaweb-0.19.3}/src/wuttaweb/templates/upgrades/view.mako +0 -0
  140. {wuttaweb-0.19.1 → wuttaweb-0.19.3}/src/wuttaweb/testing.py +0 -0
  141. {wuttaweb-0.19.1 → wuttaweb-0.19.3}/src/wuttaweb/util.py +0 -0
  142. {wuttaweb-0.19.1 → wuttaweb-0.19.3}/src/wuttaweb/views/__init__.py +0 -0
  143. {wuttaweb-0.19.1 → wuttaweb-0.19.3}/src/wuttaweb/views/auth.py +0 -0
  144. {wuttaweb-0.19.1 → wuttaweb-0.19.3}/src/wuttaweb/views/base.py +0 -0
  145. {wuttaweb-0.19.1 → wuttaweb-0.19.3}/src/wuttaweb/views/batch.py +0 -0
  146. {wuttaweb-0.19.1 → wuttaweb-0.19.3}/src/wuttaweb/views/common.py +0 -0
  147. {wuttaweb-0.19.1 → wuttaweb-0.19.3}/src/wuttaweb/views/email.py +0 -0
  148. {wuttaweb-0.19.1 → wuttaweb-0.19.3}/src/wuttaweb/views/essential.py +0 -0
  149. {wuttaweb-0.19.1 → wuttaweb-0.19.3}/src/wuttaweb/views/people.py +0 -0
  150. {wuttaweb-0.19.1 → wuttaweb-0.19.3}/src/wuttaweb/views/progress.py +0 -0
  151. {wuttaweb-0.19.1 → wuttaweb-0.19.3}/src/wuttaweb/views/roles.py +0 -0
  152. {wuttaweb-0.19.1 → wuttaweb-0.19.3}/src/wuttaweb/views/settings.py +0 -0
  153. {wuttaweb-0.19.1 → wuttaweb-0.19.3}/src/wuttaweb/views/upgrades.py +0 -0
  154. {wuttaweb-0.19.1 → wuttaweb-0.19.3}/src/wuttaweb/views/users.py +0 -0
  155. {wuttaweb-0.19.1 → wuttaweb-0.19.3}/tasks.py +0 -0
  156. {wuttaweb-0.19.1 → wuttaweb-0.19.3}/tests/__init__.py +0 -0
  157. {wuttaweb-0.19.1 → wuttaweb-0.19.3}/tests/cli/__init__.py +0 -0
  158. {wuttaweb-0.19.1 → wuttaweb-0.19.3}/tests/cli/test_webapp.py +0 -0
  159. {wuttaweb-0.19.1 → wuttaweb-0.19.3}/tests/db/__init__.py +0 -0
  160. {wuttaweb-0.19.1 → wuttaweb-0.19.3}/tests/db/test_continuum.py +0 -0
  161. {wuttaweb-0.19.1 → wuttaweb-0.19.3}/tests/forms/test_base.py +0 -0
  162. {wuttaweb-0.19.1/tests/forms → wuttaweb-0.19.3/tests/grids}/__init__.py +0 -0
  163. {wuttaweb-0.19.1 → wuttaweb-0.19.3}/tests/grids/test_base.py +0 -0
  164. {wuttaweb-0.19.1 → wuttaweb-0.19.3}/tests/grids/test_filters.py +0 -0
  165. {wuttaweb-0.19.1 → wuttaweb-0.19.3}/tests/libcache/bb_fontawesome_svg_core.js +0 -0
  166. {wuttaweb-0.19.1 → wuttaweb-0.19.3}/tests/libcache/bb_free_solid_svg_icons.js +0 -0
  167. {wuttaweb-0.19.1 → wuttaweb-0.19.3}/tests/libcache/bb_oruga.js +0 -0
  168. {wuttaweb-0.19.1 → wuttaweb-0.19.3}/tests/libcache/bb_oruga_bulma.css +0 -0
  169. {wuttaweb-0.19.1 → wuttaweb-0.19.3}/tests/libcache/bb_oruga_bulma.js +0 -0
  170. {wuttaweb-0.19.1 → wuttaweb-0.19.3}/tests/libcache/bb_vue.js +0 -0
  171. {wuttaweb-0.19.1 → wuttaweb-0.19.3}/tests/libcache/bb_vue_fontawesome.js +0 -0
  172. {wuttaweb-0.19.1 → wuttaweb-0.19.3}/tests/libcache/buefy.css +0 -0
  173. {wuttaweb-0.19.1 → wuttaweb-0.19.3}/tests/libcache/buefy.js +0 -0
  174. {wuttaweb-0.19.1 → wuttaweb-0.19.3}/tests/libcache/fontawesome.js +0 -0
  175. {wuttaweb-0.19.1 → wuttaweb-0.19.3}/tests/libcache/vue.js +0 -0
  176. {wuttaweb-0.19.1 → wuttaweb-0.19.3}/tests/libcache/vue_resource.js +0 -0
  177. {wuttaweb-0.19.1 → wuttaweb-0.19.3}/tests/test_app.py +0 -0
  178. {wuttaweb-0.19.1 → wuttaweb-0.19.3}/tests/test_auth.py +0 -0
  179. {wuttaweb-0.19.1 → wuttaweb-0.19.3}/tests/test_emails.py +0 -0
  180. {wuttaweb-0.19.1 → wuttaweb-0.19.3}/tests/test_handler.py +0 -0
  181. {wuttaweb-0.19.1 → wuttaweb-0.19.3}/tests/test_helpers.py +0 -0
  182. {wuttaweb-0.19.1 → wuttaweb-0.19.3}/tests/test_menus.py +0 -0
  183. {wuttaweb-0.19.1 → wuttaweb-0.19.3}/tests/test_progress.py +0 -0
  184. {wuttaweb-0.19.1 → wuttaweb-0.19.3}/tests/test_static.py +0 -0
  185. {wuttaweb-0.19.1 → wuttaweb-0.19.3}/tests/test_subscribers.py +0 -0
  186. {wuttaweb-0.19.1 → wuttaweb-0.19.3}/tests/test_util.py +0 -0
  187. {wuttaweb-0.19.1 → wuttaweb-0.19.3}/tests/util.py +0 -0
  188. {wuttaweb-0.19.1/tests/grids → wuttaweb-0.19.3/tests/views}/__init__.py +0 -0
  189. {wuttaweb-0.19.1 → wuttaweb-0.19.3}/tests/views/test___init__.py +0 -0
  190. {wuttaweb-0.19.1 → wuttaweb-0.19.3}/tests/views/test_auth.py +0 -0
  191. {wuttaweb-0.19.1 → wuttaweb-0.19.3}/tests/views/test_base.py +0 -0
  192. {wuttaweb-0.19.1 → wuttaweb-0.19.3}/tests/views/test_batch.py +0 -0
  193. {wuttaweb-0.19.1 → wuttaweb-0.19.3}/tests/views/test_common.py +0 -0
  194. {wuttaweb-0.19.1 → wuttaweb-0.19.3}/tests/views/test_email.py +0 -0
  195. {wuttaweb-0.19.1 → wuttaweb-0.19.3}/tests/views/test_essential.py +0 -0
  196. {wuttaweb-0.19.1 → wuttaweb-0.19.3}/tests/views/test_people.py +0 -0
  197. {wuttaweb-0.19.1 → wuttaweb-0.19.3}/tests/views/test_progress.py +0 -0
  198. {wuttaweb-0.19.1 → wuttaweb-0.19.3}/tests/views/test_roles.py +0 -0
  199. {wuttaweb-0.19.1 → wuttaweb-0.19.3}/tests/views/test_settings.py +0 -0
  200. {wuttaweb-0.19.1 → wuttaweb-0.19.3}/tests/views/test_upgrades.py +0 -0
  201. {wuttaweb-0.19.1 → wuttaweb-0.19.3}/tests/views/test_users.py +0 -0
  202. {wuttaweb-0.19.1 → wuttaweb-0.19.3}/tox.ini +0 -0
@@ -5,6 +5,24 @@ All notable changes to wuttaweb will be documented in this file.
5
5
  The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
6
6
  and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## v0.19.3 (2025-01-09)
9
+
10
+ ### Fix
11
+
12
+ - use `request.url` instead of `current_route_url()`
13
+ - add basic `<wutta-autocomplete>` component
14
+ - add `WuttaDictEnum` form schema type
15
+
16
+ ## v0.19.2 (2025-01-07)
17
+
18
+ ### Fix
19
+
20
+ - always use prop key for default grid filters
21
+ - avoid `request.current_route_url()` for user menu
22
+ - add `scale` kwarg for `WuttaMoney` schema type, widget
23
+ - make WuttaQuantity serialize w/ app handler, remove custom widget
24
+ - bugfix for bool simple settings with default value
25
+
8
26
  ## v0.19.1 (2025-01-06)
9
27
 
10
28
  ### Fix
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: WuttaWeb
3
- Version: 0.19.1
3
+ Version: 0.19.3
4
4
  Summary: Web App for Wutta Framework
5
5
  Project-URL: Homepage, https://wuttaproject.org/
6
6
  Project-URL: Repository, https://forgejo.wuttaproject.org/wutta/wuttaweb
@@ -37,10 +37,9 @@ Requires-Dist: pyramid-fanstatic
37
37
  Requires-Dist: pyramid-mako
38
38
  Requires-Dist: pyramid-tm
39
39
  Requires-Dist: pyramid>=2
40
- Requires-Dist: sqlalchemy-utils
41
40
  Requires-Dist: waitress
42
41
  Requires-Dist: webhelpers2
43
- Requires-Dist: wuttjamaican[db]>=0.19.2
42
+ Requires-Dist: wuttjamaican[db]>=0.19.3
44
43
  Requires-Dist: zope-sqlalchemy>=1.5
45
44
  Provides-Extra: continuum
46
45
  Requires-Dist: wutta-continuum; extra == 'continuum'
@@ -6,7 +6,7 @@ build-backend = "hatchling.build"
6
6
 
7
7
  [project]
8
8
  name = "WuttaWeb"
9
- version = "0.19.1"
9
+ version = "0.19.3"
10
10
  description = "Web App for Wutta Framework"
11
11
  readme = "README.md"
12
12
  authors = [{name = "Lance Edgar", email = "lance@wuttaproject.org"}]
@@ -42,10 +42,9 @@ dependencies = [
42
42
  "pyramid_fanstatic",
43
43
  "pyramid_mako",
44
44
  "pyramid_tm",
45
- "SQLAlchemy-Utils",
46
45
  "waitress",
47
46
  "WebHelpers2",
48
- "WuttJamaican[db]>=0.19.2",
47
+ "WuttJamaican[db]>=0.19.3",
49
48
  "zope.sqlalchemy>=1.5",
50
49
  ]
51
50
 
@@ -155,6 +155,37 @@ class WuttaEnum(colander.Enum):
155
155
  return widgets.SelectWidget(**kwargs)
156
156
 
157
157
 
158
+ class WuttaDictEnum(colander.String):
159
+ """
160
+ Schema type for "pseudo-enum" fields which reference a dict for
161
+ known values instead of a true enum class.
162
+
163
+ This is primarily for use with "status" fields such as
164
+ :attr:`~wuttjamaican:wuttjamaican.db.model.batch.BatchRowMixin.status_code`.
165
+
166
+ This is a subclass of :class:`colander.String`, but adds a default
167
+ widget (``SelectWidget``) with enum choices.
168
+
169
+ :param request: Current :term:`request` object.
170
+
171
+ :param enum_dct: Dict with possible enum values and labels.
172
+ """
173
+
174
+ def __init__(self, request, enum_dct, *args, **kwargs):
175
+ super().__init__(*args, **kwargs)
176
+ self.request = request
177
+ self.config = self.request.wutta_config
178
+ self.app = self.config.get_app()
179
+ self.enum_dct = enum_dct
180
+
181
+ def widget_maker(self, **kwargs):
182
+ """ """
183
+ if 'values' not in kwargs:
184
+ kwargs['values'] = [(k, v) for k, v in self.enum_dct.items()]
185
+
186
+ return widgets.SelectWidget(**kwargs)
187
+
188
+
158
189
  class WuttaMoney(colander.Money):
159
190
  """
160
191
  Custom schema type for "money" fields.
@@ -164,9 +195,13 @@ class WuttaMoney(colander.Money):
164
195
  by default.
165
196
 
166
197
  :param request: Current :term:`request` object.
198
+
199
+ :param scale: If this kwarg is specified, it will be passed along
200
+ to the widget constructor.
167
201
  """
168
202
 
169
203
  def __init__(self, request, *args, **kwargs):
204
+ self.scale = kwargs.pop('scale', None)
170
205
  super().__init__(*args, **kwargs)
171
206
  self.request = request
172
207
  self.config = self.request.wutta_config
@@ -174,6 +209,8 @@ class WuttaMoney(colander.Money):
174
209
 
175
210
  def widget_maker(self, **kwargs):
176
211
  """ """
212
+ if self.scale:
213
+ kwargs.setdefault('scale', self.scale)
177
214
  return widgets.WuttaMoneyInputWidget(self.request, **kwargs)
178
215
 
179
216
 
@@ -181,8 +218,9 @@ class WuttaQuantity(colander.Decimal):
181
218
  """
182
219
  Custom schema type for "quantity" fields.
183
220
 
184
- This is a subclass of :class:`colander:colander.Decimal` but uses
185
- :class:`~wuttaweb.forms.widgets.WuttaQuantityWidget` by default.
221
+ This is a subclass of :class:`colander:colander.Decimal` but will
222
+ serialize values via
223
+ :meth:`~wuttjamaican:wuttjamaican.app.AppHandler.render_quantity()`.
186
224
 
187
225
  :param request: Current :term:`request` object.
188
226
  """
@@ -193,9 +231,14 @@ class WuttaQuantity(colander.Decimal):
193
231
  self.config = self.request.wutta_config
194
232
  self.app = self.config.get_app()
195
233
 
196
- def widget_maker(self, **kwargs):
234
+ def serialize(self, node, appstruct):
197
235
  """ """
198
- return widgets.WuttaQuantityWidget(self.request, **kwargs)
236
+ if appstruct in (colander.null, None):
237
+ return colander.null
238
+
239
+ # nb. we render as quantity here to avoid values like 12.0000,
240
+ # so we just show value like 12 instead
241
+ return self.app.render_quantity(appstruct)
199
242
 
200
243
 
201
244
  class WuttaSet(colander.Set):
@@ -206,45 +206,13 @@ class WuttaMoneyInputWidget(MoneyInputWidget):
206
206
  * ``moneyinput``
207
207
 
208
208
  :param request: Current :term:`request` object.
209
- """
210
-
211
- def __init__(self, request, *args, **kwargs):
212
- super().__init__(*args, **kwargs)
213
- self.request = request
214
- self.config = self.request.wutta_config
215
- self.app = self.config.get_app()
216
-
217
- def serialize(self, field, cstruct, **kw):
218
- """ """
219
- readonly = kw.get('readonly', self.readonly)
220
- if readonly:
221
- if cstruct in (colander.null, None):
222
- return HTML.tag('span')
223
- cstruct = decimal.Decimal(cstruct)
224
- return HTML.tag('span', c=[self.app.render_currency(cstruct)])
225
-
226
- return super().serialize(field, cstruct, **kw)
227
-
228
-
229
- class WuttaQuantityWidget(TextInputWidget):
230
- """
231
- Custom widget for "quantity" fields. This is used by default for
232
- :class:`~wuttaweb.forms.schema.WuttaQuantity` type nodes.
233
-
234
- The main purpose of this widget is to leverage
235
- :meth:`~wuttjamaican:wuttjamaican.app.AppHandler.render_quantity()`
236
- for the readonly display.
237
209
 
238
- This is a subclass of
239
- :class:`deform:deform.widget.TextInputWidget` and uses these
240
- Deform templates:
241
-
242
- * ``textinput``
243
-
244
- :param request: Current :term:`request` object.
210
+ :param scale: If this kwarg is specified, it will be passed along
211
+ to ``render_currency()`` call.
245
212
  """
246
213
 
247
214
  def __init__(self, request, *args, **kwargs):
215
+ self.scale = kwargs.pop('scale', 2)
248
216
  super().__init__(*args, **kwargs)
249
217
  self.request = request
250
218
  self.config = self.request.wutta_config
@@ -257,7 +225,8 @@ class WuttaQuantityWidget(TextInputWidget):
257
225
  if cstruct in (colander.null, None):
258
226
  return HTML.tag('span')
259
227
  cstruct = decimal.Decimal(cstruct)
260
- return HTML.tag('span', c=[self.app.render_quantity(cstruct)])
228
+ text = self.app.render_currency(cstruct, scale=self.scale)
229
+ return HTML.tag('span', c=[text])
261
230
 
262
231
  return super().serialize(field, cstruct, **kw)
263
232
 
@@ -32,7 +32,6 @@ from collections import namedtuple, OrderedDict
32
32
 
33
33
  import sqlalchemy as sa
34
34
  from sqlalchemy import orm
35
- from sqlalchemy_utils import get_columns
36
35
 
37
36
  import paginate
38
37
  from paginate_sqlalchemy import SqlalchemyOrmPage
@@ -1147,16 +1146,29 @@ class Grid:
1147
1146
  filters = filters or {}
1148
1147
 
1149
1148
  if self.model_class:
1150
- # nb. i first tried self.get_model_columns() but my notes
1151
- # say that was too aggressive in many cases. then i tried
1152
- # using the *subset* of self.columns, just the ones which
1153
- # corresponded to a property on the model class. and now
1154
- # i am using sa-utils to give the "true" column list..
1155
- for col in get_columns(self.model_class):
1156
- if col.key in filters:
1157
- continue
1158
- prop = getattr(self.model_class, col.key)
1159
- filters[prop.key] = self.make_filter(prop)
1149
+
1150
+ # nb. i have found this confusing for some reason. some
1151
+ # things i've tried so far include:
1152
+ #
1153
+ # i first tried self.get_model_columns() but my notes say
1154
+ # that was too aggressive in many cases.
1155
+ #
1156
+ # then i tried using the *subset* of self.columns, just
1157
+ # the ones which correspond to a property on the model
1158
+ # class. but sometimes that skips filters we need.
1159
+ #
1160
+ # then i tried get_columns() from sa-utils to give the
1161
+ # "true" column list, but that fails when the underlying
1162
+ # column has different name than the prop/attr key.
1163
+ #
1164
+ # so now, we are looking directly at the sa mapper, for
1165
+ # all column attrs and then using the prop key.
1166
+
1167
+ inspector = sa.inspect(self.model_class)
1168
+ for prop in inspector.column_attrs:
1169
+ if prop.key not in filters:
1170
+ attr = getattr(self.model_class, prop.key)
1171
+ filters[prop.key] = self.make_filter(attr)
1160
1172
 
1161
1173
  return filters
1162
1174
 
@@ -659,7 +659,7 @@
659
659
  % if request.is_root:
660
660
  ${h.form(url('stop_root'), ref='stopBeingRootForm')}
661
661
  ${h.csrf_token(request)}
662
- <input type="hidden" name="referrer" value="${request.current_route_url()}" />
662
+ <input type="hidden" name="referrer" value="${request.url}" />
663
663
  <a @click="stopBeingRoot()"
664
664
  class="navbar-item has-background-danger has-text-white">
665
665
  Stop being root
@@ -18,7 +18,7 @@
18
18
  <br />
19
19
  ${self.buttons_content()}
20
20
 
21
- ${h.form(request.current_route_url(), enctype='multipart/form-data', ref='saveSettingsForm', **{'@submit': 'saveSettingsFormSubmit'})}
21
+ ${h.form(request.url, enctype='multipart/form-data', ref='saveSettingsForm', **{'@submit': 'saveSettingsFormSubmit'})}
22
22
  ${h.csrf_token(request)}
23
23
  <div class="wutta-form-wrapper">
24
24
  ${self.form_content()}
@@ -53,7 +53,7 @@
53
53
  <b-button @click="purgeSettingsShowDialog = false">
54
54
  Cancel
55
55
  </b-button>
56
- ${h.form(request.current_route_url(), **{'@submit': 'purgingSettings = true'})}
56
+ ${h.form(request.url, **{'@submit': 'purgingSettings = true'})}
57
57
  ${h.csrf_token(request)}
58
58
  ${h.hidden('remove_settings', 'true')}
59
59
  <b-button type="is-danger"
@@ -107,7 +107,7 @@
107
107
  icon-left="save">
108
108
  {{ savingSettings ? "Working, please wait..." : "Save All Settings" }}
109
109
  </b-button>
110
- <b-button tag="a" href="${request.current_route_url()}"
110
+ <b-button tag="a" href="${request.url}"
111
111
  icon-pack="fas"
112
112
  icon-left="undo"
113
113
  @click="undoChanges = true"
@@ -1,6 +1,7 @@
1
1
 
2
2
  <%def name="make_wutta_components()">
3
3
  ${self.make_wutta_request_mixin()}
4
+ ${self.make_wutta_autocomplete_component()}
4
5
  ${self.make_wutta_button_component()}
5
6
  ${self.make_wutta_datepicker_component()}
6
7
  ${self.make_wutta_timepicker_component()}
@@ -86,6 +87,150 @@
86
87
  </script>
87
88
  </%def>
88
89
 
90
+ <%def name="make_wutta_autocomplete_component()">
91
+ <script type="text/x-template" id="wutta-autocomplete-template">
92
+ <div>
93
+ <b-autocomplete ref="autocomplete"
94
+ v-show="!value"
95
+ v-model="entry"
96
+ :data="data"
97
+ @typing="getAsyncData"
98
+ @select="selectionMade"
99
+ keep-first>
100
+ <template slot-scope="props">
101
+ {{ props.option.label }}
102
+ </template>
103
+ </b-autocomplete>
104
+ <b-button v-if="value"
105
+ @click="clearValue(true, true)">
106
+ {{ recordLabel }} (click to change)
107
+ </b-button>
108
+ </div>
109
+ </script>
110
+ <script>
111
+ const WuttaAutocomplete = {
112
+ template: '#wutta-autocomplete-template',
113
+
114
+ props: {
115
+
116
+ // callers do not specify this directly but rather by way
117
+ // of the `v-model` directive. the component will emit
118
+ // `input` events when this value changes
119
+ value: String,
120
+
121
+ // caller must specify initial display string, if the
122
+ // (v-model) value is not empty when component loads
123
+ display: String,
124
+
125
+ // the url from which search results are obtained. the
126
+ // endpoint should expect a GET with single `term` param
127
+ // in query string, and return list of objects, each with
128
+ // (at least) `value` and `label` properties.
129
+ serviceUrl: String,
130
+ },
131
+
132
+ data() {
133
+ return {
134
+
135
+ // user search input
136
+ entry: null,
137
+
138
+ // display label for button, when value is set
139
+ recordLabel: this.display,
140
+
141
+ // this contains the latest search results; it will
142
+ // change over time as user types. when an option is
143
+ // selected, it will be an element from this list.
144
+ data: [],
145
+ }
146
+ },
147
+
148
+ watch: {
149
+
150
+ value(val) {
151
+ // reset ourself when model value is cleared
152
+ if (!val) {
153
+ this.clearValue()
154
+ }
155
+ },
156
+ },
157
+
158
+ methods: {
159
+
160
+ focus() {
161
+ this.$refs.autocomplete.focus()
162
+ },
163
+
164
+ // convenience for parent component to fetch current label
165
+ getLabel() {
166
+ return this.recordLabel
167
+ },
168
+
169
+ // fetch new search results from server. this is invoked
170
+ // when user types new input
171
+ getAsyncData(entry) {
172
+
173
+ // nb. skip search until we have at least 3 chars of input
174
+ if (entry.length < 3) {
175
+ this.data = []
176
+ return
177
+ }
178
+
179
+ // search results become autocomplete options
180
+ this.$http.get(this.serviceUrl + '?term=' + encodeURIComponent(entry))
181
+ .then(({ data }) => {
182
+ this.data = data
183
+ })
184
+ .catch((error) => {
185
+ this.data = []
186
+ throw error
187
+ })
188
+ },
189
+
190
+ // handle selection change. this is invoked when user
191
+ // chooses an autocomplete option
192
+ selectionMade(option) {
193
+
194
+ // reset user input
195
+ this.entry = null
196
+
197
+ // nb. this method can be triggered when a selection
198
+ // is made *or cleared* - if the latter then we do not
199
+ // want to emit event for the empty value; that part
200
+ // is handled in clearValue()
201
+ if (option) {
202
+ this.recordLabel = option.label
203
+ this.$emit('input', option.value)
204
+ } else {
205
+ this.recordLabel = null
206
+ }
207
+ },
208
+
209
+ // clear the component value
210
+ clearValue(emit, focus) {
211
+
212
+ // clear autocomplete selection
213
+ this.$refs.autocomplete.setSelected(null)
214
+
215
+ // maybe emit event for new value
216
+ if (emit) {
217
+ this.$emit('input', null)
218
+ }
219
+
220
+ // maybe set focus to autocomplete
221
+ if (focus) {
222
+ this.$nextTick(function() {
223
+ this.focus()
224
+ })
225
+ }
226
+ },
227
+ },
228
+
229
+ }
230
+ Vue.component('wutta-autocomplete', WuttaAutocomplete)
231
+ </script>
232
+ </%def>
233
+
89
234
  <%def name="make_wutta_button_component()">
90
235
  <script type="text/x-template" id="wutta-button-template">
91
236
  <b-button :type="type"
@@ -1178,7 +1178,7 @@ class MasterView(View):
1178
1178
  self.request.session.flash("Settings have been saved.")
1179
1179
 
1180
1180
  # reload configure page
1181
- return self.redirect(self.request.current_route_url())
1181
+ return self.redirect(self.request.url)
1182
1182
 
1183
1183
  # render configure page
1184
1184
  context = self.configure_get_context()
@@ -1329,6 +1329,9 @@ class MasterView(View):
1329
1329
 
1330
1330
  if name in data:
1331
1331
  value = data[name]
1332
+ elif simple.get('type') is bool:
1333
+ # nb. bool false will be *missing* from data
1334
+ value = False
1332
1335
  else:
1333
1336
  value = simple.get('default')
1334
1337
 
@@ -1,6 +1,7 @@
1
1
  # -*- coding: utf-8; -*-
2
2
 
3
3
  import datetime
4
+ import decimal
4
5
  from unittest import TestCase
5
6
  from unittest.mock import patch
6
7
 
@@ -15,7 +16,7 @@ from wuttaweb.forms import widgets
15
16
  from wuttaweb.testing import DataTestCase, WebTestCase
16
17
 
17
18
 
18
- class TestWutaDateTime(TestCase):
19
+ class TestWuttaDateTime(TestCase):
19
20
 
20
21
  def test_deserialize(self):
21
22
  typ = mod.WuttaDateTime()
@@ -80,22 +81,64 @@ class TestWuttaEnum(WebTestCase):
80
81
  self.assertIsInstance(widget, widgets.SelectWidget)
81
82
 
82
83
 
84
+ MOCK_STATUS_ONE = 1
85
+ MOCK_STATUS_TWO = 2
86
+ MOCK_STATUS = {
87
+ MOCK_STATUS_ONE: 'one',
88
+ MOCK_STATUS_TWO: 'two',
89
+ }
90
+
91
+ class TestWuttaDictEnum(WebTestCase):
92
+
93
+ def test_widget_maker(self):
94
+ typ = mod.WuttaDictEnum(self.request, MOCK_STATUS)
95
+ widget = typ.widget_maker()
96
+ self.assertIsInstance(widget, widgets.SelectWidget)
97
+ self.assertEqual(widget.values, [
98
+ (1, 'one'),
99
+ (2, 'two'),
100
+ ])
101
+
102
+
83
103
  class TestWuttaMoney(WebTestCase):
84
104
 
85
105
  def test_widget_maker(self):
86
106
  enum = self.app.enum
107
+
108
+ # default scale
87
109
  typ = mod.WuttaMoney(self.request)
88
110
  widget = typ.widget_maker()
89
111
  self.assertIsInstance(widget, widgets.WuttaMoneyInputWidget)
112
+ self.assertEqual(widget.scale, 2)
113
+
114
+ # custom scale
115
+ typ = mod.WuttaMoney(self.request, scale=4)
116
+ widget = typ.widget_maker()
117
+ self.assertIsInstance(widget, widgets.WuttaMoneyInputWidget)
118
+ self.assertEqual(widget.scale, 4)
90
119
 
91
120
 
92
121
  class TestWuttaQuantity(WebTestCase):
93
122
 
94
- def test_widget_maker(self):
95
- enum = self.app.enum
96
- typ = mod.WuttaQuantity(self.request)
97
- widget = typ.widget_maker()
98
- self.assertIsInstance(widget, widgets.WuttaQuantityWidget)
123
+ def test_serialize(self):
124
+ node = colander.SchemaNode(mod.WuttaQuantity(self.request))
125
+ typ = node.typ
126
+
127
+ # null
128
+ result = typ.serialize(node, colander.null)
129
+ self.assertIs(result, colander.null)
130
+ result = typ.serialize(node, None)
131
+ self.assertIs(result, colander.null)
132
+
133
+ # quantity
134
+ result = typ.serialize(node, 42)
135
+ self.assertEqual(result, '42')
136
+ result = typ.serialize(node, 42.00)
137
+ self.assertEqual(result, '42')
138
+ result = typ.serialize(node, decimal.Decimal('42.00'))
139
+ self.assertEqual(result, '42')
140
+ result = typ.serialize(node, 42.13)
141
+ self.assertEqual(result, '42.13')
99
142
 
100
143
 
101
144
  class TestObjectRef(DataTestCase):
@@ -143,36 +143,6 @@ class TestWuttaMoneyInputWidget(WebTestCase):
143
143
  self.assertEqual(result, '<span></span>')
144
144
 
145
145
 
146
- class TestWuttaQuantityWidget(WebTestCase):
147
-
148
- def make_field(self, node, **kwargs):
149
- # TODO: not sure why default renderer is in use even though
150
- # pyramid_deform was included in setup? but this works..
151
- kwargs.setdefault('renderer', deform.Form.default_renderer)
152
- return deform.Field(node, **kwargs)
153
-
154
- def make_widget(self, **kwargs):
155
- return mod.WuttaQuantityWidget(self.request, **kwargs)
156
-
157
- def test_serialize(self):
158
- node = colander.SchemaNode(schema.WuttaQuantity(self.request))
159
- field = self.make_field(node)
160
- widget = self.make_widget()
161
- amount = decimal.Decimal('42.00')
162
-
163
- # editable widget has normal text input
164
- result = widget.serialize(field, str(amount))
165
- self.assertIn('<b-input', result)
166
-
167
- # readonly is rendered per app convention
168
- result = widget.serialize(field, str(amount), readonly=True)
169
- self.assertEqual(result, '<span>42</span>')
170
-
171
- # readonly w/ null value
172
- result = widget.serialize(field, None, readonly=True)
173
- self.assertEqual(result, '<span></span>')
174
-
175
-
176
146
  class TestFileDownloadWidget(WebTestCase):
177
147
 
178
148
  def make_field(self, node, **kwargs):
@@ -1565,6 +1565,38 @@ class TestMasterView(WebTestCase):
1565
1565
  count = self.session.query(model.Setting).count()
1566
1566
  self.assertEqual(count, 0)
1567
1567
 
1568
+ def test_configure_gather_settings(self):
1569
+ view = self.make_view()
1570
+
1571
+ simple_settings = [
1572
+ {'name': 'wutta.app_title'},
1573
+ {'name': 'wutta.foo'},
1574
+ {'name': 'wutta.flag', 'type': bool, 'default': True},
1575
+ {'name': 'wutta.number', 'type': int, 'default': 42},
1576
+ {'name': 'wutta.value1', 'save_if_empty': True},
1577
+ {'name': 'wutta.value2', 'save_if_empty': False},
1578
+ {'name': 'wutta.value3', 'save_if_empty': False, 'default': 'baz'},
1579
+ ]
1580
+
1581
+ data = {
1582
+ 'wutta.app_title': 'Poser',
1583
+ 'wutta.foo': 'bar',
1584
+ 'wutta.number': 44,
1585
+ 'wutta.value1': None,
1586
+ }
1587
+
1588
+ with patch.object(view, 'configure_get_simple_settings', return_value=simple_settings):
1589
+ settings = view.configure_gather_settings(data)
1590
+ self.assertEqual(len(settings), 6)
1591
+ self.assertEqual(settings, [
1592
+ {'name': 'wutta.app_title', 'value': 'Poser'},
1593
+ {'name': 'wutta.foo', 'value': 'bar'},
1594
+ {'name': 'wutta.flag', 'value': 'false'},
1595
+ {'name': 'wutta.number', 'value': '44'},
1596
+ {'name': 'wutta.value1', 'value': ''},
1597
+ {'name': 'wutta.value3', 'value': 'baz'},
1598
+ ])
1599
+
1568
1600
  ##############################
1569
1601
  # row methods
1570
1602
  ##############################
File without changes
File without changes
File without changes
File without changes
File without changes