WuttaWeb 0.17.1__tar.gz → 0.18.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 (193) hide show
  1. {wuttaweb-0.17.1 → wuttaweb-0.18.0}/CHANGELOG.md +14 -0
  2. {wuttaweb-0.17.1 → wuttaweb-0.18.0}/PKG-INFO +4 -2
  3. wuttaweb-0.18.0/docs/api/wuttaweb.cli.rst +6 -0
  4. wuttaweb-0.18.0/docs/api/wuttaweb.cli.webapp.rst +6 -0
  5. {wuttaweb-0.17.1 → wuttaweb-0.18.0}/docs/conf.py +1 -0
  6. {wuttaweb-0.17.1 → wuttaweb-0.18.0}/docs/index.rst +3 -0
  7. wuttaweb-0.18.0/docs/narr/cli/builtin.rst +28 -0
  8. wuttaweb-0.18.0/docs/narr/cli/index.rst +14 -0
  9. {wuttaweb-0.17.1 → wuttaweb-0.18.0}/pyproject.toml +7 -6
  10. {wuttaweb-0.17.1 → wuttaweb-0.18.0}/src/wuttaweb/app.py +99 -24
  11. wuttaweb-0.18.0/src/wuttaweb/cli/__init__.py +28 -0
  12. wuttaweb-0.18.0/src/wuttaweb/cli/webapp.py +88 -0
  13. {wuttaweb-0.17.1 → wuttaweb-0.18.0}/src/wuttaweb/grids/base.py +1 -0
  14. {wuttaweb-0.17.1 → wuttaweb-0.18.0}/src/wuttaweb/grids/filters.py +98 -0
  15. {wuttaweb-0.17.1 → wuttaweb-0.18.0}/src/wuttaweb/templates/grids/vue_template.mako +24 -23
  16. {wuttaweb-0.17.1 → wuttaweb-0.18.0}/src/wuttaweb/templates/wutta-components.mako +67 -2
  17. wuttaweb-0.18.0/tests/cli/test_webapp.py +104 -0
  18. {wuttaweb-0.17.1 → wuttaweb-0.18.0}/tests/grids/test_filters.py +156 -0
  19. wuttaweb-0.18.0/tests/test_app.py +131 -0
  20. wuttaweb-0.18.0/tests/views/__init__.py +0 -0
  21. wuttaweb-0.17.1/tests/test_app.py +0 -61
  22. {wuttaweb-0.17.1 → wuttaweb-0.18.0}/.gitignore +0 -0
  23. {wuttaweb-0.17.1 → wuttaweb-0.18.0}/COPYING.txt +0 -0
  24. {wuttaweb-0.17.1 → wuttaweb-0.18.0}/README.md +0 -0
  25. {wuttaweb-0.17.1 → wuttaweb-0.18.0}/docs/Makefile +0 -0
  26. {wuttaweb-0.17.1 → wuttaweb-0.18.0}/docs/_static/.keepme +0 -0
  27. {wuttaweb-0.17.1 → wuttaweb-0.18.0}/docs/api/wuttaweb.app.rst +0 -0
  28. {wuttaweb-0.17.1 → wuttaweb-0.18.0}/docs/api/wuttaweb.auth.rst +0 -0
  29. {wuttaweb-0.17.1 → wuttaweb-0.18.0}/docs/api/wuttaweb.conf.rst +0 -0
  30. {wuttaweb-0.17.1 → wuttaweb-0.18.0}/docs/api/wuttaweb.db.continuum.rst +0 -0
  31. {wuttaweb-0.17.1 → wuttaweb-0.18.0}/docs/api/wuttaweb.db.rst +0 -0
  32. {wuttaweb-0.17.1 → wuttaweb-0.18.0}/docs/api/wuttaweb.db.sess.rst +0 -0
  33. {wuttaweb-0.17.1 → wuttaweb-0.18.0}/docs/api/wuttaweb.forms.base.rst +0 -0
  34. {wuttaweb-0.17.1 → wuttaweb-0.18.0}/docs/api/wuttaweb.forms.rst +0 -0
  35. {wuttaweb-0.17.1 → wuttaweb-0.18.0}/docs/api/wuttaweb.forms.schema.rst +0 -0
  36. {wuttaweb-0.17.1 → wuttaweb-0.18.0}/docs/api/wuttaweb.forms.widgets.rst +0 -0
  37. {wuttaweb-0.17.1 → wuttaweb-0.18.0}/docs/api/wuttaweb.grids.base.rst +0 -0
  38. {wuttaweb-0.17.1 → wuttaweb-0.18.0}/docs/api/wuttaweb.grids.filters.rst +0 -0
  39. {wuttaweb-0.17.1 → wuttaweb-0.18.0}/docs/api/wuttaweb.grids.rst +0 -0
  40. {wuttaweb-0.17.1 → wuttaweb-0.18.0}/docs/api/wuttaweb.handler.rst +0 -0
  41. {wuttaweb-0.17.1 → wuttaweb-0.18.0}/docs/api/wuttaweb.helpers.rst +0 -0
  42. {wuttaweb-0.17.1 → wuttaweb-0.18.0}/docs/api/wuttaweb.menus.rst +0 -0
  43. {wuttaweb-0.17.1 → wuttaweb-0.18.0}/docs/api/wuttaweb.progress.rst +0 -0
  44. {wuttaweb-0.17.1 → wuttaweb-0.18.0}/docs/api/wuttaweb.rst +0 -0
  45. {wuttaweb-0.17.1 → wuttaweb-0.18.0}/docs/api/wuttaweb.static.rst +0 -0
  46. {wuttaweb-0.17.1 → wuttaweb-0.18.0}/docs/api/wuttaweb.subscribers.rst +0 -0
  47. {wuttaweb-0.17.1 → wuttaweb-0.18.0}/docs/api/wuttaweb.util.rst +0 -0
  48. {wuttaweb-0.17.1 → wuttaweb-0.18.0}/docs/api/wuttaweb.views.auth.rst +0 -0
  49. {wuttaweb-0.17.1 → wuttaweb-0.18.0}/docs/api/wuttaweb.views.base.rst +0 -0
  50. {wuttaweb-0.17.1 → wuttaweb-0.18.0}/docs/api/wuttaweb.views.batch.rst +0 -0
  51. {wuttaweb-0.17.1 → wuttaweb-0.18.0}/docs/api/wuttaweb.views.common.rst +0 -0
  52. {wuttaweb-0.17.1 → wuttaweb-0.18.0}/docs/api/wuttaweb.views.essential.rst +0 -0
  53. {wuttaweb-0.17.1 → wuttaweb-0.18.0}/docs/api/wuttaweb.views.master.rst +0 -0
  54. {wuttaweb-0.17.1 → wuttaweb-0.18.0}/docs/api/wuttaweb.views.people.rst +0 -0
  55. {wuttaweb-0.17.1 → wuttaweb-0.18.0}/docs/api/wuttaweb.views.progress.rst +0 -0
  56. {wuttaweb-0.17.1 → wuttaweb-0.18.0}/docs/api/wuttaweb.views.roles.rst +0 -0
  57. {wuttaweb-0.17.1 → wuttaweb-0.18.0}/docs/api/wuttaweb.views.rst +0 -0
  58. {wuttaweb-0.17.1 → wuttaweb-0.18.0}/docs/api/wuttaweb.views.settings.rst +0 -0
  59. {wuttaweb-0.17.1 → wuttaweb-0.18.0}/docs/api/wuttaweb.views.upgrades.rst +0 -0
  60. {wuttaweb-0.17.1 → wuttaweb-0.18.0}/docs/api/wuttaweb.views.users.rst +0 -0
  61. {wuttaweb-0.17.1 → wuttaweb-0.18.0}/docs/glossary.rst +0 -0
  62. {wuttaweb-0.17.1 → wuttaweb-0.18.0}/docs/make.bat +0 -0
  63. {wuttaweb-0.17.1 → wuttaweb-0.18.0}/docs/narr/templates/base.rst +0 -0
  64. {wuttaweb-0.17.1 → wuttaweb-0.18.0}/docs/narr/templates/index.rst +0 -0
  65. {wuttaweb-0.17.1 → wuttaweb-0.18.0}/docs/narr/templates/lookup.rst +0 -0
  66. {wuttaweb-0.17.1 → wuttaweb-0.18.0}/docs/narr/templates/overview.rst +0 -0
  67. {wuttaweb-0.17.1 → wuttaweb-0.18.0}/src/wuttaweb/__init__.py +0 -0
  68. {wuttaweb-0.17.1 → wuttaweb-0.18.0}/src/wuttaweb/_version.py +0 -0
  69. {wuttaweb-0.17.1 → wuttaweb-0.18.0}/src/wuttaweb/auth.py +0 -0
  70. {wuttaweb-0.17.1 → wuttaweb-0.18.0}/src/wuttaweb/conf.py +0 -0
  71. {wuttaweb-0.17.1 → wuttaweb-0.18.0}/src/wuttaweb/db/__init__.py +0 -0
  72. {wuttaweb-0.17.1 → wuttaweb-0.18.0}/src/wuttaweb/db/continuum.py +0 -0
  73. {wuttaweb-0.17.1 → wuttaweb-0.18.0}/src/wuttaweb/db/sess.py +0 -0
  74. {wuttaweb-0.17.1 → wuttaweb-0.18.0}/src/wuttaweb/email/templates/feedback.html.mako +0 -0
  75. {wuttaweb-0.17.1 → wuttaweb-0.18.0}/src/wuttaweb/email/templates/feedback.txt.mako +0 -0
  76. {wuttaweb-0.17.1 → wuttaweb-0.18.0}/src/wuttaweb/forms/__init__.py +0 -0
  77. {wuttaweb-0.17.1 → wuttaweb-0.18.0}/src/wuttaweb/forms/base.py +0 -0
  78. {wuttaweb-0.17.1 → wuttaweb-0.18.0}/src/wuttaweb/forms/schema.py +0 -0
  79. {wuttaweb-0.17.1 → wuttaweb-0.18.0}/src/wuttaweb/forms/widgets.py +0 -0
  80. {wuttaweb-0.17.1 → wuttaweb-0.18.0}/src/wuttaweb/grids/__init__.py +0 -0
  81. {wuttaweb-0.17.1 → wuttaweb-0.18.0}/src/wuttaweb/handler.py +0 -0
  82. {wuttaweb-0.17.1 → wuttaweb-0.18.0}/src/wuttaweb/helpers.py +0 -0
  83. {wuttaweb-0.17.1 → wuttaweb-0.18.0}/src/wuttaweb/menus.py +0 -0
  84. {wuttaweb-0.17.1 → wuttaweb-0.18.0}/src/wuttaweb/progress.py +0 -0
  85. {wuttaweb-0.17.1 → wuttaweb-0.18.0}/src/wuttaweb/static/__init__.py +0 -0
  86. {wuttaweb-0.17.1 → wuttaweb-0.18.0}/src/wuttaweb/static/img/favicon.ico +0 -0
  87. {wuttaweb-0.17.1 → wuttaweb-0.18.0}/src/wuttaweb/static/img/logo.png +0 -0
  88. {wuttaweb-0.17.1 → wuttaweb-0.18.0}/src/wuttaweb/static/img/testing.png +0 -0
  89. {wuttaweb-0.17.1 → wuttaweb-0.18.0}/src/wuttaweb/subscribers.py +0 -0
  90. {wuttaweb-0.17.1 → wuttaweb-0.18.0}/src/wuttaweb/templates/appinfo/configure.mako +0 -0
  91. {wuttaweb-0.17.1 → wuttaweb-0.18.0}/src/wuttaweb/templates/appinfo/index.mako +0 -0
  92. {wuttaweb-0.17.1 → wuttaweb-0.18.0}/src/wuttaweb/templates/auth/change_password.mako +0 -0
  93. {wuttaweb-0.17.1 → wuttaweb-0.18.0}/src/wuttaweb/templates/auth/login.mako +0 -0
  94. {wuttaweb-0.17.1 → wuttaweb-0.18.0}/src/wuttaweb/templates/base.mako +0 -0
  95. {wuttaweb-0.17.1 → wuttaweb-0.18.0}/src/wuttaweb/templates/base_meta.mako +0 -0
  96. {wuttaweb-0.17.1 → wuttaweb-0.18.0}/src/wuttaweb/templates/batch/view.mako +0 -0
  97. {wuttaweb-0.17.1 → wuttaweb-0.18.0}/src/wuttaweb/templates/configure.mako +0 -0
  98. {wuttaweb-0.17.1 → wuttaweb-0.18.0}/src/wuttaweb/templates/deform/checkbox.pt +0 -0
  99. {wuttaweb-0.17.1 → wuttaweb-0.18.0}/src/wuttaweb/templates/deform/checkbox_choice.pt +0 -0
  100. {wuttaweb-0.17.1 → wuttaweb-0.18.0}/src/wuttaweb/templates/deform/checked_password.pt +0 -0
  101. {wuttaweb-0.17.1 → wuttaweb-0.18.0}/src/wuttaweb/templates/deform/dateinput.pt +0 -0
  102. {wuttaweb-0.17.1 → wuttaweb-0.18.0}/src/wuttaweb/templates/deform/datetimeinput.pt +0 -0
  103. {wuttaweb-0.17.1 → wuttaweb-0.18.0}/src/wuttaweb/templates/deform/moneyinput.pt +0 -0
  104. {wuttaweb-0.17.1 → wuttaweb-0.18.0}/src/wuttaweb/templates/deform/password.pt +0 -0
  105. {wuttaweb-0.17.1 → wuttaweb-0.18.0}/src/wuttaweb/templates/deform/permissions.pt +0 -0
  106. {wuttaweb-0.17.1 → wuttaweb-0.18.0}/src/wuttaweb/templates/deform/readonly/checkbox.pt +0 -0
  107. {wuttaweb-0.17.1 → wuttaweb-0.18.0}/src/wuttaweb/templates/deform/readonly/filedownload.pt +0 -0
  108. {wuttaweb-0.17.1 → wuttaweb-0.18.0}/src/wuttaweb/templates/deform/readonly/notes.pt +0 -0
  109. {wuttaweb-0.17.1 → wuttaweb-0.18.0}/src/wuttaweb/templates/deform/readonly/objectref.pt +0 -0
  110. {wuttaweb-0.17.1 → wuttaweb-0.18.0}/src/wuttaweb/templates/deform/readonly/permissions.pt +0 -0
  111. {wuttaweb-0.17.1 → wuttaweb-0.18.0}/src/wuttaweb/templates/deform/readonly/rolerefs.pt +0 -0
  112. {wuttaweb-0.17.1 → wuttaweb-0.18.0}/src/wuttaweb/templates/deform/select.pt +0 -0
  113. {wuttaweb-0.17.1 → wuttaweb-0.18.0}/src/wuttaweb/templates/deform/textarea.pt +0 -0
  114. {wuttaweb-0.17.1 → wuttaweb-0.18.0}/src/wuttaweb/templates/deform/textinput.pt +0 -0
  115. {wuttaweb-0.17.1 → wuttaweb-0.18.0}/src/wuttaweb/templates/forbidden.mako +0 -0
  116. {wuttaweb-0.17.1 → wuttaweb-0.18.0}/src/wuttaweb/templates/form.mako +0 -0
  117. {wuttaweb-0.17.1 → wuttaweb-0.18.0}/src/wuttaweb/templates/forms/vue_template.mako +0 -0
  118. {wuttaweb-0.17.1 → wuttaweb-0.18.0}/src/wuttaweb/templates/grids/table_element.mako +0 -0
  119. {wuttaweb-0.17.1 → wuttaweb-0.18.0}/src/wuttaweb/templates/home.mako +0 -0
  120. {wuttaweb-0.17.1 → wuttaweb-0.18.0}/src/wuttaweb/templates/master/configure.mako +0 -0
  121. {wuttaweb-0.17.1 → wuttaweb-0.18.0}/src/wuttaweb/templates/master/create.mako +0 -0
  122. {wuttaweb-0.17.1 → wuttaweb-0.18.0}/src/wuttaweb/templates/master/delete.mako +0 -0
  123. {wuttaweb-0.17.1 → wuttaweb-0.18.0}/src/wuttaweb/templates/master/edit.mako +0 -0
  124. {wuttaweb-0.17.1 → wuttaweb-0.18.0}/src/wuttaweb/templates/master/form.mako +0 -0
  125. {wuttaweb-0.17.1 → wuttaweb-0.18.0}/src/wuttaweb/templates/master/index.mako +0 -0
  126. {wuttaweb-0.17.1 → wuttaweb-0.18.0}/src/wuttaweb/templates/master/view.mako +0 -0
  127. {wuttaweb-0.17.1 → wuttaweb-0.18.0}/src/wuttaweb/templates/notfound.mako +0 -0
  128. {wuttaweb-0.17.1 → wuttaweb-0.18.0}/src/wuttaweb/templates/page.mako +0 -0
  129. {wuttaweb-0.17.1 → wuttaweb-0.18.0}/src/wuttaweb/templates/people/view_profile.mako +0 -0
  130. {wuttaweb-0.17.1 → wuttaweb-0.18.0}/src/wuttaweb/templates/progress.mako +0 -0
  131. {wuttaweb-0.17.1 → wuttaweb-0.18.0}/src/wuttaweb/templates/setup.mako +0 -0
  132. {wuttaweb-0.17.1 → wuttaweb-0.18.0}/src/wuttaweb/templates/upgrade.mako +0 -0
  133. {wuttaweb-0.17.1 → wuttaweb-0.18.0}/src/wuttaweb/templates/upgrades/configure.mako +0 -0
  134. {wuttaweb-0.17.1 → wuttaweb-0.18.0}/src/wuttaweb/templates/upgrades/view.mako +0 -0
  135. {wuttaweb-0.17.1 → wuttaweb-0.18.0}/src/wuttaweb/util.py +0 -0
  136. {wuttaweb-0.17.1 → wuttaweb-0.18.0}/src/wuttaweb/views/__init__.py +0 -0
  137. {wuttaweb-0.17.1 → wuttaweb-0.18.0}/src/wuttaweb/views/auth.py +0 -0
  138. {wuttaweb-0.17.1 → wuttaweb-0.18.0}/src/wuttaweb/views/base.py +0 -0
  139. {wuttaweb-0.17.1 → wuttaweb-0.18.0}/src/wuttaweb/views/batch.py +0 -0
  140. {wuttaweb-0.17.1 → wuttaweb-0.18.0}/src/wuttaweb/views/common.py +0 -0
  141. {wuttaweb-0.17.1 → wuttaweb-0.18.0}/src/wuttaweb/views/essential.py +0 -0
  142. {wuttaweb-0.17.1 → wuttaweb-0.18.0}/src/wuttaweb/views/master.py +0 -0
  143. {wuttaweb-0.17.1 → wuttaweb-0.18.0}/src/wuttaweb/views/people.py +0 -0
  144. {wuttaweb-0.17.1 → wuttaweb-0.18.0}/src/wuttaweb/views/progress.py +0 -0
  145. {wuttaweb-0.17.1 → wuttaweb-0.18.0}/src/wuttaweb/views/roles.py +0 -0
  146. {wuttaweb-0.17.1 → wuttaweb-0.18.0}/src/wuttaweb/views/settings.py +0 -0
  147. {wuttaweb-0.17.1 → wuttaweb-0.18.0}/src/wuttaweb/views/upgrades.py +0 -0
  148. {wuttaweb-0.17.1 → wuttaweb-0.18.0}/src/wuttaweb/views/users.py +0 -0
  149. {wuttaweb-0.17.1 → wuttaweb-0.18.0}/tasks.py +0 -0
  150. {wuttaweb-0.17.1 → wuttaweb-0.18.0}/tests/__init__.py +0 -0
  151. {wuttaweb-0.17.1/tests/db → wuttaweb-0.18.0/tests/cli}/__init__.py +0 -0
  152. {wuttaweb-0.17.1/tests/forms → wuttaweb-0.18.0/tests/db}/__init__.py +0 -0
  153. {wuttaweb-0.17.1 → wuttaweb-0.18.0}/tests/db/test_continuum.py +0 -0
  154. {wuttaweb-0.17.1/tests/grids → wuttaweb-0.18.0/tests/forms}/__init__.py +0 -0
  155. {wuttaweb-0.17.1 → wuttaweb-0.18.0}/tests/forms/test_base.py +0 -0
  156. {wuttaweb-0.17.1 → wuttaweb-0.18.0}/tests/forms/test_schema.py +0 -0
  157. {wuttaweb-0.17.1 → wuttaweb-0.18.0}/tests/forms/test_widgets.py +0 -0
  158. {wuttaweb-0.17.1/tests/views → wuttaweb-0.18.0/tests/grids}/__init__.py +0 -0
  159. {wuttaweb-0.17.1 → wuttaweb-0.18.0}/tests/grids/test_base.py +0 -0
  160. {wuttaweb-0.17.1 → wuttaweb-0.18.0}/tests/libcache/bb_fontawesome_svg_core.js +0 -0
  161. {wuttaweb-0.17.1 → wuttaweb-0.18.0}/tests/libcache/bb_free_solid_svg_icons.js +0 -0
  162. {wuttaweb-0.17.1 → wuttaweb-0.18.0}/tests/libcache/bb_oruga.js +0 -0
  163. {wuttaweb-0.17.1 → wuttaweb-0.18.0}/tests/libcache/bb_oruga_bulma.css +0 -0
  164. {wuttaweb-0.17.1 → wuttaweb-0.18.0}/tests/libcache/bb_oruga_bulma.js +0 -0
  165. {wuttaweb-0.17.1 → wuttaweb-0.18.0}/tests/libcache/bb_vue.js +0 -0
  166. {wuttaweb-0.17.1 → wuttaweb-0.18.0}/tests/libcache/bb_vue_fontawesome.js +0 -0
  167. {wuttaweb-0.17.1 → wuttaweb-0.18.0}/tests/libcache/buefy.css +0 -0
  168. {wuttaweb-0.17.1 → wuttaweb-0.18.0}/tests/libcache/buefy.js +0 -0
  169. {wuttaweb-0.17.1 → wuttaweb-0.18.0}/tests/libcache/fontawesome.js +0 -0
  170. {wuttaweb-0.17.1 → wuttaweb-0.18.0}/tests/libcache/vue.js +0 -0
  171. {wuttaweb-0.17.1 → wuttaweb-0.18.0}/tests/libcache/vue_resource.js +0 -0
  172. {wuttaweb-0.17.1 → wuttaweb-0.18.0}/tests/test_auth.py +0 -0
  173. {wuttaweb-0.17.1 → wuttaweb-0.18.0}/tests/test_handler.py +0 -0
  174. {wuttaweb-0.17.1 → wuttaweb-0.18.0}/tests/test_helpers.py +0 -0
  175. {wuttaweb-0.17.1 → wuttaweb-0.18.0}/tests/test_menus.py +0 -0
  176. {wuttaweb-0.17.1 → wuttaweb-0.18.0}/tests/test_progress.py +0 -0
  177. {wuttaweb-0.17.1 → wuttaweb-0.18.0}/tests/test_static.py +0 -0
  178. {wuttaweb-0.17.1 → wuttaweb-0.18.0}/tests/test_subscribers.py +0 -0
  179. {wuttaweb-0.17.1 → wuttaweb-0.18.0}/tests/test_util.py +0 -0
  180. {wuttaweb-0.17.1 → wuttaweb-0.18.0}/tests/util.py +0 -0
  181. {wuttaweb-0.17.1 → wuttaweb-0.18.0}/tests/views/test___init__.py +0 -0
  182. {wuttaweb-0.17.1 → wuttaweb-0.18.0}/tests/views/test_auth.py +0 -0
  183. {wuttaweb-0.17.1 → wuttaweb-0.18.0}/tests/views/test_base.py +0 -0
  184. {wuttaweb-0.17.1 → wuttaweb-0.18.0}/tests/views/test_batch.py +0 -0
  185. {wuttaweb-0.17.1 → wuttaweb-0.18.0}/tests/views/test_common.py +0 -0
  186. {wuttaweb-0.17.1 → wuttaweb-0.18.0}/tests/views/test_master.py +0 -0
  187. {wuttaweb-0.17.1 → wuttaweb-0.18.0}/tests/views/test_people.py +0 -0
  188. {wuttaweb-0.17.1 → wuttaweb-0.18.0}/tests/views/test_progress.py +0 -0
  189. {wuttaweb-0.17.1 → wuttaweb-0.18.0}/tests/views/test_roles.py +0 -0
  190. {wuttaweb-0.17.1 → wuttaweb-0.18.0}/tests/views/test_settings.py +0 -0
  191. {wuttaweb-0.17.1 → wuttaweb-0.18.0}/tests/views/test_upgrades.py +0 -0
  192. {wuttaweb-0.17.1 → wuttaweb-0.18.0}/tests/views/test_users.py +0 -0
  193. {wuttaweb-0.17.1 → wuttaweb-0.18.0}/tox.ini +0 -0
