WuttaWeb 0.20.6__tar.gz → 0.21.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 (206) hide show
  1. {wuttaweb-0.20.6 → wuttaweb-0.21.0}/CHANGELOG.md +10 -0
  2. {wuttaweb-0.20.6 → wuttaweb-0.21.0}/PKG-INFO +2 -2
  3. {wuttaweb-0.20.6 → wuttaweb-0.21.0}/pyproject.toml +2 -2
  4. {wuttaweb-0.20.6 → wuttaweb-0.21.0}/src/wuttaweb/forms/schema.py +0 -22
  5. {wuttaweb-0.20.6 → wuttaweb-0.21.0}/src/wuttaweb/forms/widgets.py +0 -58
  6. {wuttaweb-0.20.6 → wuttaweb-0.21.0}/src/wuttaweb/grids/base.py +18 -7
  7. {wuttaweb-0.20.6 → wuttaweb-0.21.0}/src/wuttaweb/views/people.py +54 -13
  8. {wuttaweb-0.20.6 → wuttaweb-0.21.0}/src/wuttaweb/views/users.py +52 -16
  9. {wuttaweb-0.20.6 → wuttaweb-0.21.0}/tests/forms/test_schema.py +0 -14
  10. {wuttaweb-0.20.6 → wuttaweb-0.21.0}/tests/forms/test_widgets.py +1 -46
  11. {wuttaweb-0.20.6 → wuttaweb-0.21.0}/tests/grids/test_base.py +3 -0
  12. {wuttaweb-0.20.6 → wuttaweb-0.21.0}/tests/views/test_people.py +49 -15
  13. wuttaweb-0.21.0/tests/views/test_users.py +319 -0
  14. wuttaweb-0.20.6/tests/views/test_users.py +0 -219
  15. {wuttaweb-0.20.6 → wuttaweb-0.21.0}/.gitignore +0 -0
  16. {wuttaweb-0.20.6 → wuttaweb-0.21.0}/COPYING.txt +0 -0
  17. {wuttaweb-0.20.6 → wuttaweb-0.21.0}/README.md +0 -0
  18. {wuttaweb-0.20.6 → wuttaweb-0.21.0}/docs/Makefile +0 -0
  19. {wuttaweb-0.20.6 → wuttaweb-0.21.0}/docs/_static/.keepme +0 -0
  20. {wuttaweb-0.20.6 → wuttaweb-0.21.0}/docs/api/wuttaweb.app.rst +0 -0
  21. {wuttaweb-0.20.6 → wuttaweb-0.21.0}/docs/api/wuttaweb.auth.rst +0 -0
  22. {wuttaweb-0.20.6 → wuttaweb-0.21.0}/docs/api/wuttaweb.cli.rst +0 -0
  23. {wuttaweb-0.20.6 → wuttaweb-0.21.0}/docs/api/wuttaweb.cli.webapp.rst +0 -0
  24. {wuttaweb-0.20.6 → wuttaweb-0.21.0}/docs/api/wuttaweb.conf.rst +0 -0
  25. {wuttaweb-0.20.6 → wuttaweb-0.21.0}/docs/api/wuttaweb.db.continuum.rst +0 -0
  26. {wuttaweb-0.20.6 → wuttaweb-0.21.0}/docs/api/wuttaweb.db.rst +0 -0
  27. {wuttaweb-0.20.6 → wuttaweb-0.21.0}/docs/api/wuttaweb.db.sess.rst +0 -0
  28. {wuttaweb-0.20.6 → wuttaweb-0.21.0}/docs/api/wuttaweb.emails.rst +0 -0
  29. {wuttaweb-0.20.6 → wuttaweb-0.21.0}/docs/api/wuttaweb.forms.base.rst +0 -0
  30. {wuttaweb-0.20.6 → wuttaweb-0.21.0}/docs/api/wuttaweb.forms.rst +0 -0
  31. {wuttaweb-0.20.6 → wuttaweb-0.21.0}/docs/api/wuttaweb.forms.schema.rst +0 -0
  32. {wuttaweb-0.20.6 → wuttaweb-0.21.0}/docs/api/wuttaweb.forms.widgets.rst +0 -0
  33. {wuttaweb-0.20.6 → wuttaweb-0.21.0}/docs/api/wuttaweb.grids.base.rst +0 -0
  34. {wuttaweb-0.20.6 → wuttaweb-0.21.0}/docs/api/wuttaweb.grids.filters.rst +0 -0
  35. {wuttaweb-0.20.6 → wuttaweb-0.21.0}/docs/api/wuttaweb.grids.rst +0 -0
  36. {wuttaweb-0.20.6 → wuttaweb-0.21.0}/docs/api/wuttaweb.handler.rst +0 -0
  37. {wuttaweb-0.20.6 → wuttaweb-0.21.0}/docs/api/wuttaweb.helpers.rst +0 -0
  38. {wuttaweb-0.20.6 → wuttaweb-0.21.0}/docs/api/wuttaweb.menus.rst +0 -0
  39. {wuttaweb-0.20.6 → wuttaweb-0.21.0}/docs/api/wuttaweb.progress.rst +0 -0
  40. {wuttaweb-0.20.6 → wuttaweb-0.21.0}/docs/api/wuttaweb.rst +0 -0
  41. {wuttaweb-0.20.6 → wuttaweb-0.21.0}/docs/api/wuttaweb.static.rst +0 -0
  42. {wuttaweb-0.20.6 → wuttaweb-0.21.0}/docs/api/wuttaweb.subscribers.rst +0 -0
  43. {wuttaweb-0.20.6 → wuttaweb-0.21.0}/docs/api/wuttaweb.util.rst +0 -0
  44. {wuttaweb-0.20.6 → wuttaweb-0.21.0}/docs/api/wuttaweb.views.auth.rst +0 -0
  45. {wuttaweb-0.20.6 → wuttaweb-0.21.0}/docs/api/wuttaweb.views.base.rst +0 -0
  46. {wuttaweb-0.20.6 → wuttaweb-0.21.0}/docs/api/wuttaweb.views.batch.rst +0 -0
  47. {wuttaweb-0.20.6 → wuttaweb-0.21.0}/docs/api/wuttaweb.views.common.rst +0 -0
  48. {wuttaweb-0.20.6 → wuttaweb-0.21.0}/docs/api/wuttaweb.views.email.rst +0 -0
  49. {wuttaweb-0.20.6 → wuttaweb-0.21.0}/docs/api/wuttaweb.views.essential.rst +0 -0
  50. {wuttaweb-0.20.6 → wuttaweb-0.21.0}/docs/api/wuttaweb.views.master.rst +0 -0
  51. {wuttaweb-0.20.6 → wuttaweb-0.21.0}/docs/api/wuttaweb.views.people.rst +0 -0
  52. {wuttaweb-0.20.6 → wuttaweb-0.21.0}/docs/api/wuttaweb.views.progress.rst +0 -0
  53. {wuttaweb-0.20.6 → wuttaweb-0.21.0}/docs/api/wuttaweb.views.reports.rst +0 -0
  54. {wuttaweb-0.20.6 → wuttaweb-0.21.0}/docs/api/wuttaweb.views.roles.rst +0 -0
  55. {wuttaweb-0.20.6 → wuttaweb-0.21.0}/docs/api/wuttaweb.views.rst +0 -0
  56. {wuttaweb-0.20.6 → wuttaweb-0.21.0}/docs/api/wuttaweb.views.settings.rst +0 -0
  57. {wuttaweb-0.20.6 → wuttaweb-0.21.0}/docs/api/wuttaweb.views.upgrades.rst +0 -0
  58. {wuttaweb-0.20.6 → wuttaweb-0.21.0}/docs/api/wuttaweb.views.users.rst +0 -0
  59. {wuttaweb-0.20.6 → wuttaweb-0.21.0}/docs/conf.py +0 -0
  60. {wuttaweb-0.20.6 → wuttaweb-0.21.0}/docs/glossary.rst +0 -0
  61. {wuttaweb-0.20.6 → wuttaweb-0.21.0}/docs/index.rst +0 -0
  62. {wuttaweb-0.20.6 → wuttaweb-0.21.0}/docs/make.bat +0 -0
  63. {wuttaweb-0.20.6 → wuttaweb-0.21.0}/docs/narr/cli/builtin.rst +0 -0
  64. {wuttaweb-0.20.6 → wuttaweb-0.21.0}/docs/narr/cli/index.rst +0 -0
  65. {wuttaweb-0.20.6 → wuttaweb-0.21.0}/docs/narr/templates/base.rst +0 -0
  66. {wuttaweb-0.20.6 → wuttaweb-0.21.0}/docs/narr/templates/index.rst +0 -0
  67. {wuttaweb-0.20.6 → wuttaweb-0.21.0}/docs/narr/templates/lookup.rst +0 -0
  68. {wuttaweb-0.20.6 → wuttaweb-0.21.0}/docs/narr/templates/overview.rst +0 -0
  69. {wuttaweb-0.20.6 → wuttaweb-0.21.0}/src/wuttaweb/__init__.py +0 -0
  70. {wuttaweb-0.20.6 → wuttaweb-0.21.0}/src/wuttaweb/_version.py +0 -0
  71. {wuttaweb-0.20.6 → wuttaweb-0.21.0}/src/wuttaweb/app.py +0 -0
  72. {wuttaweb-0.20.6 → wuttaweb-0.21.0}/src/wuttaweb/auth.py +0 -0
  73. {wuttaweb-0.20.6 → wuttaweb-0.21.0}/src/wuttaweb/cli/__init__.py +0 -0
  74. {wuttaweb-0.20.6 → wuttaweb-0.21.0}/src/wuttaweb/cli/webapp.py +0 -0
  75. {wuttaweb-0.20.6 → wuttaweb-0.21.0}/src/wuttaweb/conf.py +0 -0
  76. {wuttaweb-0.20.6 → wuttaweb-0.21.0}/src/wuttaweb/db/__init__.py +0 -0
  77. {wuttaweb-0.20.6 → wuttaweb-0.21.0}/src/wuttaweb/db/continuum.py +0 -0
  78. {wuttaweb-0.20.6 → wuttaweb-0.21.0}/src/wuttaweb/db/sess.py +0 -0
  79. {wuttaweb-0.20.6 → wuttaweb-0.21.0}/src/wuttaweb/email-templates/feedback.html.mako +0 -0
  80. {wuttaweb-0.20.6 → wuttaweb-0.21.0}/src/wuttaweb/email-templates/feedback.txt.mako +0 -0
  81. {wuttaweb-0.20.6 → wuttaweb-0.21.0}/src/wuttaweb/emails.py +0 -0
  82. {wuttaweb-0.20.6 → wuttaweb-0.21.0}/src/wuttaweb/forms/__init__.py +0 -0
  83. {wuttaweb-0.20.6 → wuttaweb-0.21.0}/src/wuttaweb/forms/base.py +0 -0
  84. {wuttaweb-0.20.6 → wuttaweb-0.21.0}/src/wuttaweb/grids/__init__.py +0 -0
  85. {wuttaweb-0.20.6 → wuttaweb-0.21.0}/src/wuttaweb/grids/filters.py +0 -0
  86. {wuttaweb-0.20.6 → wuttaweb-0.21.0}/src/wuttaweb/handler.py +0 -0
  87. {wuttaweb-0.20.6 → wuttaweb-0.21.0}/src/wuttaweb/helpers.py +0 -0
  88. {wuttaweb-0.20.6 → wuttaweb-0.21.0}/src/wuttaweb/menus.py +0 -0
  89. {wuttaweb-0.20.6 → wuttaweb-0.21.0}/src/wuttaweb/progress.py +0 -0
  90. {wuttaweb-0.20.6 → wuttaweb-0.21.0}/src/wuttaweb/static/__init__.py +0 -0
  91. {wuttaweb-0.20.6 → wuttaweb-0.21.0}/src/wuttaweb/static/img/favicon.ico +0 -0
  92. {wuttaweb-0.20.6 → wuttaweb-0.21.0}/src/wuttaweb/static/img/logo.png +0 -0
  93. {wuttaweb-0.20.6 → wuttaweb-0.21.0}/src/wuttaweb/static/img/testing.png +0 -0
  94. {wuttaweb-0.20.6 → wuttaweb-0.21.0}/src/wuttaweb/subscribers.py +0 -0
  95. {wuttaweb-0.20.6 → wuttaweb-0.21.0}/src/wuttaweb/templates/appinfo/configure.mako +0 -0
  96. {wuttaweb-0.20.6 → wuttaweb-0.21.0}/src/wuttaweb/templates/appinfo/index.mako +0 -0
  97. {wuttaweb-0.20.6 → wuttaweb-0.21.0}/src/wuttaweb/templates/auth/change_password.mako +0 -0
  98. {wuttaweb-0.20.6 → wuttaweb-0.21.0}/src/wuttaweb/templates/auth/login.mako +0 -0
  99. {wuttaweb-0.20.6 → wuttaweb-0.21.0}/src/wuttaweb/templates/base.mako +0 -0
  100. {wuttaweb-0.20.6 → wuttaweb-0.21.0}/src/wuttaweb/templates/base_meta.mako +0 -0
  101. {wuttaweb-0.20.6 → wuttaweb-0.21.0}/src/wuttaweb/templates/batch/view.mako +0 -0
  102. {wuttaweb-0.20.6 → wuttaweb-0.21.0}/src/wuttaweb/templates/configure.mako +0 -0
  103. {wuttaweb-0.20.6 → wuttaweb-0.21.0}/src/wuttaweb/templates/deform/checkbox.pt +0 -0
  104. {wuttaweb-0.20.6 → wuttaweb-0.21.0}/src/wuttaweb/templates/deform/checkbox_choice.pt +0 -0
  105. {wuttaweb-0.20.6 → wuttaweb-0.21.0}/src/wuttaweb/templates/deform/checked_password.pt +0 -0
  106. {wuttaweb-0.20.6 → wuttaweb-0.21.0}/src/wuttaweb/templates/deform/dateinput.pt +0 -0
  107. {wuttaweb-0.20.6 → wuttaweb-0.21.0}/src/wuttaweb/templates/deform/datetimeinput.pt +0 -0
  108. {wuttaweb-0.20.6 → wuttaweb-0.21.0}/src/wuttaweb/templates/deform/moneyinput.pt +0 -0
  109. {wuttaweb-0.20.6 → wuttaweb-0.21.0}/src/wuttaweb/templates/deform/password.pt +0 -0
  110. {wuttaweb-0.20.6 → wuttaweb-0.21.0}/src/wuttaweb/templates/deform/permissions.pt +0 -0
  111. {wuttaweb-0.20.6 → wuttaweb-0.21.0}/src/wuttaweb/templates/deform/readonly/checkbox.pt +0 -0
  112. {wuttaweb-0.20.6 → wuttaweb-0.21.0}/src/wuttaweb/templates/deform/readonly/email_recips.pt +0 -0
  113. {wuttaweb-0.20.6 → wuttaweb-0.21.0}/src/wuttaweb/templates/deform/readonly/filedownload.pt +0 -0
  114. {wuttaweb-0.20.6 → wuttaweb-0.21.0}/src/wuttaweb/templates/deform/readonly/notes.pt +0 -0
  115. {wuttaweb-0.20.6 → wuttaweb-0.21.0}/src/wuttaweb/templates/deform/readonly/objectref.pt +0 -0
  116. {wuttaweb-0.20.6 → wuttaweb-0.21.0}/src/wuttaweb/templates/deform/readonly/permissions.pt +0 -0
  117. {wuttaweb-0.20.6 → wuttaweb-0.21.0}/src/wuttaweb/templates/deform/readonly/rolerefs.pt +0 -0
  118. {wuttaweb-0.20.6 → wuttaweb-0.21.0}/src/wuttaweb/templates/deform/select.pt +0 -0
  119. {wuttaweb-0.20.6 → wuttaweb-0.21.0}/src/wuttaweb/templates/deform/textarea.pt +0 -0
  120. {wuttaweb-0.20.6 → wuttaweb-0.21.0}/src/wuttaweb/templates/deform/textinput.pt +0 -0
  121. {wuttaweb-0.20.6 → wuttaweb-0.21.0}/src/wuttaweb/templates/email/settings/view.mako +0 -0
  122. {wuttaweb-0.20.6 → wuttaweb-0.21.0}/src/wuttaweb/templates/forbidden.mako +0 -0
  123. {wuttaweb-0.20.6 → wuttaweb-0.21.0}/src/wuttaweb/templates/form.mako +0 -0
  124. {wuttaweb-0.20.6 → wuttaweb-0.21.0}/src/wuttaweb/templates/forms/vue_template.mako +0 -0
  125. {wuttaweb-0.20.6 → wuttaweb-0.21.0}/src/wuttaweb/templates/grids/table_element.mako +0 -0
  126. {wuttaweb-0.20.6 → wuttaweb-0.21.0}/src/wuttaweb/templates/grids/vue_template.mako +0 -0
  127. {wuttaweb-0.20.6 → wuttaweb-0.21.0}/src/wuttaweb/templates/home.mako +0 -0
  128. {wuttaweb-0.20.6 → wuttaweb-0.21.0}/src/wuttaweb/templates/master/configure.mako +0 -0
  129. {wuttaweb-0.20.6 → wuttaweb-0.21.0}/src/wuttaweb/templates/master/create.mako +0 -0
  130. {wuttaweb-0.20.6 → wuttaweb-0.21.0}/src/wuttaweb/templates/master/delete.mako +0 -0
  131. {wuttaweb-0.20.6 → wuttaweb-0.21.0}/src/wuttaweb/templates/master/edit.mako +0 -0
  132. {wuttaweb-0.20.6 → wuttaweb-0.21.0}/src/wuttaweb/templates/master/form.mako +0 -0
  133. {wuttaweb-0.20.6 → wuttaweb-0.21.0}/src/wuttaweb/templates/master/index.mako +0 -0
  134. {wuttaweb-0.20.6 → wuttaweb-0.21.0}/src/wuttaweb/templates/master/view.mako +0 -0
  135. {wuttaweb-0.20.6 → wuttaweb-0.21.0}/src/wuttaweb/templates/notfound.mako +0 -0
  136. {wuttaweb-0.20.6 → wuttaweb-0.21.0}/src/wuttaweb/templates/page.mako +0 -0
  137. {wuttaweb-0.20.6 → wuttaweb-0.21.0}/src/wuttaweb/templates/people/view_profile.mako +0 -0
  138. {wuttaweb-0.20.6 → wuttaweb-0.21.0}/src/wuttaweb/templates/progress.mako +0 -0
  139. {wuttaweb-0.20.6 → wuttaweb-0.21.0}/src/wuttaweb/templates/reports/view.mako +0 -0
  140. {wuttaweb-0.20.6 → wuttaweb-0.21.0}/src/wuttaweb/templates/setup.mako +0 -0
  141. {wuttaweb-0.20.6 → wuttaweb-0.21.0}/src/wuttaweb/templates/upgrade.mako +0 -0
  142. {wuttaweb-0.20.6 → wuttaweb-0.21.0}/src/wuttaweb/templates/upgrades/configure.mako +0 -0
  143. {wuttaweb-0.20.6 → wuttaweb-0.21.0}/src/wuttaweb/templates/upgrades/view.mako +0 -0
  144. {wuttaweb-0.20.6 → wuttaweb-0.21.0}/src/wuttaweb/templates/wutta-components.mako +0 -0
  145. {wuttaweb-0.20.6 → wuttaweb-0.21.0}/src/wuttaweb/testing.py +0 -0
  146. {wuttaweb-0.20.6 → wuttaweb-0.21.0}/src/wuttaweb/util.py +0 -0
  147. {wuttaweb-0.20.6 → wuttaweb-0.21.0}/src/wuttaweb/views/__init__.py +0 -0
  148. {wuttaweb-0.20.6 → wuttaweb-0.21.0}/src/wuttaweb/views/auth.py +0 -0
  149. {wuttaweb-0.20.6 → wuttaweb-0.21.0}/src/wuttaweb/views/base.py +0 -0
  150. {wuttaweb-0.20.6 → wuttaweb-0.21.0}/src/wuttaweb/views/batch.py +0 -0
  151. {wuttaweb-0.20.6 → wuttaweb-0.21.0}/src/wuttaweb/views/common.py +0 -0
  152. {wuttaweb-0.20.6 → wuttaweb-0.21.0}/src/wuttaweb/views/email.py +0 -0
  153. {wuttaweb-0.20.6 → wuttaweb-0.21.0}/src/wuttaweb/views/essential.py +0 -0
  154. {wuttaweb-0.20.6 → wuttaweb-0.21.0}/src/wuttaweb/views/master.py +0 -0
  155. {wuttaweb-0.20.6 → wuttaweb-0.21.0}/src/wuttaweb/views/progress.py +0 -0
  156. {wuttaweb-0.20.6 → wuttaweb-0.21.0}/src/wuttaweb/views/reports.py +0 -0
  157. {wuttaweb-0.20.6 → wuttaweb-0.21.0}/src/wuttaweb/views/roles.py +0 -0
  158. {wuttaweb-0.20.6 → wuttaweb-0.21.0}/src/wuttaweb/views/settings.py +0 -0
  159. {wuttaweb-0.20.6 → wuttaweb-0.21.0}/src/wuttaweb/views/upgrades.py +0 -0
  160. {wuttaweb-0.20.6 → wuttaweb-0.21.0}/tasks.py +0 -0
  161. {wuttaweb-0.20.6/tests/views → wuttaweb-0.21.0/tests}/__init__.py +0 -0
  162. {wuttaweb-0.20.6/tests/grids → wuttaweb-0.21.0/tests/cli}/__init__.py +0 -0
  163. {wuttaweb-0.20.6 → wuttaweb-0.21.0}/tests/cli/test_webapp.py +0 -0
  164. {wuttaweb-0.20.6 → wuttaweb-0.21.0}/tests/db/__init__.py +0 -0
  165. {wuttaweb-0.20.6 → wuttaweb-0.21.0}/tests/db/test_continuum.py +0 -0
  166. {wuttaweb-0.20.6 → wuttaweb-0.21.0}/tests/forms/test_base.py +0 -0
  167. {wuttaweb-0.20.6/tests/cli → wuttaweb-0.21.0/tests/grids}/__init__.py +0 -0
  168. {wuttaweb-0.20.6 → wuttaweb-0.21.0}/tests/grids/test_filters.py +0 -0
  169. {wuttaweb-0.20.6 → wuttaweb-0.21.0}/tests/libcache/bb_fontawesome_svg_core.js +0 -0
  170. {wuttaweb-0.20.6 → wuttaweb-0.21.0}/tests/libcache/bb_free_solid_svg_icons.js +0 -0
  171. {wuttaweb-0.20.6 → wuttaweb-0.21.0}/tests/libcache/bb_oruga.js +0 -0
  172. {wuttaweb-0.20.6 → wuttaweb-0.21.0}/tests/libcache/bb_oruga_bulma.css +0 -0
  173. {wuttaweb-0.20.6 → wuttaweb-0.21.0}/tests/libcache/bb_oruga_bulma.js +0 -0
  174. {wuttaweb-0.20.6 → wuttaweb-0.21.0}/tests/libcache/bb_vue.js +0 -0
  175. {wuttaweb-0.20.6 → wuttaweb-0.21.0}/tests/libcache/bb_vue_fontawesome.js +0 -0
  176. {wuttaweb-0.20.6 → wuttaweb-0.21.0}/tests/libcache/buefy.css +0 -0
  177. {wuttaweb-0.20.6 → wuttaweb-0.21.0}/tests/libcache/buefy.js +0 -0
  178. {wuttaweb-0.20.6 → wuttaweb-0.21.0}/tests/libcache/fontawesome.js +0 -0
  179. {wuttaweb-0.20.6 → wuttaweb-0.21.0}/tests/libcache/vue.js +0 -0
  180. {wuttaweb-0.20.6 → wuttaweb-0.21.0}/tests/libcache/vue_resource.js +0 -0
  181. {wuttaweb-0.20.6 → wuttaweb-0.21.0}/tests/test_app.py +0 -0
  182. {wuttaweb-0.20.6 → wuttaweb-0.21.0}/tests/test_auth.py +0 -0
  183. {wuttaweb-0.20.6 → wuttaweb-0.21.0}/tests/test_emails.py +0 -0
  184. {wuttaweb-0.20.6 → wuttaweb-0.21.0}/tests/test_handler.py +0 -0
  185. {wuttaweb-0.20.6 → wuttaweb-0.21.0}/tests/test_helpers.py +0 -0
  186. {wuttaweb-0.20.6 → wuttaweb-0.21.0}/tests/test_menus.py +0 -0
  187. {wuttaweb-0.20.6 → wuttaweb-0.21.0}/tests/test_progress.py +0 -0
  188. {wuttaweb-0.20.6 → wuttaweb-0.21.0}/tests/test_static.py +0 -0
  189. {wuttaweb-0.20.6 → wuttaweb-0.21.0}/tests/test_subscribers.py +0 -0
  190. {wuttaweb-0.20.6 → wuttaweb-0.21.0}/tests/test_util.py +0 -0
  191. {wuttaweb-0.20.6 → wuttaweb-0.21.0}/tests/util.py +0 -0
  192. {wuttaweb-0.20.6/tests → wuttaweb-0.21.0/tests/views}/__init__.py +0 -0
  193. {wuttaweb-0.20.6 → wuttaweb-0.21.0}/tests/views/test___init__.py +0 -0
  194. {wuttaweb-0.20.6 → wuttaweb-0.21.0}/tests/views/test_auth.py +0 -0
  195. {wuttaweb-0.20.6 → wuttaweb-0.21.0}/tests/views/test_base.py +0 -0
  196. {wuttaweb-0.20.6 → wuttaweb-0.21.0}/tests/views/test_batch.py +0 -0
  197. {wuttaweb-0.20.6 → wuttaweb-0.21.0}/tests/views/test_common.py +0 -0
  198. {wuttaweb-0.20.6 → wuttaweb-0.21.0}/tests/views/test_email.py +0 -0
  199. {wuttaweb-0.20.6 → wuttaweb-0.21.0}/tests/views/test_essential.py +0 -0
  200. {wuttaweb-0.20.6 → wuttaweb-0.21.0}/tests/views/test_master.py +0 -0
  201. {wuttaweb-0.20.6 → wuttaweb-0.21.0}/tests/views/test_progress.py +0 -0
  202. {wuttaweb-0.20.6 → wuttaweb-0.21.0}/tests/views/test_reports.py +0 -0
  203. {wuttaweb-0.20.6 → wuttaweb-0.21.0}/tests/views/test_roles.py +0 -0
  204. {wuttaweb-0.20.6 → wuttaweb-0.21.0}/tests/views/test_settings.py +0 -0
  205. {wuttaweb-0.20.6 → wuttaweb-0.21.0}/tests/views/test_upgrades.py +0 -0
  206. {wuttaweb-0.20.6 → wuttaweb-0.21.0}/tox.ini +0 -0
