WuttaWeb 0.19.3__tar.gz → 0.20.1__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 (206) hide show
  1. {wuttaweb-0.19.3 → wuttaweb-0.20.1}/CHANGELOG.md +21 -0
  2. {wuttaweb-0.19.3 → wuttaweb-0.20.1}/PKG-INFO +2 -2
  3. wuttaweb-0.20.1/docs/api/wuttaweb.views.reports.rst +6 -0
  4. {wuttaweb-0.19.3 → wuttaweb-0.20.1}/docs/index.rst +1 -0
  5. {wuttaweb-0.19.3 → wuttaweb-0.20.1}/pyproject.toml +5 -2
  6. {wuttaweb-0.19.3 → wuttaweb-0.20.1}/src/wuttaweb/forms/base.py +26 -1
  7. {wuttaweb-0.19.3 → wuttaweb-0.20.1}/src/wuttaweb/grids/filters.py +29 -0
  8. {wuttaweb-0.19.3 → wuttaweb-0.20.1}/src/wuttaweb/handler.py +75 -7
  9. {wuttaweb-0.19.3 → wuttaweb-0.20.1}/src/wuttaweb/templates/appinfo/configure.mako +17 -0
  10. {wuttaweb-0.19.3 → wuttaweb-0.20.1}/src/wuttaweb/templates/forms/vue_template.mako +11 -3
  11. {wuttaweb-0.19.3 → wuttaweb-0.20.1}/src/wuttaweb/templates/grids/vue_template.mako +3 -0
  12. wuttaweb-0.20.1/src/wuttaweb/templates/reports/view.mako +61 -0
  13. {wuttaweb-0.19.3 → wuttaweb-0.20.1}/src/wuttaweb/views/master.py +51 -14
  14. wuttaweb-0.20.1/src/wuttaweb/views/reports.py +266 -0
  15. {wuttaweb-0.19.3 → wuttaweb-0.20.1}/src/wuttaweb/views/settings.py +12 -4
  16. {wuttaweb-0.19.3 → wuttaweb-0.20.1}/tests/grids/test_filters.py +24 -0
  17. wuttaweb-0.20.1/tests/test_handler.py +149 -0
  18. {wuttaweb-0.19.3 → wuttaweb-0.20.1}/tests/views/test_master.py +42 -34
  19. wuttaweb-0.20.1/tests/views/test_reports.py +236 -0
  20. wuttaweb-0.19.3/tests/test_handler.py +0 -76
  21. {wuttaweb-0.19.3 → wuttaweb-0.20.1}/.gitignore +0 -0
  22. {wuttaweb-0.19.3 → wuttaweb-0.20.1}/COPYING.txt +0 -0
  23. {wuttaweb-0.19.3 → wuttaweb-0.20.1}/README.md +0 -0
  24. {wuttaweb-0.19.3 → wuttaweb-0.20.1}/docs/Makefile +0 -0
  25. {wuttaweb-0.19.3 → wuttaweb-0.20.1}/docs/_static/.keepme +0 -0
  26. {wuttaweb-0.19.3 → wuttaweb-0.20.1}/docs/api/wuttaweb.app.rst +0 -0
  27. {wuttaweb-0.19.3 → wuttaweb-0.20.1}/docs/api/wuttaweb.auth.rst +0 -0
  28. {wuttaweb-0.19.3 → wuttaweb-0.20.1}/docs/api/wuttaweb.cli.rst +0 -0
  29. {wuttaweb-0.19.3 → wuttaweb-0.20.1}/docs/api/wuttaweb.cli.webapp.rst +0 -0
  30. {wuttaweb-0.19.3 → wuttaweb-0.20.1}/docs/api/wuttaweb.conf.rst +0 -0
  31. {wuttaweb-0.19.3 → wuttaweb-0.20.1}/docs/api/wuttaweb.db.continuum.rst +0 -0
  32. {wuttaweb-0.19.3 → wuttaweb-0.20.1}/docs/api/wuttaweb.db.rst +0 -0
  33. {wuttaweb-0.19.3 → wuttaweb-0.20.1}/docs/api/wuttaweb.db.sess.rst +0 -0
  34. {wuttaweb-0.19.3 → wuttaweb-0.20.1}/docs/api/wuttaweb.emails.rst +0 -0
  35. {wuttaweb-0.19.3 → wuttaweb-0.20.1}/docs/api/wuttaweb.forms.base.rst +0 -0
  36. {wuttaweb-0.19.3 → wuttaweb-0.20.1}/docs/api/wuttaweb.forms.rst +0 -0
  37. {wuttaweb-0.19.3 → wuttaweb-0.20.1}/docs/api/wuttaweb.forms.schema.rst +0 -0
  38. {wuttaweb-0.19.3 → wuttaweb-0.20.1}/docs/api/wuttaweb.forms.widgets.rst +0 -0
  39. {wuttaweb-0.19.3 → wuttaweb-0.20.1}/docs/api/wuttaweb.grids.base.rst +0 -0
  40. {wuttaweb-0.19.3 → wuttaweb-0.20.1}/docs/api/wuttaweb.grids.filters.rst +0 -0
  41. {wuttaweb-0.19.3 → wuttaweb-0.20.1}/docs/api/wuttaweb.grids.rst +0 -0
  42. {wuttaweb-0.19.3 → wuttaweb-0.20.1}/docs/api/wuttaweb.handler.rst +0 -0
  43. {wuttaweb-0.19.3 → wuttaweb-0.20.1}/docs/api/wuttaweb.helpers.rst +0 -0
  44. {wuttaweb-0.19.3 → wuttaweb-0.20.1}/docs/api/wuttaweb.menus.rst +0 -0
  45. {wuttaweb-0.19.3 → wuttaweb-0.20.1}/docs/api/wuttaweb.progress.rst +0 -0
  46. {wuttaweb-0.19.3 → wuttaweb-0.20.1}/docs/api/wuttaweb.rst +0 -0
  47. {wuttaweb-0.19.3 → wuttaweb-0.20.1}/docs/api/wuttaweb.static.rst +0 -0
  48. {wuttaweb-0.19.3 → wuttaweb-0.20.1}/docs/api/wuttaweb.subscribers.rst +0 -0
  49. {wuttaweb-0.19.3 → wuttaweb-0.20.1}/docs/api/wuttaweb.util.rst +0 -0
  50. {wuttaweb-0.19.3 → wuttaweb-0.20.1}/docs/api/wuttaweb.views.auth.rst +0 -0
  51. {wuttaweb-0.19.3 → wuttaweb-0.20.1}/docs/api/wuttaweb.views.base.rst +0 -0
  52. {wuttaweb-0.19.3 → wuttaweb-0.20.1}/docs/api/wuttaweb.views.batch.rst +0 -0
  53. {wuttaweb-0.19.3 → wuttaweb-0.20.1}/docs/api/wuttaweb.views.common.rst +0 -0
  54. {wuttaweb-0.19.3 → wuttaweb-0.20.1}/docs/api/wuttaweb.views.email.rst +0 -0
  55. {wuttaweb-0.19.3 → wuttaweb-0.20.1}/docs/api/wuttaweb.views.essential.rst +0 -0
  56. {wuttaweb-0.19.3 → wuttaweb-0.20.1}/docs/api/wuttaweb.views.master.rst +0 -0
  57. {wuttaweb-0.19.3 → wuttaweb-0.20.1}/docs/api/wuttaweb.views.people.rst +0 -0
  58. {wuttaweb-0.19.3 → wuttaweb-0.20.1}/docs/api/wuttaweb.views.progress.rst +0 -0
  59. {wuttaweb-0.19.3 → wuttaweb-0.20.1}/docs/api/wuttaweb.views.roles.rst +0 -0
  60. {wuttaweb-0.19.3 → wuttaweb-0.20.1}/docs/api/wuttaweb.views.rst +0 -0
  61. {wuttaweb-0.19.3 → wuttaweb-0.20.1}/docs/api/wuttaweb.views.settings.rst +0 -0
  62. {wuttaweb-0.19.3 → wuttaweb-0.20.1}/docs/api/wuttaweb.views.upgrades.rst +0 -0
  63. {wuttaweb-0.19.3 → wuttaweb-0.20.1}/docs/api/wuttaweb.views.users.rst +0 -0
  64. {wuttaweb-0.19.3 → wuttaweb-0.20.1}/docs/conf.py +0 -0
  65. {wuttaweb-0.19.3 → wuttaweb-0.20.1}/docs/glossary.rst +0 -0
  66. {wuttaweb-0.19.3 → wuttaweb-0.20.1}/docs/make.bat +0 -0
  67. {wuttaweb-0.19.3 → wuttaweb-0.20.1}/docs/narr/cli/builtin.rst +0 -0
  68. {wuttaweb-0.19.3 → wuttaweb-0.20.1}/docs/narr/cli/index.rst +0 -0
  69. {wuttaweb-0.19.3 → wuttaweb-0.20.1}/docs/narr/templates/base.rst +0 -0
  70. {wuttaweb-0.19.3 → wuttaweb-0.20.1}/docs/narr/templates/index.rst +0 -0
  71. {wuttaweb-0.19.3 → wuttaweb-0.20.1}/docs/narr/templates/lookup.rst +0 -0
  72. {wuttaweb-0.19.3 → wuttaweb-0.20.1}/docs/narr/templates/overview.rst +0 -0
  73. {wuttaweb-0.19.3 → wuttaweb-0.20.1}/src/wuttaweb/__init__.py +0 -0
  74. {wuttaweb-0.19.3 → wuttaweb-0.20.1}/src/wuttaweb/_version.py +0 -0
  75. {wuttaweb-0.19.3 → wuttaweb-0.20.1}/src/wuttaweb/app.py +0 -0
  76. {wuttaweb-0.19.3 → wuttaweb-0.20.1}/src/wuttaweb/auth.py +0 -0
  77. {wuttaweb-0.19.3 → wuttaweb-0.20.1}/src/wuttaweb/cli/__init__.py +0 -0
  78. {wuttaweb-0.19.3 → wuttaweb-0.20.1}/src/wuttaweb/cli/webapp.py +0 -0
  79. {wuttaweb-0.19.3 → wuttaweb-0.20.1}/src/wuttaweb/conf.py +0 -0
  80. {wuttaweb-0.19.3 → wuttaweb-0.20.1}/src/wuttaweb/db/__init__.py +0 -0
  81. {wuttaweb-0.19.3 → wuttaweb-0.20.1}/src/wuttaweb/db/continuum.py +0 -0
  82. {wuttaweb-0.19.3 → wuttaweb-0.20.1}/src/wuttaweb/db/sess.py +0 -0
  83. {wuttaweb-0.19.3 → wuttaweb-0.20.1}/src/wuttaweb/email-templates/feedback.html.mako +0 -0
  84. {wuttaweb-0.19.3 → wuttaweb-0.20.1}/src/wuttaweb/email-templates/feedback.txt.mako +0 -0
  85. {wuttaweb-0.19.3 → wuttaweb-0.20.1}/src/wuttaweb/emails.py +0 -0
  86. {wuttaweb-0.19.3 → wuttaweb-0.20.1}/src/wuttaweb/forms/__init__.py +0 -0
  87. {wuttaweb-0.19.3 → wuttaweb-0.20.1}/src/wuttaweb/forms/schema.py +0 -0
  88. {wuttaweb-0.19.3 → wuttaweb-0.20.1}/src/wuttaweb/forms/widgets.py +0 -0
  89. {wuttaweb-0.19.3 → wuttaweb-0.20.1}/src/wuttaweb/grids/__init__.py +0 -0
  90. {wuttaweb-0.19.3 → wuttaweb-0.20.1}/src/wuttaweb/grids/base.py +0 -0
  91. {wuttaweb-0.19.3 → wuttaweb-0.20.1}/src/wuttaweb/helpers.py +0 -0
  92. {wuttaweb-0.19.3 → wuttaweb-0.20.1}/src/wuttaweb/menus.py +0 -0
  93. {wuttaweb-0.19.3 → wuttaweb-0.20.1}/src/wuttaweb/progress.py +0 -0
  94. {wuttaweb-0.19.3 → wuttaweb-0.20.1}/src/wuttaweb/static/__init__.py +0 -0
  95. {wuttaweb-0.19.3 → wuttaweb-0.20.1}/src/wuttaweb/static/img/favicon.ico +0 -0
  96. {wuttaweb-0.19.3 → wuttaweb-0.20.1}/src/wuttaweb/static/img/logo.png +0 -0
  97. {wuttaweb-0.19.3 → wuttaweb-0.20.1}/src/wuttaweb/static/img/testing.png +0 -0
  98. {wuttaweb-0.19.3 → wuttaweb-0.20.1}/src/wuttaweb/subscribers.py +0 -0
  99. {wuttaweb-0.19.3 → wuttaweb-0.20.1}/src/wuttaweb/templates/appinfo/index.mako +0 -0
  100. {wuttaweb-0.19.3 → wuttaweb-0.20.1}/src/wuttaweb/templates/auth/change_password.mako +0 -0
  101. {wuttaweb-0.19.3 → wuttaweb-0.20.1}/src/wuttaweb/templates/auth/login.mako +0 -0
  102. {wuttaweb-0.19.3 → wuttaweb-0.20.1}/src/wuttaweb/templates/base.mako +0 -0
  103. {wuttaweb-0.19.3 → wuttaweb-0.20.1}/src/wuttaweb/templates/base_meta.mako +0 -0
  104. {wuttaweb-0.19.3 → wuttaweb-0.20.1}/src/wuttaweb/templates/batch/view.mako +0 -0
  105. {wuttaweb-0.19.3 → wuttaweb-0.20.1}/src/wuttaweb/templates/configure.mako +0 -0
  106. {wuttaweb-0.19.3 → wuttaweb-0.20.1}/src/wuttaweb/templates/deform/checkbox.pt +0 -0
  107. {wuttaweb-0.19.3 → wuttaweb-0.20.1}/src/wuttaweb/templates/deform/checkbox_choice.pt +0 -0
  108. {wuttaweb-0.19.3 → wuttaweb-0.20.1}/src/wuttaweb/templates/deform/checked_password.pt +0 -0
  109. {wuttaweb-0.19.3 → wuttaweb-0.20.1}/src/wuttaweb/templates/deform/dateinput.pt +0 -0
  110. {wuttaweb-0.19.3 → wuttaweb-0.20.1}/src/wuttaweb/templates/deform/datetimeinput.pt +0 -0
  111. {wuttaweb-0.19.3 → wuttaweb-0.20.1}/src/wuttaweb/templates/deform/moneyinput.pt +0 -0
  112. {wuttaweb-0.19.3 → wuttaweb-0.20.1}/src/wuttaweb/templates/deform/password.pt +0 -0
  113. {wuttaweb-0.19.3 → wuttaweb-0.20.1}/src/wuttaweb/templates/deform/permissions.pt +0 -0
  114. {wuttaweb-0.19.3 → wuttaweb-0.20.1}/src/wuttaweb/templates/deform/readonly/checkbox.pt +0 -0
  115. {wuttaweb-0.19.3 → wuttaweb-0.20.1}/src/wuttaweb/templates/deform/readonly/email_recips.pt +0 -0
  116. {wuttaweb-0.19.3 → wuttaweb-0.20.1}/src/wuttaweb/templates/deform/readonly/filedownload.pt +0 -0
  117. {wuttaweb-0.19.3 → wuttaweb-0.20.1}/src/wuttaweb/templates/deform/readonly/notes.pt +0 -0
  118. {wuttaweb-0.19.3 → wuttaweb-0.20.1}/src/wuttaweb/templates/deform/readonly/objectref.pt +0 -0
  119. {wuttaweb-0.19.3 → wuttaweb-0.20.1}/src/wuttaweb/templates/deform/readonly/permissions.pt +0 -0
  120. {wuttaweb-0.19.3 → wuttaweb-0.20.1}/src/wuttaweb/templates/deform/readonly/rolerefs.pt +0 -0
  121. {wuttaweb-0.19.3 → wuttaweb-0.20.1}/src/wuttaweb/templates/deform/select.pt +0 -0
  122. {wuttaweb-0.19.3 → wuttaweb-0.20.1}/src/wuttaweb/templates/deform/textarea.pt +0 -0
  123. {wuttaweb-0.19.3 → wuttaweb-0.20.1}/src/wuttaweb/templates/deform/textinput.pt +0 -0
  124. {wuttaweb-0.19.3 → wuttaweb-0.20.1}/src/wuttaweb/templates/email/settings/view.mako +0 -0
  125. {wuttaweb-0.19.3 → wuttaweb-0.20.1}/src/wuttaweb/templates/forbidden.mako +0 -0
  126. {wuttaweb-0.19.3 → wuttaweb-0.20.1}/src/wuttaweb/templates/form.mako +0 -0
  127. {wuttaweb-0.19.3 → wuttaweb-0.20.1}/src/wuttaweb/templates/grids/table_element.mako +0 -0
  128. {wuttaweb-0.19.3 → wuttaweb-0.20.1}/src/wuttaweb/templates/home.mako +0 -0
  129. {wuttaweb-0.19.3 → wuttaweb-0.20.1}/src/wuttaweb/templates/master/configure.mako +0 -0
  130. {wuttaweb-0.19.3 → wuttaweb-0.20.1}/src/wuttaweb/templates/master/create.mako +0 -0
  131. {wuttaweb-0.19.3 → wuttaweb-0.20.1}/src/wuttaweb/templates/master/delete.mako +0 -0
  132. {wuttaweb-0.19.3 → wuttaweb-0.20.1}/src/wuttaweb/templates/master/edit.mako +0 -0
  133. {wuttaweb-0.19.3 → wuttaweb-0.20.1}/src/wuttaweb/templates/master/form.mako +0 -0
  134. {wuttaweb-0.19.3 → wuttaweb-0.20.1}/src/wuttaweb/templates/master/index.mako +0 -0
  135. {wuttaweb-0.19.3 → wuttaweb-0.20.1}/src/wuttaweb/templates/master/view.mako +0 -0
  136. {wuttaweb-0.19.3 → wuttaweb-0.20.1}/src/wuttaweb/templates/notfound.mako +0 -0
  137. {wuttaweb-0.19.3 → wuttaweb-0.20.1}/src/wuttaweb/templates/page.mako +0 -0
  138. {wuttaweb-0.19.3 → wuttaweb-0.20.1}/src/wuttaweb/templates/people/view_profile.mako +0 -0
  139. {wuttaweb-0.19.3 → wuttaweb-0.20.1}/src/wuttaweb/templates/progress.mako +0 -0
  140. {wuttaweb-0.19.3 → wuttaweb-0.20.1}/src/wuttaweb/templates/setup.mako +0 -0
  141. {wuttaweb-0.19.3 → wuttaweb-0.20.1}/src/wuttaweb/templates/upgrade.mako +0 -0
  142. {wuttaweb-0.19.3 → wuttaweb-0.20.1}/src/wuttaweb/templates/upgrades/configure.mako +0 -0
  143. {wuttaweb-0.19.3 → wuttaweb-0.20.1}/src/wuttaweb/templates/upgrades/view.mako +0 -0
  144. {wuttaweb-0.19.3 → wuttaweb-0.20.1}/src/wuttaweb/templates/wutta-components.mako +0 -0
  145. {wuttaweb-0.19.3 → wuttaweb-0.20.1}/src/wuttaweb/testing.py +0 -0
  146. {wuttaweb-0.19.3 → wuttaweb-0.20.1}/src/wuttaweb/util.py +0 -0
  147. {wuttaweb-0.19.3 → wuttaweb-0.20.1}/src/wuttaweb/views/__init__.py +0 -0
  148. {wuttaweb-0.19.3 → wuttaweb-0.20.1}/src/wuttaweb/views/auth.py +0 -0
  149. {wuttaweb-0.19.3 → wuttaweb-0.20.1}/src/wuttaweb/views/base.py +0 -0
  150. {wuttaweb-0.19.3 → wuttaweb-0.20.1}/src/wuttaweb/views/batch.py +0 -0
  151. {wuttaweb-0.19.3 → wuttaweb-0.20.1}/src/wuttaweb/views/common.py +0 -0
  152. {wuttaweb-0.19.3 → wuttaweb-0.20.1}/src/wuttaweb/views/email.py +0 -0
  153. {wuttaweb-0.19.3 → wuttaweb-0.20.1}/src/wuttaweb/views/essential.py +0 -0
  154. {wuttaweb-0.19.3 → wuttaweb-0.20.1}/src/wuttaweb/views/people.py +0 -0
  155. {wuttaweb-0.19.3 → wuttaweb-0.20.1}/src/wuttaweb/views/progress.py +0 -0
  156. {wuttaweb-0.19.3 → wuttaweb-0.20.1}/src/wuttaweb/views/roles.py +0 -0
  157. {wuttaweb-0.19.3 → wuttaweb-0.20.1}/src/wuttaweb/views/upgrades.py +0 -0
  158. {wuttaweb-0.19.3 → wuttaweb-0.20.1}/src/wuttaweb/views/users.py +0 -0
  159. {wuttaweb-0.19.3 → wuttaweb-0.20.1}/tasks.py +0 -0
  160. {wuttaweb-0.19.3 → wuttaweb-0.20.1}/tests/__init__.py +0 -0
  161. {wuttaweb-0.19.3 → wuttaweb-0.20.1}/tests/cli/__init__.py +0 -0
  162. {wuttaweb-0.19.3 → wuttaweb-0.20.1}/tests/cli/test_webapp.py +0 -0
  163. {wuttaweb-0.19.3 → wuttaweb-0.20.1}/tests/db/__init__.py +0 -0
  164. {wuttaweb-0.19.3 → wuttaweb-0.20.1}/tests/db/test_continuum.py +0 -0
  165. {wuttaweb-0.19.3 → wuttaweb-0.20.1}/tests/forms/test_base.py +0 -0
  166. {wuttaweb-0.19.3 → wuttaweb-0.20.1}/tests/forms/test_schema.py +0 -0
  167. {wuttaweb-0.19.3 → wuttaweb-0.20.1}/tests/forms/test_widgets.py +0 -0
  168. {wuttaweb-0.19.3 → wuttaweb-0.20.1}/tests/grids/__init__.py +0 -0
  169. {wuttaweb-0.19.3 → wuttaweb-0.20.1}/tests/grids/test_base.py +0 -0
  170. {wuttaweb-0.19.3 → wuttaweb-0.20.1}/tests/libcache/bb_fontawesome_svg_core.js +0 -0
  171. {wuttaweb-0.19.3 → wuttaweb-0.20.1}/tests/libcache/bb_free_solid_svg_icons.js +0 -0
  172. {wuttaweb-0.19.3 → wuttaweb-0.20.1}/tests/libcache/bb_oruga.js +0 -0
  173. {wuttaweb-0.19.3 → wuttaweb-0.20.1}/tests/libcache/bb_oruga_bulma.css +0 -0
  174. {wuttaweb-0.19.3 → wuttaweb-0.20.1}/tests/libcache/bb_oruga_bulma.js +0 -0
  175. {wuttaweb-0.19.3 → wuttaweb-0.20.1}/tests/libcache/bb_vue.js +0 -0
  176. {wuttaweb-0.19.3 → wuttaweb-0.20.1}/tests/libcache/bb_vue_fontawesome.js +0 -0
  177. {wuttaweb-0.19.3 → wuttaweb-0.20.1}/tests/libcache/buefy.css +0 -0
  178. {wuttaweb-0.19.3 → wuttaweb-0.20.1}/tests/libcache/buefy.js +0 -0
  179. {wuttaweb-0.19.3 → wuttaweb-0.20.1}/tests/libcache/fontawesome.js +0 -0
  180. {wuttaweb-0.19.3 → wuttaweb-0.20.1}/tests/libcache/vue.js +0 -0
  181. {wuttaweb-0.19.3 → wuttaweb-0.20.1}/tests/libcache/vue_resource.js +0 -0
  182. {wuttaweb-0.19.3 → wuttaweb-0.20.1}/tests/test_app.py +0 -0
  183. {wuttaweb-0.19.3 → wuttaweb-0.20.1}/tests/test_auth.py +0 -0
  184. {wuttaweb-0.19.3 → wuttaweb-0.20.1}/tests/test_emails.py +0 -0
  185. {wuttaweb-0.19.3 → wuttaweb-0.20.1}/tests/test_helpers.py +0 -0
  186. {wuttaweb-0.19.3 → wuttaweb-0.20.1}/tests/test_menus.py +0 -0
  187. {wuttaweb-0.19.3 → wuttaweb-0.20.1}/tests/test_progress.py +0 -0
  188. {wuttaweb-0.19.3 → wuttaweb-0.20.1}/tests/test_static.py +0 -0
  189. {wuttaweb-0.19.3 → wuttaweb-0.20.1}/tests/test_subscribers.py +0 -0
  190. {wuttaweb-0.19.3 → wuttaweb-0.20.1}/tests/test_util.py +0 -0
  191. {wuttaweb-0.19.3 → wuttaweb-0.20.1}/tests/util.py +0 -0
  192. {wuttaweb-0.19.3 → wuttaweb-0.20.1}/tests/views/__init__.py +0 -0
  193. {wuttaweb-0.19.3 → wuttaweb-0.20.1}/tests/views/test___init__.py +0 -0
  194. {wuttaweb-0.19.3 → wuttaweb-0.20.1}/tests/views/test_auth.py +0 -0
  195. {wuttaweb-0.19.3 → wuttaweb-0.20.1}/tests/views/test_base.py +0 -0
  196. {wuttaweb-0.19.3 → wuttaweb-0.20.1}/tests/views/test_batch.py +0 -0
  197. {wuttaweb-0.19.3 → wuttaweb-0.20.1}/tests/views/test_common.py +0 -0
  198. {wuttaweb-0.19.3 → wuttaweb-0.20.1}/tests/views/test_email.py +0 -0
  199. {wuttaweb-0.19.3 → wuttaweb-0.20.1}/tests/views/test_essential.py +0 -0
  200. {wuttaweb-0.19.3 → wuttaweb-0.20.1}/tests/views/test_people.py +0 -0
  201. {wuttaweb-0.19.3 → wuttaweb-0.20.1}/tests/views/test_progress.py +0 -0
  202. {wuttaweb-0.19.3 → wuttaweb-0.20.1}/tests/views/test_roles.py +0 -0
  203. {wuttaweb-0.19.3 → wuttaweb-0.20.1}/tests/views/test_settings.py +0 -0
  204. {wuttaweb-0.19.3 → wuttaweb-0.20.1}/tests/views/test_upgrades.py +0 -0
  205. {wuttaweb-0.19.3 → wuttaweb-0.20.1}/tests/views/test_users.py +0 -0
  206. {wuttaweb-0.19.3 → wuttaweb-0.20.1}/tox.ini +0 -0
