WuttaWeb 0.19.2__tar.gz → 0.20.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 (205) hide show
  1. {wuttaweb-0.19.2 → wuttaweb-0.20.0}/CHANGELOG.md +20 -0
  2. {wuttaweb-0.19.2 → wuttaweb-0.20.0}/PKG-INFO +2 -2
  3. wuttaweb-0.20.0/docs/api/wuttaweb.views.reports.rst +6 -0
  4. {wuttaweb-0.19.2 → wuttaweb-0.20.0}/docs/index.rst +1 -0
  5. {wuttaweb-0.19.2 → wuttaweb-0.20.0}/pyproject.toml +2 -2
  6. {wuttaweb-0.19.2 → wuttaweb-0.20.0}/src/wuttaweb/forms/base.py +26 -1
  7. {wuttaweb-0.19.2 → wuttaweb-0.20.0}/src/wuttaweb/forms/schema.py +31 -0
  8. {wuttaweb-0.19.2 → wuttaweb-0.20.0}/src/wuttaweb/templates/configure.mako +3 -3
  9. {wuttaweb-0.19.2 → wuttaweb-0.20.0}/src/wuttaweb/templates/forms/vue_template.mako +11 -3
  10. {wuttaweb-0.19.2 → wuttaweb-0.20.0}/src/wuttaweb/templates/grids/vue_template.mako +3 -0
  11. wuttaweb-0.20.0/src/wuttaweb/templates/reports/view.mako +61 -0
  12. {wuttaweb-0.19.2 → wuttaweb-0.20.0}/src/wuttaweb/templates/wutta-components.mako +145 -0
  13. {wuttaweb-0.19.2 → wuttaweb-0.20.0}/src/wuttaweb/views/master.py +42 -12
  14. wuttaweb-0.20.0/src/wuttaweb/views/reports.py +266 -0
  15. {wuttaweb-0.19.2 → wuttaweb-0.20.0}/tests/forms/test_schema.py +19 -0
  16. wuttaweb-0.20.0/tests/views/test_reports.py +231 -0
  17. {wuttaweb-0.19.2 → wuttaweb-0.20.0}/.gitignore +0 -0
  18. {wuttaweb-0.19.2 → wuttaweb-0.20.0}/COPYING.txt +0 -0
  19. {wuttaweb-0.19.2 → wuttaweb-0.20.0}/README.md +0 -0
  20. {wuttaweb-0.19.2 → wuttaweb-0.20.0}/docs/Makefile +0 -0
  21. {wuttaweb-0.19.2 → wuttaweb-0.20.0}/docs/_static/.keepme +0 -0
  22. {wuttaweb-0.19.2 → wuttaweb-0.20.0}/docs/api/wuttaweb.app.rst +0 -0
  23. {wuttaweb-0.19.2 → wuttaweb-0.20.0}/docs/api/wuttaweb.auth.rst +0 -0
  24. {wuttaweb-0.19.2 → wuttaweb-0.20.0}/docs/api/wuttaweb.cli.rst +0 -0
  25. {wuttaweb-0.19.2 → wuttaweb-0.20.0}/docs/api/wuttaweb.cli.webapp.rst +0 -0
  26. {wuttaweb-0.19.2 → wuttaweb-0.20.0}/docs/api/wuttaweb.conf.rst +0 -0
  27. {wuttaweb-0.19.2 → wuttaweb-0.20.0}/docs/api/wuttaweb.db.continuum.rst +0 -0
  28. {wuttaweb-0.19.2 → wuttaweb-0.20.0}/docs/api/wuttaweb.db.rst +0 -0
  29. {wuttaweb-0.19.2 → wuttaweb-0.20.0}/docs/api/wuttaweb.db.sess.rst +0 -0
  30. {wuttaweb-0.19.2 → wuttaweb-0.20.0}/docs/api/wuttaweb.emails.rst +0 -0
  31. {wuttaweb-0.19.2 → wuttaweb-0.20.0}/docs/api/wuttaweb.forms.base.rst +0 -0
  32. {wuttaweb-0.19.2 → wuttaweb-0.20.0}/docs/api/wuttaweb.forms.rst +0 -0
  33. {wuttaweb-0.19.2 → wuttaweb-0.20.0}/docs/api/wuttaweb.forms.schema.rst +0 -0
  34. {wuttaweb-0.19.2 → wuttaweb-0.20.0}/docs/api/wuttaweb.forms.widgets.rst +0 -0
  35. {wuttaweb-0.19.2 → wuttaweb-0.20.0}/docs/api/wuttaweb.grids.base.rst +0 -0
  36. {wuttaweb-0.19.2 → wuttaweb-0.20.0}/docs/api/wuttaweb.grids.filters.rst +0 -0
  37. {wuttaweb-0.19.2 → wuttaweb-0.20.0}/docs/api/wuttaweb.grids.rst +0 -0
  38. {wuttaweb-0.19.2 → wuttaweb-0.20.0}/docs/api/wuttaweb.handler.rst +0 -0
  39. {wuttaweb-0.19.2 → wuttaweb-0.20.0}/docs/api/wuttaweb.helpers.rst +0 -0
  40. {wuttaweb-0.19.2 → wuttaweb-0.20.0}/docs/api/wuttaweb.menus.rst +0 -0
  41. {wuttaweb-0.19.2 → wuttaweb-0.20.0}/docs/api/wuttaweb.progress.rst +0 -0
  42. {wuttaweb-0.19.2 → wuttaweb-0.20.0}/docs/api/wuttaweb.rst +0 -0
  43. {wuttaweb-0.19.2 → wuttaweb-0.20.0}/docs/api/wuttaweb.static.rst +0 -0
  44. {wuttaweb-0.19.2 → wuttaweb-0.20.0}/docs/api/wuttaweb.subscribers.rst +0 -0
  45. {wuttaweb-0.19.2 → wuttaweb-0.20.0}/docs/api/wuttaweb.util.rst +0 -0
  46. {wuttaweb-0.19.2 → wuttaweb-0.20.0}/docs/api/wuttaweb.views.auth.rst +0 -0
  47. {wuttaweb-0.19.2 → wuttaweb-0.20.0}/docs/api/wuttaweb.views.base.rst +0 -0
  48. {wuttaweb-0.19.2 → wuttaweb-0.20.0}/docs/api/wuttaweb.views.batch.rst +0 -0
  49. {wuttaweb-0.19.2 → wuttaweb-0.20.0}/docs/api/wuttaweb.views.common.rst +0 -0
  50. {wuttaweb-0.19.2 → wuttaweb-0.20.0}/docs/api/wuttaweb.views.email.rst +0 -0
  51. {wuttaweb-0.19.2 → wuttaweb-0.20.0}/docs/api/wuttaweb.views.essential.rst +0 -0
  52. {wuttaweb-0.19.2 → wuttaweb-0.20.0}/docs/api/wuttaweb.views.master.rst +0 -0
  53. {wuttaweb-0.19.2 → wuttaweb-0.20.0}/docs/api/wuttaweb.views.people.rst +0 -0
  54. {wuttaweb-0.19.2 → wuttaweb-0.20.0}/docs/api/wuttaweb.views.progress.rst +0 -0
  55. {wuttaweb-0.19.2 → wuttaweb-0.20.0}/docs/api/wuttaweb.views.roles.rst +0 -0
  56. {wuttaweb-0.19.2 → wuttaweb-0.20.0}/docs/api/wuttaweb.views.rst +0 -0
  57. {wuttaweb-0.19.2 → wuttaweb-0.20.0}/docs/api/wuttaweb.views.settings.rst +0 -0
  58. {wuttaweb-0.19.2 → wuttaweb-0.20.0}/docs/api/wuttaweb.views.upgrades.rst +0 -0
  59. {wuttaweb-0.19.2 → wuttaweb-0.20.0}/docs/api/wuttaweb.views.users.rst +0 -0
  60. {wuttaweb-0.19.2 → wuttaweb-0.20.0}/docs/conf.py +0 -0
  61. {wuttaweb-0.19.2 → wuttaweb-0.20.0}/docs/glossary.rst +0 -0
  62. {wuttaweb-0.19.2 → wuttaweb-0.20.0}/docs/make.bat +0 -0
  63. {wuttaweb-0.19.2 → wuttaweb-0.20.0}/docs/narr/cli/builtin.rst +0 -0
  64. {wuttaweb-0.19.2 → wuttaweb-0.20.0}/docs/narr/cli/index.rst +0 -0
  65. {wuttaweb-0.19.2 → wuttaweb-0.20.0}/docs/narr/templates/base.rst +0 -0
  66. {wuttaweb-0.19.2 → wuttaweb-0.20.0}/docs/narr/templates/index.rst +0 -0
  67. {wuttaweb-0.19.2 → wuttaweb-0.20.0}/docs/narr/templates/lookup.rst +0 -0
  68. {wuttaweb-0.19.2 → wuttaweb-0.20.0}/docs/narr/templates/overview.rst +0 -0
  69. {wuttaweb-0.19.2 → wuttaweb-0.20.0}/src/wuttaweb/__init__.py +0 -0
  70. {wuttaweb-0.19.2 → wuttaweb-0.20.0}/src/wuttaweb/_version.py +0 -0
  71. {wuttaweb-0.19.2 → wuttaweb-0.20.0}/src/wuttaweb/app.py +0 -0
  72. {wuttaweb-0.19.2 → wuttaweb-0.20.0}/src/wuttaweb/auth.py +0 -0
  73. {wuttaweb-0.19.2 → wuttaweb-0.20.0}/src/wuttaweb/cli/__init__.py +0 -0
  74. {wuttaweb-0.19.2 → wuttaweb-0.20.0}/src/wuttaweb/cli/webapp.py +0 -0
  75. {wuttaweb-0.19.2 → wuttaweb-0.20.0}/src/wuttaweb/conf.py +0 -0
  76. {wuttaweb-0.19.2 → wuttaweb-0.20.0}/src/wuttaweb/db/__init__.py +0 -0
  77. {wuttaweb-0.19.2 → wuttaweb-0.20.0}/src/wuttaweb/db/continuum.py +0 -0
  78. {wuttaweb-0.19.2 → wuttaweb-0.20.0}/src/wuttaweb/db/sess.py +0 -0
  79. {wuttaweb-0.19.2 → wuttaweb-0.20.0}/src/wuttaweb/email-templates/feedback.html.mako +0 -0
  80. {wuttaweb-0.19.2 → wuttaweb-0.20.0}/src/wuttaweb/email-templates/feedback.txt.mako +0 -0
  81. {wuttaweb-0.19.2 → wuttaweb-0.20.0}/src/wuttaweb/emails.py +0 -0
  82. {wuttaweb-0.19.2 → wuttaweb-0.20.0}/src/wuttaweb/forms/__init__.py +0 -0
  83. {wuttaweb-0.19.2 → wuttaweb-0.20.0}/src/wuttaweb/forms/widgets.py +0 -0
  84. {wuttaweb-0.19.2 → wuttaweb-0.20.0}/src/wuttaweb/grids/__init__.py +0 -0
  85. {wuttaweb-0.19.2 → wuttaweb-0.20.0}/src/wuttaweb/grids/base.py +0 -0
  86. {wuttaweb-0.19.2 → wuttaweb-0.20.0}/src/wuttaweb/grids/filters.py +0 -0
  87. {wuttaweb-0.19.2 → wuttaweb-0.20.0}/src/wuttaweb/handler.py +0 -0
  88. {wuttaweb-0.19.2 → wuttaweb-0.20.0}/src/wuttaweb/helpers.py +0 -0
  89. {wuttaweb-0.19.2 → wuttaweb-0.20.0}/src/wuttaweb/menus.py +0 -0
  90. {wuttaweb-0.19.2 → wuttaweb-0.20.0}/src/wuttaweb/progress.py +0 -0
  91. {wuttaweb-0.19.2 → wuttaweb-0.20.0}/src/wuttaweb/static/__init__.py +0 -0
  92. {wuttaweb-0.19.2 → wuttaweb-0.20.0}/src/wuttaweb/static/img/favicon.ico +0 -0
  93. {wuttaweb-0.19.2 → wuttaweb-0.20.0}/src/wuttaweb/static/img/logo.png +0 -0
  94. {wuttaweb-0.19.2 → wuttaweb-0.20.0}/src/wuttaweb/static/img/testing.png +0 -0
  95. {wuttaweb-0.19.2 → wuttaweb-0.20.0}/src/wuttaweb/subscribers.py +0 -0
  96. {wuttaweb-0.19.2 → wuttaweb-0.20.0}/src/wuttaweb/templates/appinfo/configure.mako +0 -0
  97. {wuttaweb-0.19.2 → wuttaweb-0.20.0}/src/wuttaweb/templates/appinfo/index.mako +0 -0
  98. {wuttaweb-0.19.2 → wuttaweb-0.20.0}/src/wuttaweb/templates/auth/change_password.mako +0 -0
  99. {wuttaweb-0.19.2 → wuttaweb-0.20.0}/src/wuttaweb/templates/auth/login.mako +0 -0
  100. {wuttaweb-0.19.2 → wuttaweb-0.20.0}/src/wuttaweb/templates/base.mako +0 -0
  101. {wuttaweb-0.19.2 → wuttaweb-0.20.0}/src/wuttaweb/templates/base_meta.mako +0 -0
  102. {wuttaweb-0.19.2 → wuttaweb-0.20.0}/src/wuttaweb/templates/batch/view.mako +0 -0
  103. {wuttaweb-0.19.2 → wuttaweb-0.20.0}/src/wuttaweb/templates/deform/checkbox.pt +0 -0
  104. {wuttaweb-0.19.2 → wuttaweb-0.20.0}/src/wuttaweb/templates/deform/checkbox_choice.pt +0 -0
  105. {wuttaweb-0.19.2 → wuttaweb-0.20.0}/src/wuttaweb/templates/deform/checked_password.pt +0 -0
  106. {wuttaweb-0.19.2 → wuttaweb-0.20.0}/src/wuttaweb/templates/deform/dateinput.pt +0 -0
  107. {wuttaweb-0.19.2 → wuttaweb-0.20.0}/src/wuttaweb/templates/deform/datetimeinput.pt +0 -0
  108. {wuttaweb-0.19.2 → wuttaweb-0.20.0}/src/wuttaweb/templates/deform/moneyinput.pt +0 -0
  109. {wuttaweb-0.19.2 → wuttaweb-0.20.0}/src/wuttaweb/templates/deform/password.pt +0 -0
  110. {wuttaweb-0.19.2 → wuttaweb-0.20.0}/src/wuttaweb/templates/deform/permissions.pt +0 -0
  111. {wuttaweb-0.19.2 → wuttaweb-0.20.0}/src/wuttaweb/templates/deform/readonly/checkbox.pt +0 -0
  112. {wuttaweb-0.19.2 → wuttaweb-0.20.0}/src/wuttaweb/templates/deform/readonly/email_recips.pt +0 -0
  113. {wuttaweb-0.19.2 → wuttaweb-0.20.0}/src/wuttaweb/templates/deform/readonly/filedownload.pt +0 -0
  114. {wuttaweb-0.19.2 → wuttaweb-0.20.0}/src/wuttaweb/templates/deform/readonly/notes.pt +0 -0
  115. {wuttaweb-0.19.2 → wuttaweb-0.20.0}/src/wuttaweb/templates/deform/readonly/objectref.pt +0 -0
  116. {wuttaweb-0.19.2 → wuttaweb-0.20.0}/src/wuttaweb/templates/deform/readonly/permissions.pt +0 -0
  117. {wuttaweb-0.19.2 → wuttaweb-0.20.0}/src/wuttaweb/templates/deform/readonly/rolerefs.pt +0 -0
  118. {wuttaweb-0.19.2 → wuttaweb-0.20.0}/src/wuttaweb/templates/deform/select.pt +0 -0
  119. {wuttaweb-0.19.2 → wuttaweb-0.20.0}/src/wuttaweb/templates/deform/textarea.pt +0 -0
  120. {wuttaweb-0.19.2 → wuttaweb-0.20.0}/src/wuttaweb/templates/deform/textinput.pt +0 -0
  121. {wuttaweb-0.19.2 → wuttaweb-0.20.0}/src/wuttaweb/templates/email/settings/view.mako +0 -0
  122. {wuttaweb-0.19.2 → wuttaweb-0.20.0}/src/wuttaweb/templates/forbidden.mako +0 -0
  123. {wuttaweb-0.19.2 → wuttaweb-0.20.0}/src/wuttaweb/templates/form.mako +0 -0
  124. {wuttaweb-0.19.2 → wuttaweb-0.20.0}/src/wuttaweb/templates/grids/table_element.mako +0 -0
  125. {wuttaweb-0.19.2 → wuttaweb-0.20.0}/src/wuttaweb/templates/home.mako +0 -0
  126. {wuttaweb-0.19.2 → wuttaweb-0.20.0}/src/wuttaweb/templates/master/configure.mako +0 -0
  127. {wuttaweb-0.19.2 → wuttaweb-0.20.0}/src/wuttaweb/templates/master/create.mako +0 -0
  128. {wuttaweb-0.19.2 → wuttaweb-0.20.0}/src/wuttaweb/templates/master/delete.mako +0 -0
  129. {wuttaweb-0.19.2 → wuttaweb-0.20.0}/src/wuttaweb/templates/master/edit.mako +0 -0
  130. {wuttaweb-0.19.2 → wuttaweb-0.20.0}/src/wuttaweb/templates/master/form.mako +0 -0
  131. {wuttaweb-0.19.2 → wuttaweb-0.20.0}/src/wuttaweb/templates/master/index.mako +0 -0
  132. {wuttaweb-0.19.2 → wuttaweb-0.20.0}/src/wuttaweb/templates/master/view.mako +0 -0
  133. {wuttaweb-0.19.2 → wuttaweb-0.20.0}/src/wuttaweb/templates/notfound.mako +0 -0
  134. {wuttaweb-0.19.2 → wuttaweb-0.20.0}/src/wuttaweb/templates/page.mako +0 -0
  135. {wuttaweb-0.19.2 → wuttaweb-0.20.0}/src/wuttaweb/templates/people/view_profile.mako +0 -0
  136. {wuttaweb-0.19.2 → wuttaweb-0.20.0}/src/wuttaweb/templates/progress.mako +0 -0
  137. {wuttaweb-0.19.2 → wuttaweb-0.20.0}/src/wuttaweb/templates/setup.mako +0 -0
  138. {wuttaweb-0.19.2 → wuttaweb-0.20.0}/src/wuttaweb/templates/upgrade.mako +0 -0
  139. {wuttaweb-0.19.2 → wuttaweb-0.20.0}/src/wuttaweb/templates/upgrades/configure.mako +0 -0
  140. {wuttaweb-0.19.2 → wuttaweb-0.20.0}/src/wuttaweb/templates/upgrades/view.mako +0 -0
  141. {wuttaweb-0.19.2 → wuttaweb-0.20.0}/src/wuttaweb/testing.py +0 -0
  142. {wuttaweb-0.19.2 → wuttaweb-0.20.0}/src/wuttaweb/util.py +0 -0
  143. {wuttaweb-0.19.2 → wuttaweb-0.20.0}/src/wuttaweb/views/__init__.py +0 -0
  144. {wuttaweb-0.19.2 → wuttaweb-0.20.0}/src/wuttaweb/views/auth.py +0 -0
  145. {wuttaweb-0.19.2 → wuttaweb-0.20.0}/src/wuttaweb/views/base.py +0 -0
  146. {wuttaweb-0.19.2 → wuttaweb-0.20.0}/src/wuttaweb/views/batch.py +0 -0
  147. {wuttaweb-0.19.2 → wuttaweb-0.20.0}/src/wuttaweb/views/common.py +0 -0
  148. {wuttaweb-0.19.2 → wuttaweb-0.20.0}/src/wuttaweb/views/email.py +0 -0
  149. {wuttaweb-0.19.2 → wuttaweb-0.20.0}/src/wuttaweb/views/essential.py +0 -0
  150. {wuttaweb-0.19.2 → wuttaweb-0.20.0}/src/wuttaweb/views/people.py +0 -0
  151. {wuttaweb-0.19.2 → wuttaweb-0.20.0}/src/wuttaweb/views/progress.py +0 -0
  152. {wuttaweb-0.19.2 → wuttaweb-0.20.0}/src/wuttaweb/views/roles.py +0 -0
  153. {wuttaweb-0.19.2 → wuttaweb-0.20.0}/src/wuttaweb/views/settings.py +0 -0
  154. {wuttaweb-0.19.2 → wuttaweb-0.20.0}/src/wuttaweb/views/upgrades.py +0 -0
  155. {wuttaweb-0.19.2 → wuttaweb-0.20.0}/src/wuttaweb/views/users.py +0 -0
  156. {wuttaweb-0.19.2 → wuttaweb-0.20.0}/tasks.py +0 -0
  157. {wuttaweb-0.19.2 → wuttaweb-0.20.0}/tests/__init__.py +0 -0
  158. {wuttaweb-0.19.2 → wuttaweb-0.20.0}/tests/cli/__init__.py +0 -0
  159. {wuttaweb-0.19.2 → wuttaweb-0.20.0}/tests/cli/test_webapp.py +0 -0
  160. {wuttaweb-0.19.2 → wuttaweb-0.20.0}/tests/db/__init__.py +0 -0
  161. {wuttaweb-0.19.2 → wuttaweb-0.20.0}/tests/db/test_continuum.py +0 -0
  162. {wuttaweb-0.19.2 → wuttaweb-0.20.0}/tests/forms/test_base.py +0 -0
  163. {wuttaweb-0.19.2 → wuttaweb-0.20.0}/tests/forms/test_widgets.py +0 -0
  164. {wuttaweb-0.19.2 → wuttaweb-0.20.0}/tests/grids/__init__.py +0 -0
  165. {wuttaweb-0.19.2 → wuttaweb-0.20.0}/tests/grids/test_base.py +0 -0
  166. {wuttaweb-0.19.2 → wuttaweb-0.20.0}/tests/grids/test_filters.py +0 -0
  167. {wuttaweb-0.19.2 → wuttaweb-0.20.0}/tests/libcache/bb_fontawesome_svg_core.js +0 -0
  168. {wuttaweb-0.19.2 → wuttaweb-0.20.0}/tests/libcache/bb_free_solid_svg_icons.js +0 -0
  169. {wuttaweb-0.19.2 → wuttaweb-0.20.0}/tests/libcache/bb_oruga.js +0 -0
  170. {wuttaweb-0.19.2 → wuttaweb-0.20.0}/tests/libcache/bb_oruga_bulma.css +0 -0
  171. {wuttaweb-0.19.2 → wuttaweb-0.20.0}/tests/libcache/bb_oruga_bulma.js +0 -0
  172. {wuttaweb-0.19.2 → wuttaweb-0.20.0}/tests/libcache/bb_vue.js +0 -0
  173. {wuttaweb-0.19.2 → wuttaweb-0.20.0}/tests/libcache/bb_vue_fontawesome.js +0 -0
  174. {wuttaweb-0.19.2 → wuttaweb-0.20.0}/tests/libcache/buefy.css +0 -0
  175. {wuttaweb-0.19.2 → wuttaweb-0.20.0}/tests/libcache/buefy.js +0 -0
  176. {wuttaweb-0.19.2 → wuttaweb-0.20.0}/tests/libcache/fontawesome.js +0 -0
  177. {wuttaweb-0.19.2 → wuttaweb-0.20.0}/tests/libcache/vue.js +0 -0
  178. {wuttaweb-0.19.2 → wuttaweb-0.20.0}/tests/libcache/vue_resource.js +0 -0
  179. {wuttaweb-0.19.2 → wuttaweb-0.20.0}/tests/test_app.py +0 -0
  180. {wuttaweb-0.19.2 → wuttaweb-0.20.0}/tests/test_auth.py +0 -0
  181. {wuttaweb-0.19.2 → wuttaweb-0.20.0}/tests/test_emails.py +0 -0
  182. {wuttaweb-0.19.2 → wuttaweb-0.20.0}/tests/test_handler.py +0 -0
  183. {wuttaweb-0.19.2 → wuttaweb-0.20.0}/tests/test_helpers.py +0 -0
  184. {wuttaweb-0.19.2 → wuttaweb-0.20.0}/tests/test_menus.py +0 -0
  185. {wuttaweb-0.19.2 → wuttaweb-0.20.0}/tests/test_progress.py +0 -0
  186. {wuttaweb-0.19.2 → wuttaweb-0.20.0}/tests/test_static.py +0 -0
  187. {wuttaweb-0.19.2 → wuttaweb-0.20.0}/tests/test_subscribers.py +0 -0
  188. {wuttaweb-0.19.2 → wuttaweb-0.20.0}/tests/test_util.py +0 -0
  189. {wuttaweb-0.19.2 → wuttaweb-0.20.0}/tests/util.py +0 -0
  190. {wuttaweb-0.19.2 → wuttaweb-0.20.0}/tests/views/__init__.py +0 -0
  191. {wuttaweb-0.19.2 → wuttaweb-0.20.0}/tests/views/test___init__.py +0 -0
  192. {wuttaweb-0.19.2 → wuttaweb-0.20.0}/tests/views/test_auth.py +0 -0
  193. {wuttaweb-0.19.2 → wuttaweb-0.20.0}/tests/views/test_base.py +0 -0
  194. {wuttaweb-0.19.2 → wuttaweb-0.20.0}/tests/views/test_batch.py +0 -0
  195. {wuttaweb-0.19.2 → wuttaweb-0.20.0}/tests/views/test_common.py +0 -0
  196. {wuttaweb-0.19.2 → wuttaweb-0.20.0}/tests/views/test_email.py +0 -0
  197. {wuttaweb-0.19.2 → wuttaweb-0.20.0}/tests/views/test_essential.py +0 -0
  198. {wuttaweb-0.19.2 → wuttaweb-0.20.0}/tests/views/test_master.py +0 -0
  199. {wuttaweb-0.19.2 → wuttaweb-0.20.0}/tests/views/test_people.py +0 -0
  200. {wuttaweb-0.19.2 → wuttaweb-0.20.0}/tests/views/test_progress.py +0 -0
  201. {wuttaweb-0.19.2 → wuttaweb-0.20.0}/tests/views/test_roles.py +0 -0
  202. {wuttaweb-0.19.2 → wuttaweb-0.20.0}/tests/views/test_settings.py +0 -0
  203. {wuttaweb-0.19.2 → wuttaweb-0.20.0}/tests/views/test_upgrades.py +0 -0
  204. {wuttaweb-0.19.2 → wuttaweb-0.20.0}/tests/views/test_users.py +0 -0
  205. {wuttaweb-0.19.2 → wuttaweb-0.20.0}/tox.ini +0 -0