@@ -5,6 +5,20 @@ 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.18.0 (2024-12-18)
9
+
10
+ ### Feat
11
+
12
+ - add basic support for running in ASGI context
13
+ - add support for running via uvicorn; `wutta webapp` command
14
+
15
+ ## v0.17.2 (2024-12-17)
16
+
17
+ ### Fix
18
+
19
+ - add basic support for grid filters for Date fields
20
+ - fix style bug for grid "add filter" autocomplete
21
+
8
22
  ## v0.17.1 (2024-12-16)
9
23
 
10
24
  ### Fix
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: WuttaWeb
3
- Version: 0.17.1
3
+ Version: 0.18.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: Programming Language :: Python :: 3.11
25
25
  Classifier: Topic :: Internet :: WWW/HTTP
26
26
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
27
27
  Requires-Python: >=3.8
28
+ Requires-Dist: asgiref
28
29
  Requires-Dist: colanderalchemy
29
30
  Requires-Dist: humanize
30
31
  Requires-Dist: markdown
@@ -38,13 +39,14 @@ Requires-Dist: pyramid-tm
38
39
  Requires-Dist: pyramid>=2
39
40
  Requires-Dist: waitress
40
41
  Requires-Dist: webhelpers2
41
- Requires-Dist: wuttjamaican[db]>=0.18.0
42
+ Requires-Dist: wuttjamaican[db]>=0.18.1
42
43
  Requires-Dist: zope-sqlalchemy>=1.5
