WuttaWeb 0.18.0__tar.gz → 0.19.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 (202) hide show
  1. {wuttaweb-0.18.0 → wuttaweb-0.19.1}/CHANGELOG.md +30 -0
  2. {wuttaweb-0.18.0 → wuttaweb-0.19.1}/PKG-INFO +4 -3
  3. {wuttaweb-0.18.0 → wuttaweb-0.19.1}/README.md +1 -1
  4. wuttaweb-0.19.1/docs/api/wuttaweb.emails.rst +6 -0
  5. wuttaweb-0.19.1/docs/api/wuttaweb.views.email.rst +6 -0
  6. {wuttaweb-0.18.0 → wuttaweb-0.19.1}/docs/index.rst +2 -0
  7. {wuttaweb-0.18.0 → wuttaweb-0.19.1}/pyproject.toml +3 -2
  8. {wuttaweb-0.18.0 → wuttaweb-0.19.1}/src/wuttaweb/app.py +5 -2
  9. wuttaweb-0.19.1/src/wuttaweb/emails.py +48 -0
  10. {wuttaweb-0.18.0 → wuttaweb-0.19.1}/src/wuttaweb/forms/schema.py +91 -20
  11. {wuttaweb-0.18.0 → wuttaweb-0.19.1}/src/wuttaweb/forms/widgets.py +112 -7
  12. {wuttaweb-0.18.0 → wuttaweb-0.19.1}/src/wuttaweb/grids/base.py +112 -27
  13. {wuttaweb-0.18.0 → wuttaweb-0.19.1}/src/wuttaweb/menus.py +6 -0
  14. {wuttaweb-0.18.0 → wuttaweb-0.19.1}/src/wuttaweb/progress.py +4 -1
  15. {wuttaweb-0.18.0 → wuttaweb-0.19.1}/src/wuttaweb/templates/base.mako +12 -8
  16. {wuttaweb-0.18.0 → wuttaweb-0.19.1}/src/wuttaweb/templates/batch/view.mako +1 -14
  17. wuttaweb-0.19.1/src/wuttaweb/templates/deform/readonly/email_recips.pt +5 -0
  18. wuttaweb-0.19.1/src/wuttaweb/templates/email/settings/view.mako +39 -0
  19. {wuttaweb-0.18.0 → wuttaweb-0.19.1}/src/wuttaweb/templates/master/view.mako +15 -0
  20. wuttaweb-0.18.0/tests/util.py → wuttaweb-0.19.1/src/wuttaweb/testing.py +26 -39
  21. {wuttaweb-0.18.0 → wuttaweb-0.19.1}/src/wuttaweb/util.py +33 -6
  22. {wuttaweb-0.18.0 → wuttaweb-0.19.1}/src/wuttaweb/views/batch.py +11 -1
  23. wuttaweb-0.19.1/src/wuttaweb/views/email.py +298 -0
  24. {wuttaweb-0.18.0 → wuttaweb-0.19.1}/src/wuttaweb/views/essential.py +2 -0
  25. {wuttaweb-0.18.0 → wuttaweb-0.19.1}/src/wuttaweb/views/master.py +84 -1
  26. {wuttaweb-0.18.0 → wuttaweb-0.19.1}/tests/db/test_continuum.py +1 -2
  27. {wuttaweb-0.18.0 → wuttaweb-0.19.1}/tests/forms/test_schema.py +171 -93
  28. {wuttaweb-0.18.0 → wuttaweb-0.19.1}/tests/forms/test_widgets.py +212 -94
  29. {wuttaweb-0.18.0 → wuttaweb-0.19.1}/tests/grids/test_base.py +96 -1
  30. {wuttaweb-0.18.0 → wuttaweb-0.19.1}/tests/grids/test_filters.py +1 -1
  31. {wuttaweb-0.18.0 → wuttaweb-0.19.1}/tests/test_auth.py +1 -1
  32. wuttaweb-0.19.1/tests/test_emails.py +23 -0
  33. {wuttaweb-0.18.0 → wuttaweb-0.19.1}/tests/test_handler.py +1 -1
  34. {wuttaweb-0.18.0 → wuttaweb-0.19.1}/tests/test_menus.py +1 -1
  35. {wuttaweb-0.18.0 → wuttaweb-0.19.1}/tests/test_progress.py +5 -2
  36. {wuttaweb-0.18.0 → wuttaweb-0.19.1}/tests/test_util.py +17 -6
  37. wuttaweb-0.19.1/tests/util.py +40 -0
  38. {wuttaweb-0.18.0 → wuttaweb-0.19.1}/tests/views/test___init__.py +1 -1
  39. {wuttaweb-0.18.0 → wuttaweb-0.19.1}/tests/views/test_auth.py +1 -1
  40. {wuttaweb-0.18.0 → wuttaweb-0.19.1}/tests/views/test_base.py +1 -1
  41. {wuttaweb-0.18.0 → wuttaweb-0.19.1}/tests/views/test_batch.py +7 -1
  42. {wuttaweb-0.18.0 → wuttaweb-0.19.1}/tests/views/test_common.py +1 -1
  43. wuttaweb-0.19.1/tests/views/test_email.py +211 -0
  44. wuttaweb-0.19.1/tests/views/test_essential.py +10 -0
  45. {wuttaweb-0.18.0 → wuttaweb-0.19.1}/tests/views/test_master.py +19 -1
  46. {wuttaweb-0.18.0 → wuttaweb-0.19.1}/tests/views/test_people.py +1 -1
  47. {wuttaweb-0.18.0 → wuttaweb-0.19.1}/tests/views/test_progress.py +1 -1
  48. {wuttaweb-0.18.0 → wuttaweb-0.19.1}/tests/views/test_roles.py +1 -1
  49. {wuttaweb-0.18.0 → wuttaweb-0.19.1}/tests/views/test_settings.py +1 -1
  50. {wuttaweb-0.18.0 → wuttaweb-0.19.1}/tests/views/test_upgrades.py +1 -1
  51. {wuttaweb-0.18.0 → wuttaweb-0.19.1}/tests/views/test_users.py +1 -1
  52. {wuttaweb-0.18.0 → wuttaweb-0.19.1}/.gitignore +0 -0
  53. {wuttaweb-0.18.0 → wuttaweb-0.19.1}/COPYING.txt +0 -0
  54. {wuttaweb-0.18.0 → wuttaweb-0.19.1}/docs/Makefile +0 -0
  55. {wuttaweb-0.18.0 → wuttaweb-0.19.1}/docs/_static/.keepme +0 -0
  56. {wuttaweb-0.18.0 → wuttaweb-0.19.1}/docs/api/wuttaweb.app.rst +0 -0
  57. {wuttaweb-0.18.0 → wuttaweb-0.19.1}/docs/api/wuttaweb.auth.rst +0 -0
  58. {wuttaweb-0.18.0 → wuttaweb-0.19.1}/docs/api/wuttaweb.cli.rst +0 -0
  59. {wuttaweb-0.18.0 → wuttaweb-0.19.1}/docs/api/wuttaweb.cli.webapp.rst +0 -0
  60. {wuttaweb-0.18.0 → wuttaweb-0.19.1}/docs/api/wuttaweb.conf.rst +0 -0
  61. {wuttaweb-0.18.0 → wuttaweb-0.19.1}/docs/api/wuttaweb.db.continuum.rst +0 -0
  62. {wuttaweb-0.18.0 → wuttaweb-0.19.1}/docs/api/wuttaweb.db.rst +0 -0
  63. {wuttaweb-0.18.0 → wuttaweb-0.19.1}/docs/api/wuttaweb.db.sess.rst +0 -0
  64. {wuttaweb-0.18.0 → wuttaweb-0.19.1}/docs/api/wuttaweb.forms.base.rst +0 -0
  65. {wuttaweb-0.18.0 → wuttaweb-0.19.1}/docs/api/wuttaweb.forms.rst +0 -0
  66. {wuttaweb-0.18.0 → wuttaweb-0.19.1}/docs/api/wuttaweb.forms.schema.rst +0 -0
  67. {wuttaweb-0.18.0 → wuttaweb-0.19.1}/docs/api/wuttaweb.forms.widgets.rst +0 -0
  68. {wuttaweb-0.18.0 → wuttaweb-0.19.1}/docs/api/wuttaweb.grids.base.rst +0 -0
  69. {wuttaweb-0.18.0 → wuttaweb-0.19.1}/docs/api/wuttaweb.grids.filters.rst +0 -0
  70. {wuttaweb-0.18.0 → wuttaweb-0.19.1}/docs/api/wuttaweb.grids.rst +0 -0
  71. {wuttaweb-0.18.0 → wuttaweb-0.19.1}/docs/api/wuttaweb.handler.rst +0 -0
  72. {wuttaweb-0.18.0 → wuttaweb-0.19.1}/docs/api/wuttaweb.helpers.rst +0 -0
  73. {wuttaweb-0.18.0 → wuttaweb-0.19.1}/docs/api/wuttaweb.menus.rst +0 -0
  74. {wuttaweb-0.18.0 → wuttaweb-0.19.1}/docs/api/wuttaweb.progress.rst +0 -0
  75. {wuttaweb-0.18.0 → wuttaweb-0.19.1}/docs/api/wuttaweb.rst +0 -0
  76. {wuttaweb-0.18.0 → wuttaweb-0.19.1}/docs/api/wuttaweb.static.rst +0 -0
  77. {wuttaweb-0.18.0 → wuttaweb-0.19.1}/docs/api/wuttaweb.subscribers.rst +0 -0
  78. {wuttaweb-0.18.0 → wuttaweb-0.19.1}/docs/api/wuttaweb.util.rst +0 -0
  79. {wuttaweb-0.18.0 → wuttaweb-0.19.1}/docs/api/wuttaweb.views.auth.rst +0 -0
  80. {wuttaweb-0.18.0 → wuttaweb-0.19.1}/docs/api/wuttaweb.views.base.rst +0 -0
  81. {wuttaweb-0.18.0 → wuttaweb-0.19.1}/docs/api/wuttaweb.views.batch.rst +0 -0
  82. {wuttaweb-0.18.0 → wuttaweb-0.19.1}/docs/api/wuttaweb.views.common.rst +0 -0
  83. {wuttaweb-0.18.0 → wuttaweb-0.19.1}/docs/api/wuttaweb.views.essential.rst +0 -0
  84. {wuttaweb-0.18.0 → wuttaweb-0.19.1}/docs/api/wuttaweb.views.master.rst +0 -0
  85. {wuttaweb-0.18.0 → wuttaweb-0.19.1}/docs/api/wuttaweb.views.people.rst +0 -0
  86. {wuttaweb-0.18.0 → wuttaweb-0.19.1}/docs/api/wuttaweb.views.progress.rst +0 -0
  87. {wuttaweb-0.18.0 → wuttaweb-0.19.1}/docs/api/wuttaweb.views.roles.rst +0 -0
  88. {wuttaweb-0.18.0 → wuttaweb-0.19.1}/docs/api/wuttaweb.views.rst +0 -0
  89. {wuttaweb-0.18.0 → wuttaweb-0.19.1}/docs/api/wuttaweb.views.settings.rst +0 -0
  90. {wuttaweb-0.18.0 → wuttaweb-0.19.1}/docs/api/wuttaweb.views.upgrades.rst +0 -0
  91. {wuttaweb-0.18.0 → wuttaweb-0.19.1}/docs/api/wuttaweb.views.users.rst +0 -0
  92. {wuttaweb-0.18.0 → wuttaweb-0.19.1}/docs/conf.py +0 -0
  93. {wuttaweb-0.18.0 → wuttaweb-0.19.1}/docs/glossary.rst +0 -0
  94. {wuttaweb-0.18.0 → wuttaweb-0.19.1}/docs/make.bat +0 -0
  95. {wuttaweb-0.18.0 → wuttaweb-0.19.1}/docs/narr/cli/builtin.rst +0 -0
  96. {wuttaweb-0.18.0 → wuttaweb-0.19.1}/docs/narr/cli/index.rst +0 -0
  97. {wuttaweb-0.18.0 → wuttaweb-0.19.1}/docs/narr/templates/base.rst +0 -0
  98. {wuttaweb-0.18.0 → wuttaweb-0.19.1}/docs/narr/templates/index.rst +0 -0
  99. {wuttaweb-0.18.0 → wuttaweb-0.19.1}/docs/narr/templates/lookup.rst +0 -0
  100. {wuttaweb-0.18.0 → wuttaweb-0.19.1}/docs/narr/templates/overview.rst +0 -0
  101. {wuttaweb-0.18.0 → wuttaweb-0.19.1}/src/wuttaweb/__init__.py +0 -0
  102. {wuttaweb-0.18.0 → wuttaweb-0.19.1}/src/wuttaweb/_version.py +0 -0
  103. {wuttaweb-0.18.0 → wuttaweb-0.19.1}/src/wuttaweb/auth.py +0 -0
  104. {wuttaweb-0.18.0 → wuttaweb-0.19.1}/src/wuttaweb/cli/__init__.py +0 -0
  105. {wuttaweb-0.18.0 → wuttaweb-0.19.1}/src/wuttaweb/cli/webapp.py +0 -0
  106. {wuttaweb-0.18.0 → wuttaweb-0.19.1}/src/wuttaweb/conf.py +0 -0
  107. {wuttaweb-0.18.0 → wuttaweb-0.19.1}/src/wuttaweb/db/__init__.py +0 -0
  108. {wuttaweb-0.18.0 → wuttaweb-0.19.1}/src/wuttaweb/db/continuum.py +0 -0
  109. {wuttaweb-0.18.0 → wuttaweb-0.19.1}/src/wuttaweb/db/sess.py +0 -0
  110. {wuttaweb-0.18.0/src/wuttaweb/email/templates → wuttaweb-0.19.1/src/wuttaweb/email-templates}/feedback.html.mako +0 -0
  111. {wuttaweb-0.18.0/src/wuttaweb/email/templates → wuttaweb-0.19.1/src/wuttaweb/email-templates}/feedback.txt.mako +0 -0
  112. {wuttaweb-0.18.0 → wuttaweb-0.19.1}/src/wuttaweb/forms/__init__.py +0 -0
  113. {wuttaweb-0.18.0 → wuttaweb-0.19.1}/src/wuttaweb/forms/base.py +0 -0
  114. {wuttaweb-0.18.0 → wuttaweb-0.19.1}/src/wuttaweb/grids/__init__.py +0 -0
  115. {wuttaweb-0.18.0 → wuttaweb-0.19.1}/src/wuttaweb/grids/filters.py +0 -0
  116. {wuttaweb-0.18.0 → wuttaweb-0.19.1}/src/wuttaweb/handler.py +0 -0
  117. {wuttaweb-0.18.0 → wuttaweb-0.19.1}/src/wuttaweb/helpers.py +0 -0
  118. {wuttaweb-0.18.0 → wuttaweb-0.19.1}/src/wuttaweb/static/__init__.py +0 -0
  119. {wuttaweb-0.18.0 → wuttaweb-0.19.1}/src/wuttaweb/static/img/favicon.ico +0 -0
  120. {wuttaweb-0.18.0 → wuttaweb-0.19.1}/src/wuttaweb/static/img/logo.png +0 -0
  121. {wuttaweb-0.18.0 → wuttaweb-0.19.1}/src/wuttaweb/static/img/testing.png +0 -0
  122. {wuttaweb-0.18.0 → wuttaweb-0.19.1}/src/wuttaweb/subscribers.py +0 -0
  123. {wuttaweb-0.18.0 → wuttaweb-0.19.1}/src/wuttaweb/templates/appinfo/configure.mako +0 -0
  124. {wuttaweb-0.18.0 → wuttaweb-0.19.1}/src/wuttaweb/templates/appinfo/index.mako +0 -0
  125. {wuttaweb-0.18.0 → wuttaweb-0.19.1}/src/wuttaweb/templates/auth/change_password.mako +0 -0
  126. {wuttaweb-0.18.0 → wuttaweb-0.19.1}/src/wuttaweb/templates/auth/login.mako +0 -0
  127. {wuttaweb-0.18.0 → wuttaweb-0.19.1}/src/wuttaweb/templates/base_meta.mako +0 -0
  128. {wuttaweb-0.18.0 → wuttaweb-0.19.1}/src/wuttaweb/templates/configure.mako +0 -0
  129. {wuttaweb-0.18.0 → wuttaweb-0.19.1}/src/wuttaweb/templates/deform/checkbox.pt +0 -0
  130. {wuttaweb-0.18.0 → wuttaweb-0.19.1}/src/wuttaweb/templates/deform/checkbox_choice.pt +0 -0
  131. {wuttaweb-0.18.0 → wuttaweb-0.19.1}/src/wuttaweb/templates/deform/checked_password.pt +0 -0
  132. {wuttaweb-0.18.0 → wuttaweb-0.19.1}/src/wuttaweb/templates/deform/dateinput.pt +0 -0
  133. {wuttaweb-0.18.0 → wuttaweb-0.19.1}/src/wuttaweb/templates/deform/datetimeinput.pt +0 -0
  134. {wuttaweb-0.18.0 → wuttaweb-0.19.1}/src/wuttaweb/templates/deform/moneyinput.pt +0 -0
  135. {wuttaweb-0.18.0 → wuttaweb-0.19.1}/src/wuttaweb/templates/deform/password.pt +0 -0
  136. {wuttaweb-0.18.0 → wuttaweb-0.19.1}/src/wuttaweb/templates/deform/permissions.pt +0 -0
  137. {wuttaweb-0.18.0 → wuttaweb-0.19.1}/src/wuttaweb/templates/deform/readonly/checkbox.pt +0 -0
  138. {wuttaweb-0.18.0 → wuttaweb-0.19.1}/src/wuttaweb/templates/deform/readonly/filedownload.pt +0 -0
  139. {wuttaweb-0.18.0 → wuttaweb-0.19.1}/src/wuttaweb/templates/deform/readonly/notes.pt +0 -0
  140. {wuttaweb-0.18.0 → wuttaweb-0.19.1}/src/wuttaweb/templates/deform/readonly/objectref.pt +0 -0
  141. {wuttaweb-0.18.0 → wuttaweb-0.19.1}/src/wuttaweb/templates/deform/readonly/permissions.pt +0 -0
  142. {wuttaweb-0.18.0 → wuttaweb-0.19.1}/src/wuttaweb/templates/deform/readonly/rolerefs.pt +0 -0
  143. {wuttaweb-0.18.0 → wuttaweb-0.19.1}/src/wuttaweb/templates/deform/select.pt +0 -0
  144. {wuttaweb-0.18.0 → wuttaweb-0.19.1}/src/wuttaweb/templates/deform/textarea.pt +0 -0
  145. {wuttaweb-0.18.0 → wuttaweb-0.19.1}/src/wuttaweb/templates/deform/textinput.pt +0 -0
  146. {wuttaweb-0.18.0 → wuttaweb-0.19.1}/src/wuttaweb/templates/forbidden.mako +0 -0
  147. {wuttaweb-0.18.0 → wuttaweb-0.19.1}/src/wuttaweb/templates/form.mako +0 -0
  148. {wuttaweb-0.18.0 → wuttaweb-0.19.1}/src/wuttaweb/templates/forms/vue_template.mako +0 -0
  149. {wuttaweb-0.18.0 → wuttaweb-0.19.1}/src/wuttaweb/templates/grids/table_element.mako +0 -0
  150. {wuttaweb-0.18.0 → wuttaweb-0.19.1}/src/wuttaweb/templates/grids/vue_template.mako +0 -0
  151. {wuttaweb-0.18.0 → wuttaweb-0.19.1}/src/wuttaweb/templates/home.mako +0 -0
  152. {wuttaweb-0.18.0 → wuttaweb-0.19.1}/src/wuttaweb/templates/master/configure.mako +0 -0
  153. {wuttaweb-0.18.0 → wuttaweb-0.19.1}/src/wuttaweb/templates/master/create.mako +0 -0
  154. {wuttaweb-0.18.0 → wuttaweb-0.19.1}/src/wuttaweb/templates/master/delete.mako +0 -0
  155. {wuttaweb-0.18.0 → wuttaweb-0.19.1}/src/wuttaweb/templates/master/edit.mako +0 -0
  156. {wuttaweb-0.18.0 → wuttaweb-0.19.1}/src/wuttaweb/templates/master/form.mako +0 -0
  157. {wuttaweb-0.18.0 → wuttaweb-0.19.1}/src/wuttaweb/templates/master/index.mako +0 -0
  158. {wuttaweb-0.18.0 → wuttaweb-0.19.1}/src/wuttaweb/templates/notfound.mako +0 -0
  159. {wuttaweb-0.18.0 → wuttaweb-0.19.1}/src/wuttaweb/templates/page.mako +0 -0
  160. {wuttaweb-0.18.0 → wuttaweb-0.19.1}/src/wuttaweb/templates/people/view_profile.mako +0 -0
  161. {wuttaweb-0.18.0 → wuttaweb-0.19.1}/src/wuttaweb/templates/progress.mako +0 -0
  162. {wuttaweb-0.18.0 → wuttaweb-0.19.1}/src/wuttaweb/templates/setup.mako +0 -0
  163. {wuttaweb-0.18.0 → wuttaweb-0.19.1}/src/wuttaweb/templates/upgrade.mako +0 -0
  164. {wuttaweb-0.18.0 → wuttaweb-0.19.1}/src/wuttaweb/templates/upgrades/configure.mako +0 -0
  165. {wuttaweb-0.18.0 → wuttaweb-0.19.1}/src/wuttaweb/templates/upgrades/view.mako +0 -0
  166. {wuttaweb-0.18.0 → wuttaweb-0.19.1}/src/wuttaweb/templates/wutta-components.mako +0 -0
  167. {wuttaweb-0.18.0 → wuttaweb-0.19.1}/src/wuttaweb/views/__init__.py +0 -0
  168. {wuttaweb-0.18.0 → wuttaweb-0.19.1}/src/wuttaweb/views/auth.py +0 -0
  169. {wuttaweb-0.18.0 → wuttaweb-0.19.1}/src/wuttaweb/views/base.py +0 -0
  170. {wuttaweb-0.18.0 → wuttaweb-0.19.1}/src/wuttaweb/views/common.py +0 -0
  171. {wuttaweb-0.18.0 → wuttaweb-0.19.1}/src/wuttaweb/views/people.py +0 -0
  172. {wuttaweb-0.18.0 → wuttaweb-0.19.1}/src/wuttaweb/views/progress.py +0 -0
  173. {wuttaweb-0.18.0 → wuttaweb-0.19.1}/src/wuttaweb/views/roles.py +0 -0
  174. {wuttaweb-0.18.0 → wuttaweb-0.19.1}/src/wuttaweb/views/settings.py +0 -0
  175. {wuttaweb-0.18.0 → wuttaweb-0.19.1}/src/wuttaweb/views/upgrades.py +0 -0
  176. {wuttaweb-0.18.0 → wuttaweb-0.19.1}/src/wuttaweb/views/users.py +0 -0
  177. {wuttaweb-0.18.0 → wuttaweb-0.19.1}/tasks.py +0 -0
  178. {wuttaweb-0.18.0 → wuttaweb-0.19.1}/tests/__init__.py +0 -0
  179. {wuttaweb-0.18.0 → wuttaweb-0.19.1}/tests/cli/__init__.py +0 -0
  180. {wuttaweb-0.18.0 → wuttaweb-0.19.1}/tests/cli/test_webapp.py +0 -0
  181. {wuttaweb-0.18.0 → wuttaweb-0.19.1}/tests/db/__init__.py +0 -0
  182. {wuttaweb-0.18.0 → wuttaweb-0.19.1}/tests/forms/__init__.py +0 -0
  183. {wuttaweb-0.18.0 → wuttaweb-0.19.1}/tests/forms/test_base.py +0 -0
  184. {wuttaweb-0.18.0 → wuttaweb-0.19.1}/tests/grids/__init__.py +0 -0
  185. {wuttaweb-0.18.0 → wuttaweb-0.19.1}/tests/libcache/bb_fontawesome_svg_core.js +0 -0
  186. {wuttaweb-0.18.0 → wuttaweb-0.19.1}/tests/libcache/bb_free_solid_svg_icons.js +0 -0
  187. {wuttaweb-0.18.0 → wuttaweb-0.19.1}/tests/libcache/bb_oruga.js +0 -0
  188. {wuttaweb-0.18.0 → wuttaweb-0.19.1}/tests/libcache/bb_oruga_bulma.css +0 -0
  189. {wuttaweb-0.18.0 → wuttaweb-0.19.1}/tests/libcache/bb_oruga_bulma.js +0 -0
  190. {wuttaweb-0.18.0 → wuttaweb-0.19.1}/tests/libcache/bb_vue.js +0 -0
  191. {wuttaweb-0.18.0 → wuttaweb-0.19.1}/tests/libcache/bb_vue_fontawesome.js +0 -0
  192. {wuttaweb-0.18.0 → wuttaweb-0.19.1}/tests/libcache/buefy.css +0 -0
  193. {wuttaweb-0.18.0 → wuttaweb-0.19.1}/tests/libcache/buefy.js +0 -0
  194. {wuttaweb-0.18.0 → wuttaweb-0.19.1}/tests/libcache/fontawesome.js +0 -0
  195. {wuttaweb-0.18.0 → wuttaweb-0.19.1}/tests/libcache/vue.js +0 -0
  196. {wuttaweb-0.18.0 → wuttaweb-0.19.1}/tests/libcache/vue_resource.js +0 -0
  197. {wuttaweb-0.18.0 → wuttaweb-0.19.1}/tests/test_app.py +0 -0
  198. {wuttaweb-0.18.0 → wuttaweb-0.19.1}/tests/test_helpers.py +0 -0
  199. {wuttaweb-0.18.0 → wuttaweb-0.19.1}/tests/test_static.py +0 -0
  200. {wuttaweb-0.18.0 → wuttaweb-0.19.1}/tests/test_subscribers.py +0 -0
  201. {wuttaweb-0.18.0 → wuttaweb-0.19.1}/tests/views/__init__.py +0 -0
  202. {wuttaweb-0.18.0 → wuttaweb-0.19.1}/tox.ini +0 -0
