WuttaWeb 0.12.1__tar.gz → 0.13.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 (174) hide show
  1. {wuttaweb-0.12.1 → wuttaweb-0.13.0}/CHANGELOG.md +26 -0
  2. {wuttaweb-0.12.1 → wuttaweb-0.13.0}/PKG-INFO +3 -2
  3. wuttaweb-0.13.0/docs/api/wuttaweb/grids.filters.rst +6 -0
  4. {wuttaweb-0.12.1 → wuttaweb-0.13.0}/docs/api/wuttaweb/index.rst +4 -0
  5. wuttaweb-0.13.0/docs/api/wuttaweb/progress.rst +6 -0
  6. {wuttaweb-0.12.1 → wuttaweb-0.13.0}/docs/api/wuttaweb/views.people.rst +1 -1
  7. wuttaweb-0.13.0/docs/api/wuttaweb/views.progress.rst +6 -0
  8. wuttaweb-0.13.0/docs/api/wuttaweb/views.upgrades.rst +6 -0
  9. {wuttaweb-0.12.1 → wuttaweb-0.13.0}/docs/conf.py +1 -0
  10. {wuttaweb-0.12.1 → wuttaweb-0.13.0}/pyproject.toml +3 -2
  11. {wuttaweb-0.12.1 → wuttaweb-0.13.0}/src/wuttaweb/app.py +7 -5
  12. wuttaweb-0.13.0/src/wuttaweb/email/templates/feedback.html.mako +40 -0
  13. wuttaweb-0.13.0/src/wuttaweb/email/templates/feedback.txt.mako +23 -0
  14. {wuttaweb-0.12.1 → wuttaweb-0.13.0}/src/wuttaweb/forms/base.py +50 -24
  15. {wuttaweb-0.12.1 → wuttaweb-0.13.0}/src/wuttaweb/forms/schema.py +123 -18
  16. {wuttaweb-0.12.1 → wuttaweb-0.13.0}/src/wuttaweb/forms/widgets.py +70 -3
  17. {wuttaweb-0.12.1 → wuttaweb-0.13.0}/src/wuttaweb/grids/base.py +180 -225
  18. wuttaweb-0.13.0/src/wuttaweb/grids/filters.py +444 -0
  19. {wuttaweb-0.12.1 → wuttaweb-0.13.0}/src/wuttaweb/menus.py +5 -0
  20. wuttaweb-0.13.0/src/wuttaweb/progress.py +165 -0
  21. {wuttaweb-0.12.1 → wuttaweb-0.13.0}/src/wuttaweb/templates/appinfo/configure.mako +61 -0
  22. {wuttaweb-0.12.1 → wuttaweb-0.13.0}/src/wuttaweb/templates/appinfo/index.mako +4 -1
  23. {wuttaweb-0.12.1 → wuttaweb-0.13.0}/src/wuttaweb/templates/auth/login.mako +2 -2
  24. {wuttaweb-0.12.1 → wuttaweb-0.13.0}/src/wuttaweb/templates/base.mako +199 -28
  25. {wuttaweb-0.12.1 → wuttaweb-0.13.0}/src/wuttaweb/templates/configure.mako +5 -1
  26. {wuttaweb-0.12.1 → wuttaweb-0.13.0}/src/wuttaweb/templates/deform/checkbox.pt +1 -1
  27. wuttaweb-0.13.0/src/wuttaweb/templates/deform/moneyinput.pt +7 -0
  28. wuttaweb-0.13.0/src/wuttaweb/templates/deform/readonly/checkbox.pt +4 -0
  29. wuttaweb-0.13.0/src/wuttaweb/templates/deform/readonly/filedownload.pt +14 -0
  30. {wuttaweb-0.12.1 → wuttaweb-0.13.0}/src/wuttaweb/templates/form.mako +6 -2
  31. {wuttaweb-0.12.1 → wuttaweb-0.13.0}/src/wuttaweb/templates/forms/vue_template.mako +3 -3
  32. {wuttaweb-0.12.1 → wuttaweb-0.13.0}/src/wuttaweb/templates/grids/table_element.mako +1 -1
  33. {wuttaweb-0.12.1 → wuttaweb-0.13.0}/src/wuttaweb/templates/grids/vue_template.mako +37 -5
  34. {wuttaweb-0.12.1 → wuttaweb-0.13.0}/src/wuttaweb/templates/home.mako +2 -2
  35. wuttaweb-0.13.0/src/wuttaweb/templates/master/index.mako +66 -0
  36. wuttaweb-0.13.0/src/wuttaweb/templates/progress.mako +127 -0
  37. wuttaweb-0.13.0/src/wuttaweb/templates/upgrade.mako +64 -0
  38. wuttaweb-0.13.0/src/wuttaweb/templates/upgrades/configure.mako +20 -0
  39. wuttaweb-0.13.0/src/wuttaweb/templates/upgrades/view.mako +37 -0
  40. wuttaweb-0.13.0/src/wuttaweb/templates/wutta-components.mako +342 -0
  41. {wuttaweb-0.12.1 → wuttaweb-0.13.0}/src/wuttaweb/util.py +26 -0
  42. {wuttaweb-0.12.1 → wuttaweb-0.13.0}/src/wuttaweb/views/base.py +41 -1
  43. {wuttaweb-0.12.1 → wuttaweb-0.13.0}/src/wuttaweb/views/common.py +79 -0
  44. {wuttaweb-0.12.1 → wuttaweb-0.13.0}/src/wuttaweb/views/essential.py +4 -0
  45. {wuttaweb-0.12.1 → wuttaweb-0.13.0}/src/wuttaweb/views/master.py +656 -44
  46. {wuttaweb-0.12.1 → wuttaweb-0.13.0}/src/wuttaweb/views/people.py +6 -0
  47. wuttaweb-0.13.0/src/wuttaweb/views/progress.py +75 -0
  48. {wuttaweb-0.12.1 → wuttaweb-0.13.0}/src/wuttaweb/views/roles.py +1 -0
  49. {wuttaweb-0.12.1 → wuttaweb-0.13.0}/src/wuttaweb/views/settings.py +15 -4
  50. wuttaweb-0.13.0/src/wuttaweb/views/upgrades.py +364 -0
  51. {wuttaweb-0.12.1 → wuttaweb-0.13.0}/src/wuttaweb/views/users.py +28 -3
  52. {wuttaweb-0.12.1 → wuttaweb-0.13.0}/tests/forms/test_base.py +44 -30
  53. {wuttaweb-0.12.1 → wuttaweb-0.13.0}/tests/forms/test_schema.py +70 -6
  54. {wuttaweb-0.12.1 → wuttaweb-0.13.0}/tests/forms/test_widgets.py +51 -2
  55. {wuttaweb-0.12.1 → wuttaweb-0.13.0}/tests/grids/test_base.py +128 -94
  56. wuttaweb-0.13.0/tests/grids/test_filters.py +385 -0
  57. wuttaweb-0.13.0/tests/test_progress.py +62 -0
  58. {wuttaweb-0.12.1 → wuttaweb-0.13.0}/tests/test_util.py +99 -91
  59. {wuttaweb-0.12.1 → wuttaweb-0.13.0}/tests/util.py +4 -2
  60. {wuttaweb-0.12.1 → wuttaweb-0.13.0}/tests/views/test_base.py +20 -0
  61. {wuttaweb-0.12.1 → wuttaweb-0.13.0}/tests/views/test_common.py +76 -0
  62. {wuttaweb-0.12.1 → wuttaweb-0.13.0}/tests/views/test_master.py +390 -0
  63. wuttaweb-0.13.0/tests/views/test_progress.py +62 -0
  64. wuttaweb-0.13.0/tests/views/test_upgrades.py +364 -0
  65. {wuttaweb-0.12.1 → wuttaweb-0.13.0}/tests/views/test_users.py +11 -0
  66. wuttaweb-0.12.1/src/wuttaweb/templates/master/index.mako +0 -31
  67. wuttaweb-0.12.1/src/wuttaweb/templates/wutta-components.mako +0 -167
  68. {wuttaweb-0.12.1 → wuttaweb-0.13.0}/.gitignore +0 -0
  69. {wuttaweb-0.12.1 → wuttaweb-0.13.0}/COPYING.txt +0 -0
  70. {wuttaweb-0.12.1 → wuttaweb-0.13.0}/README.md +0 -0
  71. {wuttaweb-0.12.1 → wuttaweb-0.13.0}/docs/Makefile +0 -0
  72. {wuttaweb-0.12.1 → wuttaweb-0.13.0}/docs/_static/.keepme +0 -0
  73. {wuttaweb-0.12.1 → wuttaweb-0.13.0}/docs/api/index.rst +0 -0
  74. {wuttaweb-0.12.1 → wuttaweb-0.13.0}/docs/api/wuttaweb/app.rst +0 -0
  75. {wuttaweb-0.12.1 → wuttaweb-0.13.0}/docs/api/wuttaweb/auth.rst +0 -0
  76. {wuttaweb-0.12.1 → wuttaweb-0.13.0}/docs/api/wuttaweb/db.rst +0 -0
  77. {wuttaweb-0.12.1 → wuttaweb-0.13.0}/docs/api/wuttaweb/forms.base.rst +0 -0
  78. {wuttaweb-0.12.1 → wuttaweb-0.13.0}/docs/api/wuttaweb/forms.rst +0 -0
  79. {wuttaweb-0.12.1 → wuttaweb-0.13.0}/docs/api/wuttaweb/forms.schema.rst +0 -0
  80. {wuttaweb-0.12.1 → wuttaweb-0.13.0}/docs/api/wuttaweb/forms.widgets.rst +0 -0
  81. {wuttaweb-0.12.1 → wuttaweb-0.13.0}/docs/api/wuttaweb/grids.base.rst +0 -0
  82. {wuttaweb-0.12.1 → wuttaweb-0.13.0}/docs/api/wuttaweb/grids.rst +0 -0
  83. {wuttaweb-0.12.1 → wuttaweb-0.13.0}/docs/api/wuttaweb/handler.rst +0 -0
  84. {wuttaweb-0.12.1 → wuttaweb-0.13.0}/docs/api/wuttaweb/helpers.rst +0 -0
  85. {wuttaweb-0.12.1 → wuttaweb-0.13.0}/docs/api/wuttaweb/menus.rst +0 -0
  86. {wuttaweb-0.12.1 → wuttaweb-0.13.0}/docs/api/wuttaweb/static.rst +0 -0
  87. {wuttaweb-0.12.1 → wuttaweb-0.13.0}/docs/api/wuttaweb/subscribers.rst +0 -0
  88. {wuttaweb-0.12.1 → wuttaweb-0.13.0}/docs/api/wuttaweb/util.rst +0 -0
  89. {wuttaweb-0.12.1 → wuttaweb-0.13.0}/docs/api/wuttaweb/views.auth.rst +0 -0
  90. {wuttaweb-0.12.1 → wuttaweb-0.13.0}/docs/api/wuttaweb/views.base.rst +0 -0
  91. {wuttaweb-0.12.1 → wuttaweb-0.13.0}/docs/api/wuttaweb/views.common.rst +0 -0
  92. {wuttaweb-0.12.1 → wuttaweb-0.13.0}/docs/api/wuttaweb/views.essential.rst +0 -0
  93. {wuttaweb-0.12.1 → wuttaweb-0.13.0}/docs/api/wuttaweb/views.master.rst +0 -0
  94. {wuttaweb-0.12.1 → wuttaweb-0.13.0}/docs/api/wuttaweb/views.roles.rst +0 -0
  95. {wuttaweb-0.12.1 → wuttaweb-0.13.0}/docs/api/wuttaweb/views.rst +0 -0
  96. {wuttaweb-0.12.1 → wuttaweb-0.13.0}/docs/api/wuttaweb/views.settings.rst +0 -0
  97. {wuttaweb-0.12.1 → wuttaweb-0.13.0}/docs/api/wuttaweb/views.users.rst +0 -0
  98. {wuttaweb-0.12.1 → wuttaweb-0.13.0}/docs/glossary.rst +0 -0
  99. {wuttaweb-0.12.1 → wuttaweb-0.13.0}/docs/index.rst +0 -0
  100. {wuttaweb-0.12.1 → wuttaweb-0.13.0}/docs/make.bat +0 -0
  101. {wuttaweb-0.12.1 → wuttaweb-0.13.0}/docs/narr/index.rst +0 -0
  102. {wuttaweb-0.12.1 → wuttaweb-0.13.0}/docs/narr/templates/base.rst +0 -0
  103. {wuttaweb-0.12.1 → wuttaweb-0.13.0}/docs/narr/templates/index.rst +0 -0
  104. {wuttaweb-0.12.1 → wuttaweb-0.13.0}/docs/narr/templates/lookup.rst +0 -0
  105. {wuttaweb-0.12.1 → wuttaweb-0.13.0}/docs/narr/templates/overview.rst +0 -0
  106. {wuttaweb-0.12.1 → wuttaweb-0.13.0}/src/wuttaweb/__init__.py +0 -0
  107. {wuttaweb-0.12.1 → wuttaweb-0.13.0}/src/wuttaweb/_version.py +0 -0
  108. {wuttaweb-0.12.1 → wuttaweb-0.13.0}/src/wuttaweb/auth.py +0 -0
  109. {wuttaweb-0.12.1 → wuttaweb-0.13.0}/src/wuttaweb/db.py +0 -0
  110. {wuttaweb-0.12.1 → wuttaweb-0.13.0}/src/wuttaweb/forms/__init__.py +0 -0
  111. {wuttaweb-0.12.1 → wuttaweb-0.13.0}/src/wuttaweb/grids/__init__.py +0 -0
  112. {wuttaweb-0.12.1 → wuttaweb-0.13.0}/src/wuttaweb/handler.py +0 -0
  113. {wuttaweb-0.12.1 → wuttaweb-0.13.0}/src/wuttaweb/helpers.py +0 -0
  114. {wuttaweb-0.12.1 → wuttaweb-0.13.0}/src/wuttaweb/static/__init__.py +0 -0
  115. {wuttaweb-0.12.1 → wuttaweb-0.13.0}/src/wuttaweb/static/img/favicon.ico +0 -0
  116. {wuttaweb-0.12.1 → wuttaweb-0.13.0}/src/wuttaweb/static/img/logo.png +0 -0
  117. {wuttaweb-0.12.1 → wuttaweb-0.13.0}/src/wuttaweb/static/img/testing.png +0 -0
  118. {wuttaweb-0.12.1 → wuttaweb-0.13.0}/src/wuttaweb/subscribers.py +0 -0
  119. {wuttaweb-0.12.1 → wuttaweb-0.13.0}/src/wuttaweb/templates/auth/change_password.mako +0 -0
  120. {wuttaweb-0.12.1 → wuttaweb-0.13.0}/src/wuttaweb/templates/base_meta.mako +0 -0
  121. {wuttaweb-0.12.1 → wuttaweb-0.13.0}/src/wuttaweb/templates/deform/checkbox_choice.pt +0 -0
  122. {wuttaweb-0.12.1 → wuttaweb-0.13.0}/src/wuttaweb/templates/deform/checked_password.pt +0 -0
  123. {wuttaweb-0.12.1 → wuttaweb-0.13.0}/src/wuttaweb/templates/deform/password.pt +0 -0
  124. {wuttaweb-0.12.1 → wuttaweb-0.13.0}/src/wuttaweb/templates/deform/permissions.pt +0 -0
  125. {wuttaweb-0.12.1 → wuttaweb-0.13.0}/src/wuttaweb/templates/deform/readonly/notes.pt +0 -0
  126. {wuttaweb-0.12.1 → wuttaweb-0.13.0}/src/wuttaweb/templates/deform/readonly/objectref.pt +0 -0
  127. {wuttaweb-0.12.1 → wuttaweb-0.13.0}/src/wuttaweb/templates/deform/readonly/permissions.pt +0 -0
  128. {wuttaweb-0.12.1 → wuttaweb-0.13.0}/src/wuttaweb/templates/deform/readonly/rolerefs.pt +0 -0
  129. {wuttaweb-0.12.1 → wuttaweb-0.13.0}/src/wuttaweb/templates/deform/select.pt +0 -0
  130. {wuttaweb-0.12.1 → wuttaweb-0.13.0}/src/wuttaweb/templates/deform/textarea.pt +0 -0
  131. {wuttaweb-0.12.1 → wuttaweb-0.13.0}/src/wuttaweb/templates/deform/textinput.pt +0 -0
  132. {wuttaweb-0.12.1 → wuttaweb-0.13.0}/src/wuttaweb/templates/forbidden.mako +0 -0
  133. {wuttaweb-0.12.1 → wuttaweb-0.13.0}/src/wuttaweb/templates/master/configure.mako +0 -0
  134. {wuttaweb-0.12.1 → wuttaweb-0.13.0}/src/wuttaweb/templates/master/create.mako +0 -0
  135. {wuttaweb-0.12.1 → wuttaweb-0.13.0}/src/wuttaweb/templates/master/delete.mako +0 -0
  136. {wuttaweb-0.12.1 → wuttaweb-0.13.0}/src/wuttaweb/templates/master/edit.mako +0 -0
  137. {wuttaweb-0.12.1 → wuttaweb-0.13.0}/src/wuttaweb/templates/master/form.mako +0 -0
  138. {wuttaweb-0.12.1 → wuttaweb-0.13.0}/src/wuttaweb/templates/master/view.mako +0 -0
  139. {wuttaweb-0.12.1 → wuttaweb-0.13.0}/src/wuttaweb/templates/notfound.mako +0 -0
  140. {wuttaweb-0.12.1 → wuttaweb-0.13.0}/src/wuttaweb/templates/page.mako +0 -0
  141. {wuttaweb-0.12.1 → wuttaweb-0.13.0}/src/wuttaweb/templates/people/view_profile.mako +0 -0
  142. {wuttaweb-0.12.1 → wuttaweb-0.13.0}/src/wuttaweb/templates/setup.mako +0 -0
  143. {wuttaweb-0.12.1 → wuttaweb-0.13.0}/src/wuttaweb/views/__init__.py +0 -0
  144. {wuttaweb-0.12.1 → wuttaweb-0.13.0}/src/wuttaweb/views/auth.py +0 -0
  145. {wuttaweb-0.12.1 → wuttaweb-0.13.0}/tasks.py +0 -0
  146. {wuttaweb-0.12.1 → wuttaweb-0.13.0}/tests/__init__.py +0 -0
  147. {wuttaweb-0.12.1 → wuttaweb-0.13.0}/tests/forms/__init__.py +0 -0
  148. {wuttaweb-0.12.1 → wuttaweb-0.13.0}/tests/grids/__init__.py +0 -0
  149. {wuttaweb-0.12.1 → wuttaweb-0.13.0}/tests/libcache/bb_fontawesome_svg_core.js +0 -0
  150. {wuttaweb-0.12.1 → wuttaweb-0.13.0}/tests/libcache/bb_free_solid_svg_icons.js +0 -0
  151. {wuttaweb-0.12.1 → wuttaweb-0.13.0}/tests/libcache/bb_oruga.js +0 -0
  152. {wuttaweb-0.12.1 → wuttaweb-0.13.0}/tests/libcache/bb_oruga_bulma.css +0 -0
  153. {wuttaweb-0.12.1 → wuttaweb-0.13.0}/tests/libcache/bb_oruga_bulma.js +0 -0
  154. {wuttaweb-0.12.1 → wuttaweb-0.13.0}/tests/libcache/bb_vue.js +0 -0
  155. {wuttaweb-0.12.1 → wuttaweb-0.13.0}/tests/libcache/bb_vue_fontawesome.js +0 -0
  156. {wuttaweb-0.12.1 → wuttaweb-0.13.0}/tests/libcache/buefy.css +0 -0
  157. {wuttaweb-0.12.1 → wuttaweb-0.13.0}/tests/libcache/buefy.js +0 -0
  158. {wuttaweb-0.12.1 → wuttaweb-0.13.0}/tests/libcache/fontawesome.js +0 -0
  159. {wuttaweb-0.12.1 → wuttaweb-0.13.0}/tests/libcache/vue.js +0 -0
  160. {wuttaweb-0.12.1 → wuttaweb-0.13.0}/tests/libcache/vue_resource.js +0 -0
  161. {wuttaweb-0.12.1 → wuttaweb-0.13.0}/tests/test_app.py +0 -0
  162. {wuttaweb-0.12.1 → wuttaweb-0.13.0}/tests/test_auth.py +0 -0
  163. {wuttaweb-0.12.1 → wuttaweb-0.13.0}/tests/test_handler.py +0 -0
  164. {wuttaweb-0.12.1 → wuttaweb-0.13.0}/tests/test_helpers.py +0 -0
  165. {wuttaweb-0.12.1 → wuttaweb-0.13.0}/tests/test_menus.py +0 -0
  166. {wuttaweb-0.12.1 → wuttaweb-0.13.0}/tests/test_static.py +0 -0
  167. {wuttaweb-0.12.1 → wuttaweb-0.13.0}/tests/test_subscribers.py +0 -0
  168. {wuttaweb-0.12.1 → wuttaweb-0.13.0}/tests/views/__init__.py +0 -0
  169. {wuttaweb-0.12.1 → wuttaweb-0.13.0}/tests/views/test___init__.py +0 -0
  170. {wuttaweb-0.12.1 → wuttaweb-0.13.0}/tests/views/test_auth.py +0 -0
  171. {wuttaweb-0.12.1 → wuttaweb-0.13.0}/tests/views/test_people.py +0 -0
  172. {wuttaweb-0.12.1 → wuttaweb-0.13.0}/tests/views/test_roles.py +0 -0
  173. {wuttaweb-0.12.1 → wuttaweb-0.13.0}/tests/views/test_settings.py +0 -0
  174. {wuttaweb-0.12.1 → wuttaweb-0.13.0}/tox.ini +0 -0