@@ -5,6 +5,27 @@ 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.1 (2025-01-13)
9
+
10
+ ### Fix
11
+
12
+ - expose setting to choose menu handler, in appinfo/configure
13
+ - use prop key instead of column name, for master view model key
14
+ - add grid filters specific to numeric, integer types
15
+ - use default value for config settings
16
+
17
+ ## v0.20.0 (2025-01-11)
18
+
19
+ ### Feat
20
+
21
+ - add basic views for Reports
22
+
23
+ ### Fix
24
+
25
+ - add `action_method` and `reset_url` params for Form class
26
+ - add placeholder when grid has no filters
27
+ - add `get_page_templates()` method for master view
28
+
8
29
  ## v0.19.3 (2025-01-09)
9
30
 
10
31
  ### Fix
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: WuttaWeb
3
- Version: 0.19.3
3
+ Version: 0.20.1
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.3
42
+ Requires-Dist: wuttjamaican[db]>=0.20.1
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.3"
9
+ version = "0.20.1"
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.3",
47
+ "WuttJamaican[db]>=0.20.1",
48
48
  "zope.sqlalchemy>=1.5",
49
49
  ]
50
50
 
@@ -70,6 +70,9 @@ wuttaweb = "wuttaweb.conf:WuttaWebConfigExtension"
70
70
  [project.entry-points."wutta.typer_imports"]