@@ -5,6 +5,36 @@ All notable changes to wuttaweb will be documented in this file.
5
5
  The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
6
6
  and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## v0.19.1 (2025-01-06)
9
+
10
+ ### Fix
11
+
12
+ - improve built-in grid renderer logic
13
+ - allow session injection for ObjectRef constructor
14
+ - improve rendering for batch row status
15
+ - add basic support for row grid "view" action links
16
+ - add "xref buttons" tool panel for master view
17
+ - add WuttaQuantity schema type, widget
18
+ - remove `session` param from some form schema, widget classes
19
+ - add grid renderers for bool, currency, quantity
20
+ - use proper bulma styles for markdown content
21
+ - use span element for readonly money field widget render
22
+ - include grid filters for all column properties of model class
23
+ - use app handler to render error string, when progress fails
24
+ - add schema node type, widget for "money" (currency) fields
25
+ - exclude FK fields by default, for model forms
26
+ - fix style for header title text
27
+
28
+ ## v0.19.0 (2024-12-23)
29
+
30
+ ### Feat
31
+
32
+ - add feature to edit email settings, basic message preview
33
+
34
+ ### Fix
35
+
36
+ - move CRUD header buttons toward center of screen
37
+
8
38
  ## v0.18.0 (2024-12-18)
9
39
 