@@ -5,6 +5,26 @@ 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.20.0 (2025-01-11)
9
+
10
+ ### Feat
11
+
12
+ - add basic views for Reports
13
+
14
+ ### Fix
15
+
16
+ - add `action_method` and `reset_url` params for Form class
17
+ - add placeholder when grid has no filters
18
+ - add `get_page_templates()` method for master view
19
+
20
+ ## v0.19.3 (2025-01-09)
21
+
22
+ ### Fix
23
+
24
+ - use `request.url` instead of `current_route_url()`
25
+ - add basic `<wutta-autocomplete>` component
26
+ - add `WuttaDictEnum` form schema type
27
+
8
28
  ## v0.19.2 (2025-01-07)
9
29
 
10
30
  ### Fix
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: WuttaWeb
3
- Version: 0.19.2
3
+ Version: 0.20.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
@@ -39,7 +39,7 @@ Requires-Dist: pyramid-tm
39
39
  Requires-Dist: pyramid>=2
40
40
  Requires-Dist: waitress
41
41
  Requires-Dist: webhelpers2
42
- Requires-Dist: wuttjamaican[db]>=0.19.2
42
+ Requires-Dist: wuttjamaican[db]>=0.20.0
43
43
  Requires-Dist: zope-sqlalchemy>=1.5