71
71
  wuttaweb = "wuttaweb.cli"
72
72
 
73
+ [project.entry-points."wutta.web.menus"]
74
+ wuttaweb = "wuttaweb.menus:MenuHandler"
75
+
73
76
 
74
77
  [project.urls]
75
78
  Homepage = "https://wuttaproject.org/"
@@ -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'
@@ -465,6 +465,33 @@ class StringAlchemyFilter(AlchemyFilter):
465
465
  sa.and_(*criteria)))
466
466
 
467
467
 
468
+ class NumericAlchemyFilter(AlchemyFilter):
469
+ """
470
+ SQLAlchemy filter option for a numeric data column.
471
+
472
+ Subclass of :class:`AlchemyFilter`.
473
+ """
474
+ default_verbs = ['equal', 'not_equal',
475
+ 'greater_than', 'greater_equal',
476
+ 'less_than', 'less_equal']
477
+
478
+
479
+ class IntegerAlchemyFilter(NumericAlchemyFilter):
480
+ """
481
+ SQLAlchemy filter option for an integer data column.
482
+
483
+ Subclass of :class:`NumericAlchemyFilter`.
484
+ """
485
+
486
+ def coerce_value(self, value):
487
+ """ """
488
+ if value:
489
+ try:
490
+ return int(value)
491
+ except:
492
+ pass
493
+
494
+
468
495
  class BooleanAlchemyFilter(AlchemyFilter):
