accrete 0.0.104__py3-none-any.whl → 0.0.105__py3-none-any.whl

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 (248) hide show
  1. accrete/contrib/ui/__init__.py +6 -29
  2. accrete/contrib/ui/context.py +26 -297
  3. accrete/contrib/ui/filter.py +316 -324
  4. accrete/contrib/ui/middleware.py +44 -0
  5. accrete/contrib/ui/models.py +3 -0
  6. accrete/contrib/ui/static/bulma/README.md +4 -2
  7. accrete/contrib/ui/static/bulma/bulma.css +21551 -0
  8. accrete/contrib/ui/static/bulma/bulma.css.map +1 -0
  9. accrete/contrib/ui/static/bulma/bulma.scss +1 -1
  10. accrete/contrib/ui/static/bulma/css/bulma.css +1988 -2874
  11. accrete/contrib/ui/static/bulma/css/bulma.css.map +1 -1
  12. accrete/contrib/ui/static/bulma/css/bulma.min.css +2 -2
  13. accrete/contrib/ui/static/bulma/css/versions/bulma-no-dark-mode.css +19648 -0
  14. accrete/contrib/ui/static/bulma/css/versions/bulma-no-dark-mode.css.map +1 -0
  15. accrete/contrib/ui/static/bulma/css/versions/bulma-no-dark-mode.min.css +2 -2
  16. accrete/contrib/ui/static/bulma/css/versions/bulma-no-helpers-prefixed.css +11136 -0
  17. accrete/contrib/ui/static/bulma/css/versions/bulma-no-helpers-prefixed.css.map +1 -0
  18. accrete/contrib/ui/static/bulma/css/versions/bulma-no-helpers-prefixed.min.css +2 -2
  19. accrete/contrib/ui/static/bulma/css/versions/bulma-no-helpers.css +11136 -0
  20. accrete/contrib/ui/static/bulma/css/versions/bulma-no-helpers.css.map +1 -0
  21. accrete/contrib/ui/static/bulma/css/versions/bulma-no-helpers.min.css +2 -2
  22. accrete/contrib/ui/static/bulma/css/versions/bulma-prefixed.min.css +21550 -2
  23. accrete/contrib/ui/static/bulma/css/versions/bulma-prefixed.min.css.map +1 -1
  24. accrete/contrib/ui/static/bulma/css/versions/bulma-prefixed.min.min.css +3 -0
  25. accrete/contrib/ui/static/bulma/package.json +12 -11
  26. accrete/contrib/ui/static/bulma/sass/base/animations.css +15 -0
  27. accrete/contrib/ui/static/bulma/sass/base/animations.css.map +1 -0
  28. accrete/contrib/ui/static/bulma/sass/base/generic.css +196 -0
  29. accrete/contrib/ui/static/bulma/sass/base/generic.css.map +1 -0
  30. accrete/contrib/ui/static/bulma/sass/base/minireset.css +82 -0
  31. accrete/contrib/ui/static/bulma/sass/base/minireset.css.map +1 -0
  32. accrete/contrib/ui/static/bulma/sass/base/skeleton.css +113 -0
  33. accrete/contrib/ui/static/bulma/sass/base/skeleton.css.map +1 -0
  34. accrete/contrib/ui/static/bulma/sass/base/skeleton.scss +0 -12
  35. accrete/contrib/ui/static/bulma/sass/components/breadcrumb.css +108 -0
  36. accrete/contrib/ui/static/bulma/sass/components/breadcrumb.css.map +1 -0
  37. accrete/contrib/ui/static/bulma/sass/components/card.css +130 -0
  38. accrete/contrib/ui/static/bulma/sass/components/card.css.map +1 -0
  39. accrete/contrib/ui/static/bulma/sass/components/dropdown.css +119 -0
  40. accrete/contrib/ui/static/bulma/sass/components/dropdown.css.map +1 -0
  41. accrete/contrib/ui/static/bulma/sass/components/menu.css +119 -0
  42. accrete/contrib/ui/static/bulma/sass/components/menu.css.map +1 -0
  43. accrete/contrib/ui/static/bulma/sass/components/message.css +191 -0
  44. accrete/contrib/ui/static/bulma/sass/components/message.css.map +1 -0
  45. accrete/contrib/ui/static/bulma/sass/components/modal.css +194 -0
  46. accrete/contrib/ui/static/bulma/sass/components/modal.css.map +1 -0
  47. accrete/contrib/ui/static/bulma/sass/components/navbar.css +768 -0
  48. accrete/contrib/ui/static/bulma/sass/components/navbar.css.map +1 -0
  49. accrete/contrib/ui/static/bulma/sass/components/navbar.scss +41 -30
  50. accrete/contrib/ui/static/bulma/sass/components/pagination.css +302 -0
  51. accrete/contrib/ui/static/bulma/sass/components/pagination.css.map +1 -0
  52. accrete/contrib/ui/static/bulma/sass/components/panel.css +224 -0
  53. accrete/contrib/ui/static/bulma/sass/components/panel.css.map +1 -0
  54. accrete/contrib/ui/static/bulma/sass/components/panel.scss +2 -2
  55. accrete/contrib/ui/static/bulma/sass/components/tabs.css +192 -0
  56. accrete/contrib/ui/static/bulma/sass/components/tabs.css.map +1 -0
  57. accrete/contrib/ui/static/bulma/sass/elements/block.css +17 -0
  58. accrete/contrib/ui/static/bulma/sass/elements/block.css.map +1 -0
  59. accrete/contrib/ui/static/bulma/sass/elements/box.css +43 -0
  60. accrete/contrib/ui/static/bulma/sass/elements/box.css.map +1 -0
  61. accrete/contrib/ui/static/bulma/sass/elements/button.css +685 -0
  62. accrete/contrib/ui/static/bulma/sass/elements/button.css.map +1 -0
  63. accrete/contrib/ui/static/bulma/sass/elements/button.scss +9 -2
  64. accrete/contrib/ui/static/bulma/sass/elements/content.css +208 -0
  65. accrete/contrib/ui/static/bulma/sass/elements/content.css.map +1 -0
  66. accrete/contrib/ui/static/bulma/sass/elements/content.scss +8 -2
  67. accrete/contrib/ui/static/bulma/sass/elements/delete.css +60 -0
  68. accrete/contrib/ui/static/bulma/sass/elements/delete.css.map +1 -0
  69. accrete/contrib/ui/static/bulma/sass/elements/icon.css +51 -0
  70. accrete/contrib/ui/static/bulma/sass/elements/icon.css.map +1 -0
  71. accrete/contrib/ui/static/bulma/sass/elements/image.css +253 -0
  72. accrete/contrib/ui/static/bulma/sass/elements/image.css.map +1 -0
  73. accrete/contrib/ui/static/bulma/sass/elements/loader.css +14 -0
  74. accrete/contrib/ui/static/bulma/sass/elements/loader.css.map +1 -0
  75. accrete/contrib/ui/static/bulma/sass/elements/notification.css +213 -0
  76. accrete/contrib/ui/static/bulma/sass/elements/notification.css.map +1 -0
  77. accrete/contrib/ui/static/bulma/sass/elements/progress.css +119 -0
  78. accrete/contrib/ui/static/bulma/sass/elements/progress.css.map +1 -0
  79. accrete/contrib/ui/static/bulma/sass/elements/table.css +294 -0
  80. accrete/contrib/ui/static/bulma/sass/elements/table.css.map +1 -0
  81. accrete/contrib/ui/static/bulma/sass/elements/tag.css +252 -0
  82. accrete/contrib/ui/static/bulma/sass/elements/tag.css.map +1 -0
  83. accrete/contrib/ui/static/bulma/sass/elements/title.css +131 -0
  84. accrete/contrib/ui/static/bulma/sass/elements/title.css.map +1 -0
  85. accrete/contrib/ui/static/bulma/sass/form/checkbox-radio.css +72 -0
  86. accrete/contrib/ui/static/bulma/sass/form/checkbox-radio.css.map +1 -0
  87. accrete/contrib/ui/static/bulma/sass/form/checkbox-radio.scss +7 -3
  88. accrete/contrib/ui/static/bulma/sass/form/file.css +374 -0
  89. accrete/contrib/ui/static/bulma/sass/form/file.css.map +1 -0
  90. accrete/contrib/ui/static/bulma/sass/form/input-textarea.css +284 -0
  91. accrete/contrib/ui/static/bulma/sass/form/input-textarea.css.map +1 -0
  92. accrete/contrib/ui/static/bulma/sass/form/input-textarea.scss +0 -10
  93. accrete/contrib/ui/static/bulma/sass/form/select.css +347 -0
  94. accrete/contrib/ui/static/bulma/sass/form/select.css.map +1 -0
  95. accrete/contrib/ui/static/bulma/sass/form/select.scss +1 -0
  96. accrete/contrib/ui/static/bulma/sass/form/shared.css +48 -0
  97. accrete/contrib/ui/static/bulma/sass/form/shared.css.map +1 -0
  98. accrete/contrib/ui/static/bulma/sass/form/shared.scss +5 -1
  99. accrete/contrib/ui/static/bulma/sass/form/tools.css +356 -0
  100. accrete/contrib/ui/static/bulma/sass/form/tools.css.map +1 -0
  101. accrete/contrib/ui/static/bulma/sass/form/tools.scss +23 -12
  102. accrete/contrib/ui/static/bulma/sass/grid/columns-v2.css +1635 -0
  103. accrete/contrib/ui/static/bulma/sass/grid/columns-v2.css.map +1 -0
  104. accrete/contrib/ui/static/bulma/sass/grid/columns.css +1652 -0
  105. accrete/contrib/ui/static/bulma/sass/grid/columns.css.map +1 -0
  106. accrete/contrib/ui/static/bulma/sass/grid/columns.scss +109 -25
  107. accrete/contrib/ui/static/bulma/sass/grid/grid.css +3011 -0
  108. accrete/contrib/ui/static/bulma/sass/grid/grid.css.map +1 -0
  109. accrete/contrib/ui/static/bulma/sass/grid/grid.scss +3 -3
  110. accrete/contrib/ui/static/bulma/sass/helpers/aspect-ratio.css +61 -0
  111. accrete/contrib/ui/static/bulma/sass/helpers/aspect-ratio.css.map +1 -0
  112. accrete/contrib/ui/static/bulma/sass/helpers/border.css +17 -0
  113. accrete/contrib/ui/static/bulma/sass/helpers/border.css.map +1 -0
  114. accrete/contrib/ui/static/bulma/sass/helpers/color.css +5582 -0
  115. accrete/contrib/ui/static/bulma/sass/helpers/color.css.map +1 -0
  116. accrete/contrib/ui/static/bulma/sass/helpers/color.scss +166 -186
  117. accrete/contrib/ui/static/bulma/sass/helpers/flexbox.css +217 -0
  118. accrete/contrib/ui/static/bulma/sass/helpers/flexbox.css.map +1 -0
  119. accrete/contrib/ui/static/bulma/sass/helpers/float.css +37 -0
  120. accrete/contrib/ui/static/bulma/sass/helpers/float.css.map +1 -0
  121. accrete/contrib/ui/static/bulma/sass/helpers/gap.css +209 -0
  122. accrete/contrib/ui/static/bulma/sass/helpers/gap.css.map +1 -0
  123. accrete/contrib/ui/static/bulma/sass/helpers/other.css +34 -0
  124. accrete/contrib/ui/static/bulma/sass/helpers/other.css.map +1 -0
  125. accrete/contrib/ui/static/bulma/sass/helpers/overflow.css +65 -0
  126. accrete/contrib/ui/static/bulma/sass/helpers/overflow.css.map +1 -0
  127. accrete/contrib/ui/static/bulma/sass/helpers/position.css +45 -0
  128. accrete/contrib/ui/static/bulma/sass/helpers/position.css.map +1 -0
  129. accrete/contrib/ui/static/bulma/sass/helpers/spacing.css +489 -0
  130. accrete/contrib/ui/static/bulma/sass/helpers/spacing.css.map +1 -0
  131. accrete/contrib/ui/static/bulma/sass/helpers/typography.css +423 -0
  132. accrete/contrib/ui/static/bulma/sass/helpers/typography.css.map +1 -0
  133. accrete/contrib/ui/static/bulma/sass/helpers/visibility.css +485 -0
  134. accrete/contrib/ui/static/bulma/sass/helpers/visibility.css.map +1 -0
  135. accrete/contrib/ui/static/bulma/sass/layout/container.scss +16 -8
  136. accrete/contrib/ui/static/bulma/sass/layout/footer.css +9 -0
  137. accrete/contrib/ui/static/bulma/sass/layout/footer.css.map +1 -0
  138. accrete/contrib/ui/static/bulma/sass/layout/hero.css +534 -0
  139. accrete/contrib/ui/static/bulma/sass/layout/hero.css.map +1 -0
  140. accrete/contrib/ui/static/bulma/sass/layout/level.css +102 -0
  141. accrete/contrib/ui/static/bulma/sass/layout/level.css.map +1 -0
  142. accrete/contrib/ui/static/bulma/sass/layout/media.css +90 -0
  143. accrete/contrib/ui/static/bulma/sass/layout/media.css.map +1 -0
  144. accrete/contrib/ui/static/bulma/sass/layout/section.css +23 -0
  145. accrete/contrib/ui/static/bulma/sass/layout/section.css.map +1 -0
  146. accrete/contrib/ui/static/bulma/sass/layout/section.scss +4 -0
  147. accrete/contrib/ui/static/bulma/sass/themes/dark.css +3 -0
  148. accrete/contrib/ui/static/bulma/sass/themes/dark.css.map +1 -0
  149. accrete/contrib/ui/static/bulma/sass/themes/light.css +3 -0
  150. accrete/contrib/ui/static/bulma/sass/themes/light.css.map +1 -0
  151. accrete/contrib/ui/static/bulma/sass/themes/light.scss +1 -0
  152. accrete/contrib/ui/static/bulma/sass/themes/setup.css +3 -0
  153. accrete/contrib/ui/static/bulma/sass/themes/setup.css.map +1 -0
  154. accrete/contrib/ui/static/bulma/sass/utilities/controls.css +13 -0
  155. accrete/contrib/ui/static/bulma/sass/utilities/controls.css.map +1 -0
  156. accrete/contrib/ui/static/bulma/sass/utilities/css-variables.css +3 -0
  157. accrete/contrib/ui/static/bulma/sass/utilities/css-variables.css.map +1 -0
  158. accrete/contrib/ui/static/bulma/sass/utilities/css-variables.scss +3 -2
  159. accrete/contrib/ui/static/bulma/sass/utilities/derived-variables.css +3 -0
  160. accrete/contrib/ui/static/bulma/sass/utilities/derived-variables.css.map +1 -0
  161. accrete/contrib/ui/static/bulma/sass/utilities/extends.css +13 -0
  162. accrete/contrib/ui/static/bulma/sass/utilities/extends.css.map +1 -0
  163. accrete/contrib/ui/static/bulma/sass/utilities/functions.scss +2 -2
  164. accrete/contrib/ui/static/bulma/sass/utilities/initial-variables.css +3 -0
  165. accrete/contrib/ui/static/bulma/sass/utilities/initial-variables.css.map +1 -0
  166. accrete/contrib/ui/static/bulma/sass/utilities/initial-variables.scss +4 -4
  167. accrete/contrib/ui/static/bulma/sass/utilities/mixins.css +3 -0
  168. accrete/contrib/ui/static/bulma/sass/utilities/mixins.css.map +1 -0
  169. accrete/contrib/ui/static/bulma/sass/utilities/mixins.scss +1 -1
  170. accrete/contrib/ui/static/bulma/versions/bulma-no-dark-mode.css +19648 -0
  171. accrete/contrib/ui/static/bulma/versions/bulma-no-dark-mode.css.map +1 -0
  172. accrete/contrib/ui/static/bulma/versions/bulma-no-dark-mode.scss +2 -1
  173. accrete/contrib/ui/static/bulma/versions/bulma-no-helpers-prefixed.css +11136 -0
  174. accrete/contrib/ui/static/bulma/versions/bulma-no-helpers-prefixed.css.map +1 -0
  175. accrete/contrib/ui/static/bulma/versions/bulma-no-helpers-prefixed.scss +1 -1
  176. accrete/contrib/ui/static/bulma/versions/bulma-no-helpers.css +11136 -0
  177. accrete/contrib/ui/static/bulma/versions/bulma-no-helpers.css.map +1 -0
  178. accrete/contrib/ui/static/bulma/versions/bulma-no-helpers.scss +1 -1
  179. accrete/contrib/ui/static/bulma/versions/bulma-prefixed.css +21551 -0
  180. accrete/contrib/ui/static/bulma/versions/bulma-prefixed.css.map +1 -0
  181. accrete/contrib/ui/static/bulma/versions/bulma-prefixed.scss +1 -1
  182. accrete/contrib/ui/static/css/accrete.css +20757 -19997
  183. accrete/contrib/ui/static/css/accrete.css.map +1 -1
  184. accrete/contrib/ui/static/css/accrete.scss +185 -462
  185. accrete/contrib/ui/static/js/filter.js +97 -679
  186. accrete/contrib/ui/static/js/htmx.min.js +1 -1
  187. accrete/contrib/ui/templates/django/forms/widgets/date.html +5 -9
  188. accrete/contrib/ui/templates/django/forms/widgets/select.html +7 -5
  189. accrete/contrib/ui/templates/ui/content_right.html +7 -0
  190. accrete/contrib/ui/templates/ui/filter/filter.html +27 -0
  191. accrete/contrib/ui/templates/ui/filter/query_input.html +31 -0
  192. accrete/contrib/ui/templates/ui/filter/query_operator.html +11 -0
  193. accrete/contrib/ui/templates/ui/filter/query_params.html +106 -0
  194. accrete/contrib/ui/templates/ui/filter/query_tags.html +26 -0
  195. accrete/contrib/ui/templates/ui/layout.html +162 -233
  196. accrete/contrib/ui/templates/ui/list.html +39 -28
  197. accrete/contrib/ui/templates/ui/list_update.html +3 -0
  198. accrete/contrib/ui/templates/ui/message.html +13 -0
  199. accrete/contrib/ui/templates/ui/{partials/modal.html → modal.html} +9 -5
  200. accrete/contrib/ui/templates/ui/oob.html +3 -0
  201. accrete/contrib/ui/templates/ui/table.html +67 -71
  202. accrete/contrib/ui/templates/ui/table_row_update.html +14 -0
  203. accrete/contrib/ui/templates/ui/widgets/model_search_select.html +2 -2
  204. accrete/contrib/ui/templates/ui/widgets/model_search_select_multi.html +24 -13
  205. accrete/contrib/ui/templates/ui/widgets/model_search_select_options.html +1 -1
  206. accrete/contrib/ui/templatetags/{accrete_ui.py → ui.py} +67 -56
  207. accrete/contrib/ui/urls.py +3 -1
  208. accrete/contrib/ui/views.py +33 -3
  209. accrete/contrib/ui/widgets/__init__.py +1 -0
  210. accrete/contrib/ui/{forms/widgets.py → widgets/search_select.py} +7 -2
  211. accrete/contrib/user/templates/user/login.html +71 -23
  212. accrete/forms.py +0 -2
  213. accrete/managers.py +4 -4
  214. accrete/middleware.py +43 -66
  215. accrete/models.py +7 -1
  216. accrete/storage.py +4 -1
  217. accrete/tenant.py +9 -4
  218. accrete/utils/__init__.py +2 -0
  219. accrete/utils/models.py +9 -1
  220. accrete/utils/views.py +36 -20
  221. accrete/views.py +9 -5
  222. {accrete-0.0.104.dist-info → accrete-0.0.105.dist-info}/METADATA +3 -2
  223. accrete-0.0.105.dist-info/RECORD +370 -0
  224. {accrete-0.0.104.dist-info → accrete-0.0.105.dist-info}/WHEEL +1 -1
  225. accrete/contrib/ui/components/__init__.py +0 -1
  226. accrete/contrib/ui/components/search_select.py +0 -18
  227. accrete/contrib/ui/elements.py +0 -95
  228. accrete/contrib/ui/forms/__init__.py +0 -0
  229. accrete/contrib/ui/static/bulma/css/versions/bulma-no-dark-mode.min.css.map +0 -1
  230. accrete/contrib/ui/static/bulma/css/versions/bulma-no-helpers-prefixed.min.css.map +0 -1
  231. accrete/contrib/ui/static/bulma/css/versions/bulma-no-helpers.min.css.map +0 -1
  232. accrete/contrib/ui/static/bulma/sass/grid/columns-v2.scss +0 -957
  233. accrete/contrib/ui/static/js/ui.js +0 -30
  234. accrete/contrib/ui/templates/ui/dashboard.html +0 -7
  235. accrete/contrib/ui/templates/ui/detail.html +0 -19
  236. accrete/contrib/ui/templates/ui/form.html +0 -21
  237. accrete/contrib/ui/templates/ui/partials/filter.html +0 -146
  238. accrete/contrib/ui/templates/ui/partials/form_errors.html +0 -34
  239. accrete/contrib/ui/templates/ui/partials/header.html +0 -123
  240. accrete/contrib/ui/templates/ui/partials/modal_form.html +0 -46
  241. accrete/contrib/ui/templates/ui/partials/onchange_form.html +0 -1
  242. accrete/contrib/ui/templates/ui/partials/pagination_detail.html +0 -23
  243. accrete/contrib/ui/templates/ui/partials/pagination_list.html +0 -28
  244. accrete/contrib/ui/templates/ui/partials/table_field_tags.html +0 -17
  245. accrete/contrib/ui/templates/ui/widgets/model_search_select_multi_selected_options.html +0 -3
  246. accrete/contrib/ui/templates/ui/widgets/model_search_select_multi_tags.html +0 -6
  247. accrete-0.0.104.dist-info/RECORD +0 -238
  248. {accrete-0.0.104.dist-info → accrete-0.0.105.dist-info}/licenses/LICENSE +0 -0