44
44
  Provides-Extra: continuum
45
45
  Requires-Dist: wutta-continuum; extra == 'continuum'
@@ -0,0 +1,6 @@
1
+
2
+ ``wuttaweb.views.reports``
3
+ ==========================
4
+
5
+ .. automodule:: wuttaweb.views.reports
6
+ :members:
@@ -60,6 +60,7 @@ the narrative docs are pretty scant. That will eventually change.
60
60
  api/wuttaweb.views.master
61
61
  api/wuttaweb.views.people
62
62
  api/wuttaweb.views.progress
63
+ api/wuttaweb.views.reports
63
64
  api/wuttaweb.views.roles
64
65
  api/wuttaweb.views.settings
65
66
  api/wuttaweb.views.upgrades
@@ -6,7 +6,7 @@ build-backend = "hatchling.build"
6
6
 
7
7
  [project]
8
8
  name = "WuttaWeb"
9
- version = "0.19.2"
9
+ version = "0.20.0"
10
10
  description = "Web App for Wutta Framework"
11
11
  readme = "README.md"
12
12
  authors = [{name = "Lance Edgar", email = "lance@wuttaproject.org"}]
@@ -44,7 +44,7 @@ dependencies = [
44
44
  "pyramid_tm",
45
45
  "waitress",
46
46
  "WebHelpers2",
47
- "WuttJamaican[db]>=0.19.2",
47
+ "WuttJamaican[db]>=0.20.0",
48
48
  "zope.sqlalchemy>=1.5",
49
49
  ]