10
40
  ### Feat
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: WuttaWeb
3
- Version: 0.18.0
3
+ Version: 0.19.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
@@ -37,9 +37,10 @@ Requires-Dist: pyramid-fanstatic
37
37
  Requires-Dist: pyramid-mako
38
38
  Requires-Dist: pyramid-tm
39
39
  Requires-Dist: pyramid>=2
40
+ Requires-Dist: sqlalchemy-utils
40
41
  Requires-Dist: waitress
41
42
  Requires-Dist: webhelpers2
42
- Requires-Dist: wuttjamaican[db]>=0.18.1
43
+ Requires-Dist: wuttjamaican[db]>=0.19.2
43
44
  Requires-Dist: zope-sqlalchemy>=1.5
44
45
  Provides-Extra: continuum
45
46
  Requires-Dist: wutta-continuum; extra == 'continuum'
@@ -53,7 +54,7 @@ Requires-Dist: tox; extra == 'tests'
53
54
  Description-Content-Type: text/markdown
54
55
 
55
56
 
56
- # wuttaweb
57
+ # WuttaWeb
57
58
 
58
59
  Web app for Wutta Framework
59
60
 
@@ -1,5 +1,5 @@
1
1
 
2
- # wuttaweb
2
+ # WuttaWeb
3
3
 
