WuttaWeb 0.17.1__tar.gz → 0.17.2__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 (184) hide show
  1. {wuttaweb-0.17.1 → wuttaweb-0.17.2}/CHANGELOG.md +7 -0
  2. {wuttaweb-0.17.1 → wuttaweb-0.17.2}/PKG-INFO +1 -1
  3. {wuttaweb-0.17.1 → wuttaweb-0.17.2}/pyproject.toml +1 -1
  4. {wuttaweb-0.17.1 → wuttaweb-0.17.2}/src/wuttaweb/grids/base.py +1 -0
  5. {wuttaweb-0.17.1 → wuttaweb-0.17.2}/src/wuttaweb/grids/filters.py +98 -0
  6. {wuttaweb-0.17.1 → wuttaweb-0.17.2}/src/wuttaweb/templates/grids/vue_template.mako +24 -23
  7. {wuttaweb-0.17.1 → wuttaweb-0.17.2}/src/wuttaweb/templates/wutta-components.mako +67 -2
  8. {wuttaweb-0.17.1 → wuttaweb-0.17.2}/tests/grids/test_filters.py +156 -0
  9. {wuttaweb-0.17.1 → wuttaweb-0.17.2}/.gitignore +0 -0
  10. {wuttaweb-0.17.1 → wuttaweb-0.17.2}/COPYING.txt +0 -0
  11. {wuttaweb-0.17.1 → wuttaweb-0.17.2}/README.md +0 -0
  12. {wuttaweb-0.17.1 → wuttaweb-0.17.2}/docs/Makefile +0 -0
  13. {wuttaweb-0.17.1 → wuttaweb-0.17.2}/docs/_static/.keepme +0 -0
  14. {wuttaweb-0.17.1 → wuttaweb-0.17.2}/docs/api/wuttaweb.app.rst +0 -0
  15. {wuttaweb-0.17.1 → wuttaweb-0.17.2}/docs/api/wuttaweb.auth.rst +0 -0
  16. {wuttaweb-0.17.1 → wuttaweb-0.17.2}/docs/api/wuttaweb.conf.rst +0 -0
  17. {wuttaweb-0.17.1 → wuttaweb-0.17.2}/docs/api/wuttaweb.db.continuum.rst +0 -0
  18. {wuttaweb-0.17.1 → wuttaweb-0.17.2}/docs/api/wuttaweb.db.rst +0 -0
  19. {wuttaweb-0.17.1 → wuttaweb-0.17.2}/docs/api/wuttaweb.db.sess.rst +0 -0
  20. {wuttaweb-0.17.1 → wuttaweb-0.17.2}/docs/api/wuttaweb.forms.base.rst +0 -0
  21. {wuttaweb-0.17.1 → wuttaweb-0.17.2}/docs/api/wuttaweb.forms.rst +0 -0
  22. {wuttaweb-0.17.1 → wuttaweb-0.17.2}/docs/api/wuttaweb.forms.schema.rst +0 -0
  23. {wuttaweb-0.17.1 → wuttaweb-0.17.2}/docs/api/wuttaweb.forms.widgets.rst +0 -0
  24. {wuttaweb-0.17.1 → wuttaweb-0.17.2}/docs/api/wuttaweb.grids.base.rst +0 -0
  25. {wuttaweb-0.17.1 → wuttaweb-0.17.2}/docs/api/wuttaweb.grids.filters.rst +0 -0
  26. {wuttaweb-0.17.1 → wuttaweb-0.17.2}/docs/api/wuttaweb.grids.rst +0 -0
  27. {wuttaweb-0.17.1 → wuttaweb-0.17.2}/docs/api/wuttaweb.handler.rst +0 -0
  28. {wuttaweb-0.17.1 → wuttaweb-0.17.2}/docs/api/wuttaweb.helpers.rst +0 -0
  29. {wuttaweb-0.17.1 → wuttaweb-0.17.2}/docs/api/wuttaweb.menus.rst +0 -0
  30. {wuttaweb-0.17.1 → wuttaweb-0.17.2}/docs/api/wuttaweb.progress.rst +0 -0
  31. {wuttaweb-0.17.1 → wuttaweb-0.17.2}/docs/api/wuttaweb.rst +0 -0
  32. {wuttaweb-0.17.1 → wuttaweb-0.17.2}/docs/api/wuttaweb.static.rst +0 -0
  33. {wuttaweb-0.17.1 → wuttaweb-0.17.2}/docs/api/wuttaweb.subscribers.rst +0 -0
  34. {wuttaweb-0.17.1 → wuttaweb-0.17.2}/docs/api/wuttaweb.util.rst +0 -0
  35. {wuttaweb-0.17.1 → wuttaweb-0.17.2}/docs/api/wuttaweb.views.auth.rst +0 -0
  36. {wuttaweb-0.17.1 → wuttaweb-0.17.2}/docs/api/wuttaweb.views.base.rst +0 -0
  37. {wuttaweb-0.17.1 → wuttaweb-0.17.2}/docs/api/wuttaweb.views.batch.rst +0 -0
  38. {wuttaweb-0.17.1 → wuttaweb-0.17.2}/docs/api/wuttaweb.views.common.rst +0 -0
  39. {wuttaweb-0.17.1 → wuttaweb-0.17.2}/docs/api/wuttaweb.views.essential.rst +0 -0
  40. {wuttaweb-0.17.1 → wuttaweb-0.17.2}/docs/api/wuttaweb.views.master.rst +0 -0
  41. {wuttaweb-0.17.1 → wuttaweb-0.17.2}/docs/api/wuttaweb.views.people.rst +0 -0
  42. {wuttaweb-0.17.1 → wuttaweb-0.17.2}/docs/api/wuttaweb.views.progress.rst +0 -0
  43. {wuttaweb-0.17.1 → wuttaweb-0.17.2}/docs/api/wuttaweb.views.roles.rst +0 -0
  44. {wuttaweb-0.17.1 → wuttaweb-0.17.2}/docs/api/wuttaweb.views.rst +0 -0
  45. {wuttaweb-0.17.1 → wuttaweb-0.17.2}/docs/api/wuttaweb.views.settings.rst +0 -0
  46. {wuttaweb-0.17.1 → wuttaweb-0.17.2}/docs/api/wuttaweb.views.upgrades.rst +0 -0
  47. {wuttaweb-0.17.1 → wuttaweb-0.17.2}/docs/api/wuttaweb.views.users.rst +0 -0
  48. {wuttaweb-0.17.1 → wuttaweb-0.17.2}/docs/conf.py +0 -0
  49. {wuttaweb-0.17.1 → wuttaweb-0.17.2}/docs/glossary.rst +0 -0
  50. {wuttaweb-0.17.1 → wuttaweb-0.17.2}/docs/index.rst +0 -0
  51. {wuttaweb-0.17.1 → wuttaweb-0.17.2}/docs/make.bat +0 -0
  52. {wuttaweb-0.17.1 → wuttaweb-0.17.2}/docs/narr/templates/base.rst +0 -0
  53. {wuttaweb-0.17.1 → wuttaweb-0.17.2}/docs/narr/templates/index.rst +0 -0
  54. {wuttaweb-0.17.1 → wuttaweb-0.17.2}/docs/narr/templates/lookup.rst +0 -0
  55. {wuttaweb-0.17.1 → wuttaweb-0.17.2}/docs/narr/templates/overview.rst +0 -0
  56. {wuttaweb-0.17.1 → wuttaweb-0.17.2}/src/wuttaweb/__init__.py +0 -0
  57. {wuttaweb-0.17.1 → wuttaweb-0.17.2}/src/wuttaweb/_version.py +0 -0
  58. {wuttaweb-0.17.1 → wuttaweb-0.17.2}/src/wuttaweb/app.py +0 -0
  59. {wuttaweb-0.17.1 → wuttaweb-0.17.2}/src/wuttaweb/auth.py +0 -0
  60. {wuttaweb-0.17.1 → wuttaweb-0.17.2}/src/wuttaweb/conf.py +0 -0
  61. {wuttaweb-0.17.1 → wuttaweb-0.17.2}/src/wuttaweb/db/__init__.py +0 -0
  62. {wuttaweb-0.17.1 → wuttaweb-0.17.2}/src/wuttaweb/db/continuum.py +0 -0
  63. {wuttaweb-0.17.1 → wuttaweb-0.17.2}/src/wuttaweb/db/sess.py +0 -0
  64. {wuttaweb-0.17.1 → wuttaweb-0.17.2}/src/wuttaweb/email/templates/feedback.html.mako +0 -0
  65. {wuttaweb-0.17.1 → wuttaweb-0.17.2}/src/wuttaweb/email/templates/feedback.txt.mako +0 -0
  66. {wuttaweb-0.17.1 → wuttaweb-0.17.2}/src/wuttaweb/forms/__init__.py +0 -0
  67. {wuttaweb-0.17.1 → wuttaweb-0.17.2}/src/wuttaweb/forms/base.py +0 -0
  68. {wuttaweb-0.17.1 → wuttaweb-0.17.2}/src/wuttaweb/forms/schema.py +0 -0
  69. {wuttaweb-0.17.1 → wuttaweb-0.17.2}/src/wuttaweb/forms/widgets.py +0 -0
  70. {wuttaweb-0.17.1 → wuttaweb-0.17.2}/src/wuttaweb/grids/__init__.py +0 -0
  71. {wuttaweb-0.17.1 → wuttaweb-0.17.2}/src/wuttaweb/handler.py +0 -0
  72. {wuttaweb-0.17.1 → wuttaweb-0.17.2}/src/wuttaweb/helpers.py +0 -0
  73. {wuttaweb-0.17.1 → wuttaweb-0.17.2}/src/wuttaweb/menus.py +0 -0
  74. {wuttaweb-0.17.1 → wuttaweb-0.17.2}/src/wuttaweb/progress.py +0 -0
  75. {wuttaweb-0.17.1 → wuttaweb-0.17.2}/src/wuttaweb/static/__init__.py +0 -0
  76. {wuttaweb-0.17.1 → wuttaweb-0.17.2}/src/wuttaweb/static/img/favicon.ico +0 -0
  77. {wuttaweb-0.17.1 → wuttaweb-0.17.2}/src/wuttaweb/static/img/logo.png +0 -0
  78. {wuttaweb-0.17.1 → wuttaweb-0.17.2}/src/wuttaweb/static/img/testing.png +0 -0
  79. {wuttaweb-0.17.1 → wuttaweb-0.17.2}/src/wuttaweb/subscribers.py +0 -0
  80. {wuttaweb-0.17.1 → wuttaweb-0.17.2}/src/wuttaweb/templates/appinfo/configure.mako +0 -0
  81. {wuttaweb-0.17.1 → wuttaweb-0.17.2}/src/wuttaweb/templates/appinfo/index.mako +0 -0
  82. {wuttaweb-0.17.1 → wuttaweb-0.17.2}/src/wuttaweb/templates/auth/change_password.mako +0 -0
  83. {wuttaweb-0.17.1 → wuttaweb-0.17.2}/src/wuttaweb/templates/auth/login.mako +0 -0
  84. {wuttaweb-0.17.1 → wuttaweb-0.17.2}/src/wuttaweb/templates/base.mako +0 -0
  85. {wuttaweb-0.17.1 → wuttaweb-0.17.2}/src/wuttaweb/templates/base_meta.mako +0 -0
  86. {wuttaweb-0.17.1 → wuttaweb-0.17.2}/src/wuttaweb/templates/batch/view.mako +0 -0
  87. {wuttaweb-0.17.1 → wuttaweb-0.17.2}/src/wuttaweb/templates/configure.mako +0 -0
  88. {wuttaweb-0.17.1 → wuttaweb-0.17.2}/src/wuttaweb/templates/deform/checkbox.pt +0 -0
  89. {wuttaweb-0.17.1 → wuttaweb-0.17.2}/src/wuttaweb/templates/deform/checkbox_choice.pt +0 -0
  90. {wuttaweb-0.17.1 → wuttaweb-0.17.2}/src/wuttaweb/templates/deform/checked_password.pt +0 -0
  91. {wuttaweb-0.17.1 → wuttaweb-0.17.2}/src/wuttaweb/templates/deform/dateinput.pt +0 -0
  92. {wuttaweb-0.17.1 → wuttaweb-0.17.2}/src/wuttaweb/templates/deform/datetimeinput.pt +0 -0
  93. {wuttaweb-0.17.1 → wuttaweb-0.17.2}/src/wuttaweb/templates/deform/moneyinput.pt +0 -0
  94. {wuttaweb-0.17.1 → wuttaweb-0.17.2}/src/wuttaweb/templates/deform/password.pt +0 -0
  95. {wuttaweb-0.17.1 → wuttaweb-0.17.2}/src/wuttaweb/templates/deform/permissions.pt +0 -0
  96. {wuttaweb-0.17.1 → wuttaweb-0.17.2}/src/wuttaweb/templates/deform/readonly/checkbox.pt +0 -0
  97. {wuttaweb-0.17.1 → wuttaweb-0.17.2}/src/wuttaweb/templates/deform/readonly/filedownload.pt +0 -0
  98. {wuttaweb-0.17.1 → wuttaweb-0.17.2}/src/wuttaweb/templates/deform/readonly/notes.pt +0 -0
  99. {wuttaweb-0.17.1 → wuttaweb-0.17.2}/src/wuttaweb/templates/deform/readonly/objectref.pt +0 -0
  100. {wuttaweb-0.17.1 → wuttaweb-0.17.2}/src/wuttaweb/templates/deform/readonly/permissions.pt +0 -0
  101. {wuttaweb-0.17.1 → wuttaweb-0.17.2}/src/wuttaweb/templates/deform/readonly/rolerefs.pt +0 -0
  102. {wuttaweb-0.17.1 → wuttaweb-0.17.2}/src/wuttaweb/templates/deform/select.pt +0 -0
  103. {wuttaweb-0.17.1 → wuttaweb-0.17.2}/src/wuttaweb/templates/deform/textarea.pt +0 -0
  104. {wuttaweb-0.17.1 → wuttaweb-0.17.2}/src/wuttaweb/templates/deform/textinput.pt +0 -0
  105. {wuttaweb-0.17.1 → wuttaweb-0.17.2}/src/wuttaweb/templates/forbidden.mako +0 -0
  106. {wuttaweb-0.17.1 → wuttaweb-0.17.2}/src/wuttaweb/templates/form.mako +0 -0
  107. {wuttaweb-0.17.1 → wuttaweb-0.17.2}/src/wuttaweb/templates/forms/vue_template.mako +0 -0
  108. {wuttaweb-0.17.1 → wuttaweb-0.17.2}/src/wuttaweb/templates/grids/table_element.mako +0 -0
  109. {wuttaweb-0.17.1 → wuttaweb-0.17.2}/src/wuttaweb/templates/home.mako +0 -0
  110. {wuttaweb-0.17.1 → wuttaweb-0.17.2}/src/wuttaweb/templates/master/configure.mako +0 -0
  111. {wuttaweb-0.17.1 → wuttaweb-0.17.2}/src/wuttaweb/templates/master/create.mako +0 -0
  112. {wuttaweb-0.17.1 → wuttaweb-0.17.2}/src/wuttaweb/templates/master/delete.mako +0 -0
  113. {wuttaweb-0.17.1 → wuttaweb-0.17.2}/src/wuttaweb/templates/master/edit.mako +0 -0
  114. {wuttaweb-0.17.1 → wuttaweb-0.17.2}/src/wuttaweb/templates/master/form.mako +0 -0
  115. {wuttaweb-0.17.1 → wuttaweb-0.17.2}/src/wuttaweb/templates/master/index.mako +0 -0
  116. {wuttaweb-0.17.1 → wuttaweb-0.17.2}/src/wuttaweb/templates/master/view.mako +0 -0
  117. {wuttaweb-0.17.1 → wuttaweb-0.17.2}/src/wuttaweb/templates/notfound.mako +0 -0
  118. {wuttaweb-0.17.1 → wuttaweb-0.17.2}/src/wuttaweb/templates/page.mako +0 -0
  119. {wuttaweb-0.17.1 → wuttaweb-0.17.2}/src/wuttaweb/templates/people/view_profile.mako +0 -0
  120. {wuttaweb-0.17.1 → wuttaweb-0.17.2}/src/wuttaweb/templates/progress.mako +0 -0
  121. {wuttaweb-0.17.1 → wuttaweb-0.17.2}/src/wuttaweb/templates/setup.mako +0 -0
  122. {wuttaweb-0.17.1 → wuttaweb-0.17.2}/src/wuttaweb/templates/upgrade.mako +0 -0
  123. {wuttaweb-0.17.1 → wuttaweb-0.17.2}/src/wuttaweb/templates/upgrades/configure.mako +0 -0
  124. {wuttaweb-0.17.1 → wuttaweb-0.17.2}/src/wuttaweb/templates/upgrades/view.mako +0 -0
  125. {wuttaweb-0.17.1 → wuttaweb-0.17.2}/src/wuttaweb/util.py +0 -0
  126. {wuttaweb-0.17.1 → wuttaweb-0.17.2}/src/wuttaweb/views/__init__.py +0 -0
  127. {wuttaweb-0.17.1 → wuttaweb-0.17.2}/src/wuttaweb/views/auth.py +0 -0
  128. {wuttaweb-0.17.1 → wuttaweb-0.17.2}/src/wuttaweb/views/base.py +0 -0
  129. {wuttaweb-0.17.1 → wuttaweb-0.17.2}/src/wuttaweb/views/batch.py +0 -0
  130. {wuttaweb-0.17.1 → wuttaweb-0.17.2}/src/wuttaweb/views/common.py +0 -0
  131. {wuttaweb-0.17.1 → wuttaweb-0.17.2}/src/wuttaweb/views/essential.py +0 -0
  132. {wuttaweb-0.17.1 → wuttaweb-0.17.2}/src/wuttaweb/views/master.py +0 -0
  133. {wuttaweb-0.17.1 → wuttaweb-0.17.2}/src/wuttaweb/views/people.py +0 -0
  134. {wuttaweb-0.17.1 → wuttaweb-0.17.2}/src/wuttaweb/views/progress.py +0 -0
  135. {wuttaweb-0.17.1 → wuttaweb-0.17.2}/src/wuttaweb/views/roles.py +0 -0
  136. {wuttaweb-0.17.1 → wuttaweb-0.17.2}/src/wuttaweb/views/settings.py +0 -0
  137. {wuttaweb-0.17.1 → wuttaweb-0.17.2}/src/wuttaweb/views/upgrades.py +0 -0
  138. {wuttaweb-0.17.1 → wuttaweb-0.17.2}/src/wuttaweb/views/users.py +0 -0
  139. {wuttaweb-0.17.1 → wuttaweb-0.17.2}/tasks.py +0 -0
  140. {wuttaweb-0.17.1 → wuttaweb-0.17.2}/tests/__init__.py +0 -0
  141. {wuttaweb-0.17.1 → wuttaweb-0.17.2}/tests/db/__init__.py +0 -0
  142. {wuttaweb-0.17.1 → wuttaweb-0.17.2}/tests/db/test_continuum.py +0 -0
  143. {wuttaweb-0.17.1 → wuttaweb-0.17.2}/tests/forms/__init__.py +0 -0
  144. {wuttaweb-0.17.1 → wuttaweb-0.17.2}/tests/forms/test_base.py +0 -0
  145. {wuttaweb-0.17.1 → wuttaweb-0.17.2}/tests/forms/test_schema.py +0 -0
  146. {wuttaweb-0.17.1 → wuttaweb-0.17.2}/tests/forms/test_widgets.py +0 -0
  147. {wuttaweb-0.17.1 → wuttaweb-0.17.2}/tests/grids/__init__.py +0 -0
  148. {wuttaweb-0.17.1 → wuttaweb-0.17.2}/tests/grids/test_base.py +0 -0
  149. {wuttaweb-0.17.1 → wuttaweb-0.17.2}/tests/libcache/bb_fontawesome_svg_core.js +0 -0
  150. {wuttaweb-0.17.1 → wuttaweb-0.17.2}/tests/libcache/bb_free_solid_svg_icons.js +0 -0
  151. {wuttaweb-0.17.1 → wuttaweb-0.17.2}/tests/libcache/bb_oruga.js +0 -0
  152. {wuttaweb-0.17.1 → wuttaweb-0.17.2}/tests/libcache/bb_oruga_bulma.css +0 -0
  153. {wuttaweb-0.17.1 → wuttaweb-0.17.2}/tests/libcache/bb_oruga_bulma.js +0 -0
  154. {wuttaweb-0.17.1 → wuttaweb-0.17.2}/tests/libcache/bb_vue.js +0 -0
  155. {wuttaweb-0.17.1 → wuttaweb-0.17.2}/tests/libcache/bb_vue_fontawesome.js +0 -0
  156. {wuttaweb-0.17.1 → wuttaweb-0.17.2}/tests/libcache/buefy.css +0 -0
  157. {wuttaweb-0.17.1 → wuttaweb-0.17.2}/tests/libcache/buefy.js +0 -0
  158. {wuttaweb-0.17.1 → wuttaweb-0.17.2}/tests/libcache/fontawesome.js +0 -0
  159. {wuttaweb-0.17.1 → wuttaweb-0.17.2}/tests/libcache/vue.js +0 -0
  160. {wuttaweb-0.17.1 → wuttaweb-0.17.2}/tests/libcache/vue_resource.js +0 -0
  161. {wuttaweb-0.17.1 → wuttaweb-0.17.2}/tests/test_app.py +0 -0
  162. {wuttaweb-0.17.1 → wuttaweb-0.17.2}/tests/test_auth.py +0 -0
  163. {wuttaweb-0.17.1 → wuttaweb-0.17.2}/tests/test_handler.py +0 -0
  164. {wuttaweb-0.17.1 → wuttaweb-0.17.2}/tests/test_helpers.py +0 -0
  165. {wuttaweb-0.17.1 → wuttaweb-0.17.2}/tests/test_menus.py +0 -0
  166. {wuttaweb-0.17.1 → wuttaweb-0.17.2}/tests/test_progress.py +0 -0
  167. {wuttaweb-0.17.1 → wuttaweb-0.17.2}/tests/test_static.py +0 -0
  168. {wuttaweb-0.17.1 → wuttaweb-0.17.2}/tests/test_subscribers.py +0 -0
  169. {wuttaweb-0.17.1 → wuttaweb-0.17.2}/tests/test_util.py +0 -0
  170. {wuttaweb-0.17.1 → wuttaweb-0.17.2}/tests/util.py +0 -0
  171. {wuttaweb-0.17.1 → wuttaweb-0.17.2}/tests/views/__init__.py +0 -0
  172. {wuttaweb-0.17.1 → wuttaweb-0.17.2}/tests/views/test___init__.py +0 -0
  173. {wuttaweb-0.17.1 → wuttaweb-0.17.2}/tests/views/test_auth.py +0 -0
  174. {wuttaweb-0.17.1 → wuttaweb-0.17.2}/tests/views/test_base.py +0 -0
  175. {wuttaweb-0.17.1 → wuttaweb-0.17.2}/tests/views/test_batch.py +0 -0
  176. {wuttaweb-0.17.1 → wuttaweb-0.17.2}/tests/views/test_common.py +0 -0
  177. {wuttaweb-0.17.1 → wuttaweb-0.17.2}/tests/views/test_master.py +0 -0
  178. {wuttaweb-0.17.1 → wuttaweb-0.17.2}/tests/views/test_people.py +0 -0
  179. {wuttaweb-0.17.1 → wuttaweb-0.17.2}/tests/views/test_progress.py +0 -0
  180. {wuttaweb-0.17.1 → wuttaweb-0.17.2}/tests/views/test_roles.py +0 -0
  181. {wuttaweb-0.17.1 → wuttaweb-0.17.2}/tests/views/test_settings.py +0 -0
  182. {wuttaweb-0.17.1 → wuttaweb-0.17.2}/tests/views/test_upgrades.py +0 -0
  183. {wuttaweb-0.17.1 → wuttaweb-0.17.2}/tests/views/test_users.py +0 -0
  184. {wuttaweb-0.17.1 → wuttaweb-0.17.2}/tox.ini +0 -0