accrete/managers.py CHANGED
@@ -1,15 +1,15 @@
1
1
  from django.db import models
2
2
  from accrete.tenant import get_tenant
3
- from accrete.annotation import AnnotationManagerMixin
4
3
 
5
4
 
6
- class TenantManager(models.Manager, AnnotationManagerMixin):
5
+ class TenantManager(models.Manager):
7
6
 
8
7
  def get_queryset(self):
9
8
  queryset = super().get_queryset()
10
- if tenant := get_tenant():
9
+ tenant = get_tenant()
10
+ if tenant:
11
11
  queryset = queryset.filter(tenant=tenant)
12
- return queryset.annotate(**self.get_annotations(queryset))
12
+ return queryset
13
13
 
14
14
  def bulk_create(
15
15
  self,
accrete/middleware.py CHANGED
@@ -1,92 +1,69 @@
1
+ import logging
2
+
1
3
  from django.utils.deprecation import MiddlewareMixin
2
- from django.conf import settings
3
- from django.http.response import HttpResponseRedirect, HttpResponsePermanentRedirect
4
- from accrete.tenant import set_tenant, set_member
4
+ from accrete.tenant import set_tenant, set_member, get_tenant
5
+
6
+ from .models import Tenant, Member
5
7
 
6
- from .models import Tenant
8
+ _logger = logging.getLogger(__name__)
7
9
 
8
10
 
9
11
  class TenantMiddleware(MiddlewareMixin):
10
12
  @staticmethod
11
13
  def get_tenant_id_from_request(request):
12
- if tenant := request.META.get('X-TENANT_ID'):
13
- tenant_id = tenant
14
- elif tenant_id := request.GET.get('tenant_id'):
15
- tenant_id = tenant_id
16
- elif tenant := request.COOKIES.get('tenant_id'):
17
- tenant_id = tenant
18
- else:
19
- tenant_id = None
20
-
14
+ tenant_id = (
15
+ request.GET.get('tenant_id')
16
+ or request.META.get('HTTP_X_TENANT_ID')
17
+ )
21
18
  try:
22
19
  tenant_id = int(tenant_id)
23
- except (ValueError, TypeError):
20
+ assert tenant_id > 0
21
+ except (ValueError, TypeError, AssertionError):
24
22
  tenant_id = None
25
-
26
23
  return tenant_id
27
24
 
28
25
  def process_request(self, request):
29
26
  request.tenant = None
30
27
  request.member = None
28
+ set_member(False)
31
29
  if request.path.startswith('/admin'):
30
+ return
31
+ if not request.user.is_authenticated:
32
32
  set_member(None)
33
33
  return
34
+ tenant_id = self.get_tenant_id_from_request(request)
35
+ tenant = Tenant.objects.none()
36
+ memberships = Member.objects.filter(
37
+ user=request.user, is_active=True, tenant__is_active=True
38
+ ).prefetch_related('tenant', 'user').order_by('pk')
39
+ if tenant_id:
40
+ tenant = Tenant.objects.get(pk=tenant_id)
41
+ memberships = memberships.filter(tenant=tenant)
42
+ if memberships.count() == 1:
43
+ request.member = memberships.first()
44
+ request.tenant = request.member.tenant
45
+ set_member(request.member)
46
+ self.update_post_data(request)
47
+ return
48
+ if memberships.count() > 1:
49
+ set_member(None)
50
+ return
51
+ if request.user.is_staff and tenant:
52
+ set_member(None)
53
+ set_tenant(tenant)
54
+ request.tenant = tenant
55
+ return
56
+ set_member(None)
57
+ return
34
58
 
35
- if request.user.is_authenticated:
36
- memberships = request.user.memberships.filter(
37
- is_active=True,
38
- tenant__is_active=True
39
- )
40
-
41
- if request.user.is_staff:
42
- tenant_id = self.get_tenant_id_from_request(request)
43
- if not tenant_id:
44
- tenant_id = memberships and memberships.first().tenant.pk
45
- request.tenant = tenant_id and Tenant.objects.filter(pk=tenant_id).first()
46
- request.member = request.tenant.members.filter(user=request.user).first()
47
-
48
- elif memberships and len(memberships) == 1:
49
- request.member = memberships.first()
50
- request.tenant = request.member.tenant
51
- elif memberships and len(memberships) > 1:
52
- tenant_id = self.get_tenant_id_from_request(request)
53
- if not tenant_id:
54
- tenant_id = memberships.first().tenant.pk
55
- request.member = memberships.filter(tenant_id=tenant_id).first()
56
- request.tenant = request.member.tenant
57
-
58
- set_member(request.member)
59
- if request.user.is_staff:
60
- set_tenant(request.tenant)
61
-
59
+ @staticmethod
60
+ def update_post_data(request):
62
61
  if request.POST and not request.POST.get('tenant') and request.tenant:
63
62
  request.POST = request.POST.copy()
64
63
  request.POST['tenant'] = request.tenant.pk
65
64
 
66
65
  @staticmethod
67
66
  def process_response(request, response):
68
- if request.tenant:
69
- response['X-TENANT_ID'] = request.tenant.id
70
- response.set_cookie(
71
- 'tenant_id',
72
- request.tenant.pk,
73
- samesite=settings.SESSION_COOKIE_SAMESITE,
74
- max_age=31536000
75
- )
76
- return response
77
-
78
-
79
- class HtmxRedirectMiddleware(MiddlewareMixin):
80
-
81
- @staticmethod
82
- def process_response(request, response):
83
- is_htmx = request.headers.get('HX-Request', 'false') == 'true'
84
- if not is_htmx:
85
- return response
86
- is_redirect = isinstance(response, HttpResponseRedirect)
87
- is_permanent_redirect = isinstance(response, HttpResponsePermanentRedirect)
88
- if is_redirect or is_permanent_redirect:
89
- response.status_code = 200
90
- header = 'HX-Location' if is_redirect else 'HX-Redirect'
91
- response[header] = response['Location']
67
+ tenant = get_tenant()
68
+ response['X-TENANT-ID'] = tenant and tenant.id or 0
92
69
  return response
accrete/models.py CHANGED
@@ -14,6 +14,7 @@ class TenantModel(models.Model, AnnotationModelMixin):
14
14
  abstract = True
15
15
 
16
16
  tenant = models.ForeignKey(
17
+ verbose_name=_('Tenant'),
17
18
  to='accrete.Tenant',
18
19
  on_delete=models.CASCADE
19
20
  )
@@ -34,7 +35,8 @@ class TenantModel(models.Model, AnnotationModelMixin):
34
35
  if self.pk and not self.tenant and not tenant:
35
36
  raise ValueError(
36
37
  'Tenant not provided! '
37
- 'Use accrete.tenant.set_tenant() or set the tenant explicitly on the instance.'
38
+ 'Use accrete.tenant.set_tenant() '
39
+ 'or set the tenant explicitly on the instance.'
38
40
  )
39
41
  if tenant:
40
42
  self.tenant = tenant
@@ -45,6 +47,10 @@ class TenantModel(models.Model, AnnotationModelMixin):
45
47
  update_fields=update_fields
46
48
  )