@@ -5,6 +5,16 @@ 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.21.0 (2025-02-01)
9
+
10
+ ### Feat
11
+
12
+ - overhaul some User/Person form fields etc.
13
+
14
+ ### Fix
15
+
16
+ - do not auto-create grid filters for uuid columns
17
+
8
18
  ## v0.20.6 (2025-01-26)
9
19
 
10
20
  ### Fix
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: WuttaWeb
3
- Version: 0.20.6
3
+ Version: 0.21.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
@@ -39,7 +39,7 @@ Requires-Dist: pyramid-tm
39
39
  Requires-Dist: pyramid>=2
40
40
  Requires-Dist: waitress
41
41
  Requires-Dist: webhelpers2
42
- Requires-Dist: wuttjamaican[db]>=0.20.2
42
+ Requires-Dist: wuttjamaican[db]>=0.20.4
43
43
  Requires-Dist: zope-sqlalchemy>=1.5
44
44
  Provides-Extra: continuum
45
45
  Requires-Dist: wutta-continuum; extra == 'continuum'
@@ -6,7 +6,7 @@ build-backend = "hatchling.build"
6
6
 
7
7
  [project]
8
8
  name = "WuttaWeb"
9
- version = "0.20.6"
9
+ version = "0.21.0"
10
10
  description = "Web App for Wutta Framework"