469
496
  """
470
497
  SQLAlchemy filter option for a boolean data column.
@@ -568,6 +595,8 @@ default_sqlalchemy_filters = {
568
595
  None: AlchemyFilter,
569
596
  sa.String: StringAlchemyFilter,
570
597
  sa.Text: StringAlchemyFilter,
598
+ sa.Numeric: NumericAlchemyFilter,
599
+ sa.Integer: IntegerAlchemyFilter,
571
600
  sa.Boolean: BooleanAlchemyFilter,
572
601
  sa.Date: DateAlchemyFilter,
573
602
  }
@@ -24,7 +24,10 @@
24
24
  Web Handler
25
25
  """
26
26
 
27
+ import warnings
28
+
27
29
  from wuttjamaican.app import GenericHandler
30
+ from wuttjamaican.util import load_entry_points
28
31
 
29
32
  from wuttaweb import static, forms, grids
30
33
 
@@ -106,22 +109,87 @@ class WebHandler(GenericHandler):
106
109
 
107
110
  def get_menu_handler(self, **kwargs):
108
111
  """
109
- Get the configured "menu" handler for the web app.
112
+ Get the configured :term:`menu handler` for the web app.
110
113
 
111
114
  Specify a custom handler in your config file like this:
112
115
 
113
116
  .. code-block:: ini
114
117
 
115
118
  [wutta.web]
116
- menus.handler_spec = poser.web.menus:PoserMenuHandler
119
+ menus.handler.spec = poser.web.menus:PoserMenuHandler
117
120
 
118
121
  :returns: Instance of :class:`~wuttaweb.menus.MenuHandler`.
119
122
  """