@@ -5,6 +5,13 @@ 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.17.2 (2024-12-17)
9
+
10
+ ### Fix
11
+
12
+ - add basic support for grid filters for Date fields
13
+ - fix style bug for grid "add filter" autocomplete
14
+
8
15
  ## v0.17.1 (2024-12-16)
9
16
 
10
17
  ### Fix
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: WuttaWeb
3
- Version: 0.17.1
3
+ Version: 0.17.2
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
@@ -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.17.2"
10
10
  description = "Web App for Wutta Framework"
11
11
  readme = "README.md"
12
12
  authors = [{name = "Lance Edgar", email = "lance@wuttaproject.org"}]
@@ -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
  }
@@ -14,7 +14,7 @@
14
14
  :is-small="smallFilters"
15
15
  ref="gridFilters" />
16
16
 
17
- <div class="buttons">
17
+ <div style="display: flex; gap: 0.5rem;">
18
18
 
19
19
  <b-button @click="copyDirectLink()"
20
20
  title="Copy grid link to clipboard"
@@ -30,28 +30,29 @@
30
30
  Apply Filters
31
31
  </b-button>
32
32
 
33
- <b-button v-if="!addFilterShow"
34
- @click="addFilterInit()"
35
- icon-pack="fas"
36
- icon-left="plus"
37
- :size="smallFilters ? 'is-small' : null">
38
- Add Filter
39
- </b-button>
40
-
41
- <b-autocomplete v-if="addFilterShow"
42
- ref="addFilterAutocomplete"
43
- :data="addFilterChoices"
44
- v-model="addFilterTerm"
45
- placeholder="Add Filter"
46
- field="key"
47
- :custom-formatter="formatAddFilterItem"
48
- open-on-focus
49
- keep-first
50
- clearable
51
- clear-on-select
52
- @select="addFilterSelect"
53
- icon-pack="fas"
54
- :size="smallFilters ? 'is-small' : null" />
33
+ <div>
34
+ <b-button v-if="!addFilterShow"
35
+ @click="addFilterInit()"
36
+ icon-pack="fas"
37
+ icon-left="plus"
38
+ :size="smallFilters ? 'is-small' : null">
39
+ Add Filter
40
+ </b-button>
41
+ <b-autocomplete v-if="addFilterShow"
42
+ ref="addFilterAutocomplete"
43
+ :data="addFilterChoices"
44
+ v-model="addFilterTerm"
45
+ placeholder="Add Filter"
46
+ field="key"
47
+ :custom-formatter="formatAddFilterItem"
48
+ open-on-focus
49
+ keep-first
50
+ clearable
51
+ clear-on-select
52
+ @select="addFilterSelect"
53
+ icon-pack="fas"
54
+ :size="smallFilters ? 'is-small' : null" />
55
+ </div>
55
56
 