11
11
  readme = "README.md"
12
12
  authors = [{name = "Lance Edgar", email = "lance@wuttaproject.org"}]
@@ -44,7 +44,7 @@ dependencies = [
44
44
  "pyramid_tm",
45
45
  "waitress",
46
46
  "WebHelpers2",
47
- "WuttJamaican[db]>=0.20.2",
47
+ "WuttJamaican[db]>=0.20.4",
48
48
  "zope.sqlalchemy>=1.5",
49
49
  ]
50
50
 
@@ -572,28 +572,6 @@ class RoleRefs(WuttaSet):
572
572
  return widgets.RoleRefsWidget(self.request, **kwargs)
573
573
 
574
574
 
575
- class UserRefs(WuttaSet):
576
- """
577
- Form schema type for the Role
578
- :attr:`~wuttjamaican:wuttjamaican.db.model.auth.Role.users`
579
- association proxy field.
580
-
581
- This is a subclass of :class:`WuttaSet`. It uses a ``set`` of
582
- :class:`~wuttjamaican:wuttjamaican.db.model.auth.User` ``uuid``
583
- values for underlying data format.
584
- """
585
-
586
- def widget_maker(self, **kwargs):
587
- """
588
- Constructs a default widget for the field.
589
-
590
- :returns: Instance of
591
- :class:`~wuttaweb.forms.widgets.UserRefsWidget`.
592
- """
593
- kwargs.setdefault('session', Session())
594
- return widgets.UserRefsWidget(self.request, **kwargs)
595
-
596
-
597
575
  class Permissions(WuttaSet):