47
49
 
50
+ @staticmethod
51
+ def exclude_from_filter():
52
+ return ['tenant']
53
+
48
54
 
49
55
  class Tenant(models.Model):
50
56
 
accrete/storage.py CHANGED
@@ -25,7 +25,10 @@ class TenantFileSystemStorage(FileSystemStorage):
25
25
  def base_url(self):
26
26
  if self._base_url is not None and not self._base_url.endswith("/"):
27
27
  self._base_url += "/"
28
- res = self._value_or_setting(self._base_url, f'{settings.MEDIA_URL.strip("/")}/{get_tenant().id}')
28
+ res = self._value_or_setting(
29
+ self._base_url,
30
+ f'{settings.MEDIA_URL.strip("/")}/{get_tenant().id}'
31
+ )
29
32
  return res
30
33
 
31
34
  def url(self, name):
accrete/tenant.py CHANGED
@@ -2,7 +2,7 @@ import contextvars
2
2
  import logging
3
3
  import time
4
4
  from django.apps import apps
5
- from django.db.models import Q
5
+ from django.db.models import Q, QuerySet
6
6
 
7
7
  _logger = logging.getLogger(__name__)
8
8
 
@@ -19,7 +19,12 @@ def get_tenant():
19
19
 
20
20
 
21
21
  def set_member(member):