@@ -5,6 +5,32 @@ 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.13.0 (2024-08-26)
9
+
10
+ ### Feat
11
+
12
+ - use native wuttjamaican app to send feedback email
13
+ - add basic user feedback email mechanism
14
+ - add "progress" page for executing upgrades
15
+ - add basic support for execute upgrades, download stdout/stderr
16
+ - add basic progress page/indicator support
17
+ - add basic "delete results" grid tool
18
+ - add initial views for upgrades
19
+ - allow app db to be rattail-native instead of wutta-native
20
+ - add per-row css class support for grids
21
+ - improve grid filter API a bit, support string/bool filters
22
+
23
+ ### Fix
24
+
25
+ - tweak max image size for full logo on home, login pages
26
+ - improve handling of boolean form fields
27
+ - misc. improvements for display of grids, form errors
28
+ - use autocomplete for grid filter verb choices
29
+ - small cleanup for grid filters template
30
+ - add once-button action for grid Reset View
31
+ - set sort defaults for users, roles
32
+ - add override hook for base form template
33
+
8
34
  ## v0.12.1 (2024-08-22)
9
35
 
10
36
  ### Fix
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: WuttaWeb
3
- Version: 0.12.1
3
+ Version: 0.13.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
@@ -25,6 +25,7 @@ Classifier: Topic :: Internet :: WWW/HTTP
25
25
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
26
26
  Requires-Python: >=3.8