4
4
  Web app for Wutta Framework
5
5
 
@@ -0,0 +1,6 @@
1
+
2
+ ``wuttaweb.emails``
3
+ ===================
4
+
5
+ .. automodule:: wuttaweb.emails
6
+ :members:
@@ -0,0 +1,6 @@
1
+
2
+ ``wuttaweb.views.email``
3
+ ========================
4
+
5
+ .. automodule:: wuttaweb.views.email
6
+ :members:
@@ -35,6 +35,7 @@ the narrative docs are pretty scant. That will eventually change.
35
35
  api/wuttaweb.db
36
36
  api/wuttaweb.db.continuum
37
37
  api/wuttaweb.db.sess
38
+ api/wuttaweb.emails
38
39
  api/wuttaweb.forms
39
40
  api/wuttaweb.forms.base
40
41
  api/wuttaweb.forms.schema
@@ -54,6 +55,7 @@ the narrative docs are pretty scant. That will eventually change.
54
55
  api/wuttaweb.views.base
55
56
  api/wuttaweb.views.batch
56
57
  api/wuttaweb.views.common
58
+ api/wuttaweb.views.email
57
59
  api/wuttaweb.views.essential
58
60
  api/wuttaweb.views.master
59
61
  api/wuttaweb.views.people
@@ -6,7 +6,7 @@ build-backend = "hatchling.build"
6
6
 
