WuttaWeb 0.16.2__tar.gz → 0.17.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 (190) hide show
  1. {wuttaweb-0.16.2 → wuttaweb-0.17.0}/CHANGELOG.md +23 -0
  2. {wuttaweb-0.16.2 → wuttaweb-0.17.0}/PKG-INFO +3 -2
  3. wuttaweb-0.17.0/docs/api/wuttaweb.views.batch.rst +6 -0
  4. {wuttaweb-0.16.2 → wuttaweb-0.17.0}/docs/conf.py +2 -0
  5. wuttaweb-0.17.0/docs/glossary.rst +30 -0
  6. {wuttaweb-0.16.2 → wuttaweb-0.17.0}/docs/index.rst +1 -0
  7. {wuttaweb-0.16.2 → wuttaweb-0.17.0}/pyproject.toml +7 -2
  8. {wuttaweb-0.16.2 → wuttaweb-0.17.0}/src/wuttaweb/forms/base.py +104 -3
  9. {wuttaweb-0.16.2 → wuttaweb-0.17.0}/src/wuttaweb/forms/schema.py +75 -1
  10. {wuttaweb-0.16.2 → wuttaweb-0.17.0}/src/wuttaweb/forms/widgets.py +60 -2
  11. {wuttaweb-0.16.2 → wuttaweb-0.17.0}/src/wuttaweb/grids/base.py +47 -0
  12. wuttaweb-0.17.0/src/wuttaweb/handler.py +144 -0
  13. {wuttaweb-0.16.2 → wuttaweb-0.17.0}/src/wuttaweb/menus.py +6 -1
  14. wuttaweb-0.17.0/src/wuttaweb/static/__init__.py +74 -0
  15. {wuttaweb-0.16.2 → wuttaweb-0.17.0}/src/wuttaweb/subscribers.py +8 -3
  16. {wuttaweb-0.16.2 → wuttaweb-0.17.0}/src/wuttaweb/templates/base.mako +10 -0
  17. wuttaweb-0.17.0/src/wuttaweb/templates/base_meta.mako +23 -0
  18. wuttaweb-0.17.0/src/wuttaweb/templates/batch/view.mako +124 -0
  19. wuttaweb-0.17.0/src/wuttaweb/templates/deform/dateinput.pt +6 -0
  20. wuttaweb-0.17.0/src/wuttaweb/templates/deform/datetimeinput.pt +10 -0
  21. {wuttaweb-0.16.2 → wuttaweb-0.17.0}/src/wuttaweb/templates/deform/readonly/objectref.pt +1 -1
  22. {wuttaweb-0.16.2 → wuttaweb-0.17.0}/src/wuttaweb/templates/form.mako +21 -0
  23. {wuttaweb-0.16.2 → wuttaweb-0.17.0}/src/wuttaweb/templates/forms/vue_template.mako +8 -0
  24. wuttaweb-0.17.0/src/wuttaweb/templates/master/view.mako +52 -0
  25. {wuttaweb-0.16.2 → wuttaweb-0.17.0}/src/wuttaweb/templates/page.mako +5 -1
  26. {wuttaweb-0.16.2 → wuttaweb-0.17.0}/src/wuttaweb/templates/wutta-components.mako +163 -0
  27. {wuttaweb-0.16.2 → wuttaweb-0.17.0}/src/wuttaweb/util.py +16 -4
  28. {wuttaweb-0.16.2 → wuttaweb-0.17.0}/src/wuttaweb/views/base.py +5 -3
  29. wuttaweb-0.17.0/src/wuttaweb/views/batch.py +404 -0
  30. {wuttaweb-0.16.2 → wuttaweb-0.17.0}/src/wuttaweb/views/master.py +278 -12
  31. {wuttaweb-0.16.2 → wuttaweb-0.17.0}/src/wuttaweb/views/roles.py +64 -2
  32. {wuttaweb-0.16.2 → wuttaweb-0.17.0}/src/wuttaweb/views/upgrades.py +1 -2
  33. {wuttaweb-0.16.2 → wuttaweb-0.17.0}/src/wuttaweb/views/users.py +1 -2
  34. {wuttaweb-0.16.2 → wuttaweb-0.17.0}/tests/forms/test_base.py +74 -0
  35. {wuttaweb-0.16.2 → wuttaweb-0.17.0}/tests/forms/test_schema.py +62 -1
  36. {wuttaweb-0.16.2 → wuttaweb-0.17.0}/tests/forms/test_widgets.py +66 -5
  37. {wuttaweb-0.16.2 → wuttaweb-0.17.0}/tests/grids/test_base.py +38 -0
  38. wuttaweb-0.17.0/tests/test_handler.py +76 -0
  39. {wuttaweb-0.16.2 → wuttaweb-0.17.0}/tests/test_util.py +25 -0
  40. {wuttaweb-0.16.2 → wuttaweb-0.17.0}/tests/util.py +5 -0
  41. wuttaweb-0.17.0/tests/views/test_batch.py +373 -0
  42. {wuttaweb-0.16.2 → wuttaweb-0.17.0}/tests/views/test_master.py +159 -2
  43. {wuttaweb-0.16.2 → wuttaweb-0.17.0}/tests/views/test_roles.py +31 -0
  44. wuttaweb-0.16.2/docs/glossary.rst +0 -15
  45. wuttaweb-0.16.2/src/wuttaweb/handler.py +0 -57
  46. wuttaweb-0.16.2/src/wuttaweb/static/__init__.py +0 -37
  47. wuttaweb-0.16.2/src/wuttaweb/templates/base_meta.mako +0 -23
  48. wuttaweb-0.16.2/src/wuttaweb/templates/master/view.mako +0 -9
  49. wuttaweb-0.16.2/tests/test_handler.py +0 -20
  50. {wuttaweb-0.16.2 → wuttaweb-0.17.0}/.gitignore +0 -0
  51. {wuttaweb-0.16.2 → wuttaweb-0.17.0}/COPYING.txt +0 -0
  52. {wuttaweb-0.16.2 → wuttaweb-0.17.0}/README.md +0 -0
  53. {wuttaweb-0.16.2 → wuttaweb-0.17.0}/docs/Makefile +0 -0
  54. {wuttaweb-0.16.2 → wuttaweb-0.17.0}/docs/_static/.keepme +0 -0
  55. {wuttaweb-0.16.2 → wuttaweb-0.17.0}/docs/api/wuttaweb.app.rst +0 -0
  56. {wuttaweb-0.16.2 → wuttaweb-0.17.0}/docs/api/wuttaweb.auth.rst +0 -0
  57. {wuttaweb-0.16.2 → wuttaweb-0.17.0}/docs/api/wuttaweb.conf.rst +0 -0
  58. {wuttaweb-0.16.2 → wuttaweb-0.17.0}/docs/api/wuttaweb.db.continuum.rst +0 -0
  59. {wuttaweb-0.16.2 → wuttaweb-0.17.0}/docs/api/wuttaweb.db.rst +0 -0
  60. {wuttaweb-0.16.2 → wuttaweb-0.17.0}/docs/api/wuttaweb.db.sess.rst +0 -0
  61. {wuttaweb-0.16.2 → wuttaweb-0.17.0}/docs/api/wuttaweb.forms.base.rst +0 -0
  62. {wuttaweb-0.16.2 → wuttaweb-0.17.0}/docs/api/wuttaweb.forms.rst +0 -0
  63. {wuttaweb-0.16.2 → wuttaweb-0.17.0}/docs/api/wuttaweb.forms.schema.rst +0 -0
  64. {wuttaweb-0.16.2 → wuttaweb-0.17.0}/docs/api/wuttaweb.forms.widgets.rst +0 -0
  65. {wuttaweb-0.16.2 → wuttaweb-0.17.0}/docs/api/wuttaweb.grids.base.rst +0 -0
  66. {wuttaweb-0.16.2 → wuttaweb-0.17.0}/docs/api/wuttaweb.grids.filters.rst +0 -0
  67. {wuttaweb-0.16.2 → wuttaweb-0.17.0}/docs/api/wuttaweb.grids.rst +0 -0
  68. {wuttaweb-0.16.2 → wuttaweb-0.17.0}/docs/api/wuttaweb.handler.rst +0 -0
  69. {wuttaweb-0.16.2 → wuttaweb-0.17.0}/docs/api/wuttaweb.helpers.rst +0 -0
  70. {wuttaweb-0.16.2 → wuttaweb-0.17.0}/docs/api/wuttaweb.menus.rst +0 -0
  71. {wuttaweb-0.16.2 → wuttaweb-0.17.0}/docs/api/wuttaweb.progress.rst +0 -0
  72. {wuttaweb-0.16.2 → wuttaweb-0.17.0}/docs/api/wuttaweb.rst +0 -0
  73. {wuttaweb-0.16.2 → wuttaweb-0.17.0}/docs/api/wuttaweb.static.rst +0 -0
  74. {wuttaweb-0.16.2 → wuttaweb-0.17.0}/docs/api/wuttaweb.subscribers.rst +0 -0
  75. {wuttaweb-0.16.2 → wuttaweb-0.17.0}/docs/api/wuttaweb.util.rst +0 -0
  76. {wuttaweb-0.16.2 → wuttaweb-0.17.0}/docs/api/wuttaweb.views.auth.rst +0 -0
  77. {wuttaweb-0.16.2 → wuttaweb-0.17.0}/docs/api/wuttaweb.views.base.rst +0 -0
  78. {wuttaweb-0.16.2 → wuttaweb-0.17.0}/docs/api/wuttaweb.views.common.rst +0 -0
  79. {wuttaweb-0.16.2 → wuttaweb-0.17.0}/docs/api/wuttaweb.views.essential.rst +0 -0
  80. {wuttaweb-0.16.2 → wuttaweb-0.17.0}/docs/api/wuttaweb.views.master.rst +0 -0
  81. {wuttaweb-0.16.2 → wuttaweb-0.17.0}/docs/api/wuttaweb.views.people.rst +0 -0
  82. {wuttaweb-0.16.2 → wuttaweb-0.17.0}/docs/api/wuttaweb.views.progress.rst +0 -0
  83. {wuttaweb-0.16.2 → wuttaweb-0.17.0}/docs/api/wuttaweb.views.roles.rst +0 -0
  84. {wuttaweb-0.16.2 → wuttaweb-0.17.0}/docs/api/wuttaweb.views.rst +0 -0
  85. {wuttaweb-0.16.2 → wuttaweb-0.17.0}/docs/api/wuttaweb.views.settings.rst +0 -0
  86. {wuttaweb-0.16.2 → wuttaweb-0.17.0}/docs/api/wuttaweb.views.upgrades.rst +0 -0
  87. {wuttaweb-0.16.2 → wuttaweb-0.17.0}/docs/api/wuttaweb.views.users.rst +0 -0
  88. {wuttaweb-0.16.2 → wuttaweb-0.17.0}/docs/make.bat +0 -0
  89. {wuttaweb-0.16.2 → wuttaweb-0.17.0}/docs/narr/templates/base.rst +0 -0
  90. {wuttaweb-0.16.2 → wuttaweb-0.17.0}/docs/narr/templates/index.rst +0 -0
  91. {wuttaweb-0.16.2 → wuttaweb-0.17.0}/docs/narr/templates/lookup.rst +0 -0
  92. {wuttaweb-0.16.2 → wuttaweb-0.17.0}/docs/narr/templates/overview.rst +0 -0
  93. {wuttaweb-0.16.2 → wuttaweb-0.17.0}/src/wuttaweb/__init__.py +0 -0
  94. {wuttaweb-0.16.2 → wuttaweb-0.17.0}/src/wuttaweb/_version.py +0 -0
  95. {wuttaweb-0.16.2 → wuttaweb-0.17.0}/src/wuttaweb/app.py +0 -0
  96. {wuttaweb-0.16.2 → wuttaweb-0.17.0}/src/wuttaweb/auth.py +0 -0
  97. {wuttaweb-0.16.2 → wuttaweb-0.17.0}/src/wuttaweb/conf.py +0 -0
  98. {wuttaweb-0.16.2 → wuttaweb-0.17.0}/src/wuttaweb/db/__init__.py +0 -0
  99. {wuttaweb-0.16.2 → wuttaweb-0.17.0}/src/wuttaweb/db/continuum.py +0 -0
  100. {wuttaweb-0.16.2 → wuttaweb-0.17.0}/src/wuttaweb/db/sess.py +0 -0
  101. {wuttaweb-0.16.2 → wuttaweb-0.17.0}/src/wuttaweb/email/templates/feedback.html.mako +0 -0
  102. {wuttaweb-0.16.2 → wuttaweb-0.17.0}/src/wuttaweb/email/templates/feedback.txt.mako +0 -0
  103. {wuttaweb-0.16.2 → wuttaweb-0.17.0}/src/wuttaweb/forms/__init__.py +0 -0
  104. {wuttaweb-0.16.2 → wuttaweb-0.17.0}/src/wuttaweb/grids/__init__.py +0 -0
  105. {wuttaweb-0.16.2 → wuttaweb-0.17.0}/src/wuttaweb/grids/filters.py +0 -0
  106. {wuttaweb-0.16.2 → wuttaweb-0.17.0}/src/wuttaweb/helpers.py +0 -0
  107. {wuttaweb-0.16.2 → wuttaweb-0.17.0}/src/wuttaweb/progress.py +0 -0
  108. {wuttaweb-0.16.2 → wuttaweb-0.17.0}/src/wuttaweb/static/img/favicon.ico +0 -0
  109. {wuttaweb-0.16.2 → wuttaweb-0.17.0}/src/wuttaweb/static/img/logo.png +0 -0
  110. {wuttaweb-0.16.2 → wuttaweb-0.17.0}/src/wuttaweb/static/img/testing.png +0 -0
  111. {wuttaweb-0.16.2 → wuttaweb-0.17.0}/src/wuttaweb/templates/appinfo/configure.mako +0 -0
  112. {wuttaweb-0.16.2 → wuttaweb-0.17.0}/src/wuttaweb/templates/appinfo/index.mako +0 -0
  113. {wuttaweb-0.16.2 → wuttaweb-0.17.0}/src/wuttaweb/templates/auth/change_password.mako +0 -0
  114. {wuttaweb-0.16.2 → wuttaweb-0.17.0}/src/wuttaweb/templates/auth/login.mako +0 -0
  115. {wuttaweb-0.16.2 → wuttaweb-0.17.0}/src/wuttaweb/templates/configure.mako +0 -0
  116. {wuttaweb-0.16.2 → wuttaweb-0.17.0}/src/wuttaweb/templates/deform/checkbox.pt +0 -0
  117. {wuttaweb-0.16.2 → wuttaweb-0.17.0}/src/wuttaweb/templates/deform/checkbox_choice.pt +0 -0
  118. {wuttaweb-0.16.2 → wuttaweb-0.17.0}/src/wuttaweb/templates/deform/checked_password.pt +0 -0
  119. {wuttaweb-0.16.2 → wuttaweb-0.17.0}/src/wuttaweb/templates/deform/moneyinput.pt +0 -0
  120. {wuttaweb-0.16.2 → wuttaweb-0.17.0}/src/wuttaweb/templates/deform/password.pt +0 -0
  121. {wuttaweb-0.16.2 → wuttaweb-0.17.0}/src/wuttaweb/templates/deform/permissions.pt +0 -0
  122. {wuttaweb-0.16.2 → wuttaweb-0.17.0}/src/wuttaweb/templates/deform/readonly/checkbox.pt +0 -0
  123. {wuttaweb-0.16.2 → wuttaweb-0.17.0}/src/wuttaweb/templates/deform/readonly/filedownload.pt +0 -0
  124. {wuttaweb-0.16.2 → wuttaweb-0.17.0}/src/wuttaweb/templates/deform/readonly/notes.pt +0 -0
  125. {wuttaweb-0.16.2 → wuttaweb-0.17.0}/src/wuttaweb/templates/deform/readonly/permissions.pt +0 -0
  126. {wuttaweb-0.16.2 → wuttaweb-0.17.0}/src/wuttaweb/templates/deform/readonly/rolerefs.pt +0 -0
  127. {wuttaweb-0.16.2 → wuttaweb-0.17.0}/src/wuttaweb/templates/deform/select.pt +0 -0
  128. {wuttaweb-0.16.2 → wuttaweb-0.17.0}/src/wuttaweb/templates/deform/textarea.pt +0 -0
  129. {wuttaweb-0.16.2 → wuttaweb-0.17.0}/src/wuttaweb/templates/deform/textinput.pt +0 -0
  130. {wuttaweb-0.16.2 → wuttaweb-0.17.0}/src/wuttaweb/templates/forbidden.mako +0 -0
  131. {wuttaweb-0.16.2 → wuttaweb-0.17.0}/src/wuttaweb/templates/grids/table_element.mako +0 -0
  132. {wuttaweb-0.16.2 → wuttaweb-0.17.0}/src/wuttaweb/templates/grids/vue_template.mako +0 -0
  133. {wuttaweb-0.16.2 → wuttaweb-0.17.0}/src/wuttaweb/templates/home.mako +0 -0
  134. {wuttaweb-0.16.2 → wuttaweb-0.17.0}/src/wuttaweb/templates/master/configure.mako +0 -0
  135. {wuttaweb-0.16.2 → wuttaweb-0.17.0}/src/wuttaweb/templates/master/create.mako +0 -0
  136. {wuttaweb-0.16.2 → wuttaweb-0.17.0}/src/wuttaweb/templates/master/delete.mako +0 -0
  137. {wuttaweb-0.16.2 → wuttaweb-0.17.0}/src/wuttaweb/templates/master/edit.mako +0 -0
  138. {wuttaweb-0.16.2 → wuttaweb-0.17.0}/src/wuttaweb/templates/master/form.mako +0 -0
  139. {wuttaweb-0.16.2 → wuttaweb-0.17.0}/src/wuttaweb/templates/master/index.mako +0 -0
  140. {wuttaweb-0.16.2 → wuttaweb-0.17.0}/src/wuttaweb/templates/notfound.mako +0 -0
  141. {wuttaweb-0.16.2 → wuttaweb-0.17.0}/src/wuttaweb/templates/people/view_profile.mako +0 -0
  142. {wuttaweb-0.16.2 → wuttaweb-0.17.0}/src/wuttaweb/templates/progress.mako +0 -0
  143. {wuttaweb-0.16.2 → wuttaweb-0.17.0}/src/wuttaweb/templates/setup.mako +0 -0
  144. {wuttaweb-0.16.2 → wuttaweb-0.17.0}/src/wuttaweb/templates/upgrade.mako +0 -0
  145. {wuttaweb-0.16.2 → wuttaweb-0.17.0}/src/wuttaweb/templates/upgrades/configure.mako +0 -0
  146. {wuttaweb-0.16.2 → wuttaweb-0.17.0}/src/wuttaweb/templates/upgrades/view.mako +0 -0
  147. {wuttaweb-0.16.2 → wuttaweb-0.17.0}/src/wuttaweb/views/__init__.py +0 -0
  148. {wuttaweb-0.16.2 → wuttaweb-0.17.0}/src/wuttaweb/views/auth.py +0 -0
  149. {wuttaweb-0.16.2 → wuttaweb-0.17.0}/src/wuttaweb/views/common.py +0 -0
  150. {wuttaweb-0.16.2 → wuttaweb-0.17.0}/src/wuttaweb/views/essential.py +0 -0
  151. {wuttaweb-0.16.2 → wuttaweb-0.17.0}/src/wuttaweb/views/people.py +0 -0
  152. {wuttaweb-0.16.2 → wuttaweb-0.17.0}/src/wuttaweb/views/progress.py +0 -0
  153. {wuttaweb-0.16.2 → wuttaweb-0.17.0}/src/wuttaweb/views/settings.py +0 -0
  154. {wuttaweb-0.16.2 → wuttaweb-0.17.0}/tasks.py +0 -0
  155. {wuttaweb-0.16.2 → wuttaweb-0.17.0}/tests/__init__.py +0 -0
  156. {wuttaweb-0.16.2 → wuttaweb-0.17.0}/tests/db/__init__.py +0 -0
  157. {wuttaweb-0.16.2 → wuttaweb-0.17.0}/tests/db/test_continuum.py +0 -0
  158. {wuttaweb-0.16.2 → wuttaweb-0.17.0}/tests/forms/__init__.py +0 -0
  159. {wuttaweb-0.16.2 → wuttaweb-0.17.0}/tests/grids/__init__.py +0 -0
  160. {wuttaweb-0.16.2 → wuttaweb-0.17.0}/tests/grids/test_filters.py +0 -0
  161. {wuttaweb-0.16.2 → wuttaweb-0.17.0}/tests/libcache/bb_fontawesome_svg_core.js +0 -0
  162. {wuttaweb-0.16.2 → wuttaweb-0.17.0}/tests/libcache/bb_free_solid_svg_icons.js +0 -0
  163. {wuttaweb-0.16.2 → wuttaweb-0.17.0}/tests/libcache/bb_oruga.js +0 -0
  164. {wuttaweb-0.16.2 → wuttaweb-0.17.0}/tests/libcache/bb_oruga_bulma.css +0 -0
  165. {wuttaweb-0.16.2 → wuttaweb-0.17.0}/tests/libcache/bb_oruga_bulma.js +0 -0
  166. {wuttaweb-0.16.2 → wuttaweb-0.17.0}/tests/libcache/bb_vue.js +0 -0
  167. {wuttaweb-0.16.2 → wuttaweb-0.17.0}/tests/libcache/bb_vue_fontawesome.js +0 -0
  168. {wuttaweb-0.16.2 → wuttaweb-0.17.0}/tests/libcache/buefy.css +0 -0
  169. {wuttaweb-0.16.2 → wuttaweb-0.17.0}/tests/libcache/buefy.js +0 -0
  170. {wuttaweb-0.16.2 → wuttaweb-0.17.0}/tests/libcache/fontawesome.js +0 -0
  171. {wuttaweb-0.16.2 → wuttaweb-0.17.0}/tests/libcache/vue.js +0 -0
  172. {wuttaweb-0.16.2 → wuttaweb-0.17.0}/tests/libcache/vue_resource.js +0 -0
  173. {wuttaweb-0.16.2 → wuttaweb-0.17.0}/tests/test_app.py +0 -0
  174. {wuttaweb-0.16.2 → wuttaweb-0.17.0}/tests/test_auth.py +0 -0
  175. {wuttaweb-0.16.2 → wuttaweb-0.17.0}/tests/test_helpers.py +0 -0
  176. {wuttaweb-0.16.2 → wuttaweb-0.17.0}/tests/test_menus.py +0 -0
  177. {wuttaweb-0.16.2 → wuttaweb-0.17.0}/tests/test_progress.py +0 -0
  178. {wuttaweb-0.16.2 → wuttaweb-0.17.0}/tests/test_static.py +0 -0
  179. {wuttaweb-0.16.2 → wuttaweb-0.17.0}/tests/test_subscribers.py +0 -0
  180. {wuttaweb-0.16.2 → wuttaweb-0.17.0}/tests/views/__init__.py +0 -0
  181. {wuttaweb-0.16.2 → wuttaweb-0.17.0}/tests/views/test___init__.py +0 -0
  182. {wuttaweb-0.16.2 → wuttaweb-0.17.0}/tests/views/test_auth.py +0 -0
  183. {wuttaweb-0.16.2 → wuttaweb-0.17.0}/tests/views/test_base.py +0 -0
  184. {wuttaweb-0.16.2 → wuttaweb-0.17.0}/tests/views/test_common.py +0 -0
  185. {wuttaweb-0.16.2 → wuttaweb-0.17.0}/tests/views/test_people.py +0 -0
  186. {wuttaweb-0.16.2 → wuttaweb-0.17.0}/tests/views/test_progress.py +0 -0
  187. {wuttaweb-0.16.2 → wuttaweb-0.17.0}/tests/views/test_settings.py +0 -0
  188. {wuttaweb-0.16.2 → wuttaweb-0.17.0}/tests/views/test_upgrades.py +0 -0
  189. {wuttaweb-0.16.2 → wuttaweb-0.17.0}/tests/views/test_users.py +0 -0
  190. {wuttaweb-0.16.2 → wuttaweb-0.17.0}/tox.ini +0 -0