22
- tenant = member.tenant if member is not None else None
22
+ if member is False:
23
+ tenant = False
24
+ elif member is None:
25
+ tenant = None
26
+ else:
27
+ tenant = member.tenant
23
28
  member_contextvar.set(member)
24
29
  tenant_contextvar.set(tenant)
25
30
 
@@ -39,7 +44,7 @@ class Unscoped:
39
44
  'Entering unscoped context manager with tenant already set to None!',
40
45
  stack_info=True
41
46
  )
42
- set_tenant(None)
47
+ set_tenant(False)
43
48
 
44
49
  def __exit__(self, exc_type, exc_val, exc_tb):
45
50
  set_tenant(self.tenant)
@@ -52,7 +57,7 @@ def unscoped():
52
57
  def per_tenant(include: Q = None, exclude: Q = None):
53
58
  def decorator(f):
54
59
  def wrapper(*args, **kwargs):
55
- tenants = apps.get_model('accrete', 'Tenant').objects.all()
60
+ tenants: QuerySet = apps.get_model('accrete', 'Tenant').objects.all()
56
61
  if include is not None:
57
62
  tenants = tenants.filter(include)
58
63
  if exclude is not None:
accrete/utils/__init__.py CHANGED
@@ -6,6 +6,8 @@ from .forms import (
6
6
  extend_formset
7
7
  )