7
7
  [project]
8
8
  name = "WuttaWeb"
9
- version = "0.18.0"
9
+ version = "0.19.1"
10
10
  description = "Web App for Wutta Framework"
11
11
  readme = "README.md"
12
12
  authors = [{name = "Lance Edgar", email = "lance@wuttaproject.org"}]
@@ -42,9 +42,10 @@ dependencies = [
42
42
  "pyramid_fanstatic",
43
43
  "pyramid_mako",
44
44
  "pyramid_tm",
45
+ "SQLAlchemy-Utils",
45
46
  "waitress",
46
47
  "WebHelpers2",
47
- "WuttJamaican[db]>=0.18.1",
48
+ "WuttJamaican[db]>=0.19.2",
48
49
  "zope.sqlalchemy>=1.5",
49
50
  ]
50
51
 
@@ -43,9 +43,12 @@ log = logging.getLogger(__name__)
43
43
  class WebAppProvider(AppProvider):
44
44
  """
45
45
  The :term:`app provider` for WuttaWeb. This adds some methods to
46
- the :term:`app handler`, which are specific to web apps.
46
+ the :term:`app handler`, which are specific to web apps. It also
47
+ registers some :term:`email templates <email template>` for the
48
+ app, etc.
47
49
  """
48
- email_templates = 'wuttaweb:email/templates'
50
+ email_modules = ['wuttaweb.emails']
51
+ email_templates = ['wuttaweb:email-templates']
49
52
 