43
44
  Provides-Extra: continuum
44
45
  Requires-Dist: wutta-continuum; extra == 'continuum'
45
46
  Provides-Extra: docs
46
47
  Requires-Dist: furo; extra == 'docs'
47
48
  Requires-Dist: sphinx; extra == 'docs'
49
+ Requires-Dist: sphinxcontrib-programoutput; extra == 'docs'
48
50
  Provides-Extra: tests
49
51
  Requires-Dist: pytest-cov; extra == 'tests'
50
52
  Requires-Dist: tox; extra == 'tests'
@@ -0,0 +1,6 @@
1
+
2
+ ``wuttaweb.cli``
3
+ ================
4
+
5
+ .. automodule:: wuttaweb.cli
6
+ :members:
@@ -0,0 +1,6 @@
1
+
2
+ ``wuttaweb.cli.webapp``
3
+ =======================
4
+
5
+ .. automodule:: wuttaweb.cli.webapp
6
+ :members:
@@ -21,6 +21,7 @@ extensions = [
21
21
  'sphinx.ext.intersphinx',
22
22
  'sphinx.ext.viewcode',
23
23
  'sphinx.ext.todo',
24
+ 'sphinxcontrib.programoutput',
24
25
  ]
25
26
 
26
27
  templates_path = ['_templates']