50
50
 
@@ -161,10 +161,23 @@ class Form:
161
161
 
162
162
  See also :meth:`set_required()` and :meth:`is_required()`.
163
163
 
164
+ .. attribute:: action_method
165
+
166
+ HTTP method to use when submitting form; ``'post'`` is default.
167
+
164
168
  .. attribute:: action_url
165
169
 
166
170
  String URL to which the form should be submitted, if applicable.
167
171
 
172
+ .. attribute:: reset_url
173
+
174
+ String URL to which the reset button should "always" redirect,
175
+ if applicable.
176
+
177
+ This is null by default, in which case it will use standard
178
+ browser behavior for the form reset button (if shown). See
179
+ also :attr:`show_button_reset`.
180
+
168
181
  .. attribute:: cancel_url
169
182
 
170
183
  String URL to which the Cancel button should "always" redirect,
@@ -227,6 +240,9 @@ class Form:
227
240
  Flag indicating whether a Reset button should be shown.
228
241
  Default is ``False``.
229
242
 
243
+ Unless there is a :attr:`reset_url`, the reset button will use
244
+ standard behavior per the browser.
245
+
230
246
  .. attribute:: show_button_cancel
231
247
 
232
248
  Flag indicating whether a Cancel button should be shown.
@@ -266,7 +282,9 @@ class Form:
266
282
  readonly_fields=[],