50
53
  def get_web_handler(self, **kwargs):
51
54
  """
@@ -0,0 +1,48 @@
1
+ # -*- coding: utf-8; -*-
2
+ ################################################################################
3
+ #
4
+ # wuttaweb -- Web App for Wutta Framework
5
+ # Copyright © 2024 Lance Edgar
6
+ #
7
+ # This file is part of Wutta Framework.
8
+ #
9
+ # Wutta Framework is free software: you can redistribute it and/or modify it
10
+ # under the terms of the GNU General Public License as published by the Free
11
+ # Software Foundation, either version 3 of the License, or (at your option) any
12
+ # later version.
13
+ #
14
+ # Wutta Framework is distributed in the hope that it will be useful, but
15
+ # WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
16
+ # FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
17
+ # more details.
18
+ #
19
+ # You should have received a copy of the GNU General Public License along with
20
+ # Wutta Framework. If not, see <http://www.gnu.org/licenses/>.
21
+ #
22
+ ################################################################################
23
+ """
24
+ :term:`Email Settings <email setting>` for WuttaWeb
25
+ """
26
+
27
+ from wuttjamaican.email import EmailSetting
28
+
29
+
30
+ class feedback(EmailSetting):
31
+ """
32
+ Sent when user submits feedback via the web app.
33
+ """
34
+ default_subject = "User Feedback"
35
+
36
+ def sample_data(self):
37
+ """ """
38
+ model = self.app.model
39
+ person = model.Person(full_name="Barney Rubble")
40
+ user = model.User(username='barney', person=person)
41
+ return {
42
+ 'user': user,
43
+ 'user_name': str(person),
44
+ 'user_url': '#',
45
+ 'referrer': 'http://example.com/',
46
+ 'client_ip': '127.0.0.1',
47
+ 'message': "This app is cool but needs a new feature.\n\nAllow me to describe...",
48
+ }
@@ -30,9 +30,11 @@ import uuid as _uuid
30
30
  import colander
31
31
  import sqlalchemy as sa
32
32
 
33
+ from wuttjamaican.db.model import Person
34
+ from wuttjamaican.conf import parse_list
35
+
33
36
  from wuttaweb.db import Session
34
37
  from wuttaweb.forms import widgets
35
- from wuttjamaican.db.model import Person
36
38
 
37
39
 
38
40
  class WuttaDateTime(colander.DateTime):
@@ -153,25 +155,63 @@ class WuttaEnum(colander.Enum):
153
155
  return widgets.SelectWidget(**kwargs)
154
156
 
155
157
 
158
+ class WuttaMoney(colander.Money):
159
+ """
160
+ Custom schema type for "money" fields.
161
+
162
+ This is a subclass of :class:`colander:colander.Money`, but uses
163
+ the custom :class:`~wuttaweb.forms.widgets.WuttaMoneyInputWidget`
164
+ by default.
165
+
166
+ :param request: Current :term:`request` object.
167
+ """
168
+
169
+ def __init__(self, request, *args, **kwargs):
170
+ super().__init__(*args, **kwargs)
171
+ self.request = request
172
+ self.config = self.request.wutta_config
173
+ self.app = self.config.get_app()
174
+
175
+ def widget_maker(self, **kwargs):
176
+ """ """
177
+ return widgets.WuttaMoneyInputWidget(self.request, **kwargs)
178
+
179
+
180
+ class WuttaQuantity(colander.Decimal):
181
+ """
182
+ Custom schema type for "quantity" fields.
183
+
184
+ This is a subclass of :class:`colander:colander.Decimal` but uses
185
+ :class:`~wuttaweb.forms.widgets.WuttaQuantityWidget` by default.
186
+
187
+ :param request: Current :term:`request` object.
188
+ """
189
+
190
+ def __init__(self, request, *args, **kwargs):
191
+ super().__init__(*args, **kwargs)
192
+ self.request = request
193
+ self.config = self.request.wutta_config
194
+ self.app = self.config.get_app()
195
+
196
+ def widget_maker(self, **kwargs):
197
+ """ """
198
+ return widgets.WuttaQuantityWidget(self.request, **kwargs)
199
+
200
+
156
201
  class WuttaSet(colander.Set):
157
202
  """