56
57
  <b-button @click="resetView()"
57
58
  :disabled="viewResetting"
@@ -6,6 +6,7 @@
6
6
  ${self.make_wutta_timepicker_component()}
7
7
  ${self.make_wutta_filter_component()}
8
8
  ${self.make_wutta_filter_value_component()}
9
+ ${self.make_wutta_filter_date_value_component()}
9
10
  ${self.make_wutta_tool_panel_component()}
10
11
  </%def>
11
12
 
@@ -155,11 +156,14 @@
155
156
  <%def name="make_wutta_datepicker_component()">
156
157
  <script type="text/x-template" id="wutta-datepicker-template">
157
158
  <b-datepicker :name="name"
159
+ ref="datepicker"
158
160
  :editable="editable"
159
161
  icon-pack="fas"
160
162
  icon="calendar-alt"
161
163
  :date-formatter="formatDate"
162
- :value="buefyValue" />
164
+ :value="buefyValue"
165
+ :size="size"
166
+ @input="val => $emit('input', val)" />
163
167
  </script>
164
168
  <script>
165
169
  const WuttaDatepicker = {
@@ -167,6 +171,7 @@
167
171
  props: {
168
172
  name: String,
169
173
  value: String,
174
+ size: String,
170
175
  editable: {
171
176
  type: Boolean,
172
177
  default: true,
@@ -179,6 +184,10 @@
179
184
  },
180
185
  methods: {
181
186
 
187
+ focus() {
188
+ this.$refs.datepicker.focus()
189
+ },
190
+
182
191
  formatDate(date) {
183
192
  if (date === null) {
184
193
  return null
@@ -324,7 +333,16 @@
324
333
  icon-pack="fas"
325
334
  :size="isSmall ? 'is-small' : null" />
326
335
 
327
- <wutta-filter-value v-model="filter.value"
336
+ ## nb. only *ONE* of the following is used, per filter data type
337
+
338
+ <wutta-filter-date-value v-if="filter.data_type == 'date'"
339
+ v-model="filter.value"
340
+ ref="filterValue"
341
+ v-show="valuedVerb()"
342
+ :is-small="isSmall" />
343
+
344
+ <wutta-filter-value v-else
345
+ v-model="filter.value"
328
346
  ref="filterValue"
329
347
  v-show="valuedVerb()"
330
348
  :is-small="isSmall" />
@@ -479,6 +497,53 @@
479
497
  </script>
480
498
  </%def>
481
499
 
500
+ <%def name="make_wutta_filter_date_value_component()">
501
+ <script type="text/x-template" id="wutta-filter-date-value-template">
502
+ <div class="wutta-filter-value">
503
+
504
+ <wutta-datepicker v-model="inputValue"
505
+ ref="valueInput"
506
+ :size="isSmall ? 'is-small' : null"
507
+ @input="valueChanged" >
508
+ </wutta-datepicker>
509
+
510
+ </div>
511
+ </script>
512
+ <script>
513
+
514
+ const WuttaFilterDateValue = {
515
+ template: '#wutta-filter-date-value-template',
516
+ props: {
517
+ value: String,
518
+ isSmall: Boolean,
519
+ },
520
+
521
+ data() {
522
+ return {
523
+ inputValue: this.value,
524
+ }
525
+ },
526
+
527
+ methods: {
528
+
529
+ focus() {
530
+ this.$refs.valueInput.focus()
531
+ },
532
+
533
+ valueChanged(value) {
534
+ if (value) {
535
+ value = this.$refs.valueInput.formatDate(value)
536
+ }
537
+ this.$emit('input', value)
538
+ },
539
+ },
540
+ }
541
+
542
+ Vue.component('wutta-filter-date-value', WuttaFilterDateValue)
543
+
544
+ </script>
545
+ </%def>
546
+
482
547
  <%def name="make_wutta_tool_panel_component()">
483
548
  <script type="text/x-template" id="wutta-tool-panel-template">
484
549
  <nav class="panel tool-panel">
@@ -1,8 +1,13 @@
1
1
  # -*- coding: utf-8; -*-
2
2
 
3
+ import datetime
3
4
  from unittest import TestCase
4
5
  from unittest.mock import patch
5
6
 
7
+ import sqlalchemy as sa
8
+
9
+ from wuttjamaican.db.model import Base
10
+
6
11
  from wuttaweb.grids import filters as mod
7
12
  from tests.util import WebTestCase
8
13
 
@@ -422,6 +427,157 @@ class TestBooleanAlchemyFilter(WebTestCase):
422
427
  self.assertEqual(filtered_query.count(), 2)
423
428
 
424
429
 
430
+ class TheLocalThing(Base):
431
+ __tablename__ = 'the_local_thing'
432
+ id = sa.Column(sa.Integer(), primary_key=True, autoincrement=False)
433
+ date = sa.Column(sa.DateTime(timezone=True), nullable=True)
434
+
435
+
436
+ class TestDateAlchemyFilter(WebTestCase):
437
+
438
+ def setUp(self):
439
+ self.setup_web()
440
+ model = self.app.model
441
+
442
+ # nb. create table for TheLocalThing
443
+ model.Base.metadata.create_all(bind=self.session.bind)
444
+
445
+ self.sample_data = [
446
+ {'id': 1, 'date': datetime.date(2024, 1, 1)},
447
+ {'id': 2, 'date': datetime.date(2024, 1, 1)},
448
+ {'id': 3, 'date': datetime.date(2024, 3, 1)},
449
+ {'id': 4, 'date': datetime.date(2024, 3, 1)},
450
+ {'id': 5, 'date': None},
451
+ {'id': 6, 'date': None},
452
+ ]
453
+
454
+ for thing in self.sample_data:
455
+ thing = TheLocalThing(**thing)
456
+ self.session.add(thing)
457
+ self.session.commit()
458
+
459
+ self.sample_query = self.session.query(TheLocalThing)
460
+
461
+ def make_filter(self, model_property, **kwargs):
462
+ factory = kwargs.pop('factory', mod.DateAlchemyFilter)
463
+ kwargs['model_property'] = model_property
464
+ return factory(self.request, model_property.key, **kwargs)
465
+
466
+ def test_coerce_value(self):
467
+ filtr = self.make_filter(TheLocalThing.date)
468
+
469
+ # null value
470
+ self.assertIsNone(filtr.coerce_value(None))
471
+
472
+ # value as datetime
473
+ value = datetime.date(2024, 1, 1)
474
+ result = filtr.coerce_value(value)
475
+ self.assertIs(value, result)
476
+
477
+ # value as string
478
+ result = filtr.coerce_value('2024-04-01')
479
+ self.assertIsInstance(result, datetime.date)
480
+ self.assertEqual(result, datetime.date(2024, 4, 1))
481
+
482
+ # invalid
483
+ result = filtr.coerce_value('thisinputisbad')
484
+ self.assertIsNone(result)
485
+
486
+ def test_greater_than(self):
487
+ model = self.app.model
488
+
489
+ filtr = self.make_filter(TheLocalThing.date)
490
+ self.assertEqual(self.sample_query.count(), 6)
491
+
492
+ # null value ignored
493
+ filtered_query = filtr.filter_greater_than(self.sample_query, None)
494
+ self.assertIs(filtered_query, self.sample_query)
495
+ self.assertEqual(filtered_query.count(), 6)
496
+
497
+ # value as date
498
+ filtered_query = filtr.filter_greater_than(self.sample_query, datetime.date(2024, 2, 1))
499
+ self.assertIsNot(filtered_query, self.sample_query)
500
+ self.assertEqual(filtered_query.count(), 2)
501
+
502
+ # value as string
503
+ filtered_query = filtr.filter_greater_than(self.sample_query, '2024-02-01')
504
+ self.assertIsNot(filtered_query, self.sample_query)
505
+ self.assertEqual(filtered_query.count(), 2)
506
+
507
+ def test_greater_equal(self):
508
+ model = self.app.model
509
+
510
+ filtr = self.make_filter(TheLocalThing.date)
511
+ self.assertEqual(self.sample_query.count(), 6)
512
+
513
+ # null value ignored
514
+ filtered_query = filtr.filter_greater_equal(self.sample_query, None)
515
+ self.assertIs(filtered_query, self.sample_query)
516
+ self.assertEqual(filtered_query.count(), 6)
517
+
518
+ # value as date (clear of boundary)
519
+ filtered_query = filtr.filter_greater_equal(self.sample_query, datetime.date(2024, 2, 1))
520
+ self.assertIsNot(filtered_query, self.sample_query)
521
+ self.assertEqual(filtered_query.count(), 2)
522
+
523
+ # value as date (at boundary)
524
+ filtered_query = filtr.filter_greater_equal(self.sample_query, datetime.date(2024, 3, 1))
525
+ self.assertIsNot(filtered_query, self.sample_query)
526
+ self.assertEqual(filtered_query.count(), 2)
527
+
528
+ # value as string
529
+ filtered_query = filtr.filter_greater_equal(self.sample_query, '2024-01-01')
530
+ self.assertIsNot(filtered_query, self.sample_query)
531
+ self.assertEqual(filtered_query.count(), 4)
532
+
533
+ def test_less_than(self):
534
+ model = self.app.model
535
+
536
+ filtr = self.make_filter(TheLocalThing.date)
537
+ self.assertEqual(self.sample_query.count(), 6)
538
+
539
+ # null value ignored
540
+ filtered_query = filtr.filter_less_than(self.sample_query, None)
541
+ self.assertIs(filtered_query, self.sample_query)
542
+ self.assertEqual(filtered_query.count(), 6)
543
+
544
+ # value as date
545
+ filtered_query = filtr.filter_less_than(self.sample_query, datetime.date(2024, 2, 1))
546
+ self.assertIsNot(filtered_query, self.sample_query)
547
+ self.assertEqual(filtered_query.count(), 2)
548
+
549
+ # value as string
550
+ filtered_query = filtr.filter_less_than(self.sample_query, '2024-04-01')
551
+ self.assertIsNot(filtered_query, self.sample_query)
552
+ self.assertEqual(filtered_query.count(), 4)
553
+
554
+ def test_less_equal(self):
555
+ model = self.app.model
556
+
557
+ filtr = self.make_filter(TheLocalThing.date)
558
+ self.assertEqual(self.sample_query.count(), 6)
559
+
560
+ # null value ignored
561
+ filtered_query = filtr.filter_less_equal(self.sample_query, None)
562
+ self.assertIs(filtered_query, self.sample_query)
563
+ self.assertEqual(filtered_query.count(), 6)
564
+
565
+ # value as date (clear of boundary)
566
+ filtered_query = filtr.filter_less_equal(self.sample_query, datetime.date(2024, 2, 1))
567
+ self.assertIsNot(filtered_query, self.sample_query)
568
+ self.assertEqual(filtered_query.count(), 2)
569
+
570
+ # value as date (at boundary)
571
+ filtered_query = filtr.filter_less_equal(self.sample_query, datetime.date(2024, 3, 1))
572
+ self.assertIsNot(filtered_query, self.sample_query)
573
+ self.assertEqual(filtered_query.count(), 2)
574
+
575
+ # value as string
576
+ filtered_query = filtr.filter_less_equal(self.sample_query, '2024-04-01')
577
+ self.assertIsNot(filtered_query, self.sample_query)
578
+ self.assertEqual(filtered_query.count(), 4)
579
+
580
+
425
581
  class TestVerbNotSupported(TestCase):
426
582
 
427
583
  def test_basic(self):
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes