accrete 0.0.38__tar.gz → 0.0.40__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 (200) hide show
  1. {accrete-0.0.38 → accrete-0.0.40}/PKG-INFO +1 -1
  2. {accrete-0.0.38 → accrete-0.0.40}/accrete/annotation.py +7 -10
  3. {accrete-0.0.38 → accrete-0.0.40}/accrete/contrib/ui/__init__.py +1 -1
  4. {accrete-0.0.38 → accrete-0.0.40}/accrete/contrib/ui/context.py +19 -10
  5. {accrete-0.0.38 → accrete-0.0.40}/accrete/contrib/ui/elements.py +1 -0
  6. {accrete-0.0.38 → accrete-0.0.40}/accrete/contrib/ui/filter.py +18 -7
  7. {accrete-0.0.38 → accrete-0.0.40}/accrete/contrib/ui/static/js/filter.js +71 -16
  8. {accrete-0.0.38 → accrete-0.0.40}/accrete/contrib/ui/templates/ui/list.html +12 -2
  9. {accrete-0.0.38 → accrete-0.0.40}/accrete/contrib/ui/templates/ui/partials/filter.html +21 -21
  10. {accrete-0.0.38 → accrete-0.0.40}/accrete/contrib/ui/templates/ui/partials/pagination_list.html +1 -1
  11. {accrete-0.0.38 → accrete-0.0.40}/accrete/contrib/ui/templates/ui/table.html +17 -14
  12. {accrete-0.0.38 → accrete-0.0.40}/accrete/contrib/ui/templatetags/accrete_ui.py +18 -10
  13. {accrete-0.0.38 → accrete-0.0.40}/accrete/contrib/user/views.py +1 -4
  14. {accrete-0.0.38 → accrete-0.0.40}/accrete/forms.py +0 -19
  15. accrete-0.0.40/accrete/managers.py +35 -0
  16. {accrete-0.0.38 → accrete-0.0.40}/accrete/models.py +2 -33
  17. accrete-0.0.40/accrete/utils/__init__.py +4 -0
  18. accrete-0.0.40/accrete/utils/forms.py +24 -0
  19. accrete-0.0.38/accrete/querystring.py → accrete-0.0.40/accrete/utils/http.py +15 -5
  20. {accrete-0.0.38 → accrete-0.0.40}/pyproject.toml +1 -1
  21. accrete-0.0.38/accrete/queries.py +0 -18
  22. accrete-0.0.38/accrete/utils/__init__.py +0 -0
  23. {accrete-0.0.38 → accrete-0.0.40}/.gitignore +0 -0
  24. {accrete-0.0.38 → accrete-0.0.40}/LICENSE +0 -0
  25. {accrete-0.0.38 → accrete-0.0.40}/README.md +0 -0
  26. {accrete-0.0.38 → accrete-0.0.40}/accrete/__init__.py +0 -0
  27. {accrete-0.0.38 → accrete-0.0.40}/accrete/admin.py +0 -0
  28. {accrete-0.0.38 → accrete-0.0.40}/accrete/apps.py +0 -0
  29. {accrete-0.0.38 → accrete-0.0.40}/accrete/config.py +0 -0
  30. {accrete-0.0.38 → accrete-0.0.40}/accrete/contrib/__init__.py +0 -0
  31. {accrete-0.0.38 → accrete-0.0.40}/accrete/contrib/sequence/__init__.py +0 -0
  32. {accrete-0.0.38 → accrete-0.0.40}/accrete/contrib/sequence/admin.py +0 -0
  33. {accrete-0.0.38 → accrete-0.0.40}/accrete/contrib/sequence/apps.py +0 -0
  34. {accrete-0.0.38 → accrete-0.0.40}/accrete/contrib/sequence/forms.py +0 -0
  35. {accrete-0.0.38 → accrete-0.0.40}/accrete/contrib/sequence/migrations/0001_initial.py +0 -0
  36. {accrete-0.0.38 → accrete-0.0.40}/accrete/contrib/sequence/migrations/0002_alter_sequence_name.py +0 -0
  37. {accrete-0.0.38 → accrete-0.0.40}/accrete/contrib/sequence/migrations/__init__.py +0 -0
  38. {accrete-0.0.38 → accrete-0.0.40}/accrete/contrib/sequence/models.py +0 -0
  39. {accrete-0.0.38 → accrete-0.0.40}/accrete/contrib/sequence/queries.py +0 -0
  40. {accrete-0.0.38 → accrete-0.0.40}/accrete/contrib/sequence/tests.py +0 -0
  41. {accrete-0.0.38 → accrete-0.0.40}/accrete/contrib/sequence/views.py +0 -0
  42. {accrete-0.0.38 → accrete-0.0.40}/accrete/contrib/system_mail/__init__.py +0 -0
  43. {accrete-0.0.38 → accrete-0.0.40}/accrete/contrib/system_mail/admin.py +0 -0
  44. {accrete-0.0.38 → accrete-0.0.40}/accrete/contrib/system_mail/apps.py +0 -0
  45. {accrete-0.0.38 → accrete-0.0.40}/accrete/contrib/system_mail/forms.py +0 -0
  46. {accrete-0.0.38 → accrete-0.0.40}/accrete/contrib/system_mail/migrations/0001_initial.py +0 -0
  47. {accrete-0.0.38 → accrete-0.0.40}/accrete/contrib/system_mail/migrations/__init__.py +0 -0
  48. {accrete-0.0.38 → accrete-0.0.40}/accrete/contrib/system_mail/models.py +0 -0
  49. {accrete-0.0.38 → accrete-0.0.40}/accrete/contrib/system_mail/tasks.py +0 -0
  50. {accrete-0.0.38 → accrete-0.0.40}/accrete/contrib/system_mail/tests.py +0 -0
  51. {accrete-0.0.38 → accrete-0.0.40}/accrete/contrib/system_mail/views.py +0 -0
  52. {accrete-0.0.38 → accrete-0.0.40}/accrete/contrib/ui/admin.py +0 -0
  53. {accrete-0.0.38 → accrete-0.0.40}/accrete/contrib/ui/apps.py +0 -0
  54. {accrete-0.0.38 → accrete-0.0.40}/accrete/contrib/ui/migrations/__init__.py +0 -0
  55. {accrete-0.0.38 → accrete-0.0.40}/accrete/contrib/ui/static/bulma/LICENSE +0 -0
  56. {accrete-0.0.38 → accrete-0.0.40}/accrete/contrib/ui/static/bulma/README.md +0 -0
  57. {accrete-0.0.38 → accrete-0.0.40}/accrete/contrib/ui/static/bulma/bulma.sass +0 -0
  58. {accrete-0.0.38 → accrete-0.0.40}/accrete/contrib/ui/static/bulma/css/bulma-rtl.css +0 -0
  59. {accrete-0.0.38 → accrete-0.0.40}/accrete/contrib/ui/static/bulma/css/bulma-rtl.css.map +0 -0
  60. {accrete-0.0.38 → accrete-0.0.40}/accrete/contrib/ui/static/bulma/css/bulma-rtl.min.css +0 -0
  61. {accrete-0.0.38 → accrete-0.0.40}/accrete/contrib/ui/static/bulma/css/bulma.css +0 -0
  62. {accrete-0.0.38 → accrete-0.0.40}/accrete/contrib/ui/static/bulma/css/bulma.css.map +0 -0
  63. {accrete-0.0.38 → accrete-0.0.40}/accrete/contrib/ui/static/bulma/css/bulma.min.css +0 -0
  64. {accrete-0.0.38 → accrete-0.0.40}/accrete/contrib/ui/static/bulma/package.json +0 -0
  65. {accrete-0.0.38 → accrete-0.0.40}/accrete/contrib/ui/static/bulma/sass/base/_all.sass +0 -0
  66. {accrete-0.0.38 → accrete-0.0.40}/accrete/contrib/ui/static/bulma/sass/base/animations.sass +0 -0
  67. {accrete-0.0.38 → accrete-0.0.40}/accrete/contrib/ui/static/bulma/sass/base/generic.sass +0 -0
  68. {accrete-0.0.38 → accrete-0.0.40}/accrete/contrib/ui/static/bulma/sass/base/helpers.sass +0 -0
  69. {accrete-0.0.38 → accrete-0.0.40}/accrete/contrib/ui/static/bulma/sass/base/minireset.sass +0 -0
  70. {accrete-0.0.38 → accrete-0.0.40}/accrete/contrib/ui/static/bulma/sass/components/_all.sass +0 -0
  71. {accrete-0.0.38 → accrete-0.0.40}/accrete/contrib/ui/static/bulma/sass/components/breadcrumb.sass +0 -0
  72. {accrete-0.0.38 → accrete-0.0.40}/accrete/contrib/ui/static/bulma/sass/components/card.sass +0 -0
  73. {accrete-0.0.38 → accrete-0.0.40}/accrete/contrib/ui/static/bulma/sass/components/dropdown.sass +0 -0
  74. {accrete-0.0.38 → accrete-0.0.40}/accrete/contrib/ui/static/bulma/sass/components/level.sass +0 -0
  75. {accrete-0.0.38 → accrete-0.0.40}/accrete/contrib/ui/static/bulma/sass/components/media.sass +0 -0
  76. {accrete-0.0.38 → accrete-0.0.40}/accrete/contrib/ui/static/bulma/sass/components/menu.sass +0 -0
  77. {accrete-0.0.38 → accrete-0.0.40}/accrete/contrib/ui/static/bulma/sass/components/message.sass +0 -0
  78. {accrete-0.0.38 → accrete-0.0.40}/accrete/contrib/ui/static/bulma/sass/components/modal.sass +0 -0
  79. {accrete-0.0.38 → accrete-0.0.40}/accrete/contrib/ui/static/bulma/sass/components/navbar.sass +0 -0
  80. {accrete-0.0.38 → accrete-0.0.40}/accrete/contrib/ui/static/bulma/sass/components/pagination.sass +0 -0
  81. {accrete-0.0.38 → accrete-0.0.40}/accrete/contrib/ui/static/bulma/sass/components/panel.sass +0 -0
  82. {accrete-0.0.38 → accrete-0.0.40}/accrete/contrib/ui/static/bulma/sass/components/tabs.sass +0 -0
  83. {accrete-0.0.38 → accrete-0.0.40}/accrete/contrib/ui/static/bulma/sass/elements/_all.sass +0 -0
  84. {accrete-0.0.38 → accrete-0.0.40}/accrete/contrib/ui/static/bulma/sass/elements/box.sass +0 -0
  85. {accrete-0.0.38 → accrete-0.0.40}/accrete/contrib/ui/static/bulma/sass/elements/button.sass +0 -0
  86. {accrete-0.0.38 → accrete-0.0.40}/accrete/contrib/ui/static/bulma/sass/elements/container.sass +0 -0
  87. {accrete-0.0.38 → accrete-0.0.40}/accrete/contrib/ui/static/bulma/sass/elements/content.sass +0 -0
  88. {accrete-0.0.38 → accrete-0.0.40}/accrete/contrib/ui/static/bulma/sass/elements/form.sass +0 -0
  89. {accrete-0.0.38 → accrete-0.0.40}/accrete/contrib/ui/static/bulma/sass/elements/icon.sass +0 -0
  90. {accrete-0.0.38 → accrete-0.0.40}/accrete/contrib/ui/static/bulma/sass/elements/image.sass +0 -0
  91. {accrete-0.0.38 → accrete-0.0.40}/accrete/contrib/ui/static/bulma/sass/elements/notification.sass +0 -0
  92. {accrete-0.0.38 → accrete-0.0.40}/accrete/contrib/ui/static/bulma/sass/elements/other.sass +0 -0
  93. {accrete-0.0.38 → accrete-0.0.40}/accrete/contrib/ui/static/bulma/sass/elements/progress.sass +0 -0
  94. {accrete-0.0.38 → accrete-0.0.40}/accrete/contrib/ui/static/bulma/sass/elements/table.sass +0 -0
  95. {accrete-0.0.38 → accrete-0.0.40}/accrete/contrib/ui/static/bulma/sass/elements/tag.sass +0 -0
  96. {accrete-0.0.38 → accrete-0.0.40}/accrete/contrib/ui/static/bulma/sass/elements/title.sass +0 -0
  97. {accrete-0.0.38 → accrete-0.0.40}/accrete/contrib/ui/static/bulma/sass/form/_all.sass +0 -0
  98. {accrete-0.0.38 → accrete-0.0.40}/accrete/contrib/ui/static/bulma/sass/form/checkbox-radio.sass +0 -0
  99. {accrete-0.0.38 → accrete-0.0.40}/accrete/contrib/ui/static/bulma/sass/form/file.sass +0 -0
  100. {accrete-0.0.38 → accrete-0.0.40}/accrete/contrib/ui/static/bulma/sass/form/input-textarea.sass +0 -0
  101. {accrete-0.0.38 → accrete-0.0.40}/accrete/contrib/ui/static/bulma/sass/form/select.sass +0 -0
  102. {accrete-0.0.38 → accrete-0.0.40}/accrete/contrib/ui/static/bulma/sass/form/shared.sass +0 -0
  103. {accrete-0.0.38 → accrete-0.0.40}/accrete/contrib/ui/static/bulma/sass/form/tools.sass +0 -0
  104. {accrete-0.0.38 → accrete-0.0.40}/accrete/contrib/ui/static/bulma/sass/grid/_all.sass +0 -0
  105. {accrete-0.0.38 → accrete-0.0.40}/accrete/contrib/ui/static/bulma/sass/grid/columns.sass +0 -0
  106. {accrete-0.0.38 → accrete-0.0.40}/accrete/contrib/ui/static/bulma/sass/grid/tiles.sass +0 -0
  107. {accrete-0.0.38 → accrete-0.0.40}/accrete/contrib/ui/static/bulma/sass/helpers/_all.sass +0 -0
  108. {accrete-0.0.38 → accrete-0.0.40}/accrete/contrib/ui/static/bulma/sass/helpers/color.sass +0 -0
  109. {accrete-0.0.38 → accrete-0.0.40}/accrete/contrib/ui/static/bulma/sass/helpers/flexbox.sass +0 -0
  110. {accrete-0.0.38 → accrete-0.0.40}/accrete/contrib/ui/static/bulma/sass/helpers/float.sass +0 -0
  111. {accrete-0.0.38 → accrete-0.0.40}/accrete/contrib/ui/static/bulma/sass/helpers/other.sass +0 -0
  112. {accrete-0.0.38 → accrete-0.0.40}/accrete/contrib/ui/static/bulma/sass/helpers/overflow.sass +0 -0
  113. {accrete-0.0.38 → accrete-0.0.40}/accrete/contrib/ui/static/bulma/sass/helpers/position.sass +0 -0
  114. {accrete-0.0.38 → accrete-0.0.40}/accrete/contrib/ui/static/bulma/sass/helpers/spacing.sass +0 -0
  115. {accrete-0.0.38 → accrete-0.0.40}/accrete/contrib/ui/static/bulma/sass/helpers/typography.sass +0 -0
  116. {accrete-0.0.38 → accrete-0.0.40}/accrete/contrib/ui/static/bulma/sass/helpers/visibility.sass +0 -0
  117. {accrete-0.0.38 → accrete-0.0.40}/accrete/contrib/ui/static/bulma/sass/layout/_all.sass +0 -0
  118. {accrete-0.0.38 → accrete-0.0.40}/accrete/contrib/ui/static/bulma/sass/layout/footer.sass +0 -0
  119. {accrete-0.0.38 → accrete-0.0.40}/accrete/contrib/ui/static/bulma/sass/layout/hero.sass +0 -0
  120. {accrete-0.0.38 → accrete-0.0.40}/accrete/contrib/ui/static/bulma/sass/layout/section.sass +0 -0
  121. {accrete-0.0.38 → accrete-0.0.40}/accrete/contrib/ui/static/bulma/sass/utilities/_all.sass +0 -0
  122. {accrete-0.0.38 → accrete-0.0.40}/accrete/contrib/ui/static/bulma/sass/utilities/animations.sass +0 -0
  123. {accrete-0.0.38 → accrete-0.0.40}/accrete/contrib/ui/static/bulma/sass/utilities/controls.sass +0 -0
  124. {accrete-0.0.38 → accrete-0.0.40}/accrete/contrib/ui/static/bulma/sass/utilities/derived-variables.sass +0 -0
  125. {accrete-0.0.38 → accrete-0.0.40}/accrete/contrib/ui/static/bulma/sass/utilities/extends.sass +0 -0
  126. {accrete-0.0.38 → accrete-0.0.40}/accrete/contrib/ui/static/bulma/sass/utilities/functions.sass +0 -0
  127. {accrete-0.0.38 → accrete-0.0.40}/accrete/contrib/ui/static/bulma/sass/utilities/initial-variables.sass +0 -0
  128. {accrete-0.0.38 → accrete-0.0.40}/accrete/contrib/ui/static/bulma/sass/utilities/mixins.sass +0 -0
  129. {accrete-0.0.38 → accrete-0.0.40}/accrete/contrib/ui/static/css/accrete.css +0 -0
  130. {accrete-0.0.38 → accrete-0.0.40}/accrete/contrib/ui/static/css/accrete.css.bak +0 -0
  131. {accrete-0.0.38 → accrete-0.0.40}/accrete/contrib/ui/static/css/accrete.css.map +0 -0
  132. {accrete-0.0.38 → accrete-0.0.40}/accrete/contrib/ui/static/css/accrete.scss +0 -0
  133. {accrete-0.0.38 → accrete-0.0.40}/accrete/contrib/ui/static/css/icons.css +0 -0
  134. {accrete-0.0.38 → accrete-0.0.40}/accrete/contrib/ui/static/icons/Logo.svg +0 -0
  135. {accrete-0.0.38 → accrete-0.0.40}/accrete/contrib/ui/static/icons/accrete.svg +0 -0
  136. {accrete-0.0.38 → accrete-0.0.40}/accrete/contrib/ui/static/js/htmx.min.js +0 -0
  137. {accrete-0.0.38 → accrete-0.0.40}/accrete/contrib/ui/static/js/list.js +0 -0
  138. {accrete-0.0.38 → accrete-0.0.40}/accrete/contrib/ui/templates/django/forms/widgets/attrs.html +0 -0
  139. {accrete-0.0.38 → accrete-0.0.40}/accrete/contrib/ui/templates/django/forms/widgets/email.html +0 -0
  140. {accrete-0.0.38 → accrete-0.0.40}/accrete/contrib/ui/templates/django/forms/widgets/file.html +0 -0
  141. {accrete-0.0.38 → accrete-0.0.40}/accrete/contrib/ui/templates/django/forms/widgets/input.html +0 -0
  142. {accrete-0.0.38 → accrete-0.0.40}/accrete/contrib/ui/templates/django/forms/widgets/select.html +0 -0
  143. {accrete-0.0.38 → accrete-0.0.40}/accrete/contrib/ui/templates/django/forms/widgets/text.html +0 -0
  144. {accrete-0.0.38 → accrete-0.0.40}/accrete/contrib/ui/templates/django/forms/widgets/textarea.html +0 -0
  145. {accrete-0.0.38 → accrete-0.0.40}/accrete/contrib/ui/templates/ui/detail.html +0 -0
  146. {accrete-0.0.38 → accrete-0.0.40}/accrete/contrib/ui/templates/ui/form.html +0 -0
  147. {accrete-0.0.38 → accrete-0.0.40}/accrete/contrib/ui/templates/ui/layout.html +0 -0
  148. {accrete-0.0.38 → accrete-0.0.40}/accrete/contrib/ui/templates/ui/partials/form_errors.html +0 -0
  149. {accrete-0.0.38 → accrete-0.0.40}/accrete/contrib/ui/templates/ui/partials/form_modal.html +0 -0
  150. {accrete-0.0.38 → accrete-0.0.40}/accrete/contrib/ui/templates/ui/partials/header.html +0 -0
  151. {accrete-0.0.38 → accrete-0.0.40}/accrete/contrib/ui/templates/ui/partials/onchange_form.html +0 -0
  152. {accrete-0.0.38 → accrete-0.0.40}/accrete/contrib/ui/templates/ui/partials/pagination_detail.html +0 -0
  153. {accrete-0.0.38 → accrete-0.0.40}/accrete/contrib/ui/templates/ui/partials/table_field.html +0 -0
  154. {accrete-0.0.38 → accrete-0.0.40}/accrete/contrib/ui/templates/ui/partials/table_field_float.html +0 -0
  155. {accrete-0.0.38 → accrete-0.0.40}/accrete/contrib/ui/templates/ui/partials/table_field_monetary.html +0 -0
  156. {accrete-0.0.38 → accrete-0.0.40}/accrete/contrib/ui/templates/ui/partials/table_field_string.html +0 -0
  157. {accrete-0.0.38 → accrete-0.0.40}/accrete/contrib/ui/templatetags/__init__.py +0 -0
  158. {accrete-0.0.38 → accrete-0.0.40}/accrete/contrib/ui/tests.py +0 -0
  159. {accrete-0.0.38 → accrete-0.0.40}/accrete/contrib/ui/urls.py +0 -0
  160. {accrete-0.0.38 → accrete-0.0.40}/accrete/contrib/ui/views.py +0 -0
  161. {accrete-0.0.38 → accrete-0.0.40}/accrete/contrib/user/__init__.py +0 -0
  162. {accrete-0.0.38 → accrete-0.0.40}/accrete/contrib/user/admin.py +0 -0
  163. {accrete-0.0.38 → accrete-0.0.40}/accrete/contrib/user/apps.py +0 -0
  164. {accrete-0.0.38 → accrete-0.0.40}/accrete/contrib/user/forms.py +0 -0
  165. {accrete-0.0.38 → accrete-0.0.40}/accrete/contrib/user/locale/de/LC_MESSAGES/django.mo +0 -0
  166. {accrete-0.0.38 → accrete-0.0.40}/accrete/contrib/user/locale/de/LC_MESSAGES/django.po +0 -0
  167. {accrete-0.0.38 → accrete-0.0.40}/accrete/contrib/user/middleware.py +0 -0
  168. {accrete-0.0.38 → accrete-0.0.40}/accrete/contrib/user/migrations/0001_initial.py +0 -0
  169. {accrete-0.0.38 → accrete-0.0.40}/accrete/contrib/user/migrations/__init__.py +0 -0
  170. {accrete-0.0.38 → accrete-0.0.40}/accrete/contrib/user/models.py +0 -0
  171. {accrete-0.0.38 → accrete-0.0.40}/accrete/contrib/user/templates/user/accrete_navbar_end_dropdown.html +0 -0
  172. {accrete-0.0.38 → accrete-0.0.40}/accrete/contrib/user/templates/user/change_email.html +0 -0
  173. {accrete-0.0.38 → accrete-0.0.40}/accrete/contrib/user/templates/user/change_password.html +0 -0
  174. {accrete-0.0.38 → accrete-0.0.40}/accrete/contrib/user/templates/user/login.html +0 -0
  175. {accrete-0.0.38 → accrete-0.0.40}/accrete/contrib/user/templates/user/user_detail.html +0 -0
  176. {accrete-0.0.38 → accrete-0.0.40}/accrete/contrib/user/templates/user/user_form.html +0 -0
  177. {accrete-0.0.38 → accrete-0.0.40}/accrete/contrib/user/tests.py +0 -0
  178. {accrete-0.0.38 → accrete-0.0.40}/accrete/contrib/user/urls.py +0 -0
  179. {accrete-0.0.38 → accrete-0.0.40}/accrete/contrib/user_registration/__init__.py +0 -0
  180. {accrete-0.0.38 → accrete-0.0.40}/accrete/contrib/user_registration/admin.py +0 -0
  181. {accrete-0.0.38 → accrete-0.0.40}/accrete/contrib/user_registration/apps.py +0 -0
  182. {accrete-0.0.38 → accrete-0.0.40}/accrete/contrib/user_registration/config.py +0 -0
  183. {accrete-0.0.38 → accrete-0.0.40}/accrete/contrib/user_registration/forms.py +0 -0
  184. {accrete-0.0.38 → accrete-0.0.40}/accrete/contrib/user_registration/migrations/0001_initial.py +0 -0
  185. {accrete-0.0.38 → accrete-0.0.40}/accrete/contrib/user_registration/migrations/__init__.py +0 -0
  186. {accrete-0.0.38 → accrete-0.0.40}/accrete/contrib/user_registration/models.py +0 -0
  187. {accrete-0.0.38 → accrete-0.0.40}/accrete/contrib/user_registration/templates/user_registration/mail_templates/confirmation_mail.html +0 -0
  188. {accrete-0.0.38 → accrete-0.0.40}/accrete/contrib/user_registration/templates/user_registration/registration.html +0 -0
  189. {accrete-0.0.38 → accrete-0.0.40}/accrete/contrib/user_registration/tests.py +0 -0
  190. {accrete-0.0.38 → accrete-0.0.40}/accrete/contrib/user_registration/urls.py +0 -0
  191. {accrete-0.0.38 → accrete-0.0.40}/accrete/contrib/user_registration/views.py +0 -0
  192. {accrete-0.0.38 → accrete-0.0.40}/accrete/middleware.py +0 -0
  193. {accrete-0.0.38 → accrete-0.0.40}/accrete/migrations/0001_initial.py +0 -0
  194. {accrete-0.0.38 → accrete-0.0.40}/accrete/migrations/0002_initial.py +0 -0
  195. {accrete-0.0.38 → accrete-0.0.40}/accrete/migrations/__init__.py +0 -0
  196. {accrete-0.0.38 → accrete-0.0.40}/accrete/tenant.py +0 -0
  197. {accrete-0.0.38 → accrete-0.0.40}/accrete/tests.py +0 -0
  198. {accrete-0.0.38 → accrete-0.0.40}/accrete/utils/dates.py +0 -0
  199. {accrete-0.0.38 → accrete-0.0.40}/accrete/utils/models.py +0 -0
  200. {accrete-0.0.38 → accrete-0.0.40}/accrete/views.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: accrete