@@ -19,6 +19,7 @@ the narrative docs are pretty scant. That will eventually change.
19
19
  :caption: Documentation:
20
20
 
21
21
  glossary
22
+ narr/cli/index
22
23
  narr/templates/index
23
24
 
24
25
  .. toctree::
@@ -28,6 +29,8 @@ the narrative docs are pretty scant. That will eventually change.
28
29
  api/wuttaweb
29
30
  api/wuttaweb.app
30
31
  api/wuttaweb.auth
32
+ api/wuttaweb.cli
33
+ api/wuttaweb.cli.webapp
31
34
  api/wuttaweb.conf
32
35
  api/wuttaweb.db
33
36
  api/wuttaweb.db.continuum
@@ -0,0 +1,28 @@
1
+
2
+ ===================
3
+ Built-in Commands
4
+ ===================
5
+
6
+ Below are the :term:`subcommands <subcommand>` which come with
7
+ WuttaWeb.
8
+
9
+
10
+ .. _wutta-webapp:
11
+
12
+ ``wutta webapp``
13
+ ----------------
14
+
15
+ Run the web app, according to config file(s).
16
+
17
+ This command is a convenience only; under the hood it can run `uvicorn
18
+ <https://www.uvicorn.org/#uvicornrun>`_ but by default will run
19
+ whatever :ref:`pserve <pyramid:pserve_script>` is setup to do (which
20
+ usually is `waitress
21
+ <https://docs.pylonsproject.org/projects/waitress/en/latest/index.html>`_).
22
+
23
+ Ultimately it's all up to config, so run different web apps with
24
+ different config files.
25
+
26
+ Defined in: :mod:`wuttaweb.cli.webapp`
27
+
28
+ .. program-output:: wutta webapp --help
@@ -0,0 +1,14 @@
1
+
2
+ ==============
3
+ Command Line
4
+ ==============
5
+
6
+ There isn't much to the command line for WuttaWeb, but here it is.
7
+
8
+ For more general info about CLI see
9
+ :doc:`wuttjamaican:narr/cli/index`.
10
+
11
+ .. toctree::
12
+ :maxdepth: 2
13
+
14
+ builtin
@@ -6,7 +6,7 @@ build-backend = "hatchling.build"
6
6
 