598
576
  """
599
577
  Form schema type for the Role
@@ -56,7 +56,6 @@ from webhelpers2.html import HTML
56
56
  from wuttjamaican.conf import parse_list
57
57
 
58
58
  from wuttaweb.db import Session
59
- from wuttaweb.grids import Grid
60
59
 
61
60
 
62
61
  class ObjectRefWidget(SelectWidget):
@@ -414,63 +413,6 @@ class RoleRefsWidget(WuttaCheckboxChoiceWidget):
414
413
  return super().serialize(field, cstruct, **kw)
415
414
 
416
415
 
417
- class UserRefsWidget(WuttaCheckboxChoiceWidget):
418
- """
419
- Widget for use with Role
420
- :attr:`~wuttjamaican:wuttjamaican.db.model.auth.Role.users` field.
421
- This is the default widget for the
422
- :class:`~wuttaweb.forms.schema.UserRefs` type.
423
-
424
- This is a subclass of :class:`WuttaCheckboxChoiceWidget`; however
425
- it only supports readonly mode and does not use a template.
426
- Rather, it generates and renders a
427
- :class:`~wuttaweb.grids.base.Grid` showing the users list.
428
- """
429
-
430
- def serialize(self, field, cstruct, **kw):
431
- """ """
432
- readonly = kw.get('readonly', self.readonly)
433
- if not readonly:
434
- raise NotImplementedError("edit not allowed for this widget")
435
-
436
- model = self.app.model
437
- columns = ['username', 'active']
438
-
439
- # generate data set for users
440
- users = []
441
- if cstruct:
442
- for uuid in cstruct:
443
- user = self.session.get(model.User, uuid)
444
- if user:
445
- users.append(dict([(key, getattr(user, key))
446
- for key in columns + ['uuid']]))
447
-
448
- # do not render if no data
449
- if not users:
450
- return HTML.tag('span')
451
-
452
- # grid
453
- grid = Grid(self.request, key='roles.view.users',
454
- columns=columns, data=users)
455
-
456
- # view action
457
- if self.request.has_perm('users.view'):
458
- url = lambda user, i: self.request.route_url('users.view', uuid=user['uuid'])
459
- grid.add_action('view', icon='eye', url=url)
460
- grid.set_link('person')
461
- grid.set_link('username')
462
-
463
- # edit action
464
- if self.request.has_perm('users.edit'):
465
- url = lambda user, i: self.request.route_url('users.edit', uuid=user['uuid'])
466
- grid.add_action('edit', url=url)
467
-
468
- # render as simple <b-table>
469
- # nb. must indicate we are a part of this form
470
- form = getattr(field.parent, 'wutta_form', None)
471
- return grid.render_table_element(form)
472
-
473
-
474
416
  class PermissionsWidget(WuttaCheckboxChoiceWidget):
475
417
  """