267
283
  required_fields={},
268
284
  labels={},
285
+ action_method='post',
269
286
  action_url=None,
287
+ reset_url=None,
270
288
  cancel_url=None,
271
289
  cancel_url_fallback=None,
272
290
  vue_tagname='wutta-form',
@@ -290,9 +308,11 @@ class Form:
290
308
  self.readonly_fields = set(readonly_fields or [])
291
309
  self.required_fields = required_fields or {}
292
310
  self.labels = labels or {}
311
+ self.action_method = action_method
293
312
  self.action_url = action_url
294
313
  self.cancel_url = cancel_url
295
314
  self.cancel_url_fallback = cancel_url_fallback
315
+ self.reset_url = reset_url
296
316
  self.vue_tagname = vue_tagname
297
317
  self.align_buttons_right = align_buttons_right
298
318
  self.auto_disable_submit = auto_disable_submit
@@ -940,10 +960,15 @@ class Form:
940
960
  """
941
961
  context['form'] = self
942
962
  context['dform'] = self.get_deform()
943
- context.setdefault('form_attrs', {})
944
963
  context.setdefault('request', self.request)
945
964
  context['model_data'] = self.get_vue_model_data()
946
965
 
966
+ # set form method, enctype
967
+ context.setdefault('form_attrs', {})
968
+ context['form_attrs'].setdefault('method', self.action_method)
969
+ if self.action_method == 'post':
970
+ context['form_attrs'].setdefault('enctype', 'multipart/form-data')
971
+
947
972
  # auto disable button on submit
948
973
  if self.auto_disable_submit:
949
974
  context['form_attrs']['@submit'] = 'formSubmitting = true'
@@ -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.
@@ -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,8 +1,10 @@
1
1
  ## -*- coding: utf-8; -*-
2
2
 
3
3
  <script type="text/x-template" id="${form.vue_tagname}-template">
4
- ${h.form(form.action_url, method='post', enctype='multipart/form-data', **form_attrs)}
5
- ${h.csrf_token(request)}
4
+ ${h.form(form.action_url, **form_attrs)}
5
+ % if form.action_method == 'post':
6
+ ${h.csrf_token(request)}
7
+ % endif
6
8
 
7
9
  % if form.has_global_errors():
8
10
  % for msg in form.get_global_errors():
@@ -33,7 +35,13 @@
33
35
  % endif
34
36
 
35
37
  % if form.show_button_reset:
36
- <b-button native-type="reset">
38
+ <b-button
39
+ % if form.reset_url:
40
+ tag="a" href="${form.reset_url}"
41
+ % else:
42
+ native-type="reset"
43
+ % endif
44
+ >
37
45
  Reset
38
46
  </b-button>
39
47
  % endif
@@ -89,6 +89,9 @@
89
89
 
90
90
  </div>
91
91
  </form>
92
+
93
+ % else:
94
+ <div></div>
92
95
  % endif
93
96
 
94
97
  <div style="display: flex; flex-direction: column; justify-content: space-between;">
@@ -0,0 +1,61 @@
1
+ ## -*- coding: utf-8; mode: html; -*-
2
+ <%inherit file="/master/view.mako" />
3
+
4
+ <%def name="page_layout()">
5
+ ${parent.page_layout()}
6
+ % if report_data is not Undefined:
7
+ <br />
8
+ <a name="report-output"></a>
9
+ <div style="display: flex; justify-content: space-between;">
10
+ <div class="report-header">
11
+ ${self.report_output_header()}
12
+ </div>
13
+ <div class="report-tools">
14
+ ${self.report_tools()}
15
+ </div>
16
+ </div>
17
+ ${self.report_output_body()}
18
+ % endif
19
+ </%def>
20
+
21
+ <%def name="report_output_header()">
22
+ <h4 class="is-size-4"><a href="#report-output">{{ reportData.output_title }}</a></h4>
23
+ </%def>
24
+
25
+ <%def name="report_tools()"></%def>
26
+
27
+ <%def name="report_output_body()">
28
+ ${self.report_output_table()}
29
+ </%def>
30
+
31
+ <%def name="report_output_table()">
32
+ <b-table :data="reportData.data"
33
+ narrowed
34
+ hoverable>
35
+ % for column in report_columns:
36
+ <b-table-column field="${column['name']}"
37
+ label="${column['label']}"
38
+ % if column.get('numeric'):
39
+ numeric
40
+ % endif
41
+ v-slot="props">
42
+ <span v-html="props.row.${column['name']}"></span>
43
+ </b-table-column>
44
+ % endfor
45
+ </b-table>
46
+ </%def>
47
+
48
+ <%def name="modify_vue_vars()">
49
+ ${parent.modify_vue_vars()}
50
+ % if report_data is not Undefined:
51
+ <script>
52
+
53
+ ThisPageData.reportData = ${json.dumps(report_data)|n}
54
+
55
+ WholePageData.mountedHooks.push(function() {
56
+ location.href = '#report-output'
57
+ })
58
+
59
+ </script>
60
+ % endif
61
+ </%def>
@@ -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()
@@ -1777,14 +1777,14 @@ class MasterView(View):
1777
1777
  context = self.get_template_context(context)
1778
1778
 
1779
1779
  # first try the template path most specific to this view
1780
- template_prefix = self.get_template_prefix()
1781
- mako_path = f'{template_prefix}/{template}.mako'
1780
+ page_templates = self.get_page_templates(template)
1781
+ mako_path = page_templates[0]
1782
1782
  try:
1783
1783
  return render_to_response(mako_path, context, request=self.request)
1784
1784
  except IOError:
1785
1785
 
1786
1786
  # failing that, try one or more fallback templates
1787
- for fallback in self.get_fallback_templates(template):
1787
+ for fallback in page_templates[1:]:
1788
1788
  try:
1789
1789
  return render_to_response(fallback, context, request=self.request)
1790
1790
  except IOError:
@@ -1815,21 +1815,51 @@ class MasterView(View):
1815
1815
  """