7
7
  [project]
8
8
  name = "WuttaWeb"
9
- version = "0.17.1"
9
+ version = "0.18.0"
10
10
  description = "Web App for Wutta Framework"
11
11
  readme = "README.md"
12
12
  authors = [{name = "Lance Edgar", email = "lance@wuttaproject.org"}]
@@ -30,6 +30,7 @@ classifiers = [
30
30
  ]
31
31
  requires-python = ">= 3.8"
32
32
  dependencies = [
33
+ "asgiref",
33
34
  "ColanderAlchemy",
34
35
  "humanize",
35
36
  "markdown",
@@ -43,32 +44,32 @@ dependencies = [
43
44
  "pyramid_tm",
44
45
  "waitress",
45
46
  "WebHelpers2",
46
- "WuttJamaican[db]>=0.18.0",
47
+ "WuttJamaican[db]>=0.18.1",
47
48
  "zope.sqlalchemy>=1.5",
48
49
  ]
49
50
 
50
51
 
51
52
  [project.optional-dependencies]
52
53
  continuum = ["Wutta-Continuum"]
53
- docs = ["Sphinx", "furo"]
54
+ docs = ["Sphinx", "furo", "sphinxcontrib-programoutput"]
54
55
  tests = ["pytest-cov", "tox"]
55
56
 
56
57
 
57
58
  [project.entry-points."fanstatic.libraries"]
58
59
  wuttaweb_img = "wuttaweb.static:img"
59
60
 
60
-
61
61
  [project.entry-points."paste.app_factory"]
62
62
  main = "wuttaweb.app:main"
63
63
 
64
-
65
64
  [project.entry-points."wutta.app.providers"]
66
65
  wuttaweb = "wuttaweb.app:WebAppProvider"
67
66
 
68
-
69
67
  [project.entry-points."wutta.config.extensions"]
70
68
  wuttaweb = "wuttaweb.conf:WuttaWebConfigExtension"
71
69
 
70
+ [project.entry-points."wutta.typer_imports"]
71
+ wuttaweb = "wuttaweb.cli"
72
+
72
73
 
73
74
  [project.urls]
74
75
  Homepage = "https://wuttaproject.org/"
@@ -24,17 +24,22 @@
24
24
  Application
25
25
  """
26
26
 
27
+ import logging
27
28
  import os
28
29
 
29
30
  from wuttjamaican.app import AppProvider
30
31
  from wuttjamaican.conf import make_config
31
32
 
33
+ from asgiref.wsgi import WsgiToAsgi
32
34
  from pyramid.config import Configurator
33
35
 
34
36
  import wuttaweb.db
35
37
  from wuttaweb.auth import WuttaSecurityPolicy
36
38
 
37
39
 
40
+ log = logging.getLogger(__name__)
41
+
42
+
38
43
  class WebAppProvider(AppProvider):
39
44
  """
40
45
  The :term:`app provider` for WuttaWeb. This adds some methods to
@@ -87,17 +92,20 @@ def make_wutta_config(settings, config_maker=None, **kwargs):
87
92
 
88
93
  If this config file path cannot be discovered, an error is raised.
89
94
  """
90
- # validate config file path
91
- path = settings.get('wutta.config')
92
- if not path or not os.path.exists(path):
93
- raise ValueError("Please set 'wutta.config' in [app:main] "
94
- "section of config to the path of your "
95
- "config file. Lame, but necessary.")
96
-
97
- # make config, add to settings
98
- config_maker = config_maker or make_config
99
- wutta_config = config_maker(path, **kwargs)
100
- settings['wutta_config'] = wutta_config
95
+ wutta_config = settings.get('wutta_config')
96
+ if not wutta_config:
97
+
98
+ # validate config file path
99
+ path = settings.get('wutta.config')
100
+ if not path or not os.path.exists(path):
101
+ raise ValueError("Please set 'wutta.config' in [app:main] "
102
+ "section of config to the path of your "
103
+ "config file. Lame, but necessary.")
104
+
105
+ # make config, add to settings
106
+ config_maker = config_maker or make_config
107
+ wutta_config = config_maker(path, **kwargs)
108
+ settings['wutta_config'] = wutta_config
101
109
 
102
110
  # configure database sessions
103
111
  if hasattr(wutta_config, 'appdb_engine'):
@@ -148,19 +156,18 @@ def make_pyramid_config(settings):
148
156
 
149
157
  def main(global_config, **settings):
150
158
  """
151
- This function returns a Pyramid WSGI application.
152
-
153
- Typically there is no need to call this function directly, but it
154
- may be configured as the web app entry point like so:
155
-
156
- .. code-block:: ini
157
-
158
- [app:main]
159
- use = egg:wuttaweb
160
-
161
- The app returned by this function is quite minimal, so most apps
162
- will need to define their own ``main()`` function, and use that
163
- instead.
159
+ Make and return the WSGI application, per given settings.
160
+
161
+ This function is designed to be called via Paste, hence it does
162
+ require params and therefore can't be used directly as app factory
163
+ for general WSGI servers. For the latter see
164
+ :func:`make_wsgi_app()` instead.
165
+
166
+ And this *particular* function is not even that useful, it only
167
+ constructs an app with minimal views built-in to WuttaWeb. Most
168
+ apps will define their own ``main()`` function (e.g. as
169
+ ``poser.web.app:main``), similar to this one but with additional
170
+ views and other config.
164
171
  """
165
172
  wutta_config = make_wutta_config(settings)
166
173
  pyramid_config = make_pyramid_config(settings)
@@ -170,3 +177,71 @@ def main(global_config, **settings):
170
177
  pyramid_config.include('wuttaweb.views')
171
178
 
172
179
  return pyramid_config.make_wsgi_app()
180
+
181
+
182
+ def make_wsgi_app(main_app=None, config=None):
183
+ """
184
+ Make and return a WSGI app, using the given Paste app factory.
185
+
186
+ See also :func:`make_asgi_app()` for the ASGI equivalent.
187
+
188
+ This function could be used directly for general WSGI servers
189
+ (e.g. uvicorn), ***if*** you just want the built-in :func:`main()`
190
+ app factory.
191
+
192
+ But most likely you do not, in which case you must define your own
193
+ function and call this one with your preferred app factory::
194
+
195
+ from wuttaweb.app import make_wsgi_app
196
+
197
+ def my_main(global_config, **settings):
198
+ # TODO: build your app
199
+ pass
200
+
201
+ def make_my_wsgi_app():
202
+ return make_wsgi_app(my_main)
203
+
204
+ So ``make_my_wsgi_app()`` could then be used as-is for general
205
+ WSGI servers. However, note that this approach will require
206
+ setting the ``WUTTA_CONFIG_FILES`` environment variable, unless
207
+ running via :ref:`wutta-webapp`.
208
+
209
+ :param main_app: Either a Paste-compatible app factory, or
210
+ :term:`spec` for one. If not specified, the built-in
211
+ :func:`main()` is assumed.
212
+
213
+ :param config: Optional :term:`config object`. If not specified,
214
+ one is created based on ``WUTTA_CONFIG_FILES`` environment
215
+ variable.
216
+ """
217
+ if not config:
218
+ config = make_config()
219
+ app = config.get_app()
220
+
221
+ # extract pyramid settings
222
+ settings = config.get_dict('app:main')
223
+
224
+ # keep same config object
225
+ settings['wutta_config'] = config
226
+
227
+ # determine the app factory
228
+ if isinstance(main_app, str):
229
+ make_wsgi_app = app.load_object(main_app)
230
+ elif callable(main_app):
231
+ make_wsgi_app = main_app
232
+ else:
233
+ raise ValueError("main_app must be spec or callable")
234
+
235
+ # construct a pyramid app "per usual"
236
+ return make_wsgi_app({}, **settings)
237
+
238
+
239
+ def make_asgi_app(main_app=None, config=None):
240
+ """
241
+ Make and return a ASGI app, using the given Paste app factory.
242
+
243
+ This works the same as :func:`make_wsgi_app()` and should be
244
+ called in the same way etc.
245
+ """
246
+ wsgi_app = make_wsgi_app(main_app, config=config)
247
+ return WsgiToAsgi(wsgi_app)
@@ -0,0 +1,28 @@
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
+ WuttaWeb - ``wutta`` subcommands
25
+ """
26
+
27
+ # nb. must bring in all modules for discovery to work
28
+ from . import webapp
@@ -0,0 +1,88 @@
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
+ See also: :ref:`wutta-webapp`
25
+ """
26
+
27
+ import os
28
+ import sys
29
+ from typing_extensions import Annotated
30
+
31
+ import typer
32
+ from pyramid.scripts import pserve
33
+
34
+ from wuttjamaican.cli import wutta_typer
35
+
36
+
37
+ @wutta_typer.command()
38
+ def webapp(
39
+ ctx: typer.Context,
40
+ auto_reload: Annotated[
41
+ bool,
42
+ typer.Option('--reload', '-r',
43
+ help="Auto-reload web app when files change.")] = False,
44
+ ):
45
+ """
46
+ Run the configured web app
47
+ """
48
+ config = ctx.parent.wutta_config
49
+
50
+ # we'll need config file(s) to specify for web app
51
+ if not config.files_read:
52
+ sys.stderr.write("no config files found!\n")
53
+ sys.exit(1)
54
+
55
+ runner = config.get(f'{config.appname}.web.app.runner', default='pserve')
56
+ if runner == 'pserve':
57
+
58
+ # run pserve
59
+ argv = ['pserve', f'file+ini:{config.files_read[0]}']
60
+ if ctx.params['auto_reload']:
61
+ argv.append('--reload')
62
+ pserve.main(argv=argv)
63
+
64
+ elif runner == 'uvicorn':
65
+
66
+ import uvicorn
67
+
68
+ # need service details from config
69
+ spec = config.require(f'{config.appname}.web.app.spec')
70
+ kw = {
71
+ 'host': config.get(f'{config.appname}.web.app.host', default='127.0.0.1'),
72
+ 'port': config.get_int(f'{config.appname}.web.app.port', default=8000),
73
+ 'reload': ctx.params['auto_reload'],
74
+ 'reload_dirs': config.get_list(f'{config.appname}.web.app.reload_dirs'),
75
+ 'factory': config.get_bool(f'{config.appname}.web.app.factory', default=False),
76
+ 'interface': config.get(f'{config.appname}.web.app.interface', default='auto'),
77
+ }
78
+
79
+ # also must inject our config files to env, since there is no
80
+ # other way to specify when running via uvicorn
81
+ os.environ['WUTTA_CONFIG_FILES'] = os.pathsep.join(config.files_read)
82
+
83
+ # run uvicorn
84
+ uvicorn.run(spec, **kw)
85
+
86
+ else:
87
+ sys.stderr.write(f"unknown web app runner: {runner}\n")
88
+ sys.exit(2)
@@ -1976,6 +1976,7 @@ class Grid:
1976
1976
  for filtr in self.filters.values():
1977
1977
  filters.append({
1978
1978
  'key': filtr.key,
1979
+ 'data_type': filtr.data_type,
1979
1980
  'active': filtr.active,
1980
1981
  'visible': filtr.active,
1981
1982
  'verbs': filtr.get_verbs(),
@@ -24,6 +24,7 @@
24
24
  Grid Filters
25
25
  """
26
26
 
27
+ import datetime
27
28
  import logging
28
29
 
29
30
  import sqlalchemy as sa
@@ -69,6 +70,18 @@ class GridFilter:
69
70
 
70
71
  Display label for the filter field.
71
72
 
73
+ .. attribute:: data_type
74
+
75
+ Simplistic "data type" which the filter supports. So far this
76
+ will be one of:
77
+
78
+ * ``'string'``
79
+ * ``'date'``
80
+
81
+ Note that this mainly applies to the "value input" used by the
82
+ filter. There is no data type for boolean since it does not
83
+ need a value input; the verb is enough.
84
+
72
85
  .. attribute:: active
73
86
 
74
87
  Boolean indicating whether the filter is currently active.
@@ -110,12 +123,18 @@ class GridFilter:
110
123
 
111
124
  See also :attr:`default_verb`.
112
125
  """
126
+ data_type = 'string'
113
127
  default_verbs = ['equal', 'not_equal']
114
128
 
115
129
  default_verb_labels = {
116
130
  'is_any': "is any",
117
131
  'equal': "equal to",
118
132
  'not_equal': "not equal to",
133
+ 'greater_than': "greater than",
134
+ 'greater_equal': "greater than or equal to",
135
+ 'less_than': "less than",
136
+ 'less_equal': "less than or equal to",
137
+ # 'between': "between",
119
138
  'is_true': "is true",
120
139
  'is_false': "is false",
121
140
  'is_false_null': "is false or null",
@@ -343,6 +362,42 @@ class AlchemyFilter(GridFilter):
343
362
  self.model_property != value,
344
363
  ))
345
364
 
365
+ def filter_greater_than(self, query, value):
366
+ """
367
+ Filter data with a greater than (``>``) condition.
368
+ """
369
+ value = self.coerce_value(value)
370
+ if value is None:
371
+ return query
372
+ return query.filter(self.model_property > value)
373
+
374
+ def filter_greater_equal(self, query, value):
375
+ """
376
+ Filter data with a greater than or equal (``>=``) condition.
377
+ """
378
+ value = self.coerce_value(value)
379
+ if value is None:
380
+ return query
381
+ return query.filter(self.model_property >= value)
382
+
383
+ def filter_less_than(self, query, value):
384
+ """
385
+ Filter data with a less than (``<``) condition.
386
+ """
387
+ value = self.coerce_value(value)
388
+ if value is None:
389
+ return query
390
+ return query.filter(self.model_property < value)
391
+
392
+ def filter_less_equal(self, query, value):
393
+ """
394
+ Filter data with a less than or equal (``<=``) condition.
395
+ """
396
+ value = self.coerce_value(value)
397
+ if value is None:
398
+ return query
399
+ return query.filter(self.model_property <= value)
400
+
346
401
  def filter_is_null(self, query, value):
347
402
  """
348
403
  Filter data with an ``IS NULL`` query. The value is ignored.
@@ -467,9 +522,52 @@ class BooleanAlchemyFilter(AlchemyFilter):
467
522
  self.model_property == None))