476
418
  Widget for use with Role
@@ -41,6 +41,7 @@ from webhelpers2.html import HTML
41
41
  from wuttaweb.db import Session
42
42
  from wuttaweb.util import FieldList, get_model_fields, make_json_safe
43
43
  from wuttjamaican.util import UNSPECIFIED
44
+ from wuttjamaican.db.util import UUID
44
45
  from wuttaweb.grids.filters import default_sqlalchemy_filters, VerbNotSupported
45
46
 
46
47
 
@@ -1136,14 +1137,15 @@ class Grid:
1136
1137
 
1137
1138
  def make_backend_filters(self, filters=None):
1138
1139
  """
1139
- Make backend filters for all columns in the grid.
1140
+ Make "automatic" backend filters for the grid.
1140
1141
 
1141
1142
  This is called by the constructor, if :attr:`filterable` is
1142
1143
  true.
1143
1144
 
1144
- For each column in the grid, this checks the provided
1145
- ``filters`` and if the column is not yet in there, will call
1146
- :meth:`make_filter()` to add it.
1145
+ For each "column" in the model class, this will call
1146
+ :meth:`make_filter()` to add an automatic filter. However it
1147
+ first checks the provided ``filters`` and will not override
1148
+ any of those.
1147
1149
 
1148
1150
  .. note::
1149
1151
 
@@ -1181,9 +1183,18 @@ class Grid:
1181
1183
 
1182
1184
  inspector = sa.inspect(self.model_class)
1183
1185
  for prop in inspector.column_attrs:
1184
- if prop.key not in filters:
1185
- attr = getattr(self.model_class, prop.key)
1186
- filters[prop.key] = self.make_filter(attr)
1186
+
1187
+ # do not overwrite existing filters
1188
+ if prop.key in filters:
1189
+ continue
1190
+
1191
+ # do not create filter for UUID field
1192
+ if (len(prop.columns) == 1
1193
+ and isinstance(prop.columns[0].type, UUID)):
1194
+ continue
1195
+
1196
+ attr = getattr(self.model_class, prop.key)
1197
+ filters[prop.key] = self.make_filter(attr)
1187
1198
 
1188
1199
  return filters
1189
1200
 
@@ -28,7 +28,6 @@ import sqlalchemy as sa
28
28
 
29
29
  from wuttjamaican.db.model import Person
30
30
  from wuttaweb.views import MasterView
31
- from wuttaweb.forms.schema import UserRefs
32
31
 
33
32
 
34
33
  class PersonView(MasterView):
@@ -62,6 +61,14 @@ class PersonView(MasterView):
62
61
  'full_name': {'active': True},
63
62
  }
64
63
 
64
+ form_fields = [
65
+ 'full_name',
66
+ 'first_name',
67
+ 'middle_name',
68
+ 'last_name',
69
+ 'users',
70
+ ]
71
+
65
72
  def configure_grid(self, g):
66
73
  """ """
67
74
  super().configure_grid(g)
@@ -80,20 +87,54 @@ class PersonView(MasterView):
80
87
  super().configure_form(f)
81
88
  person = f.model_instance
82
89
 
83
- # TODO: master should handle these? (nullable column)
84
- f.set_required('first_name', False)
85
- f.set_required('middle_name', False)
86
- f.set_required('last_name', False)
90
+ # full_name
91
+ if self.creating or self.editing:
92
+ f.remove('full_name')
87
93
 
88
94
  # users
89
- # nb. colanderalchemy wants to do some magic for the true
90
- # 'users' relationship, so we use a different field name
91
- f.remove('users')
92
- if not (self.creating or self.editing):
93
- f.append('_users')
94
- f.set_readonly('_users')
95
- f.set_node('_users', UserRefs(self.request))
96
- f.set_default('_users', [u.uuid for u in person.users])
95
+ if self.viewing:
96
+ f.set_grid('users', self.make_users_grid(person))
97
+
98
+ def make_users_grid(self, person):
99
+ """
100
+ Make and return the grid for the Users field.
101
+
102
+ This grid is shown for the Users field when viewing a Person.
103
+
104
+ :returns: Fully configured :class:`~wuttaweb.grids.base.Grid`
105
+ instance.
106
+ """
107
+ model = self.app.model
108
+ route_prefix = self.get_route_prefix()
109
+
110
+ grid = self.make_grid(key=f'{route_prefix}.view.users',
111
+ model_class=model.User,
112
+ data=person.users,
113
+ columns=[
114
+ 'username',
115
+ 'active',
116
+ ])
117
+
118
+ if self.request.has_perm('users.view'):
119
+ url = lambda user, i: self.request.route_url('users.view', uuid=user.uuid)
120
+ grid.add_action('view', icon='eye', url=url)
121
+ grid.set_link('username')
122
+
123
+ if self.request.has_perm('users.edit'):
124
+ url = lambda user, i: self.request.route_url('users.edit', uuid=user.uuid)
125
+ grid.add_action('edit', url=url)
126
+
127
+ return grid
128
+
129
+ def objectify(self, form):
130
+ """ """
131
+ person = super().objectify(form)
132
+
133
+ # full_name
134
+ person.full_name = self.app.make_full_name(person.first_name,
135
+ person.last_name)
136
+
137
+ return person
97
138
 
98
139
  def autocomplete_query(self, term):
99
140
  """ """
@@ -2,7 +2,7 @@
2
2
  ################################################################################
3
3
  #
4
4
  # wuttaweb -- Web App for Wutta Framework
5
- # Copyright © 2024 Lance Edgar
5
+ # Copyright © 2024-2025 Lance Edgar
6
6
  #
7
7
  # This file is part of Wutta Framework.
8
8
  #
@@ -30,7 +30,6 @@ from wuttjamaican.db.model import User
30
30
  from wuttaweb.views import MasterView
31
31
  from wuttaweb.forms import widgets
32
32
  from wuttaweb.forms.schema import PersonRef, RoleRefs
33
- from wuttaweb.db import Session
34
33
 
35
34
 
36
35
  class UserView(MasterView):
@@ -61,6 +60,14 @@ class UserView(MasterView):
61
60
  }
62
61
  sort_defaults = 'username'
63
62
 
63
+ form_fields = [
64
+ 'username',
65
+ 'person',
66
+ 'active',
67
+ 'prevent_edit',
68
+ 'roles',
69
+ ]
70
+
64
71
  def get_query(self, session=None):
65
72
  """ """
66
73
  query = super().get_query(session=session)
@@ -109,17 +116,24 @@ class UserView(MasterView):
109
116
  super().configure_form(f)
110
117
  user = f.model_instance
111
118
 
112
- # never show these
113
- f.remove('person_uuid',
114
- 'role_refs')
115
-
116
- # person
117
- f.set_node('person', PersonRef(self.request, empty_option=True))
118
- f.set_required('person', False)
119
-
120
119
  # username
121
120
  f.set_validator('username', self.unique_username)
122
121
 
122
+ # person
123
+ if self.creating or self.editing:
124
+ f.fields.insert_after('person', 'first_name')
125
+ f.set_required('first_name', False)
126
+ f.fields.insert_after('first_name', 'last_name')
127
+ f.set_required('last_name', False)
128
+ f.remove('person')
129
+ if self.editing:
130
+ person = user.person
131
+ if person:
132
+ f.set_default('first_name', person.first_name)
133
+ f.set_default('last_name', person.last_name)
134
+ else:
135
+ f.set_node('person', PersonRef(self.request))
136
+
123
137
  # password
124
138
  # nb. we must avoid 'password' as field name since
125
139
  # ColanderAlchemy wants to handle the raw/hashed value
@@ -140,7 +154,7 @@ class UserView(MasterView):
140
154
  def unique_username(self, node, value):
141
155
  """ """
142
156
  model = self.app.model
143
- session = Session()
157
+ session = self.Session()
144
158
 
145
159
  query = session.query(model.User)\
146
160
  .filter(model.User.username == value)
@@ -152,26 +166,48 @@ class UserView(MasterView):
152
166
  if query.count():
153
167
  node.raise_invalid("Username must be unique")
154
168
 
155
- def objectify(self, form, session=None):
169
+ def objectify(self, form):
156
170
  """ """
171
+ model = self.app.model
172
+ auth = self.app.get_auth_handler()
157
173
  data = form.validated
158
174
 
159
175
  # normal logic first
160
176
  user = super().objectify(form)
161
177
 
178
+ # maybe update person name
179
+ if 'first_name' in form or 'last_name' in form:
180
+ first_name = data.get('first_name')
181
+ last_name = data.get('last_name')
182
+ if self.creating and (first_name or last_name):
183
+ user.person = auth.make_person(first_name=first_name, last_name=last_name)
184
+ elif self.editing:
185
+ if first_name or last_name:
186
+ if user.person:
187
+ person = user.person
188
+ if 'first_name' in form:
189
+ person.first_name = first_name
190
+ if 'last_name' in form:
191
+ person.last_name = last_name
192
+ person.full_name = self.app.make_full_name(person.first_name,
193
+ person.last_name)
194
+ else:
195
+ user.person = auth.make_person(first_name=first_name, last_name=last_name)
196
+ elif user.person:
197
+ user.person = None
198
+
162
199
  # maybe set user password
163
200
  if 'set_password' in form and data.get('set_password'):
164
- auth = self.app.get_auth_handler()
165
201
  auth.set_user_password(user, data['set_password'])
166
202
 
167
203
  # update roles for user
168
204
  # TODO
169
205
  # if self.has_perm('edit_roles'):
170
- self.update_roles(user, form, session=session)
206
+ self.update_roles(user, form)
171
207
 
172
208
  return user
173
209
 
174
- def update_roles(self, user, form, session=None):
210
+ def update_roles(self, user, form):
175
211
  """ """
176
212
  # TODO
177
213
  # if not self.has_perm('edit_roles'):
@@ -181,7 +217,7 @@ class UserView(MasterView):
181
217
  return
182
218
 
183
219
  model = self.app.model
184
- session = session or Session()
220
+ session = self.Session()
185
221
  auth = self.app.get_auth_handler()
186
222
 
187
223
  old_roles = set([role.uuid for role in user.roles])
@@ -377,20 +377,6 @@ class TestUserRef(WebTestCase):
377
377
  self.assertIn(f'/users/{user.uuid}', url)
378
378
 
379
379
 
380
- class TestUserRefs(DataTestCase):
381
-
382
- def setUp(self):
383
- self.setup_db()
384
- self.request = testing.DummyRequest(wutta_config=self.config)
385
-
386
- def test_widget_maker(self):
387
- model = self.app.model
388
- with patch.object(mod, 'Session', return_value=self.session):
389
- typ = mod.UserRefs(self.request)
390
- widget = typ.widget_maker()
391
- self.assertIsInstance(widget, widgets.UserRefsWidget)
392
-
393
-
394
380
  class TestRoleRefs(DataTestCase):
395
381
 
396
382
  def setUp(self):
@@ -11,7 +11,7 @@ from pyramid import testing
11
11
  from wuttaweb import grids
12
12
  from wuttaweb.forms import widgets as mod
13
13
  from wuttaweb.forms import schema
14
- from wuttaweb.forms.schema import (FileDownload, PersonRef, RoleRefs, UserRefs, Permissions,
14
+ from wuttaweb.forms.schema import (FileDownload, PersonRef, RoleRefs, Permissions,
15
15
  WuttaDateTime, EmailRecipients)
16
16
  from wuttaweb.testing import WebTestCase
17
17
 
@@ -300,51 +300,6 @@ class TestRoleRefsWidget(WebTestCase):
300
300
  self.assertIn(str(blokes.uuid.hex), html)
301
301
 
302
302
 
303
- class TestUserRefsWidget(WebTestCase):
304
-
305
- def make_field(self, node, **kwargs):
306
- # TODO: not sure why default renderer is in use even though
307
- # pyramid_deform was included in setup? but this works..
308
- kwargs.setdefault('renderer', deform.Form.default_renderer)
309
- return deform.Field(node, **kwargs)
310
-
311
- def test_serialize(self):
312
- model = self.app.model
313
-
314
- # nb. we let the field construct the widget via our type
315
- # node = colander.SchemaNode(UserRefs(self.request, session=self.session))
316
- with patch.object(schema, 'Session', return_value=self.session):
317
- node = colander.SchemaNode(UserRefs(self.request))
318
- field = self.make_field(node)
319
- widget = field.widget
320
-
321
- # readonly is required
322
- self.assertRaises(NotImplementedError, widget.serialize, field, set())
323
- self.assertRaises(NotImplementedError, widget.serialize, field, set(), readonly=False)
324
-
325
- # empty
326
- html = widget.serialize(field, set(), readonly=True)
327
- self.assertEqual(html, '<span></span>')
328
-
329
- # with data, no actions
330
- user = model.User(username='barney')
331
- self.session.add(user)
332
- self.session.commit()
333
- html = widget.serialize(field, {user.uuid}, readonly=True)
334
- self.assertIn('<b-table ', html)
335
- self.assertNotIn('Actions', html)
336
- self.assertNotIn('View', html)
337
- self.assertNotIn('Edit', html)
338
-
339
- # with view/edit actions
340
- with patch.object(self.request, 'is_root', new=True):
341
- html = widget.serialize(field, {user.uuid}, readonly=True)
342
- self.assertIn('<b-table ', html)
343
- self.assertIn('Actions', html)
344
- self.assertIn('View', html)
345
- self.assertIn('Edit', html)
346
-
347
-
348
303
  class TestPermissionsWidget(WebTestCase):
349
304
 
350
305
  def make_field(self, node, **kwargs):
@@ -1021,6 +1021,9 @@ class TestGrid(WebTestCase):
1021
1021
  self.assertIn('active', filters)
1022
1022
  # nb. relationship not included by default
1023
1023
  self.assertNotIn('person', filters)
1024
+ # nb. uuid fields not included by default
1025
+ self.assertNotIn('uuid', filters)
1026
+ self.assertNotIn('person_uuid', filters)
1024
1027
 
1025
1028
  def test_make_filter(self):
1026
1029
  model = self.app.model
@@ -8,6 +8,8 @@ from pyramid.httpexceptions import HTTPNotFound
8
8
 
9
9
  from wuttaweb.views import people
10
10
  from wuttaweb.testing import WebTestCase
11
+ from wuttaweb.forms.widgets import GridWidget
12
+ from wuttaweb.grids import Grid
11
13
 
12
14
 
13
15
  class TestPersonView(WebTestCase):
@@ -34,27 +36,59 @@ class TestPersonView(WebTestCase):
34
36
  def test_configure_form(self):
35
37
  model = self.app.model
36
38
  view = self.make_view()
37
- form = view.make_form(model_class=model.Person)
38
39
 
39
- # required fields
40
+ # full_name
41
+ form = view.make_form(model_class=model.Person)
42
+ self.assertIn('full_name', form)
40
43
  with patch.object(view, 'creating', new=True):
41
- form.set_fields(form.get_model_fields())
42
- self.assertEqual(form.required_fields, {})
43
44
  view.configure_form(form)
44
- self.assertTrue(form.required_fields)
45
- self.assertFalse(form.required_fields['middle_name'])
46
-
47
- person = model.Person(full_name="Barney Rubble")
48
- user = model.User(username='barney', person=person)
49
- self.session.add(user)
50
- self.session.commit()
45
+ self.assertNotIn('full_name', form)
51
46
 
52
- # users field
47
+ # users
48
+ person = model.Person()
49
+ form = view.make_form(model_instance=person)
50
+ self.assertNotIn('users', form.widgets)
53
51
  with patch.object(view, 'viewing', new=True):
54
- form = view.make_form(model_instance=person)
55
- self.assertEqual(form.defaults, {})
56
52
  view.configure_form(form)
57
- self.assertIn('_users', form.defaults)
53
+ self.assertIn('users', form.widgets)
54
+ self.assertIsInstance(form.widgets['users'], GridWidget)
55
+
56
+ def test_make_users_grid(self):
57
+ model = self.app.model
58
+ view = self.make_view()
59
+ person = model.Person(full_name="John Doe")
60
+
61
+ # basic
62
+ grid = view.make_users_grid(person)
63
+ self.assertIsInstance(grid, Grid)
64
+ self.assertFalse(grid.linked_columns)
65
+ self.assertFalse(grid.actions)
66
+
67
+ # view + edit actions
68
+ with patch.object(self.request, 'is_root', new=True):
69
+ grid = view.make_users_grid(person)
70
+ self.assertIsInstance(grid, Grid)
71
+ self.assertIn('username', grid.linked_columns)
72
+ self.assertEqual(len(grid.actions), 2)
73
+ self.assertEqual(grid.actions[0].key, 'view')
74
+ self.assertEqual(grid.actions[1].key, 'edit')
75
+
76
+ def test_objectify(self):
77
+ model = self.app.model
78
+ view = self.make_view()
79
+
80
+ # creating
81
+ form = view.make_model_form()
82
+ form.validated = {'first_name': 'Barney', 'last_name': 'Rubble'}
83
+ person = view.objectify(form)
84
+ self.assertEqual(person.full_name, 'Barney Rubble')
85
+
86
+ # editing
87
+ form = view.make_model_form(model_instance=person)
88
+ form.validated = {'first_name': 'Betty', 'last_name': 'Rubble'}
89
+ person2 = view.objectify(form)
90
+ self.assertEqual(person2.full_name, 'Betty Rubble')
91
+ self.assertIs(person2, person)
58
92
 
59
93
  def test_autocomplete_query(self):
60
94
  model = self.app.model