1816
1816
  return context
1817
1817
 
1818
+ def get_page_templates(self, template):
1819
+ """
1820
+ Returns a list of all templates which can be attempted, to
1821
+ render the current page. This is called by
1822
+ :meth:`render_to_response()`.
1823
+
1824
+ The list should be in order of preference, e.g. the first
1825
+ entry will be the most "specific" template, with subsequent
1826
+ entries becoming more generic.
1827
+
1828
+ In practice this method defines the first entry but calls
1829
+ :meth:`get_fallback_templates()` for the rest.
1830
+
1831
+ :param template: Base name for a template (without prefix), e.g.
1832
+ ``'view'``.
1833
+
1834
+ :returns: List of template paths to be tried, based on the
1835
+ specified template. For instance if ``template`` is
1836
+ ``'view'`` this will (by default) return::
1837
+
1838
+ [
1839
+ '/widgets/view.mako',
1840
+ '/master/view.mako',
1841
+ ]
1842
+
1843
+ """
1844
+ template_prefix = self.get_template_prefix()
1845
+ page_templates = [f'{template_prefix}/{template}.mako']
1846
+ page_templates.extend(self.get_fallback_templates(template))
1847
+ return page_templates
1848
+
1818
1849
  def get_fallback_templates(self, template):
1819
1850
  """
1820
1851
  Returns a list of "fallback" template paths which may be
1821
- attempted for rendering a view. This is used within
1822
- :meth:`render_to_response()` if the "first guess" template
1823
- file was not found.
1852
+ attempted for rendering the current page. See also
1853
+ :meth:`get_page_templates()`.
1824
1854
 
1825
1855
  :param template: Base name for a template (without prefix), e.g.
1826
- ``'custom'``.
1856
+ ``'view'``.
1827
1857
 
1828
- :returns: List of full template paths to be tried, based on
1829
- the specified template. For instance if ``template`` is
1830
- ``'custom'`` this will (by default) return::
1858
+ :returns: List of template paths to be tried, based on the
1859
+ specified template. For instance if ``template`` is
1860
+ ``'view'`` this will (by default) return::
1831
1861
 
1832
- ['/master/custom.mako']
1862
+ ['/master/view.mako']
1833
1863
  """
1834
1864
  return [f'/master/{template}.mako']
1835
1865