468
523
 
469
524
 
525
+ class DateAlchemyFilter(AlchemyFilter):
526
+ """
527
+ SQLAlchemy filter option for a
528
+ :class:`sqlalchemy:sqlalchemy.types.Date` column.
529
+
530
+ Subclass of :class:`AlchemyFilter`.
531
+ """
532
+ data_type = 'date'
533
+ default_verbs = [
534
+ 'equal',
535
+ 'not_equal',
536
+ 'greater_than',
537
+ 'greater_equal',
538
+ 'less_than',
539
+ 'less_equal',
540
+ # 'between',
541
+ ]
542
+
543
+ default_verb_labels = {
544
+ 'equal': "on",
545
+ 'not_equal': "not on",
546
+ 'greater_than': "after",
547
+ 'greater_equal': "on or after",
548
+ 'less_than': "before",
549
+ 'less_equal': "on or before",
550
+ # 'between': "between",
551
+ }
552
+
553
+ def coerce_value(self, value):
554
+ """ """
555
+ if value:
556
+ if isinstance(value, datetime.date):
557
+ return value
558
+
559
+ try:
560
+ dt = datetime.datetime.strptime(value, '%Y-%m-%d')
561
+ except ValueError:
562
+ log.warning("invalid date value: %s", value)
563
+ else:
564
+ return dt.date()
565
+
566
+
470
567
  default_sqlalchemy_filters = {
471
568
  None: AlchemyFilter,
472
569
  sa.String: StringAlchemyFilter,
473
570
  sa.Text: StringAlchemyFilter,
474
571
  sa.Boolean: BooleanAlchemyFilter,
572
+ sa.Date: DateAlchemyFilter,
475
573
  }