@@ -5,6 +5,29 @@ 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.17.0 (2024-12-15)
9
+
10
+ ### Feat
11
+
12
+ - add basic support for batch execution
13
+ - add basic support for rows grid for master, batch views
14
+ - add basic master view class for batches
15
+
16
+ ### Fix
17
+
18
+ - add handling for decimal values and lists, in `make_json_safe()`
19
+ - fix behavior when editing Roles for a User
20
+ - add basic views for raw Permissions
21
+ - improve support for date, datetime fields in grids, forms
22
+ - add way to set field widgets using pseudo-type
23
+ - add support for date, datetime form fields
24
+ - make dropdown widgets as wide as other text fields in main form
25
+ - add fallback instance title
26
+ - display "global" errors at top of form, if present
27
+ - add `make_form()` and `make_grid()` methods on web handler
28
+ - correct "empty option" behavior for `ObjectRef` schema type
29
+ - use fanstatic to serve built-in images by default
30
+
8
31
  ## v0.16.2 (2024-12-10)
9
32
 
10
33
  ### Fix
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: WuttaWeb
3
- Version: 0.16.2
3
+ Version: 0.17.0
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
@@ -26,6 +26,7 @@ Classifier: Topic :: Software Development :: Libraries :: Python Modules
26
26
  Requires-Python: >=3.8