3
- Version: 0.0.38
3
+ Version: 0.0.40
4
4
  Summary: Django Shared Schema Multi Tenant
5
5
  Author-email: Benedikt Jilek <benedikt.jilek@pm.me>
6
6
  License: Copyright (c) 2023 Benedikt Jilek
@@ -1,4 +1,4 @@
1
- from django.db.models import Field, QuerySet
1
+ from django.db.models import Field
2
2
  from django.db.models.expressions import Func
3
3
  from django.db.models.aggregates import Aggregate
4
4
 
@@ -34,13 +34,10 @@ class AnnotationModelMixin:
34
34
 
35
35
  class AnnotationManagerMixin:
36
36
 
37
- @staticmethod
38
- def add_annotations(queryset: QuerySet):
39
- model = queryset.model
40
- if not hasattr(model, 'get_annotations'):
41
- return queryset
42
- return queryset.annotate(**{
37
+ def get_annotations(self):
38
+ if not hasattr(self.model, 'get_annotations'):
39
+ return {}
40
+ return {
43
41
  annotation['name']: annotation['annotation'].function
44
- for annotation in model.get_annotations()
45
- })
46
-
42
+ for annotation in self.model.get_annotations()
43
+ }
@@ -8,7 +8,7 @@ from .context import (
8
8
  list_page,
9
9
  detail_page,
10
10
  cast_param,
11
- prepare_url_params,
11
+ url_param_dict,
12
12
  extract_url_params,
13
13
  exclude_params,
14
14
  url_param_str,
@@ -1,4 +1,5 @@
1
1
  import logging
2
+ from urllib.parse import quote_plus
2
3
  from dataclasses import dataclass, field
3
4
  from typing import TypedDict
4
5
  from django.utils.translation import gettext_lazy as _t
@@ -56,6 +57,7 @@ class TableContext(Context):
56
57
  endless_scroll: bool
57
58
  filter: Filter
58
59
  object_param_str: str = field(default='', kw_only=True)
60
+ field_selection: bool = True
59
61
 
60
62
 
61
63
  @dataclass
@@ -69,6 +71,7 @@ class ListContext(Context):
69
71
  endless_scroll: bool = True
70
72
  column_height: int = 4
71
73
  column_width: int = 12
74
+ field_selection: bool = False
72
75
 
73
76
  def __post_init__(self):
74
77
  super().__post_init__()
@@ -107,8 +110,8 @@ def cast_param(params: dict, param: str, cast_to: callable, default):
107
110
  return default
108
111
 
109
112
 
110
- def prepare_url_params(get_params: dict) -> dict:
111
- return {key: f'&{key}={value}' for key, value in get_params.items()}
113
+ def url_param_dict(get_params: dict) -> dict:
114
+ return {key: f'&{key}={quote_plus(value)}' for key, value in get_params.items()}
112
115
 
113
116
 
114
117
  def url_param_str(params: dict, extract: list[str] = None) -> str:
@@ -118,9 +121,9 @@ def url_param_str(params: dict, extract: list[str] = None) -> str:
118
121
  them, so that each value is formatted e.g. {'page': '&page=1'}
119
122
  """
120
123
  if extract:
121
- params = prepare_url_params(extract_url_params(params, extract))
124
+ params = extract_url_params(params, extract)
122
125
  param_str = (
123
- "".join(str(value) for value in params.values())
126
+ "".join(str(value) for value in url_param_dict(params).values())
124
127
  .replace('&&', '&')
125
128
  .replace('?&', '?')
126
129
  .strip('?&')
@@ -185,7 +188,7 @@ def get_table_fields(
185
188
  field_definition: dict[str | TableField] = None
186
189
  ) -> list[TableField]:
187
190
 
188
- def get_alignment(field_attr):
191
+ def get_alignment(field_attr) -> TableFieldAlignment:
189
192
  number_field_types = (
190
193
  models.DecimalField, models.IntegerField, models.FloatField
191
194
  )
@@ -193,12 +196,11 @@ def get_table_fields(
193
196
  isinstance(field_attr, number_field_types)
194
197
  or (
195
198
  isinstance(field_attr, Annotation)
196
- and field_attr.field in number_field_types
197
- )):
199
+ and field_attr.field in number_field_types)
200
+ ):
198
201
  return TableFieldAlignment.RIGHT
199
202
  return TableFieldAlignment.LEFT
200
203
 
201
-
202
204
  def get_field_definition(f_name: str) -> TableField:
203
205
  if definition := field_definition.get(f_name):
204
206
  return definition
@@ -217,12 +219,18 @@ def get_table_fields(
217
219
  elif hasattr(attr, 'field'):
218
220
  attr = attr.field
219
221
  names.append(attr.verbose_name)
222
+ is_relation = (
223
+ attr.related_model
224
+ and hasattr(attr.related_model, 'get_absolute_url')
225
+ or False
226
+ )
220
227
  return TableField(
221
228
  label=' / '.join([str(name) for name in names[1:]]),
222
229
  name=f_name,
223
230
  header_info=str(attr.help_text),
224
231
  truncate_after=50,
225
- alignment=get_alignment(attr)
232
+ alignment=get_alignment(attr),
233
+ is_relation=is_relation
226
234
  )
227
235
 
228
236
  if field_definition is None:
@@ -233,6 +241,7 @@ def get_table_fields(
233
241
  try:
234
242
  table_fields.append(get_field_definition(field_name))
235
243
  except AttributeError as e:
236
- print(e)
244
+ _logger.error(e)
237
245
  pass
246
+
238
247
  return table_fields
@@ -69,6 +69,7 @@ class TableField:
69
69
  suffix: str = ''
70
70
  truncate_after: int = 0
71
71
  template: str = None
72
+ is_relation: bool = False
72
73
 
73
74
 
74
75
  @dataclass
@@ -298,14 +298,25 @@ class Filter:
298
298
 
299
299
  def field_path_selection(self):
300
300
  html = ''
301
- for field in sorted(self.field_paths, key=lambda f: (len(f.split('__')), f.lower())):
302
- label = self.get_field_path_label(field)
303
- if not label:
304
- continue
301
+ filter_exclude = getattr(self.model, 'filter_exclude', [])
302
+ filter_exclude.extend(self.default_exclude)
303
+ foreign_keys = [x.name for x in filter(
304
+ lambda x: x.is_relation and x.name not in filter_exclude,
305
+ self.model._meta.get_fields()
306
+ )]
307
+ field_paths = [
308
+ (self.get_field_path_label(field_path), field_path)
309
+ for field_path in filter(
310
+ lambda x: len(x.split('__')) <= 1,
311
+ self.field_paths + foreign_keys
312
+ )
313
+ ]
314
+ sorted_field_paths = sorted(filter(lambda x: x[0], field_paths), key=lambda x: x[0].lower())
315
+ for field in sorted_field_paths:
305
316
  html += f"""
306
- <label class="checkbox my-1" style="width: 100%">
307
- <input type="checkbox" name="{field}">
308
- {label}
317
+ <label class="checkbox is-unselectable my-1" style="width: 100%">
318
+ <input type="checkbox" name="{field[1]}" data-label="{field[0].lower()}">
319
+ <span>{field[0]}</span>
309
320
  </label>
310
321
  """
311
322
  return html
@@ -1,6 +1,10 @@
1
+ document.body.addEventListener('htmx:afterSwap', function(evt) {
2
+ checkSelectedFields();
3
+ });
1
4
  window.addEventListener('click', showHide);
2
5
  buildQueryTagsFromUrl();
3
6
  resetInput();
7
+ checkSelectedFields();
4
8
 
5
9
  function buildQueryTagsFromUrl() {
6
10
  const queryTags = document.getElementById('query-tags');
@@ -249,11 +253,30 @@ function toggleFilterModalButton() {
249
253
  const button = document.getElementById('modal-filter-button');
250
254
  const query = document.getElementById('query-tags');
251
255
  if (query.firstElementChild && query.firstElementChild.childElementCount) {
252
- button.classList.add('is-primary');
256
+ button.classList.add('is-success');
253
257
  }
254
258
  else {
255
- button.classList.remove('is-primary');
256
- }
259
+ button.classList.remove('is-success');
260
+ }
261
+ }
262
+
263
+ function checkSelectedFields() {
264
+ const table = document.getElementById('content-table');
265
+ const header = table.querySelectorAll('th')
266
+ document.getElementById(
267
+ 'field-path-checkboxes'
268
+ ).querySelectorAll(
269
+ 'input:checked'
270
+ ).forEach((el) => {el.checked = ''});
271
+ header.forEach((th) => {
272
+ const name = th.getAttribute('data-name');
273
+ if (name) {
274
+ document.getElementById(
275
+ 'field-path-checkboxes'
276
+ ).querySelector(`input[name=${name}]`
277
+ ).checked = 'checked';
278
+ }
279
+ })
257
280
  }
258
281
 
259
282
  //-----------------------------------------------------------------------------
@@ -441,25 +464,57 @@ function changeOperator(event) {
441
464
 
442
465
  function toggleFieldSelection() {
443
466
  const fieldPaths = document.getElementById('field-paths');
467
+ const input = document.getElementById('field-path-search-input')
468
+ const checkBoxes = document.getElementById('field-path-checkboxes');
444
469
  this.event.target.classList.toggle('is-active');
445
470
  fieldPaths.classList.toggle('is-hidden');
471
+ input.value = ''
472
+ checkBoxes.querySelectorAll(':scope * input').forEach(
473
+ (el) => {
474
+ el.parentElement.classList.remove('is-hidden')
475
+ });
446
476
  }
447
477
 
448
478
  function filterFieldPaths() {
449
- const input = document.getElementById('field-path-search-input');
450
- const paths = document.getElementById('field-path-checkboxes').children;
451
- console.log(paths)
452
- console.log(paths.length)
453
- for (let i = 0; paths.length < i; i++) {
454
- if (!paths[i].firstElementChild.name.contains(input.value)) {
455
- paths[i].classList.add('is-hidden');
456
- }
479
+ const value = document.getElementById('field-path-search-input').value.toString().toLowerCase();
480
+ const checkBoxes = document.getElementById('field-path-checkboxes');
481
+ if (!value) {
482
+ checkBoxes.querySelectorAll(':scope * input').forEach(
483
+ (el) => {
484
+ el.parentElement.classList.remove('is-hidden')
485
+ });
486
+ return
457
487
  }
458
- // paths.forEach(el => {
459
- // if (!el.getAttribute('name').contains(input.value)) {
460
- // el.classList.add('is-hidden');
461
- // }
462
- // })
488
+ const pathsToShow = checkBoxes.querySelectorAll(`:scope * input[data-label*=${value}]`);
489
+ const pathsToHide = checkBoxes.querySelectorAll(`:scope * input:not([data-label*=${value}])`);
490
+ pathsToShow.forEach((el) => {el.parentElement.classList.remove('is-hidden')});
491
+ pathsToHide.forEach((el) => {el.parentElement.classList.add('is-hidden')})
492
+ }
493
+
494
+ function changeFieldSelection() {
495
+ const fieldPathApply = document.getElementById('field-path-apply');
496
+ const fieldPathApplyEvent = new Event('click');
497
+ const url = new URL(window.location)
498
+ const fieldPaths = document.getElementById('field-path-checkboxes');
499
+ const selected = fieldPaths.querySelectorAll('input:checked')
500
+ let fields = []
501
+ selected.forEach((input) => {
502
+ fields.push(input.name)
503
+ })
504
+ url.searchParams.set('fields', JSON.stringify(fields));
505
+ fieldPathApply.setAttribute('hx-get', url.toString());
506
+ htmx.process(fieldPathApply);
507
+ fieldPathApply.dispatchEvent(fieldPathApplyEvent);
508
+ }
509
+
510
+ function resetFieldPaths() {
511
+ const fieldPathApply = document.getElementById('field-path-apply');
512
+ const fieldPathApplyEvent = new Event('click');
513
+ const url = new URL(window.location)
514
+ url.searchParams.delete('fields');
515
+ fieldPathApply.setAttribute('hx-get', url.toString());
516
+ htmx.process(fieldPathApply);
517
+ fieldPathApply.dispatchEvent(fieldPathApplyEvent);
463
518
  }
464
519
 
465
520
  //-----------------------------------------------------------------------------
@@ -11,7 +11,7 @@
11
11
  hx-get="{{ url_params }}&page={{ page.next_page_number }}"
12
12
  hx-trigger="intersect once"
13
13
  hx-select=".list-column"
14
- hx-select-oob="#list-pagination-end-index,#list-pagination-next-button"
14
+ hx-select-oob="#list-pagination-end-index,#list-pagination-next-button,#list-pagination-prev-button"
15
15
  hx-swap="afterend"
16
16
  {% endif %}
17
17
  >
@@ -24,6 +24,16 @@
24
24
  {% endfor %}
25
25
  </div>
26
26
  {% if endless_scroll %}
27
- <progress class="htmx-indicator progress is-small is-primary" max="100">15%</progress>
27
+ <div id="endless-scroller"
28
+ {% if list_page.has_next %}
29
+ hx-get="{{ url_params }}&page={{ page.next_page_number }}"
30
+ hx-trigger="intersect once"
31
+ hx-select=".list-column"
32
+ hx-select-oob="#list-pagination-end-index,#list-pagination-next-button,#list-pagination-prev-button,#endless-scroller"
33
+ hx-swap="afterend"
34
+ {% endif %}
35
+ >
36
+ </div>
37
+ <progress class="htmx-indicator progress is-small is-success" max="100">15%</progress>
28
38
  {% endif %}
29
39
  {% endblock %}
@@ -54,27 +54,27 @@
54
54
  <div id="query-params-dropdown" class="box mt-0 mx-0 p-1 is-hidden is-fullwidth" tabindex="-1" style="z-index: 900; background: white; word-break: break-word; position: absolute; width: 300px; max-height: 300px; overflow-y: auto" onclick="toggleParams()">
55
55
  {{ filter.to_html.params }}
56
56
  </div>
57
- <div id="field-selection" class="button mt-1" style="z-index: 10; position: relative" onclick="toggleFieldSelection()">
58
- {% translate 'Fields' %}
59
- </div>
60
- <div id="field-paths" class="box mt-1 mx-0 p-1 is-hidden" tabindex="-1" style="z-index: 800; outline: none; background: white; word-break: break-word; position: absolute; width: 300px; max-height: 300px; overflow-y: auto">
61
- <div class="field has-addons mr-1">
62
- <p class="control is-expanded">
63
- <input id="field-path-search-input" class="input is-small" type="text" aria-label="Query Input" oninput="filterFieldPaths()">
64
- </p>
65
- <p class="control">
66
- <button id="field-path-search-clear" class="button is-small has-icon"><i class="icon-clear"></i></button>
67
- </p>
68
- <p class="control">
69
- <button id="field-path-search-reset" class="button is-small has-icon"><i class="icon-delete-filter"></i></button>
70
- </p>
71
- </div>
72
- <div id="field-path-checkboxes">
73
- {{ filter.to_html.field_paths }}
57
+ {% if field_selection %}
58
+ <div id="field-selection" class="button mt-1" style="z-index: 10; position: relative" onclick="toggleFieldSelection()">
59
+ {% translate 'Fields' %}
74
60
  </div>
75
- <div id="field-path-change">
76
-
61
+ <div id="field-paths" class="box mt-1 mx-0 p-1 is-hidden" tabindex="-1" style="z-index: 800; outline: none; background: white; word-break: break-word; position: absolute; width: 300px; max-height: 300px; overflow-y: auto">
62
+ <div class="field has-addons mr-1">
63
+ <p class="control is-expanded">
64
+ <input id="field-path-search-input" class="input is-small" type="text" aria-label="Query Input" oninput="filterFieldPaths()">
65
+ </p>
66
+ <p class="control">
67
+ <button id="field-path-search-reset" class="button is-small has-icon" onclick="resetFieldPaths()">
68
+ <i class="icon-delete-filter"></i>
69
+ </button>
70
+ </p>
71
+ </div>
72
+ <div id="field-path-checkboxes" onchange="changeFieldSelection()">
73
+ {{ filter.to_html.field_paths }}
74
+ </div>
75
+ <div id="field-path-apply" hx-get="" hx-trigger="click" hx-replace-url="true"
76
+ hx-select-oob="#content,#field-path-apply,#list-pagination">
77
+ </div>
77
78
  </div>
78
- </div>
79
-
79
+ {% endif %}
80
80
  </div>
@@ -1,7 +1,7 @@
1
1
  {% load i18n %}
2
2
  <div id="list-pagination" class="field has-addons" style="min-width: 100%; max-width: 100%">
3
3
  <p class="control">
4
- <button class="button"
4
+ <button id="list-pagination-prev-button" class="button"
5
5
  hx-get="{{ pagination_param_str }}&page={% if list_page.has_previous %}{{ list_page.previous_page_number }}{% else %}{{ list_page.paginator.num_pages }}{% endif %}"
6
6
  hx-replace-url="true"
7
7
  hx-select-oob="#content,#list-pagination,#panel-actions,#header-actions,#breadcrumbs"
@@ -4,14 +4,14 @@
4
4
  {% load accrete_ui %}
5
5
 
6
6
  {% block content %}
7
- <table class="table is-fullwidth is-hoverable hax-box" hx-indicator=".htmx-indicator">
7
+ <table id="content-table" class="table is-fullwidth is-hoverable hax-box" hx-indicator=".htmx-indicator">
8
8
  <thead style="position: sticky; top: 0; background: white; z-index: 10">
9
9
  {% block table_header_row %}
10
10
  <tr>
11
11
  {% block table_header %}
12
12
  <th>{{ object_label }}</th>
13
13
  {% for field in fields %}
14
- <th style="text-align: {% firstof field.header_alignment.value field.alignment.value %}">{{ field.label }}{% if field.header_info %}({{ field.header_info }}){% endif %}</th>
14
+ <th data-name="{{ field.name }}" style="text-align: {% firstof field.header_alignment.value field.alignment.value %}">{{ field.label }}{% if field.header_info %}({{ field.header_info }}){% endif %}</th>
15
15
  {% endfor %}
16
16
  {% if unselect_button %}
17
17
  <th></th>
@@ -32,18 +32,20 @@
32
32
  {% if field.template %}
33
33
  {% include field.template %}
34
34
  {% else %}
35
- {% with val=obj|get_attr:field.name|default_if_none:'---' %}
35
+ {% with val=obj|get_attr:field.name|default_if_none:'---' is_relation=field.is_relation %}
36
36
  <td style="text-align: {{ field.alignment.value }}" {% if val|length > field.truncate_after %}title="{{ val }}"{% endif %}>
37
37
  <span class="responsive-heading has-text-weight-light" style="margin-right: .2rem">{{ field.label }}:</span>
38
- {{ field.prefix }}
39
- {% block td_content %}
40
- {% if field.truncate_after > 0 %}
41
- {{ val|truncatechars:field.truncate_after }}
42
- {% else %}
43
- {{ val }}
44
- {% endif %}
45
- {% endblock %}
46
- {{ field.suffix }}
38
+ {% if is_relation %}<a href="{{ obj|related_obj_url:field.name }}" class="is-underlined">{% endif %}
39
+ {{ field.prefix }}
40
+ {% block td_content %}
41
+ {% if field.truncate_after > 0 %}
42
+ {{ val|truncatechars:field.truncate_after }}
43
+ {% else %}
44
+ {{ val }}
45
+ {% endif %}
46
+ {% endblock %}
47
+ {{ field.suffix }}
48
+ {% if is_relation %}</a>{% endif %}
47
49
  </td>
48
50
  {% endwith %}
49
51
  {% endif %}
@@ -65,10 +67,11 @@
65
67
  hx-select="tbody > tr"
66
68
  hx-target="#content-table-body"
67
69
  hx-swap="beforeend"
68
- hx-select-oob="#list-pagination-end-index,#list-pagination-next-button,#endless-scroller"
70
+ hx-replace-url="true"
71
+ hx-select-oob="#list-pagination-end-index,#list-pagination-next-button,#list-pagination-prev-button,#endless-scroller"
69
72
  {% endif %}
70
73
  >
71
74
  </div>
72
- <progress class="htmx-indicator progress is-small is-primary" max="100">15%</progress>
75
+ <progress class="htmx-indicator progress is-small is-success" max="100">15%</progress>
73
76
  {% endif %}
74
77
  {% endblock %}
@@ -1,4 +1,5 @@
1
1
  import logging
2
+ import operator
2
3
  from django import template
3
4
  from django.db.models import Model
4
5
  from django.core.cache import cache
@@ -28,18 +29,25 @@ def combine_templates(template_name, request=None):
28
29
 
29
30
  @register.filter(name='get_attr')
30
31
  def get_attr_from_string(param: object, value: str):
31
- attr_name = value.split('__')
32
- attr = param
33
- for name in attr_name:
34
- try:
35
- attr = getattr(attr, name)
36
- except AttributeError:
37
- _logger.exception(f'Object {attr} has no attribute {name}')
38
- return ''
32
+ try:
33
+ attr = operator.attrgetter(value)(param)
34
+ except AttributeError:
35
+ _logger.exception(f'Object {param} has no attribute {value}')
36
+ return ''
37
+ if attr is None:
38
+ return None
39
39
  if callable(attr):
40
- return attr()
40
+ return mark_safe(attr())
41
41
  else:
42
- return attr
42
+ return mark_safe(attr)
43
+
44
+
45
+ @register.filter(name='related_obj_url')
46
+ def related_obj_url(param: object, value: str):
47
+ attr = getattr(param, value, False)
48
+ if attr:
49
+ return attr.get_absolute_url()
50
+ return '#'
43
51
 
44
52
 
45
53
  @register.filter(name='message_class')
@@ -1,15 +1,12 @@
1
- from django.utils import timezone
2
1
  from django.contrib.auth.forms import AuthenticationForm
3
2
  from django.contrib.auth import views, update_session_auth_hash
4
3
  from django.contrib.auth.decorators import login_required
5
- from django.contrib.sessions.models import Session
6
4
  from django.contrib import messages
7
- from django.db.models import Q
8
5
  from django.shortcuts import redirect, render, reverse, resolve_url
9
6
  from django.utils.translation import gettext_lazy as _
10
7
  from django.conf import settings
11
8
 
12
- from accrete.forms import save_form
9
+ from accrete.utils import save_form
13
10
  from accrete.contrib import ui
14
11
  from .forms import UserForm, ChangePasswordForm, ChangeEmailForm
15
12
 
@@ -290,22 +290,3 @@ class TenantModelForm(One2ManyModelForm):
290
290
  self.save_o2m()
291
291
 
292
292
  return self.instance
293
-
294
-
295
- def save_form(form, reraise=False):
296
- form.is_saved = False
297
- form.save_error = None
298
- form.save_error_id = None
299
- try:
300
- if form.is_valid():
301
- with transaction.atomic():
302
- form.save()
303
- form.is_saved = True
304
- except Exception as e:
305
- form.save_error = repr(e)
306
- error_id = str(uuid4())[:8]
307
- _logger.exception(f'{error_id}: {e}')
308
- form.save_error_id = error_id
309
- if reraise:
310
- raise e
311
- return form
@@ -0,0 +1,35 @@
1
+ from django.db import models
2
+ from accrete.tenant import get_tenant
3
+ from accrete.annotation import AnnotationManagerMixin
4
+
5
+
6
+ class TenantManager(models.Manager, AnnotationManagerMixin):
7
+
8
+ def get_queryset(self):
9
+ queryset = super().get_queryset()
10
+ tenant = get_tenant()
11
+ if tenant:
12
+ queryset = queryset.filter(tenant=tenant)
13
+ return queryset.annotate(**self.get_annotations())
14
+
15
+ def bulk_create(
16
+ self,
17
+ objs,
18
+ batch_size=None,
19
+ ignore_conflicts=False,
20
+ update_conflicts=False,
21
+ update_fields=None,
22
+ unique_fields=None,
23
+ ):
24
+ tenant = get_tenant()
25
+ if tenant is None and not all(obj.tenant_id for obj in objs):
26
+ raise ValueError(
27
+ 'Tenant must be set for all objects when calling bulk_create'
28
+ )
29
+ for obj in objs:
30
+ obj.tenant_id = tenant.pk
31
+ return super().bulk_create(
32
+ objs, batch_size=batch_size, ignore_conflicts=ignore_conflicts,
33
+ update_conflicts=update_conflicts, update_fields=update_fields,
34
+ unique_fields=unique_fields
35
+ )
@@ -3,39 +3,8 @@ from django.conf import settings
3
3
  from django.utils.translation import gettext_lazy as _
4
4
  from django.contrib.auth.validators import UnicodeUsernameValidator
5
5
  from accrete.tenant import get_tenant
6
- from accrete.annotation import AnnotationModelMixin, AnnotationManagerMixin
7
-
8
-
9
- class TenantManager(models.Manager, AnnotationManagerMixin):
10
-
11
- def get_queryset(self):
12
- queryset = super().get_queryset()
13
- tenant = get_tenant()
14
- if tenant:
15
- queryset = queryset.filter(tenant=tenant)
16
- return self.add_annotations(queryset)
17
-
18
- def bulk_create(
19
- self,
20
- objs,
21
- batch_size=None,
22
- ignore_conflicts=False,
23
- update_conflicts=False,
24
- update_fields=None,
25
- unique_fields=None,
26
- ):
27
- tenant = get_tenant()
28
- if tenant is None and not all(obj.tenant_id for obj in objs):
29
- raise ValueError(
30
- 'Tenant must be set for all objects when calling bulk_create'
31
- )
32
- for obj in objs:
33
- obj.tenant_id = tenant.pk
34
- return super().bulk_create(
35
- objs, batch_size=batch_size, ignore_conflicts=ignore_conflicts,
36
- update_conflicts=update_conflicts, update_fields=update_fields,
37
- unique_fields=unique_fields
38
- )
6
+ from accrete.annotation import AnnotationModelMixin
7
+ from accrete.managers import TenantManager
39
8
 
40
9
 
41
10
  class TenantModel(models.Model, AnnotationModelMixin):
@@ -0,0 +1,4 @@
1
+ from . import dates
2
+ from .forms import save_form
3
+ from .http import filter_from_querystring
4
+ from .models import get_related_model
@@ -0,0 +1,24 @@
1
+ import logging
2
+ from uuid import uuid4
3
+ from django.db import transaction
4
+
5
+ _logger = logging.getLogger(__name__)
6
+
7
+
8
+ def save_form(form, reraise=False):
9
+ form.is_saved = False
10
+ form.save_error = None
11
+ form.save_error_id = None
12
+ try:
13
+ if form.is_valid():
14
+ with transaction.atomic():
15
+ form.save()
16
+ form.is_saved = True
17
+ except Exception as e:
18
+ form.save_error = repr(e)
19
+ error_id = str(uuid4())[:8]
20
+ _logger.exception(f'{error_id}: {e}')
21
+ form.save_error_id = error_id
22
+ if reraise:
23
+ raise e
24
+ return form