27
27
  Requires-Dist: colanderalchemy
28
+ Requires-Dist: humanize
28
29
  Requires-Dist: paginate
29
30
  Requires-Dist: paginate-sqlalchemy
30
31
  Requires-Dist: pyramid-beaker
@@ -35,7 +36,7 @@ Requires-Dist: pyramid-tm
35
36
  Requires-Dist: pyramid>=2
36
37
  Requires-Dist: waitress
37
38
  Requires-Dist: webhelpers2
38
- Requires-Dist: wuttjamaican[db]>=0.12.1
39
+ Requires-Dist: wuttjamaican[db,email]>=0.13.0
39
40
  Requires-Dist: zope-sqlalchemy>=1.5
40
41
  Provides-Extra: docs
41
42
  Requires-Dist: furo; extra == 'docs'
@@ -0,0 +1,6 @@
1
+
2
+ ``wuttaweb.grids.filters``
3
+ ==========================
4
+
5
+ .. automodule:: wuttaweb.grids.filters
6
+ :members:
@@ -16,9 +16,11 @@
16
16
  forms.widgets
17
17
  grids
18
18
  grids.base
19
+ grids.filters
19
20
  handler
20
21
  helpers
21
22
  menus
23
+ progress
22
24
  static
23
25
  subscribers
