accrete 0.0.101__py3-none-any.whl → 0.0.102__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.
@@ -9,7 +9,7 @@ from django.utils.safestring import mark_safe
9
9
  class ModelSearchSelectOptions:
10
10
 
11
11
  queryset: QuerySet
12
- template: str = 'ui/components/model_search_select_options.html'
12
+ template: str = 'ui/widgets/model_search_select_options.html'
13
13
 
14
14
  def get_context(self):
15
15
  return {'options': self.queryset}
@@ -1,5 +1,7 @@
1
1
  import logging
2
- from django.db.models.fields.related import ManyToOneRel, ManyToManyRel
2
+ from collections import Counter
3
+ from itertools import tee
4
+ from django.db.models import fields as db_fields
3
5
  from django.core.cache import cache
4
6
  from django.utils.translation import gettext_lazy as _
5
7
  from django.utils.safestring import mark_safe
@@ -56,17 +58,33 @@ class Filter:
56
58
  lambda x: x.is_relation and x.name not in filter_exclude,
57
59
  model_path[-1]._meta.get_fields()
58
60
  )
61
+ fields, fields_counter = tee(fields)
59
62
  res = []
63
+ occurrences = Counter([
64
+ f.related_model for f in filter(
65
+ lambda x: isinstance(
66
+ x, db_fields.reverse_related.ManyToOneRel
67
+ ), fields_counter
68
+ )
69
+ ])
70
+ multi_related_models = set([
71
+ key for key, val in occurrences.items() if val > 1
72
+ ])
60
73
  for field in fields:
74
+ multi_ref = field.related_model in multi_related_models
61
75
  if field.related_model in model_path:
62
76
  continue
63
77
  rel_path = f'{field_path}{"__" if field_path else ""}{field.name}'
64
78
  model_path_copy = model_path.copy()
65
79
  model_path_copy.append(field.related_model)
66
- try:
80
+ if isinstance(field, db_fields.reverse_related.ManyToManyRel):
81
+ continue
82
+ elif isinstance(field, db_fields.reverse_related.ManyToOneRel):
83
+ label = field.related_model._meta.verbose_name_plural
84
+ if multi_ref:
85
+ label = f'{label}/{field.field.verbose_name}'
86
+ else:
67
87
  label = field.verbose_name