158
203
  Custom schema type for :class:`python:set` fields.
159
204
 
160
- This is a subclass of :class:`colander.Set`, but adds
161
- Wutta-related params to the constructor.
205
+ This is a subclass of :class:`colander.Set`.
162
206
 
163
207
  :param request: Current :term:`request` object.
164
-
165
- :param session: Optional :term:`db session` to use instead of
166
- :class:`wuttaweb.db.sess.Session`.
167
208
  """
168
209
 
169
- def __init__(self, request, session=None):
210
+ def __init__(self, request):
170
211
  super().__init__()
171
212
  self.request = request
172
213
  self.config = self.request.wutta_config
173
214
  self.app = self.config.get_app()
174
- self.session = session or Session()
175
215
 
176
216
 
177
217
  class ObjectRef(colander.SchemaType):
@@ -207,16 +247,16 @@ class ObjectRef(colander.SchemaType):
207
247
  self,
208
248
  request,
209
249
  empty_option=None,
210
- session=None,
211
250
  *args,
212
251
  **kwargs,
213
252
  ):
253
+ # nb. allow session injection for tests
254
+ self.session = kwargs.pop('session', Session())
214
255
  super().__init__(*args, **kwargs)
215
256
  self.request = request
216
257
  self.config = self.request.wutta_config
217
258
  self.app = self.config.get_app()
218
259
  self.model_instance = None
219
- self.session = session or Session()
220
260
 
221
261
  if empty_option:
222
262
  if empty_option is True:
@@ -448,7 +488,7 @@ class RoleRefs(WuttaSet):
448
488
  :returns: Instance of
449
489
  :class:`~wuttaweb.forms.widgets.RoleRefsWidget`.
450
490
  """
451
- kwargs.setdefault('session', self.session)
491
+ session = kwargs.setdefault('session', Session())
452
492
 
453
493
  if 'values' not in kwargs:
454
494
  model = self.app.model
@@ -456,20 +496,20 @@ class RoleRefs(WuttaSet):
456
496
 
457
497
  # avoid built-ins which cannot be assigned to users
458
498
  avoid = {
459
- auth.get_role_authenticated(self.session),
460
- auth.get_role_anonymous(self.session),
499
+ auth.get_role_authenticated(session),
500
+ auth.get_role_anonymous(session),
461
501
  }
462
502
  avoid = set([role.uuid for role in avoid])
463
503
 
464
504
  # also avoid admin unless current user is root
465
505
  if not self.request.is_root:
466
- avoid.add(auth.get_role_administrator(self.session).uuid)
506
+ avoid.add(auth.get_role_administrator(session).uuid)
467
507
 
468
508
  # everything else can be (un)assigned for users
469
- roles = self.session.query(model.Role)\
470
- .filter(~model.Role.uuid.in_(avoid))\
471
- .order_by(model.Role.name)\
472
- .all()
509
+ roles = session.query(model.Role)\
510
+ .filter(~model.Role.uuid.in_(avoid))\
511
+ .order_by(model.Role.name)\
512
+ .all()
473
513
  values = [(role.uuid.hex, role.name) for role in roles]
474
514
  kwargs['values'] = values
475
515
 
@@ -494,7 +534,7 @@ class UserRefs(WuttaSet):
494
534
  :returns: Instance of
495
535
  :class:`~wuttaweb.forms.widgets.UserRefsWidget`.
496
536
  """
497
- kwargs.setdefault('session', self.session)
537
+ kwargs.setdefault('session', Session())
498
538
  return widgets.UserRefsWidget(self.request, **kwargs)
499
539
 
500
540
 
@@ -524,7 +564,7 @@ class Permissions(WuttaSet):
524
564
  :returns: Instance of
525
565
  :class:`~wuttaweb.forms.widgets.PermissionsWidget`.
526
566
  """
527
- kwargs.setdefault('session', self.session)
567
+ kwargs.setdefault('session', Session())
528
568
  kwargs.setdefault('permissions', self.permissions)
529
569
 
530
570
  if 'values' not in kwargs:
@@ -569,5 +609,36 @@ class FileDownload(colander.String):
569
609
  return widgets.FileDownloadWidget(self.request, **kwargs)
570
610
 
571
611
 
612
+ class EmailRecipients(colander.String):
613
+ """
614
+ Custom schema type for :term:`email setting` recipient fields
615
+ (``To``, ``Cc``, ``Bcc``).
616
+ """
617
+
618
+ def serialize(self, node, appstruct):
619
+ if appstruct is colander.null:
620
+ return colander.null
621
+
622
+ return '\n'.join(parse_list(appstruct))
623
+
624
+ def deserialize(self, node, cstruct):
625
+ """ """
626
+ if cstruct is colander.null:
627
+ return colander.null
628
+
629
+ values = [value for value in parse_list(cstruct)
630
+ if value]
631
+ return ', '.join(values)
632
+
633
+ def widget_maker(self, **kwargs):
634
+ """
635
+ Constructs a default widget for the field.
636
+
637
+ :returns: Instance of
638
+ :class:`~wuttaweb.forms.widgets.EmailRecipientsWidget`.
639
+ """
640
+ return widgets.EmailRecipientsWidget(**kwargs)
641
+
642
+
572
643
  # nb. colanderalchemy schema overrides
573
644
  sa.DateTime.__colanderalchemy_config__ = {'typ': WuttaDateTime}