120
- if not hasattr(self, 'menu_handler'):
121
- spec = self.config.get(f'{self.appname}.web.menus.handler_spec',
122
- default='wuttaweb.menus:MenuHandler')
123
- self.menu_handler = self.app.load_object(spec)(self.config)
124
- return self.menu_handler
123
+ spec = self.config.get(f'{self.appname}.web.menus.handler.spec')
124
+ if not spec:
125
+ spec = self.config.get(f'{self.appname}.web.menus.handler_spec')
126
+ if spec:
127
+ warnings.warn(f"setting '{self.appname}.web.menus.handler_spec' is deprecated; "
128
+ f"please use '{self.appname}.web.menus.handler_spec' instead",
129
+ DeprecationWarning)
130
+ else:
131
+ spec = self.config.get(f'{self.appname}.web.menus.handler.default_spec',
132
+ default='wuttaweb.menus:MenuHandler')
133
+ factory = self.app.load_object(spec)
134
+ return factory(self.config)
135
+
136
+ def get_menu_handler_specs(self, default=None):
137
+ """
138
+ Get the :term:`spec` strings for all available :term:`menu
139
+ handlers <menu handler>`. See also
140
+ :meth:`get_menu_handler()`.
141
+
142
+ :param default: Default spec string(s) to include, even if not
143
+ registered. Can be a string or list of strings.
144
+
145
+ :returns: List of menu handler spec strings.
146
+
147
+ This will gather available spec strings from the following:
148
+
149
+ First, the ``default`` as provided by caller.
150
+
151
+ Second, the default spec from config, if set; for example:
152
+
153
+ .. code-block:: ini
154
+
155
+ [wutta.web]
156
+ menus.handler.default_spec = poser.web.menus:PoserMenuHandler
157
+
158
+ Third, each spec registered via entry points. For instance in
159
+ ``pyproject.toml``:
160
+
161
+ .. code-block:: toml
162
+
163
+ [project.entry-points."wutta.web.menus"]
164
+ poser = "poser.web.menus:PoserMenuHandler"
165
+
166
+ The final list will be "sorted" according to the above, with
167
+ the latter registered handlers being sorted alphabetically.
168
+ """
169
+ handlers = []
170
+
171
+ # defaults from caller
172
+ if isinstance(default, str):
173
+ handlers.append(default)
174
+ elif default:
175
+ handlers.extend(default)
176
+
177
+ # configured default, if applicable
178
+ default = self.config.get(f'{self.config.appname}.web.menus.handler.default_spec')
179
+ if default and default not in handlers:
180
+ handlers.append(default)
181
+
182
+ # registered via entry points
183
+ registered = []
184
+ for Handler in load_entry_points(f'{self.appname}.web.menus').values():
185
+ spec = Handler.get_spec()
186
+ if spec not in handlers:
187
+ registered.append(spec)
188
+ if registered:
189
+ registered.sort()
190
+ handlers.extend(registered)
191
+
192
+ return handlers
125
193
 