24
26
  util
@@ -29,6 +31,8 @@
29
31
  views.essential
30
32
  views.master
31
33
  views.people
34
+ views.progress
32
35
  views.roles
33
36
  views.settings
37
+ views.upgrades
34
38
  views.users
@@ -0,0 +1,6 @@
1
+
2
+ ``wuttaweb.progress``
3
+ =====================
4
+
5
+ .. automodule:: wuttaweb.progress
6
+ :members:
@@ -1,6 +1,6 @@
1
1
 
2
2
  ``wuttaweb.views.people``
3
- ===========================
3
+ =========================
4
4
 
5
5
  .. automodule:: wuttaweb.views.people
6
6
  :members:
@@ -0,0 +1,6 @@
1
+
2
+ ``wuttaweb.views.progress``
3
+ ===========================
4
+
5
+ .. automodule:: wuttaweb.views.progress
6
+ :members:
@@ -0,0 +1,6 @@
1
+
2
+ ``wuttaweb.views.upgrades``
3
+ ===========================
4
+
5
+ .. automodule:: wuttaweb.views.upgrades
6
+ :members:
@@ -31,6 +31,7 @@ intersphinx_mapping = {
31
31
  'deform': ('https://docs.pylonsproject.org/projects/deform/en/latest/', None),
32
32
  'pyramid': ('https://docs.pylonsproject.org/projects/pyramid/en/latest/', None),
33
33
  'python': ('https://docs.python.org/3/', None),
34
+ 'rattail-manual': ('https://rattailproject.org/docs/rattail-manual/', None),
34
35
  'webhelpers2': ('https://webhelpers2.readthedocs.io/en/latest/', None),
35
36
  'wuttjamaican': ('https://rattailproject.org/docs/wuttjamaican/', None),
36
37
  }