8
8
  from .views import (
9
+ page_from_querystring,
10
+ get_page,
9
11
  filter_from_querystring,
10
12
  cast_param,
11
13
  method_not_allowed,
accrete/utils/models.py CHANGED
@@ -1,4 +1,4 @@
1
- from django.db.models import Model
1
+ from django.db.models import Model, Field
2
2
 
3
3
 
4
4
  def get_related_model(model: type[Model], rel_path: str) -> tuple[Model, list[str]]:
@@ -16,4 +16,12 @@ def get_related_model(model: type[Model], rel_path: str) -> tuple[Model, list[st
16
16
  related_model = next_model
17
17
  except AttributeError:
18
18
  break
19
+ names.append(str(related_model._meta.verbose_name))
19
20
  return related_model, names
21
+
22
+
23
+ def get_related_field(model: type[Model], field_path: str) -> Field:
24
+ parts = field_path.split('__')
25
+ rel_path = '__'.join(parts[:-1])
26
+ rel_model, _ = get_related_model(model, rel_path)
27
+ return rel_model._meta.get_field(parts[-1])
accrete/utils/views.py CHANGED
@@ -2,11 +2,12 @@ import logging
2
2
  import json
3
3
  import operator
4
4
  from typing import Callable
5
+
6
+ from django.core import paginator
5
7
  from django.db.models import Model, Q, QuerySet
6
8
  from django.template.loader import render_to_string, TemplateDoesNotExist
7
9
  from django.http import HttpResponseNotAllowed
8
10
  from accrete.utils.models import get_related_model
9
- from accrete.annotation import Annotation
10
11
 
11
12
  _logger = logging.getLogger(__name__)
12
13
 
@@ -18,13 +19,40 @@ QUERYSTRING_KEY_MAP = {
18
19
  }
19
20
 
20
21
 
22
+ def page_from_querystring(
23
+ model: type[Model], query_dict: dict, key_map: dict = None,
24
+ select_related: list = None, prefetch_related: list = None
25
+ ) -> paginator.Page:
26
+
27
+ key_map = key_map or QUERYSTRING_KEY_MAP
28
+ queryset = filter_from_querystring(
29
+ model, query_dict, key_map
30
+ ).select_related(
31
+ *select_related or []
32
+ ).prefetch_related(
33
+ *prefetch_related or []
34
+ )
35
+ return get_page(
36
+ queryset,
37
+ cast_param(query_dict, key_map.get('paginate_by'), int, 40),
38
+ cast_param(query_dict, key_map.get('page'), int, 1)
39
+ )
40
+
41
+
42
+ def get_page(queryset: QuerySet, paginate_by: int, page_number: int) -> paginator.Page:
43
+ pages = paginator.Paginator(queryset, per_page=paginate_by)
44
+ return pages.page(
45
+ page_number <= pages.num_pages and page_number or pages.num_pages
46
+ )
47
+
48
+
21
49
  def filter_from_querystring(
22
- model: type[Model], get_params: dict, key_map: dict = None
50
+ model: type[Model], query_dict: dict, key_map: dict = None
23
51
  ) -> QuerySet:
24
52
 
25
53
  key_map = key_map or QUERYSTRING_KEY_MAP
26
- querystring = get_params.get(key_map['querystring'], '[]')
27
- order = get_params.get(key_map['order']) or model._meta.ordering
54
+ querystring = query_dict.get(key_map['querystring'], '[]')
55
+ order = query_dict.get(key_map['order']) or model._meta.ordering
28
56
 
29
57
  return model.objects.filter(
30
58
  parse_querystring(model, querystring)
@@ -34,7 +62,7 @@ def filter_from_querystring(
34
62
  def parse_querystring(model: type[Model], query_string: str) -> Q:
35
63
  """
36
64
  param: query_string: JSON serializable string
37
- [{term: value}, '&', [{term: value}, '|', {~term: value}]]
65
+ [{"term": value}, "&", [{t"erm": value}, "|", {"~term": value}]]
38
66
  Q(term=value) & (Q(term=value) | ~Q(term=value))
39
67
  """
40
68
 
@@ -51,19 +79,7 @@ def parse_querystring(model: type[Model], query_string: str) -> Q:
51
79
 
52
80
  rel_path = '__'.join(parts[:-2])
53
81
  related_model, x = get_related_model(model, rel_path)
54
- attr = getattr(related_model, parts[-2])
55
- if not isinstance(attr, Annotation):
56
- expression = Q(**{term: value})
57
- return ~expression if invert else expression
58
-
59
- objects = related_model.objects.filter(Q(**{
60
- '__'.join(parts[-2:]): value
61
- }))
62
- expression = Q(**{
63
- f'{rel_path}{"__" if rel_path else ""}id__in':
64
- objects.values_list('id', flat=True)
65
- })
66
-
82
+ expression = Q(**{term: value})
67
83
  return ~expression if invert else expression
68
84
 
69
85
  def parse_query_block(sub_item) -> Q:
@@ -85,7 +101,6 @@ def parse_querystring(model: type[Model], query_string: str) -> Q:
85
101
  f'Operator must be one of &, |, ^'
86
102
  )
87
103
  op = ops[item]
88
-
89
104
  else:
90
105
  raise ValueError(
91
106
  f'Unsupported item in querystring: {item}.'
@@ -120,6 +135,7 @@ def method_not_allowed(method: str, allowed: list[str]) -> HttpResponseNotAllowe
120
135
  def render_templates(
121
136
  templates: list[str | tuple[str, dict]],
122
137
  context: dict = None,
138
+ request=None,
123
139
  log_not_found: bool = False
124
140
  ) -> str:
125
141
  context = {} if context is None else context
@@ -131,7 +147,7 @@ def render_templates(
131
147
  template_name = template
132
148
  template_context = context
133
149
  try:
134
- content += render_to_string(template_name, template_context)
150
+ content += render_to_string(template_name, template_context, request)
135
151
  except TemplateDoesNotExist as e:
136
152
  if log_not_found:
137
153
  _logger.warning(repr(e))
accrete/views.py CHANGED
@@ -4,9 +4,9 @@ from django.http import HttpResponse, HttpResponseNotFound
4
4
  from django.contrib.auth.mixins import LoginRequiredMixin
5
5
  from django.contrib.auth.views import login_required
6
6
  from django.core.exceptions import ImproperlyConfigured
7
- from django.shortcuts import redirect
7
+ from django.shortcuts import redirect, get_object_or_404
8
8
  from django.conf import settings
9
- from accrete.tenant import get_tenant
9
+ from accrete.models import Tenant, Member
10
10
  from . import config
11
11
 
12
12
 
@@ -80,6 +80,8 @@ def tenant_required(
80
80
  tenant = request.tenant
81
81
  if not tenant:
82
82
  return redirect(config.ACCRETE_TENANT_NOT_SET_URL)
83
+ if kwargs.get('tenant'):
84
+ kwargs.pop('tenant')
83
85
  return f(request, *args, **kwargs)
84
86
  return _wrapped_view
85
87
  return decorator
@@ -87,9 +89,11 @@ def tenant_required(
87
89
 
88
90
  @tenant_required()
89
91
  def get_tenant_file(request, tenant_id, filepath):
90
- tenant = get_tenant()
91
- if tenant.id != tenant_id:
92
- return HttpResponseNotFound()
92
+ tenant = get_object_or_404(Tenant, pk=tenant_id)
93
+ if not request.user.is_staff:
94
+ member = Member.objects.filter(user=request.user, tenant=tenant)
95
+ if not member.exists():
96
+ return HttpResponseNotFound()
93
97
  filepath = f'{settings.MEDIA_ROOT}/{tenant_id}/{filepath}'
94
98
  if not os.path.exists(filepath):
95
99
  return HttpResponseNotFound()
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.3
1
+ Metadata-Version: 2.4
2
2
  Name: accrete
3
- Version: 0.0.104
3
+ Version: 0.0.105
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
@@ -22,6 +22,7 @@ License: Copyright (c) 2023 Benedikt Jilek
22
22
  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23
23
  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24
24
  SOFTWARE.
25
+ License-File: LICENSE
25
26
  Classifier: Environment :: Web Environment
26
27
  Classifier: Framework :: Django
27
28
  Classifier: Intended Audience :: Developers