126
194
  def make_form(self, request, **kwargs):
127
195
  """
@@ -41,6 +41,21 @@
41
41
  </b-checkbox>
42
42
  </b-field>
43
43
 
44
+ <b-field label="Menu Handler">
45
+ <input type="hidden"
46
+ name="${app.appname}.web.menus.handler.spec"
47
+ :value="simpleSettings['${app.appname}.web.menus.handler.spec']" />
48
+ <b-select v-model="simpleSettings['${app.appname}.web.menus.handler.spec']"
49
+ @input="settingsNeedSaved = true">
50
+ <option :value="null">(use default)</option>
51
+ <option v-for="handler in menuHandlers"
52
+ :key="handler.spec"
53
+ :value="handler.spec">
54
+ {{ handler.spec }}
55
+ </option>
56
+ </b-select>
57
+ </b-field>
58
+
44
59
  </div>
45
60
 
46
61
  <h3 class="block is-size-3">User/Auth</h3>
@@ -241,6 +256,8 @@
241
256
  ${parent.modify_vue_vars()}
242
257
  <script>
243
258
 
259
+ ThisPageData.menuHandlers = ${json.dumps(menu_handlers)|n}
260
+
244
261
  ThisPageData.weblibs = ${json.dumps(weblibs or [])|n}
245
262
 
246
263
  ThisPageData.editWebLibraryShowDialog = false
@@ -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>
@@ -1224,7 +1224,7 @@ class MasterView(View):
1224
1224
  elif simple.get('type') is bool:
1225
1225
  value = self.config.get_bool(name, default=simple.get('default', False))
1226
1226
  else:
1227
- value = self.config.get(name)
1227
+ value = self.config.get(name, default=simple.get('default'))
1228
1228
 
1229
1229
  normalized[name] = value
1230
1230
 
@@ -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
 
@@ -2677,6 +2707,9 @@ class MasterView(View):
2677
2707
  represents a Wutta-based SQLAlchemy model, the return value
2678
2708
  for this method is: ``('uuid',)``
2679
2709
 
2710
+ Any class mapped via SQLAlchemy should be supported
2711
+ automatically, the keys are determined from class inspection.
2712
+
2680
2713
  But there is no "sane" default for other scenarios, in which
2681
2714
  case subclass should define :attr:`model_key`. If the model
2682
2715
  key cannot be determined, raises ``AttributeError``.
@@ -2691,8 +2724,12 @@ class MasterView(View):
2691
2724
 
2692
2725
  model_class = cls.get_model_class()
2693
2726
  if model_class:
2694
- mapper = sa.inspect(model_class)
2695
- return tuple([column.key for column in mapper.primary_key])
2727
+ # nb. we want the primary key but must avoid column names
2728
+ # in case mapped class uses different prop keys
2729
+ inspector = sa.inspect(model_class)
2730
+ keys = [col.name for col in inspector.primary_key]
2731
+ return tuple([prop.key for prop in inspector.column_attrs
2732
+ if [col.name for col in prop.columns] == keys])
2696
2733
 
2697
2734
  raise AttributeError(f"you must define model_key for view class: {cls}")
2698
2735