@@ -6,7 +6,7 @@ build-backend = "hatchling.build"
6
6
 
7
7
  [project]
8
8
  name = "WuttaWeb"
9
- version = "0.12.1"
9
+ version = "0.13.0"
10
10
  description = "Web App for Wutta Framework"
11
11
  readme = "README.md"
12
12
  authors = [{name = "Lance Edgar", email = "lance@edbob.org"}]
@@ -31,6 +31,7 @@ classifiers = [
31
31
  requires-python = ">= 3.8"
32
32
  dependencies = [
33
33
  "ColanderAlchemy",
34
+ "humanize",
34
35
  "paginate",
35
36
  "paginate_sqlalchemy",
36
37
  "pyramid>=2",
@@ -41,7 +42,7 @@ dependencies = [
41
42
  "pyramid_tm",
42
43
  "waitress",
43
44
  "WebHelpers2",
44
- "WuttJamaican[db]>=0.12.1",
45
+ "WuttJamaican[db,email]>=0.13.0",
45
46
  "zope.sqlalchemy>=1.5",
46
47
  ]
47
48
 
@@ -37,9 +37,10 @@ from wuttaweb.auth import WuttaSecurityPolicy
37
37
 
38
38
  class WebAppProvider(AppProvider):
39
39
  """
40
- The :term:`app provider` for WuttaWeb. This adds some methods
41
- specific to web apps.
40
+ The :term:`app provider` for WuttaWeb. This adds some methods to
41
+ the :term:`app handler`, which are specific to web apps.
42
42
  """
43
+ email_templates = 'wuttaweb:email/templates'
43
44
 
44
45
  def get_web_handler(self, **kwargs):
45
46
  """
@@ -61,7 +62,7 @@ class WebAppProvider(AppProvider):
61
62
  return self.web_handler
62
63
 
63
64
 
64
- def make_wutta_config(settings):
65
+ def make_wutta_config(settings, config_maker=None, **kwargs):
65
66
  """
66
67
  Make a WuttaConfig object from the given settings.
67
68
 
@@ -93,8 +94,9 @@ def make_wutta_config(settings):
93
94
  "section of config to the path of your "
94
95
  "config file. Lame, but necessary.")
95
96
 
96
- # make config per usual, add to settings
97
- wutta_config = make_config(path)
97
+ # make config, add to settings
98
+ config_maker = config_maker or make_config
99
+ wutta_config = config_maker(path, **kwargs)
98
100
  settings['wutta_config'] = wutta_config
99
101
 
100
102
  # configure database sessions
@@ -0,0 +1,40 @@
1
+ ## -*- coding: utf-8 -*-
2
+ <html>
3
+ <head>
4
+ <style type="text/css">
5
+ label {
6
+ display: block;
7
+ font-weight: bold;
8
+ margin-top: 1em;
9
+ }
10
+ p {
11
+ margin: 1em 0 1em 1.5em;
12
+ }
13
+ p.msg {
14
+ white-space: pre-wrap;
15
+ }
16
+ </style>
17
+ </head>
18
+ <body>
19
+ <h1>User feedback from website</h1>
20
+
21
+ <label>User Name</label>
22
+ <p>
23
+ % if user:
24
+ <a href="${user_url}">${user}</a>
25
+ % else:
26
+ ${user_name}
27
+ % endif
28
+ </p>
29
+
30
+ <label>Referring URL</label>
31
+ <p><a href="${referrer}">${referrer}</a></p>
32
+
33
+ <label>Client IP</label>
34
+ <p>${client_ip}</p>
35
+
36
+ <label>Message</label>
37
+ <p class="msg">${message}</p>
38
+
39
+ </body>
40
+ </html>
@@ -0,0 +1,23 @@
1
+ ## -*- coding: utf-8; -*-
2
+
3
+ # User feedback from website
4
+
5
+ **User Name**
6
+
7
+ % if user:
8
+ ${user}
9
+ % else:
10
+ ${user_name}
11
+ % endif
12
+
13
+ **Referring URL**
14
+
15
+ ${referrer}
16
+
17
+ **Client IP**
18
+
19
+ ${client_ip}
20
+
21
+ **Message**
22
+
23
+ ${message}
@@ -313,7 +313,7 @@ class Form:
313
313
  self.set_fields(fields or self.get_fields())
314
314
 
315
315
  # nb. this tracks grid JSON data for inclusion in page template
316
- self.grid_vue_data = OrderedDict()
316
+ self.grid_vue_context = OrderedDict()
317
317
 
318
318
  def __contains__(self, name):
319
319
  """
@@ -746,17 +746,39 @@ class Form:
746
746
  kwargs = {}
747
747
 
748
748
  if self.model_instance:
749
- # TODO: would it be smarter to test with hasattr() ?
750
- # if hasattr(schema, 'dictify'):
751
- if isinstance(self.model_instance, model.Base):
749
+
750
+ # TODO: i keep finding problems with this, not sure
751
+ # what needs to happen. some forms will have a simple
752
+ # dict for model_instance, others will have a proper
753
+ # SQLAlchemy object. and in the latter case, it may
754
+ # not be "wutta-native" but from another DB.
755
+
756
+ # so the problem is, how to detect whether we should
757
+ # use the model_instance as-is or if we should convert
758
+ # to a dict. some options include:
759
+
760
+ # - check if instance has dictify() method
761
+ # i *think* this was tried and didn't work? but do not recall
762
+
763
+ # - check if is instance of model.Base
764
+ # this is unreliable since model.Base is wutta-native
765
+
766
+ # - check if form has a model_class
767
+ # has not been tried yet
768
+
769
+ # - check if schema is from colanderalchemy
770
+ # this is what we are trying currently...
771
+
772
+ if isinstance(schema, SQLAlchemySchemaNode):
752
773
  kwargs['appstruct'] = schema.dictify(self.model_instance)
753
774
  else:
754
775
  kwargs['appstruct'] = self.model_instance
755
776
 
756
- form = deform.Form(schema, **kwargs)
777
+ # create the Deform instance
757
778
  # nb. must give a reference back to wutta form; this is
758
779
  # for sake of field schema nodes and widgets, e.g. to
759
780
  # access the main model instance
781
+ form = deform.Form(schema, **kwargs)
760
782
  form.wutta_form = self
761
783
  self.deform_form = form
762
784
 
@@ -826,16 +848,16 @@ class Form:
826
848
  output = render(template, context)
827
849
  return HTML.literal(output)
828
850
 
829
- def add_grid_vue_data(self, grid):
851
+ def add_grid_vue_context(self, grid):
830
852
  """ """
831
853
  if not grid.key:
832
854
  raise ValueError("grid must have a key!")
833
855
 
834
- if grid.key in self.grid_vue_data:
856
+ if grid.key in self.grid_vue_context:
835
857
  log.warning("grid data with key '%s' already registered, "
836
858
  "but will be replaced", grid.key)
837
859
 
838
- self.grid_vue_data[grid.key] = grid.get_vue_data()
860
+ self.grid_vue_context[grid.key] = grid.get_vue_context()
839
861
 
840
862
  def render_vue_field(
841
863
  self,
@@ -922,18 +944,13 @@ class Form:
922
944
  if field_type:
923
945
  attrs['type'] = field_type
924
946
  if messages:
925
- if len(messages) == 1:
926
- msg = messages[0]
927
- if msg.startswith('`') and msg.endswith('`'):
928
- attrs[':message'] = msg
929
- else:
930
- attrs['message'] = msg
931
- # TODO
932
- # else:
933
- # # nb. must pass an array as JSON string
934
- # attrs[':message'] = '[{}]'.format(', '.join([
935
- # "'{}'".format(msg.replace("'", r"\'"))
936
- # for msg in messages]))
947
+ cls = 'is-size-7'
948
+ if field_type == 'is-danger':
949
+ cls += ' has-text-danger'
950
+ messages = [HTML.tag('p', c=[msg], class_=cls)
951
+ for msg in messages]
952
+ slot = HTML.tag('slot', name='messages', c=messages)
953
+ html = HTML.tag('div', c=[html, slot])
937
954
 
938
955
  return HTML.tag('b-field', c=[html], **attrs)
939
956
 
@@ -978,7 +995,16 @@ class Form:
978
995
  model_data = {}
979
996
 
980
997
  def assign(field):
981
- model_data[field.oid] = make_json_safe(field.cstruct)
998
+ value = field.cstruct
999
+
1000
+ # TODO: we need a proper true/false on the Vue side,
1001
+ # but deform/colander want 'true' and 'false' ..so
1002
+ # for now we explicitly translate here, ugh. also
1003
+ # note this does not yet allow for null values.. :(
1004
+ if isinstance(field.typ, colander.Boolean):
1005
+ value = True if field.typ.true_val else False
1006
+
1007
+ model_data[field.oid] = make_json_safe(value)
982
1008
 
983
1009
  for key in self.fields:
984
1010
 
@@ -1076,7 +1102,7 @@ class Form:
1076
1102
  """
1077
1103
  dform = self.get_deform()
1078
1104
  if field in dform:
1079
- error = dform[field].errormsg
1080
- if error:
1081
- return [error]
1105
+ field = dform[field]
1106
+ if field.error:
1107
+ return field.error.messages()
1082
1108
  return []
@@ -92,6 +92,53 @@ class ObjectNode(colander.SchemaNode):
92
92
  raise NotImplementedError(f"you must define {class_name}.objectify()")
93
93
 
94
94
 
95
+ class WuttaEnum(colander.Enum):
96
+ """
97
+ Custom schema type for enum fields.
98
+
99
+ This is a subclass of :class:`colander.Enum`, but adds a
100
+ default widget (``SelectWidget``) with enum choices.
101
+
102
+ :param request: Current :term:`request` object.
103
+ """
104
+
105
+ def __init__(self, request, *args, **kwargs):
106
+ super().__init__(*args, **kwargs)
107
+ self.request = request
108
+ self.config = self.request.wutta_config
109
+ self.app = self.config.get_app()
110
+
111
+ def widget_maker(self, **kwargs):
112
+ """ """
113
+
114
+ if 'values' not in kwargs:
115
+ kwargs['values'] = [(getattr(e, self.attr), getattr(e, self.attr))
116
+ for e in self.enum_cls]
117
+
118
+ return widgets.SelectWidget(**kwargs)
119
+
120
+
121
+ class WuttaSet(colander.Set):
122
+ """
123
+ Custom schema type for :class:`python:set` fields.
124
+
125
+ This is a subclass of :class:`colander.Set`, but adds
126
+ Wutta-related params to the constructor.
127
+
128
+ :param request: Current :term:`request` object.
129
+
130
+ :param session: Optional :term:`db session` to use instead of
131
+ :class:`wuttaweb.db.Session`.
132
+ """
133
+
134
+ def __init__(self, request, session=None):
135
+ super().__init__()
136
+ self.request = request
137
+ self.config = self.request.wutta_config
138
+ self.app = self.config.get_app()
139
+ self.session = session or Session()
140
+
141
+
95
142
  class ObjectRef(colander.SchemaType):
96
143
  """
97
144
  Custom schema type for a model class reference field.
@@ -199,7 +246,7 @@ class ObjectRef(colander.SchemaType):
199
246
 
200
247
  # fetch object from DB
201
248
  model = self.app.model
202
- obj = self.session.query(self.model_class).get(value)
249
+ obj = self.session.get(self.model_class, value)
203
250
 
204
251
  # raise error if not found
205
252
  if not obj:
@@ -247,43 +294,69 @@ class ObjectRef(colander.SchemaType):
247
294
  kwargs['values'] = values
248
295
 
249
296
  if 'url' not in kwargs:
250
- kwargs['url'] = lambda person: self.request.route_url('people.view', uuid=person.uuid)
297
+ kwargs['url'] = self.get_object_url
251
298
 
252
299
  return widgets.ObjectRefWidget(self.request, **kwargs)
253
300
 
301
+ def get_object_url(self, obj):
302
+ """
303
+ Returns the "view" URL for the given object, if applicable.
304
+
305
+ This is used when rendering the field readonly. If this
306
+ method returns a URL then the field text will be wrapped with
307
+ a hyperlink, otherwise it will be shown as-is.
308
+
309
+ Default logic always returns ``None``; subclass should
310
+ override as needed.
311
+ """
312
+
254
313
 
255
314
  class PersonRef(ObjectRef):
256
315
  """
257
- Custom schema type for a ``Person`` reference field.
316
+ Custom schema type for a
317
+ :class:`~wuttjamaican:wuttjamaican.db.model.base.Person` reference
318
+ field.
258
319
 
259
320
  This is a subclass of :class:`ObjectRef`.
260
321
  """
261
- model_class = Person
322
+
323
+ @property
324
+ def model_class(self):
325
+ """ """
326
+ model = self.app.model
327
+ return model.Person
262
328
 
263
329
  def sort_query(self, query):
264
330
  """ """
265
331
  return query.order_by(self.model_class.full_name)
266
332
 
333
+ def get_object_url(self, person):
334
+ """ """
335
+ return self.request.route_url('people.view', uuid=person.uuid)
267
336
 
268
- class WuttaSet(colander.Set):
337
+
338
+ class UserRef(ObjectRef):
269
339
  """
270
- Custom schema type for :class:`python:set` fields.
340
+ Custom schema type for a
341
+ :class:`~wuttjamaican:wuttjamaican.db.model.auth.User` reference
342
+ field.
271
343
 
272
- This is a subclass of :class:`colander.Set`, but adds
273
- Wutta-related params to the constructor.
344
+ This is a subclass of :class:`ObjectRef`.
345
+ """
274
346
 
275
- :param request: Current :term:`request` object.
347
+ @property
348
+ def model_class(self):
349
+ """ """
350
+ model = self.app.model
351
+ return model.User
276
352
 
277
- :param session: Optional :term:`db session` to use instead of
278
- :class:`wuttaweb.db.Session`.
279
- """
353
+ def sort_query(self, query):
354
+ """ """
355
+ return query.order_by(self.model_class.username)
280
356
 
281
- def __init__(self, request, session=None):
282
- super().__init__()
283
- self.request = request
284
- self.config = self.request.wutta_config
285
- self.app = self.config.get_app()
286
- self.session = session or Session()
357
+ def get_object_url(self, user):
358
+ """ """
359
+ return self.request.route_url('users.view', uuid=user.uuid)
287
360
 
288
361
 
289
362
  class RoleRefs(WuttaSet):
@@ -383,3 +456,35 @@ class Permissions(WuttaSet):
383
456
  kwargs['values'] = values
384
457
 
385
458
  return widgets.PermissionsWidget(self.request, **kwargs)
459
+
460
+
461
+ class FileDownload(colander.String):
462
+ """
463
+ Custom schema type for a file download field.
464
+
465
+ This field is only meant for readonly use, it does not handle file
466
+ uploads.
467
+
468
+ It expects the incoming ``appstruct`` to be the path to a file on
469
+ disk (or null).
470
+
471
+ Uses the :class:`~wuttaweb.forms.widgets.FileDownloadWidget` by
472
+ default.
473
+
474
+ :param request: Current :term:`request` object.
475
+
476
+ :param url: Optional URL for hyperlink. If not specified, file
477
+ name/size is shown with no hyperlink.
478
+ """
479
+
480
+ def __init__(self, request, *args, **kwargs):
481
+ self.url = kwargs.pop('url', None)
482
+ super().__init__(*args, **kwargs)
483
+ self.request = request
484
+ self.config = self.request.wutta_config
485
+ self.app = self.config.get_app()
486
+
487
+ def widget_maker(self, **kwargs):
488
+ """ """
489
+ kwargs.setdefault('url', self.url)
490
+ return widgets.FileDownloadWidget(self.request, **kwargs)
@@ -33,14 +33,20 @@ in the namespace:
33
33
  * :class:`deform:deform.widget.TextAreaWidget`
34
34
  * :class:`deform:deform.widget.PasswordWidget`
35
35
  * :class:`deform:deform.widget.CheckedPasswordWidget`
36
+ * :class:`deform:deform.widget.CheckboxWidget`
36
37
  * :class:`deform:deform.widget.SelectWidget`
37
38
  * :class:`deform:deform.widget.CheckboxChoiceWidget`
39
+ * :class:`deform:deform.widget.MoneyInputWidget`
38
40
  """
39
41
 
42
+ import os
43
+
40
44
  import colander
45
+ import humanize
41
46
  from deform.widget import (Widget, TextInputWidget, TextAreaWidget,
42
47
  PasswordWidget, CheckedPasswordWidget,
43
- SelectWidget, CheckboxChoiceWidget)
48
+ CheckboxWidget, SelectWidget, CheckboxChoiceWidget,
49
+ MoneyInputWidget)
44
50
  from webhelpers2.html import HTML
45
51
 
46
52
  from wuttaweb.db import Session
@@ -144,6 +150,63 @@ class WuttaCheckboxChoiceWidget(CheckboxChoiceWidget):
144
150
  self.session = session or Session()
145
151
 
146
152
 
153
+ class FileDownloadWidget(Widget):
154
+ """
155
+ Widget for use with :class:`~wuttaweb.forms.schema.FileDownload`
156
+ fields.
157
+
158
+ This only supports readonly, and shows a hyperlink to download the
159
+ file. Link text is the filename plus file size.
160
+
161
+ This is a subclass of :class:`deform:deform.widget.Widget` and
162
+ uses these Deform templates:
163
+
164
+ * ``readonly/filedownload``
165
+
166
+ :param request: Current :term:`request` object.
167
+
168
+ :param url: Optional URL for hyperlink. If not specified, file
169
+ name/size is shown with no hyperlink.
170
+ """
171
+ readonly_template = 'readonly/filedownload'
172
+
173
+ def __init__(self, request, *args, **kwargs):
174
+ self.url = kwargs.pop('url', None)
175
+ super().__init__(*args, **kwargs)
176
+ self.request = request
177
+ self.config = self.request.wutta_config
178
+ self.app = self.config.get_app()
179
+
180
+ def serialize(self, field, cstruct, **kw):
181
+ """ """
182
+ # nb. readonly is the only way this rolls
183
+ kw['readonly'] = True
184
+ template = self.readonly_template
185
+
186
+ path = cstruct or None
187
+ if path:
188
+ kw.setdefault('filename', os.path.basename(path))
189
+ kw.setdefault('filesize', self.readable_size(path))
190
+ if self.url:
191
+ kw.setdefault('url', self.url)
192
+
193
+ else:
194
+ kw.setdefault('filename', None)
195
+ kw.setdefault('filesize', None)
196
+
197
+ kw.setdefault('url', None)
198
+ values = self.get_template_values(field, cstruct, kw)
199
+ return field.renderer(template, **values)
200
+
201
+ def readable_size(self, path):
202
+ """ """
203
+ try:
204
+ size = os.path.getsize(path)
205
+ except os.error:
206
+ size = 0
207
+ return humanize.naturalsize(size)
208
+
209
+
147
210
  class RoleRefsWidget(WuttaCheckboxChoiceWidget):
148
211
  """
149
212
  Widget for use with User
@@ -181,7 +244,7 @@ class RoleRefsWidget(WuttaCheckboxChoiceWidget):
181
244
  roles = []
182
245
  if cstruct:
183
246
  for uuid in cstruct:
184
- role = self.session.query(model.Role).get(uuid)
247
+ role = self.session.get(model.Role, uuid)
185
248
  if role:
186
249
  roles.append(role)
187
250
  kw['roles'] = roles
@@ -220,11 +283,15 @@ class UserRefsWidget(WuttaCheckboxChoiceWidget):
220
283
  users = []
221
284
  if cstruct:
222
285
  for uuid in cstruct:
223
- user = self.session.query(model.User).get(uuid)
286
+ user = self.session.get(model.User, uuid)
224
287
  if user:
225
288
  users.append(dict([(key, getattr(user, key))
226
289
  for key in columns + ['uuid']]))
227
290
 
291
+ # do not render if no data
292
+ if not users:
293
+ return HTML.tag('span')
294
+
228
295
  # grid
229
296
  grid = Grid(self.request, key='roles.view.users',
230
297
  columns=columns, data=users)