68
- except AttributeError:
69
- label = field.related_model._meta.verbose_name
70
88
  res.append({
71
89
  'name': f'{rel_path}',
72
90
  'label': str(label),
@@ -139,7 +157,9 @@ class Filter:
139
157
  for f in field.get('fields', []):
140
158
  params += self.field_params(f)
141
159
  return f"""
142
- <div class="query-param" tabindex="-1" data-param="{field['name']}" data-param-label="{field['label']}">
160
+ <div class="query-param" tabindex="-1" data-param="{field['name']}"
161
+ data-param-label="{field['label']}"
162
+ >
143
163
  <p class="px-1 arrow">{field['label']}</p>
144
164
  <div class="query-params is-hidden" data-param="{field['name']}">
145
165
  {params}
@@ -160,7 +180,9 @@ class Filter:
160
180
  'PositiveSmallIntegerField': self.int_param,
161
181
  'DateTimeField': self.date_time_param,
162
182
  'DateField': self.date_param,
183
+ 'TimeField': self.time_param,
163
184
  'ForeignKey': self.foreign_key_param,
185
+ 'ManyToManyField': self.many_to_many_param,
164
186
  'FileField': self.file_param,
165
187
  'ImageField': self.file_param
166
188
  }
@@ -268,11 +290,23 @@ class Filter:
268
290
  html += self.null_param(key, value)
269
291
  return html
270
292
 
293
+ def time_param(self, key, value):
294
+ options = self.parse_choices(value.get('choices', ''))
295
+ html = self.param(key, value, 'exact', 'time', options)
296
+ html += self.param(key, value, 'gte', 'time', options, False)
297
+ html += self.param(key, value, 'lte', 'time', options, False)
298
+ if value.get('null'):
299
+ html += self.null_param(key, value)
300
+ return html
301
+
271
302
  def foreign_key_param(self, key, value):
272
303
  if value.get('null'):
273
304
  return self.null_param(key, value)
274
305
  return ''
275
306
 
307
+ def many_to_many_param(self, key, value):
308
+ return self.null_param(key, value)
309
+
276
310
  def file_param(self, key, value):
277
311
  return self.null_param(key, value)
278
312
 
@@ -304,7 +338,10 @@ class Filter:
304
338
  fields = [(x.verbose_name, x.name) for x in filter(
305
339
  lambda x:
306
340
  x.name not in filter_exclude
307
- and not isinstance(x, (ManyToOneRel, ManyToManyRel)),
341
+ and not isinstance(x, (
342
+ db_fields.related.ManyToOneRel,
343
+ db_fields.related.ManyToManyRel
344
+ )),
308
345
  self.model._meta.get_fields()
309
346
  )]
310
347
  if hasattr(self.model, 'get_annotations'):
File without changes
@@ -36,7 +36,7 @@
36
36
  <input id="query-input"
37
37
  type="text"
38
38
  aria-label="Query Input"
39
- class="input"
39
+ class="input is-fullwidth"
40
40
  placeholder="Enter a search term"
41
41
  data-default-term="{{ filter.default_filter_term }}"
42
42
  onkeyup="inputConfirm()"
@@ -44,7 +44,10 @@
44
44
  </p>
45
45
  <p id="query-input-select-control" class="control is-expanded is-hidden">
46
46
  <span class="select is-fullwidth">
47
- <select id="query-input-select" aria-label="query-input-select"></select>
47
+ <select id="query-input-select" aria-label="query-input-select"
48
+ style="border-bottom-left-radius: var(--bulma-radius-medium); border-top-left-radius: var(--bulma-radius-medium);"
49
+ >
50
+ </select>
48
51
  </span>
49
52
  </p>
50
53
  <p id="query-input-apply" class="control" onclick="applyInput()"><button class="button">&#x21D2;</button></p>
@@ -2,20 +2,9 @@
2
2
  {% load i18n %}
3
3
 
4
4
 
5
- <script>
6
- function modelSearchSelect() {
7
- return {
8
- open: false,
9
-
10
- shouldClose(event) {
11
- return event.relatedTarget === this.$refs.dropdownContent || this.$refs.dropdownContent.contains(event.relatedTarget)
12
- }
13
- }
14
- }
15
- </script>
16
-
17
-
18
- <div class="field select" x-data="modelSearchSelect()" @click.outside="open = false">
5
+ <div class="field select" x-data="{open: false, shouldClose(event) {return event.relatedTarget == $refs.dropdownContent || $refs.dropdownContent.contains(event.relatedTarget);} }"
6
+ @click.outside="open = false"
7
+ >
19
8
  <input id="id_{{ widget.name }}_value" hidden="hidden" type="{{ widget.type }}" name="{{ widget.name }}" x-ref="inputValue" aria-label="inputValue"
20
9
  value="{{ widget.value }}"
21
10
  >
@@ -26,7 +15,7 @@
26
15
  x-on:focusout="open = shouldClose($event)"
27
16
  >
28
17
  <div style="position: relative; z-index: 90;">
29
- <div x-show="open" class="box pt-1" x-ref="dropdownContent" tabindex="-1" x-transition>
18
+ <div x-show="open" x-cloak="" class="box pt-1 mb-2" x-ref="dropdownContent" tabindex="-1" x-transition style="position: absolute">
30
19
  <input id="id_{{ widget.name }}_search" type="search" autocomplete="off" class="input" x-ref="searchInput" aria-label="search"
31
20
  name="{{ widget.search_parameter }}"
32
21
  placeholder="{% translate 'Type to search' %}"
@@ -45,7 +34,7 @@
45
34
  </div>
46
35
  {% endif %}
47
36
  <div id="{{ widget.name }}_dropdown_items" class="hoverable" style="max-height: 200px; width: 100%; overflow-y: auto;">
48
- {% include 'ui/components/model_search_select_options.html' %}
37
+ {% include 'ui/widgets/model_search_select_options.html' %}
49
38
  </div>
50
39
  </div>
51
40
  </div>
@@ -4,7 +4,7 @@
4
4
  @click.outside="open = false" style="width: 100%"
5
5
  >
6
6
  <select multiple id="{{ widget.attrs.id }}" hidden="hidden" name="{{ widget.name }}" aria-label="{{ widget.name }}" x-ref="inputValue">
7
- {% include 'ui/components/model_search_select_multi_selected_options.html' %}
7
+ {% include 'ui/widgets/model_search_select_multi_selected_options.html' %}
8
8
  </select>
9
9
  <div id="{{ widget.attrs.id }}_display" class="input select py-1 pl-1" tabindex="0" x-ref="inputDisplay" aria-label="inputDisplay"
10
10
  style="padding-right: 30px; border-radius: 0; border-top: 0; border-right:0; border-left: 0; min-height: var(--bulma-control-height); height: fit-content; width: 100%"
@@ -20,11 +20,11 @@
20
20
  $event.target.parentElement.remove();
21
21
  }
22
22
  ">
23
- {% include 'ui/components/model_search_select_multi_tags.html' %}
23
+ {% include 'ui/widgets/model_search_select_multi_tags.html' %}
24
24
  </div>
25
25
  </div>
26
26
  <div style="position: relative; z-index: 90;">
27
- <div x-show="open" class="box pt-1" x-ref="dropdownContent" tabindex="-1" x-transition style="position: absolute; min-width: 100%">
27
+ <div x-show="open" class="box pt-1 mb-2" x-ref="dropdownContent" tabindex="-1" x-transition style="position: absolute; min-width: 100%;">
28
28
  <input id="id_{{ widget.name }}_search" type="search" autocomplete="off" class="input" x-ref="searchInput" aria-label="search"
29
29
  name="{{ widget.search_parameter }}"
30
30
  placeholder="{% translate 'Type to search' %}"
@@ -60,7 +60,7 @@
60
60
  else if ($event.keyCode != 9 && $event.keyCode != 13) {$refs.searchInput.focus();}
61
61
  "
62
62
  >
63
- {% include 'ui/components/model_search_select_options.html' %}
63
+ {% include 'ui/widgets/model_search_select_options.html' %}
64
64
  </div>
65
65
  </div>
66
66
  </div>
accrete/middleware.py CHANGED
@@ -1,7 +1,7 @@
1
1
  from django.utils.deprecation import MiddlewareMixin
2
2
  from django.conf import settings
3
3
  from django.http.response import HttpResponseRedirect, HttpResponsePermanentRedirect
4
- from accrete.tenant import set_tenant, set_member, unscoped
4
+ from accrete.tenant import set_tenant, set_member
5
5
 
6
6
  from .models import Tenant
7
7
 
@@ -9,9 +9,7 @@ from .models import Tenant
9
9
  class TenantMiddleware(MiddlewareMixin):
10
10
  @staticmethod
11
11
  def get_tenant_id_from_request(request):
12
- if tenant := request.POST.get('tenant'):
13
- tenant_id = tenant
14
- elif tenant := request.META.get('X_TENANT_ID'):
12
+ if tenant := request.META.get('X-TENANT_ID'):
15
13
  tenant_id = tenant
16
14
  elif tenant_id := request.GET.get('tenant_id'):
17
15
  tenant_id = tenant_id
@@ -35,11 +33,10 @@ class TenantMiddleware(MiddlewareMixin):
35
33
  return
36
34
 
37
35
  if request.user.is_authenticated:
38
- with unscoped():
39
- memberships = request.user.memberships.filter(
40
- is_active=True,
41
- tenant__is_active=True
42
- )
36
+ memberships = request.user.memberships.filter(
37
+ is_active=True,
38
+ tenant__is_active=True
39
+ )
43
40
 
44
41
  if request.user.is_staff:
45
42
  tenant_id = self.get_tenant_id_from_request(request)
@@ -69,6 +66,7 @@ class TenantMiddleware(MiddlewareMixin):
69
66
  @staticmethod
70
67
  def process_response(request, response):
71
68
  if request.tenant:
69
+ response['X-TENANT_ID'] = request.tenant.id
72
70
  response.set_cookie(
73
71
  'tenant_id',
74
72
  request.tenant.pk,
accrete/tenant.py CHANGED
@@ -36,7 +36,8 @@ class Unscoped:
36
36
  def __enter__(self):
37
37
  if self.tenant is None:
38
38
  _logger.warning(
39
- 'Entering unscoped context manager with tenant already set to None!'
39
+ 'Entering unscoped context manager with tenant already set to None!',
40
+ stack_info=True
40
41
  )
41
42
  set_tenant(None)
42
43
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: accrete
3
- Version: 0.0.101
3
+ Version: 0.0.102
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
@@ -5,10 +5,10 @@ accrete/apps.py,sha256=F7ynMLHJr_6bRujWtZVUzCliY2CGKiDvyUmL4F68L2E,146
5
5
  accrete/config.py,sha256=eJUbvyBO3DvAD6xkVKjTAzlXy7V7EK9bVyb91girfUs,299
6
6
  accrete/forms.py,sha256=2vUh80qNvPDD8Zl3agKBSJEQeY7bXVLOx_SAB34wf8E,1359
7
7
  accrete/managers.py,sha256=oZOA45HViNstaeIaGD2szg717DwByifW5GfvVsi4hOk,1561
8
- accrete/middleware.py,sha256=NhtT3YPq0EynAi4F81LLLUZd5rsWDjGVn1tkgUObT3M,3477
8
+ accrete/middleware.py,sha256=JvUwvrKbM8NEmCUdWT4pxeorPXSmv27XFsjGBinqSs8,3396
9
9
  accrete/models.py,sha256=xliEVR0XPW46ZDJn3-F46s54tG9dxz7BVLbkq1pC144,5434
10
10
  accrete/storage.py,sha256=z7pHdQFw0hFGrrbfqIh7KFxabQ_JGqoPebmiX9TLmeU,1254
11
- accrete/tenant.py,sha256=g3ZuTrQr2zqmIopNBRQeCmHEK2R3dlUme_hOV765J6U,1778
11
+ accrete/tenant.py,sha256=T1cPBhFp20tmlO1o_QXSOTe2qE_cF0k8kkchpBojQWU,1811
12
12
  accrete/tests.py,sha256=Agltbzwwh5htvq_Qi9vqvxutzmg_GwgPS_N19xJZRlw,7197
13
13
  accrete/urls.py,sha256=goDFR-yhOlLLy7AMi9pmh2aBkxdtZtwXNg6mwI2zPhU,227
14
14
  accrete/views.py,sha256=eGWaGHiNplhvYQFkW1gP5j2JEvjAkf1YIFrcOPj0GJw,3433
@@ -39,12 +39,13 @@ accrete/contrib/ui/admin.py,sha256=suMo4x8I3JBxAFBVIdE-5qnqZ6JAZV0FESABHOSc-vg,6
39
39
  accrete/contrib/ui/apps.py,sha256=E0ao2ox6PQ3ldfeR17FXJUUJuGiWjm2DPCxHbPXGzls,152
40
40
  accrete/contrib/ui/context.py,sha256=Awpt3kcjn_REJsZ3xDq71WitPefBLCj2N5FrAZfDXYE,10155
41
41
  accrete/contrib/ui/elements.py,sha256=l62e3uKDDYI1AfLVhE1AVht-GXKv5DwNdUZ3GkHudio,1948
42
- accrete/contrib/ui/filter.py,sha256=N3_h-AeN3VQdkGAGREHTGTRDRzcOrlk0Q2rbdTQyevA,12472
42
+ accrete/contrib/ui/filter.py,sha256=f3L-ZKN5Yz3-iObYlRISNzz2Yvr9-TdMyydn_EY00M0,13917
43
43
  accrete/contrib/ui/tests.py,sha256=mrbGGRNg5jwbTJtWWa7zSKdDyeB4vmgZCRc2nk6VY-g,60
44
44
  accrete/contrib/ui/urls.py,sha256=UrRRroqP6ANW_jpkRWqo2yLvBhYOVhczzbxbfkGnoq4,124
45
45
  accrete/contrib/ui/views.py,sha256=Eewx1n_fOAqx_nP4mvGltaksUtp1_WhCJThtK13YiQc,106
46
46
  accrete/contrib/ui/components/__init__.py,sha256=B-0S010wPC-XSsEGGURCJDlaEdpSMphiO67XkOFRmQc,52
47
- accrete/contrib/ui/components/search_select.py,sha256=s1VeJm0Jhn0ix2kG7hAXO-jd6jlPjwGxh-tw86fBs1k,481
47
+ accrete/contrib/ui/components/search_select.py,sha256=t9i37ZVyhKiYUwuPumXhC3DPMVHU9R7sPdFESdNmzIE,478
48
+ accrete/contrib/ui/forms/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
48
49
  accrete/contrib/ui/forms/widgets.py,sha256=mo5E6qT8zDBEGHJQ6GCN8epjOYIo0kLxNgvQnq19vGU,3298
49
50
  accrete/contrib/ui/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
50
51
  accrete/contrib/ui/static/bulma/LICENSE,sha256=--fY7Bi3Lt7RXXnKc9pe756OJY9fGv-D5OmT5pdSJ5w,1080
@@ -174,10 +175,7 @@ accrete/contrib/ui/templates/ui/form.html,sha256=uCtP16THdOuRfs3JdsjadIW0k9b2rN3
174
175
  accrete/contrib/ui/templates/ui/layout.html,sha256=MUrb19FvoOKAgxek8NCxA23uFr-Jgmvg7eqK1rqgUOc,15650
175
176
  accrete/contrib/ui/templates/ui/list.html,sha256=ahN8SgF4kE3OEy6EBeDBdDQvuXx-CyIgbixYd-KdV4k,1751
176
177
  accrete/contrib/ui/templates/ui/table.html,sha256=8ELtgxoapCyNsvmGISAGXe712lG6AkP_nekb4OVLK3I,4481
177
- accrete/contrib/ui/templates/ui/components/model_search_select_multi_selected_options.html,sha256=1fDpCnotpGFH7bzqllwbVixH1DB1-akbso5eH8cL4iU,91
178
- accrete/contrib/ui/templates/ui/components/model_search_select_multi_tags.html,sha256=FV2pc5qaJil19reZXWzLgrUlcXyugdwunZ12lv6COfg,231
179
- accrete/contrib/ui/templates/ui/components/model_search_select_options.html,sha256=TECaTLnkmVoUZBhehLwcSjyfgwvhXX2ZBiqSro9ap_U,203
180
- accrete/contrib/ui/templates/ui/partials/filter.html,sha256=fMxi5L1daqKElzZpHg8bItv0FlBjOkAEBxmT_OXgg4M,7434
178
+ accrete/contrib/ui/templates/ui/partials/filter.html,sha256=LtsYKB-cHlpGngCjBHkXDdeuunzkuC1UA0xnq-aSGMA,7621
181
179
  accrete/contrib/ui/templates/ui/partials/form_errors.html,sha256=C5ktasYff2xBTiWfM6QR8qaGKSyK9QufB3B9N77KGpg,1386
182
180
  accrete/contrib/ui/templates/ui/partials/header.html,sha256=wBiyo02mON3z7l4hJAPTAJn_vDhekYZ-a0g8TuW4RmY,7015
183
181
  accrete/contrib/ui/templates/ui/partials/modal.html,sha256=Lk7ViKJS99QLjGFHKna90j6VUo_U03IDAT1Rq__QVLU,1472
@@ -189,8 +187,11 @@ accrete/contrib/ui/templates/ui/partials/table_field.html,sha256=4oQw0na9UgHP8lo
189
187
  accrete/contrib/ui/templates/ui/partials/table_field_float.html,sha256=GH_jFdpk8wEJXv4QfO6c3URYrZGGLeuSyWwWl2cWxwQ,45
190
188
  accrete/contrib/ui/templates/ui/partials/table_field_monetary.html,sha256=Wtod9vel2dD4vG8lUVLbtls4aU_2EG3p0E1QRRUSH10,166
191
189
  accrete/contrib/ui/templates/ui/partials/table_field_string.html,sha256=GH_jFdpk8wEJXv4QfO6c3URYrZGGLeuSyWwWl2cWxwQ,45
192
- accrete/contrib/ui/templates/ui/widgets/model_search_select.html,sha256=prJLy58XSJKlqOagI7gcVyHg-pcaUKqCFMfAP4aQEn8,2979
193
- accrete/contrib/ui/templates/ui/widgets/model_search_select_multi.html,sha256=AFoAKEeB99f63CWwL-ougERnj-IQVya_jdDcwW7Bo80,4109
190
+ accrete/contrib/ui/templates/ui/widgets/model_search_select.html,sha256=yLmXgPVksfB5LMt0q_imE3eFxk8xNac90dxrOIVpB1M,2843
191
+ accrete/contrib/ui/templates/ui/widgets/model_search_select_multi.html,sha256=6KXUIeSQAZqI8ssf8RCH5j7p-__en1zTJLqZl7MBIhs,4106
192
+ accrete/contrib/ui/templates/ui/widgets/model_search_select_multi_selected_options.html,sha256=1fDpCnotpGFH7bzqllwbVixH1DB1-akbso5eH8cL4iU,91
193
+ accrete/contrib/ui/templates/ui/widgets/model_search_select_multi_tags.html,sha256=FV2pc5qaJil19reZXWzLgrUlcXyugdwunZ12lv6COfg,231
194
+ accrete/contrib/ui/templates/ui/widgets/model_search_select_options.html,sha256=TECaTLnkmVoUZBhehLwcSjyfgwvhXX2ZBiqSro9ap_U,203
194
195
  accrete/contrib/ui/templatetags/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
195
196
  accrete/contrib/ui/templatetags/accrete_ui.py,sha256=nGd6U5AVZvfwi4yHBoR6YUIIA3vYGkrHCs_m-T__pFk,3839
196
197
  accrete/contrib/user/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -234,7 +235,7 @@ accrete/utils/forms.py,sha256=IvxbXNpSd4a-JBgsTJhs2GHe-DCRWX-xnVPRcoiCzbI,3104
234
235
  accrete/utils/log.py,sha256=BH0MBDweAjx30wGBO4F3sFhbgkSoEs7T1lLLjlYZNnA,407
235
236
  accrete/utils/models.py,sha256=EEhv7-sQVtQD24PEb3XcDUAh3VVhVFoMMLyFrDjGEaI,706
236
237
  accrete/utils/views.py,sha256=iWZSYbd3qYMrV9wAsX26ofGb5wxn1N_nRrQ6s2lpp2I,4557
237
- accrete-0.0.101.dist-info/METADATA,sha256=ZF8MU0HdYFEsShI4phQv86kglt-GWT_BAgpAdqc3Ks8,4953
238
- accrete-0.0.101.dist-info/WHEEL,sha256=1yFddiXMmvYK7QYTqtRNtX66WJ0Mz8PYEiEUoOUUxRY,87
239
- accrete-0.0.101.dist-info/licenses/LICENSE,sha256=_7laeMIHnsd3Y2vJEXDYXq_PEXxIcjgJsGt8UIKTRWc,1057
240
- accrete-0.0.101.dist-info/RECORD,,
238
+ accrete-0.0.102.dist-info/METADATA,sha256=WwkK-4Wp5Eal_Gwzf_kI7am_oUP_CB0Sh9DzuXQJgMw,4953
239
+ accrete-0.0.102.dist-info/WHEEL,sha256=1yFddiXMmvYK7QYTqtRNtX66WJ0Mz8PYEiEUoOUUxRY,87
240
+ accrete-0.0.102.dist-info/licenses/LICENSE,sha256=_7laeMIHnsd3Y2vJEXDYXq_PEXxIcjgJsGt8UIKTRWc,1057
241
+ accrete-0.0.102.dist-info/RECORD,,