@@ -41,6 +41,7 @@ in the namespace:
41
41
  """
42
42
 
43
43
  import datetime
44
+ import decimal
44
45
  import os
45
46
 
46
47
  import colander
@@ -51,6 +52,8 @@ from deform.widget import (Widget, TextInputWidget, TextAreaWidget,
51
52
  DateTimeInputWidget, MoneyInputWidget)
52
53
  from webhelpers2.html import HTML
53
54
 
55
+ from wuttjamaican.conf import parse_list
56
+
54
57
  from wuttaweb.db import Session
55
58
  from wuttaweb.grids import Grid
56
59
 
@@ -133,26 +136,21 @@ class WuttaCheckboxChoiceWidget(CheckboxChoiceWidget):
133
136
  Custom widget for :class:`python:set` fields.
134
137
 
135
138
  This is a subclass of
136
- :class:`deform:deform.widget.CheckboxChoiceWidget`, but adds
137
- Wutta-related params to the constructor.
139
+ :class:`deform:deform.widget.CheckboxChoiceWidget`.
138
140
 
139
141
  :param request: Current :term:`request` object.
140
142
 
141
- :param session: Optional :term:`db session` to use instead of
142
- :class:`wuttaweb.db.sess.Session`.
143
-
144
143
  It uses these Deform templates:
145
144
 
146
145
  * ``checkbox_choice``
147
146
  * ``readonly/checkbox_choice``
148
147
  """
149
148
 
150
- def __init__(self, request, session=None, *args, **kwargs):
149
+ def __init__(self, request, *args, **kwargs):
151
150
  super().__init__(*args, **kwargs)
152
151
  self.request = request
153
152
  self.config = self.request.wutta_config
154
153
  self.app = self.config.get_app()
155
- self.session = session or Session()
156
154
 
157
155
 
158
156
  class WuttaDateTimeWidget(DateTimeInputWidget):
@@ -192,6 +190,78 @@ class WuttaDateTimeWidget(DateTimeInputWidget):
192
190
  return super().serialize(field, cstruct, **kw)
193
191
 
194
192
 
193
+ class WuttaMoneyInputWidget(MoneyInputWidget):
194
+ """
195
+ Custom widget for "money" fields. This is used by default for
196
+ :class:`~wuttaweb.forms.schema.WuttaMoney` type nodes.
197
+
198
+ The main purpose of this widget is to leverage
199
+ :meth:`~wuttjamaican:wuttjamaican.app.AppHandler.render_currency()`
200
+ for the readonly display.
201
+
202
+ This is a subclass of
203
+ :class:`deform:deform.widget.MoneyInputWidget` and uses these
204
+ Deform templates:
205
+
206
+ * ``moneyinput``
207
+
208
+ :param request: Current :term:`request` object.
209
+ """
210
+
211
+ def __init__(self, request, *args, **kwargs):
212
+ super().__init__(*args, **kwargs)
213
+ self.request = request
214
+ self.config = self.request.wutta_config
215
+ self.app = self.config.get_app()
216
+
217
+ def serialize(self, field, cstruct, **kw):
218
+ """ """
219
+ readonly = kw.get('readonly', self.readonly)
220
+ if readonly:
221
+ if cstruct in (colander.null, None):
222
+ return HTML.tag('span')
223
+ cstruct = decimal.Decimal(cstruct)
224
+ return HTML.tag('span', c=[self.app.render_currency(cstruct)])
225
+
226
+ return super().serialize(field, cstruct, **kw)
227
+
228
+
229
+ class WuttaQuantityWidget(TextInputWidget):
230
+ """
231
+ Custom widget for "quantity" fields. This is used by default for
232
+ :class:`~wuttaweb.forms.schema.WuttaQuantity` type nodes.
233
+
234
+ The main purpose of this widget is to leverage
235
+ :meth:`~wuttjamaican:wuttjamaican.app.AppHandler.render_quantity()`
236
+ for the readonly display.
237
+
238
+ This is a subclass of
239
+ :class:`deform:deform.widget.TextInputWidget` and uses these
240
+ Deform templates:
241
+
242
+ * ``textinput``
243
+
244
+ :param request: Current :term:`request` object.
245
+ """
246
+
247
+ def __init__(self, request, *args, **kwargs):
248
+ super().__init__(*args, **kwargs)
249
+ self.request = request
250
+ self.config = self.request.wutta_config
251
+ self.app = self.config.get_app()
252
+
253
+ def serialize(self, field, cstruct, **kw):
254
+ """ """
255
+ readonly = kw.get('readonly', self.readonly)
256
+ if readonly:
257
+ if cstruct in (colander.null, None):
258
+ return HTML.tag('span')
259
+ cstruct = decimal.Decimal(cstruct)
260
+ return HTML.tag('span', c=[self.app.render_quantity(cstruct)])
261
+
262
+ return super().serialize(field, cstruct, **kw)
263
+
264
+
195
265
  class FileDownloadWidget(Widget):
196
266
  """
197
267
  Widget for use with :class:`~wuttaweb.forms.schema.FileDownload`
@@ -423,6 +493,41 @@ class PermissionsWidget(WuttaCheckboxChoiceWidget):
423
493
  return super().serialize(field, cstruct, **kw)
424
494
 
425
495
 
496
+ class EmailRecipientsWidget(TextAreaWidget):
497
+ """
498
+ Widget for :term:`email setting` recipient fields (``To``, ``Cc``,
499
+ ``Bcc``).
500
+
501
+ This is a subclass of
502
+ :class:`deform:deform.widget.TextAreaWidget`. It uses these
503
+ Deform templates:
504
+
505
+ * ``textarea``
506
+ * ``readonly/email_recips``
507
+
508
+ See also the :class:`~wuttaweb.forms.schema.EmailRecipients`
509
+ schema type, which uses this widget.
510
+ """
511
+ readonly_template = 'readonly/email_recips'
512
+
513
+ def serialize(self, field, cstruct, **kw):
514
+ """ """
515
+ readonly = kw.get('readonly', self.readonly)
516
+ if readonly:
517
+ kw['recips'] = parse_list(cstruct or '')
518
+
519
+ return super().serialize(field, cstruct, **kw)
520
+
521
+ def deserialize(self, field, pstruct):
522
+ """ """
523
+ if pstruct is colander.null:
524
+ return colander.null
525
+
526
+ values = [value for value in parse_list(pstruct)
527
+ if value]
528
+ return ', '.join(values)
529
+
530
+
426
531
  class BatchIdWidget(Widget):
427
532
  """
428
533
  Widget for use with the