27
27
  Requires-Dist: colanderalchemy
28
28
  Requires-Dist: humanize
29
+ Requires-Dist: markdown
29
30
  Requires-Dist: paginate
30
31
  Requires-Dist: paginate-sqlalchemy
31
32
  Requires-Dist: pyramid-beaker
@@ -36,7 +37,7 @@ Requires-Dist: pyramid-tm
36
37
  Requires-Dist: pyramid>=2
37
38
  Requires-Dist: waitress
38
39
  Requires-Dist: webhelpers2
39
- Requires-Dist: wuttjamaican[db]>=0.17.1
40
+ Requires-Dist: wuttjamaican[db]>=0.18.0
40
41
  Requires-Dist: zope-sqlalchemy>=1.5
41
42
  Provides-Extra: continuum
42
43
  Requires-Dist: wutta-continuum; extra == 'continuum'
@@ -0,0 +1,6 @@
1
+
2
+ ``wuttaweb.views.batch``
3
+ ========================
4
+
5
+ .. automodule:: wuttaweb.views.batch
6
+ :members:
@@ -29,9 +29,11 @@ exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']
29
29
  intersphinx_mapping = {
30
30
  'colander': ('https://docs.pylonsproject.org/projects/colander/en/latest/', None),
31
31
  'deform': ('https://docs.pylonsproject.org/projects/deform/en/latest/', None),
32
+ 'fanstatic': ('https://www.fanstatic.org/en/latest/', None),
32
33
  'pyramid': ('https://docs.pylonsproject.org/projects/pyramid/en/latest/', None),
33
34
  'python': ('https://docs.python.org/3/', None),
34
35
  'rattail-manual': ('https://rattailproject.org/docs/rattail-manual/', None),
36
+ 'sqlalchemy': ('http://docs.sqlalchemy.org/en/latest/', None),
35
37
  'webhelpers2': ('https://webhelpers2.readthedocs.io/en/latest/', None),
36
38
  'wuttjamaican': ('https://rattailproject.org/docs/wuttjamaican/', None),
37
39
  'wutta-continuum': ('https://rattailproject.org/docs/wutta-continuum/', None),
@@ -0,0 +1,30 @@
1
+ .. _glossary:
2
+
3
+ Glossary
4
+ ========
5
+
6
+ .. glossary::
7
+ :sorted:
8
+
9
+ grid
10
+ This refers to a "table of data, with features" essentially.
11
+ Sometimes it may be displayed as a simple table with no features,
12
+ or sometimes it has sortable columns, search filters and other
13
+ tools.
14
+
15
+ See also the :class:`~wuttaweb.grids.base.Grid` base class.
16
+
17
+ menu handler
18
+ This is the :term:`handler` responsible for constructing the main
19
+ app menu at top of page.
20
+
21
+ The menu handler is accessed by way of the :term:`web handler`.
22
+
23
+ See also the :class:`~wuttaweb.menus.MenuHandler` base class.
24
+
25
+ web handler
26
+ This is the :term:`handler` responsible for overall web layer
27
+ customizations, e.g. logo image and menu overrides. Although
28
+ the latter it delegates to the :term:`menu handler`.
29
+
30
+ See also the :class:`~wuttaweb.handler.WebHandler` base class.
@@ -49,6 +49,7 @@ the narrative docs are pretty scant. That will eventually change.
49
49
  api/wuttaweb.views
50
50
  api/wuttaweb.views.auth
51
51
  api/wuttaweb.views.base
52
+ api/wuttaweb.views.batch
52
53
  api/wuttaweb.views.common
53
54
  api/wuttaweb.views.essential
54
55
  api/wuttaweb.views.master
@@ -6,7 +6,7 @@ build-backend = "hatchling.build"
6
6
 
7
7
  [project]
8
8
  name = "WuttaWeb"
9
- version = "0.16.2"
9
+ version = "0.17.0"
10
10
  description = "Web App for Wutta Framework"
11
11
  readme = "README.md"
12
12
  authors = [{name = "Lance Edgar", email = "lance@wuttaproject.org"}]
@@ -32,6 +32,7 @@ requires-python = ">= 3.8"
32
32
  dependencies = [
33
33
  "ColanderAlchemy",
34
34
  "humanize",
35
+ "markdown",
35
36
  "paginate",
36
37
  "paginate_sqlalchemy",
37
38
  "pyramid>=2",
@@ -42,7 +43,7 @@ dependencies = [
42
43
  "pyramid_tm",
43
44
  "waitress",
44
45
  "WebHelpers2",
45
- "WuttJamaican[db]>=0.17.1",
46
+ "WuttJamaican[db]>=0.18.0",
46
47
  "zope.sqlalchemy>=1.5",
47
48
  ]
48
49
 
@@ -53,6 +54,10 @@ docs = ["Sphinx", "furo"]
53
54
  tests = ["pytest-cov", "tox"]
54
55
 
55
56
 
57
+ [project.entry-points."fanstatic.libraries"]
58
+ wuttaweb_img = "wuttaweb.static:img"
59
+
60
+
56
61
  [project.entry-points."paste.app_factory"]
57
62
  main = "wuttaweb.app:main"
58
63
 
@@ -27,6 +27,9 @@ Base form classes
27
27
  import logging
28
28
  from collections import OrderedDict
29
29
 
30
+ import sqlalchemy as sa
31
+ from sqlalchemy import orm
32
+
30
33
  import colander
31
34
  import deform
32
35
  from colanderalchemy import SQLAlchemySchemaNode
@@ -311,6 +314,7 @@ class Form:
311
314
  self.model_class = type(self.model_instance)
312
315
 
313
316
  self.set_fields(fields or self.get_fields())
317
+ self.set_default_widgets()
314
318
 
315
319
  # nb. this tracks grid JSON data for inclusion in page template
316
320
  self.grid_vue_context = OrderedDict()
@@ -457,23 +461,94 @@ class Form:
457
461
  if self.schema:
458
462
  self.schema[key] = node
459
463
 
460
- def set_widget(self, key, widget):
464
+ def set_widget(self, key, widget, **kwargs):
461
465
  """
462
466
  Set/override the widget for a field.
463
467
 
468
+ You can specify a widget instance or else a named "type" of
469
+ widget, in which case that is passed along to
470
+ :meth:`make_widget()`.
471
+
464
472
  :param key: Name of field.
465
473
 
466
- :param widget: Instance of
467
- :class:`deform:deform.widget.Widget`.
474
+ :param widget: Either a :class:`deform:deform.widget.Widget`
475
+ instance, or else a widget "type" name.
476
+
477
+ :param \**kwargs: Any remaining kwargs are passed along to
478
+ :meth:`make_widget()` - if applicable.
468
479
 
469
480
  Widget overrides are tracked via :attr:`widgets`.
470
481
  """
482
+ if not isinstance(widget, deform.widget.Widget):
483
+ widget_obj = self.make_widget(widget, **kwargs)
484
+ if not widget_obj:
485
+ raise ValueError(f"widget type not supported: {widget}")
486
+ widget = widget_obj
487
+
471
488
  self.widgets[key] = widget
472
489
 
473
490
  # update schema if necessary
474
491
  if self.schema and key in self.schema:
475
492
  self.schema[key].widget = widget
476
493
 
494
+ def make_widget(self, widget_type, **kwargs):
495
+ """
496
+ Make and return a new field widget of the given type.
497
+
498
+ This has built-in support for the following types (although
499
+ subclass can override as needed):
500
+
501
+ * ``'notes'`` => :class:`~wuttaweb.forms.widgets.NotesWidget`
502
+
503
+ See also :meth:`set_widget()` which may call this method
504
+ automatically.
505
+
506
+ :param widget_type: Which of the above (or custom) widget
507
+ type to create.
508
+
509
+ :param \**kwargs: Remaining kwargs are passed as-is to the
510
+ widget factory.
511
+
512
+ :returns: New widget instance, or ``None`` if e.g. it could
513
+ not determine how to create the widget.
514
+ """
515
+ from wuttaweb.forms import widgets
516
+
517
+ if widget_type == 'notes':
518
+ return widgets.NotesWidget(**kwargs)
519
+
520
+ def set_default_widgets(self):
521
+ """
522
+ Set default field widgets, where applicable.
523
+
524
+ This will add new entries to :attr:`widgets` for columns
525
+ whose data type implies a default widget should be used.
526
+ This is generally only possible if :attr:`model_class` is set
527
+ to a valid SQLAlchemy mapped class.
528
+
529
+ As of writing this only looks for
530
+ :class:`sqlalchemy:sqlalchemy.types.DateTime` fields and if
531
+ any are found, they are configured to use
532
+ :class:`~wuttaweb.forms.widgets.WuttaDateTimeWidget()`.
533
+ """
534
+ from wuttaweb.forms import widgets
535
+
536
+ if not self.model_class:
537
+ return
538
+
539
+ for key in self.fields:
540
+ if key in self.widgets:
541
+ continue
542
+
543
+ attr = getattr(self.model_class, key, None)
544
+ if attr:
545
+ prop = getattr(attr, 'prop', None)
546
+ if prop and isinstance(prop, orm.ColumnProperty):
547
+ column = prop.columns[0]
548
+ if isinstance(column.type, sa.DateTime):
549
+ # self.set_renderer(key, self.render_datetime)
550
+ self.set_widget(key, widgets.WuttaDateTimeWidget(self.request))
551
+
477
552
  def set_grid(self, key, grid):
478
553
  """
479
554
  Establish a :term:`grid` to be displayed for a field. This
@@ -1111,6 +1186,32 @@ class Form:
1111
1186
 
1112
1187
  return self.validated
1113
1188
 
1189
+ def has_global_errors(self):
1190
+ """
1191
+ Convenience function to check if the form has any "global"
1192
+ (not field-level) errors.
1193
+
1194
+ See also :meth:`get_global_errors()`.
1195
+
1196
+ :returns: ``True`` if global errors present, else ``False``.
1197
+ """
1198
+ dform = self.get_deform()
1199
+ return bool(dform.error)
1200
+
1201
+ def get_global_errors(self):
1202
+ """
1203
+ Returns a list of "global" (not field-level) error messages
1204
+ for the form.
1205
+
1206
+ See also :meth:`has_global_errors()`.
1207
+
1208
+ :returns: List of error messages (possibly empty).
1209
+ """
1210
+ dform = self.get_deform()
1211
+ if dform.error is None:
1212
+ return []
1213
+ return dform.error.messages()
1214
+
1114
1215
  def get_field_errors(self, field):
1115
1216
  """
1116
1217
  Return a list of error messages for the given field.
@@ -24,15 +24,48 @@
24
24
  Form schema types
25
25
  """
26
26
 
27
+ import datetime
27
28
  import uuid as _uuid
28
29
 
29
30
  import colander
31
+ import sqlalchemy as sa
30
32
 
31
33
  from wuttaweb.db import Session
32
34
  from wuttaweb.forms import widgets
33
35
  from wuttjamaican.db.model import Person
34
36
 
35
37
 
38
+ class WuttaDateTime(colander.DateTime):
39
+ """
40
+ Custom schema type for ``datetime`` fields.
41
+
42
+ This should be used automatically for
43
+ :class:`sqlalchemy:sqlalchemy.types.DateTime` columns unless you
44
+ register another default.
45
+
46
+ This schema type exists for sake of convenience, when working with
47
+ the Buefy datepicker + timepicker widgets.
48
+ """
49
+
50
+ def deserialize(self, node, cstruct):
51
+ """ """
52
+ if not cstruct:
53
+ return colander.null
54
+
55
+ formats = [
56
+ '%Y-%m-%dT%H:%M:%S',
57
+ '%Y-%m-%dT%I:%M %p',
58
+ ]
59
+
60
+ for fmt in formats:
61
+ try:
62
+ return datetime.datetime.strptime(cstruct, fmt)
63
+ except:
64
+ pass
65
+
66
+ node.raise_invalid("Invalid date and/or time")
67
+
68
+
36
69
  class ObjectNode(colander.SchemaNode):
37
70
  """
38
71
  Custom schema node class which adds methods for compatibility with
@@ -207,6 +240,11 @@ class ObjectRef(colander.SchemaType):
207
240
 
208
241
  def serialize(self, node, appstruct):
209
242
  """ """
243
+ # nb. normalize to empty option if no object ref, so that
244
+ # works as expected
245
+ if self.empty_option and not appstruct:
246
+ return self.empty_option[0]
247
+
210
248
  if appstruct is colander.null:
211
249
  return colander.null
212
250
 
@@ -344,6 +382,30 @@ class PersonRef(ObjectRef):
344
382
  return self.request.route_url('people.view', uuid=person.uuid)
345
383
 
346
384
 
385
+ class RoleRef(ObjectRef):
386
+ """
387
+ Custom schema type for a
388
+ :class:`~wuttjamaican:wuttjamaican.db.model.auth.Role` reference
389
+ field.
390
+
391
+ This is a subclass of :class:`ObjectRef`.
392
+ """
393
+
394
+ @property
395
+ def model_class(self):
396
+ """ """
397
+ model = self.app.model
398
+ return model.Role
399
+
400
+ def sort_query(self, query):
401
+ """ """
402
+ return query.order_by(self.model_class.name)
403
+
404
+ def get_object_url(self, role):
405
+ """ """
406
+ return self.request.route_url('roles.view', uuid=role.uuid)
407
+
408
+
347
409
  class UserRef(ObjectRef):
348
410
  """
349
411
  Custom schema type for a
@@ -391,16 +453,24 @@ class RoleRefs(WuttaSet):
391
453
  if 'values' not in kwargs:
392
454
  model = self.app.model
393
455
  auth = self.app.get_auth_handler()
456
+
457
+ # avoid built-ins which cannot be assigned to users
394
458
  avoid = {
395
459
  auth.get_role_authenticated(self.session),
396
460
  auth.get_role_anonymous(self.session),
397
461
  }
398
462
  avoid = set([role.uuid for role in avoid])
463
+
464
+ # also avoid admin unless current user is root
465
+ if not self.request.is_root:
466
+ avoid.add(auth.get_role_administrator(self.session).uuid)
467
+
468
+ # everything else can be (un)assigned for users
399
469
  roles = self.session.query(model.Role)\
400
470
  .filter(~model.Role.uuid.in_(avoid))\
401
471
  .order_by(model.Role.name)\
402
472
  .all()
403
- values = [(role.uuid, role.name) for role in roles]
473
+ values = [(role.uuid.hex, role.name) for role in roles]
404
474
  kwargs['values'] = values
405
475
 
406
476
  return widgets.RoleRefsWidget(self.request, **kwargs)
@@ -497,3 +567,7 @@ class FileDownload(colander.String):
497
567
  """ """
498
568
  kwargs.setdefault('url', self.url)
499
569
  return widgets.FileDownloadWidget(self.request, **kwargs)
570
+
571
+
572
+ # nb. colanderalchemy schema overrides
573
+ sa.DateTime.__colanderalchemy_config__ = {'typ': WuttaDateTime}
@@ -36,9 +36,11 @@ in the namespace:
36
36
  * :class:`deform:deform.widget.CheckboxWidget`
37
37
  * :class:`deform:deform.widget.SelectWidget`
38
38
  * :class:`deform:deform.widget.CheckboxChoiceWidget`
39
+ * :class:`deform:deform.widget.DateTimeInputWidget`
39
40
  * :class:`deform:deform.widget.MoneyInputWidget`
40
41
  """
41
42
 
43
+ import datetime
42
44
  import os
43
45
 
44
46
  import colander
@@ -46,7 +48,7 @@ import humanize
46
48
  from deform.widget import (Widget, TextInputWidget, TextAreaWidget,
47
49
  PasswordWidget, CheckedPasswordWidget,
48
50
  CheckboxWidget, SelectWidget, CheckboxChoiceWidget,
49
- MoneyInputWidget)
51
+ DateTimeInputWidget, MoneyInputWidget)
50
52
  from webhelpers2.html import HTML
51
53
 
52
54
  from wuttaweb.db import Session
@@ -102,7 +104,7 @@ class ObjectRefWidget(SelectWidget):
102
104
  # add url, only if rendering readonly
103
105
  readonly = kw.get('readonly', self.readonly)
104
106
  if readonly:
105
- if 'url' not in values and self.url and field.schema.model_instance:
107
+ if 'url' not in values and self.url and getattr(field.schema, 'model_instance', None):
106
108
  values['url'] = self.url(field.schema.model_instance)
107
109
 
108
110
  return values
@@ -153,6 +155,43 @@ class WuttaCheckboxChoiceWidget(CheckboxChoiceWidget):
153
155
  self.session = session or Session()
154
156
 
155
157
 
158
+ class WuttaDateTimeWidget(DateTimeInputWidget):
159
+ """
160
+ Custom widget for :class:`python:datetime.datetime` fields.
161
+
162
+ The main purpose of this widget is to leverage
163
+ :meth:`~wuttjamaican:wuttjamaican.app.AppHandler.render_datetime()`
164
+ for the readonly display.
165
+
166
+ It is automatically used for SQLAlchemy mapped classes where the
167
+ field maps to a :class:`sqlalchemy:sqlalchemy.types.DateTime`
168
+ column. For other (non-mapped) datetime fields, you may have to
169
+ use it explicitly via
170
+ :meth:`~wuttaweb.forms.base.Form.set_widget()`.
171
+
172
+ This is a subclass of
173
+ :class:`deform:deform.widget.DateTimeInputWidget` and uses these
174
+ Deform templates:
175
+
176
+ * ``datetimeinput``
177
+ """
178
+
179
+ def __init__(self, request, *args, **kwargs):
180
+ super().__init__(*args, **kwargs)
181
+ self.request = request
182
+ self.config = self.request.wutta_config
183
+ self.app = self.config.get_app()
184
+
185
+ def serialize(self, field, cstruct, **kw):
186
+ """ """
187
+ readonly = kw.get('readonly', self.readonly)
188
+ if readonly and cstruct:
189
+ dt = datetime.datetime.fromisoformat(cstruct)
190
+ return self.app.render_datetime(dt)
191
+
192
+ return super().serialize(field, cstruct, **kw)
193
+
194
+
156
195
  class FileDownloadWidget(Widget):
157
196
  """
158
197
  Widget for use with :class:`~wuttaweb.forms.schema.FileDownload`
@@ -382,3 +421,22 @@ class PermissionsWidget(WuttaCheckboxChoiceWidget):
382
421
  kw['values'] = values
383
422
 
384
423
  return super().serialize(field, cstruct, **kw)
424
+
425
+
426
+ class BatchIdWidget(Widget):
427
+ """
428
+ Widget for use with the
429
+ :attr:`~wuttjamaican:wuttjamaican.db.model.batch.BatchMixin.id`
430
+ field of a :term:`batch` model.
431
+
432
+ This widget is "always" read-only and renders the Batch ID as
433
+ zero-padded 8-char string
434
+ """
435
+
436
+ def serialize(self, field, cstruct, **kw):
437
+ """ """
438
+ if cstruct is colander.null:
439
+ return colander.null
440
+
441
+ batch_id = int(cstruct)
442
+ return f'{batch_id:08d}'
@@ -397,6 +397,7 @@ class Grid:
397
397
  self.app = self.config.get_app()
398
398
 
399
399
  self.set_columns(columns or self.get_columns())
400
+ self.set_default_renderers()
400
401
  self.set_tools(tools)
401
402
 
402
403
  # sorting
@@ -596,6 +597,35 @@ class Grid:
596
597
  renderer = functools.partial(renderer, **kwargs)
597
598
  self.renderers[key] = renderer
598
599
 
600
+ def set_default_renderers(self):
601
+ """
602
+ Set default column value renderers, where applicable.
603
+
604
+ This will add new entries to :attr:`renderers` for columns
605
+ whose data type implies a default renderer should be used.
606
+ This is generally only possible if :attr:`model_class` is set
607
+ to a valid SQLAlchemy mapped class.
608
+
609
+ This (for now?) only looks for
610
+ :class:`sqlalchemy:sqlalchemy.types.DateTime` columns and if
611
+ any are found, they are configured to use
612
+ :meth:`render_datetime()`.
613
+ """
614
+ if not self.model_class:
615
+ return
616
+
617
+ for key in self.columns:
618
+ if key in self.renderers:
619
+ continue
620
+
621
+ attr = getattr(self.model_class, key, None)
622
+ if attr:
623
+ prop = getattr(attr, 'prop', None)
624
+ if prop and isinstance(prop, orm.ColumnProperty):
625
+ column = prop.columns[0]
626
+ if isinstance(column.type, sa.DateTime):
627
+ self.set_renderer(key, self.render_datetime)
628
+
599
629
  def set_link(self, key, link=True):
600
630
  """
601
631
  Explicitly enable or disable auto-link behavior for a given
@@ -1725,6 +1755,23 @@ class Grid:
1725
1755
  # rendering methods
1726
1756
  ##############################
1727
1757
 
1758
+ def render_datetime(self, obj, key, value):
1759
+ """
1760
+ Default cell value renderer for
1761
+ :class:`sqlalchemy:sqlalchemy.types.DateTime` columns, which
1762
+ calls
1763
+ :meth:`~wuttjamaican:wuttjamaican.app.AppHandler.render_datetime()`
1764
+ for the return value.
1765
+
1766
+ This may be used automatically per
1767
+ :meth:`set_default_renderers()` or you can use it explicitly
1768
+ for any :class:`python:datetime.datetime` column with::
1769
+
1770
+ grid.set_renderer('foo', grid.render_datetime)
1771
+ """
1772
+ dt = getattr(obj, key)
1773
+ return self.app.render_datetime(dt)
1774
+
1728
1775
  def render_table_element(
1729
1776
  self,
1730
1777
  form=None,