accrete 0.0.36__py3-none-any.whl → 0.0.38__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.
@@ -1,8 +1,12 @@
1
1
  import time
2
2
  import logging
3
+ from django.db import models
3
4
  from django.core.cache import cache
4
5
  from django.utils.translation import gettext_lazy as _
5
6
  from django.utils.safestring import mark_safe
7
+ from django.utils.translation import get_language
8
+
9
+ from accrete.annotation import Annotation
6
10
 
7
11
  _logger = logging.getLogger(__name__)
8
12
 
@@ -30,7 +34,7 @@ class Filter:
30
34
  self.query_relation_depth = query_relation_depth
31
35
  self.default_exclude = default_exclude or ['tenant', 'user']
32
36
  self.default_filter_term = default_filter_term or ''
33
- self.fields = {}
37
+ self.fields = []
34
38
  self.field_paths = []
35
39
 
36
40
  @staticmethod
@@ -43,8 +47,8 @@ class Filter:
43
47
  def get_fields(self, model_path: list, field_path: str):
44
48
  fields = self.get_local_fields(model_path[-1], field_path)
45
49
  if len(model_path) <= self.query_relation_depth:
46
- fields.update(self.get_relation_fields(model_path, field_path))
47
- return fields
50
+ fields.extend(self.get_relation_fields(model_path, field_path))
51
+ return sorted(fields, key=lambda x: x['label'].lower())
48
52
 
49
53
  def get_relation_fields(self, model_path, field_path):
50
54
  filter_exclude = getattr(model_path[-1], 'filter_exclude', [])
@@ -53,7 +57,7 @@ class Filter:
53
57
  lambda x: x.is_relation and x.name not in filter_exclude,
54
58
  model_path[-1]._meta.get_fields()
55
59
  )
56
- res = {}
60
+ res = []
57
61
  for field in fields:
58
62
  if field.related_model in model_path:
59
63
  continue
@@ -64,13 +68,14 @@ class Filter:
64
68
  label = field.verbose_name
65
69
  except AttributeError:
66
70
  label = field.related_model._meta.verbose_name
67
- res[field.name] = {
68
- 'label': label,
71
+ res.append({
72
+ 'name': f'{rel_path}',
73
+ 'label': str(label),
69
74
  'type': field.get_internal_type(),
70
75
  'null': field.null,
71
76
  'choices': [],
72
77
  'fields': self.get_fields(model_path_copy, rel_path)
73
- }
78
+ })
74
79
  return res
75
80
 
76
81
  def get_local_fields(self, model, path):
@@ -80,34 +85,39 @@ class Filter:
80
85
  lambda x: not x.is_relation and x.name not in filter_exclude,
81
86
  model._meta.get_fields()
82
87
  )
83
- res = {}
88
+ res = []
84
89
  for field in fields:
85
- res.update({
86
- f'{path}{"__" if path else ""}{field.name}': {
87
- 'label': field.verbose_name,
88
- 'type': field.get_internal_type(),
89
- 'choices': field.choices or [],
90
- 'null': field.null,
91
- 'step': hasattr(field, 'decimal_places')
92
- and self.cast_decimal_places_to_step(field.decimal_places)
93
- or 1
94
- }
90
+ field_path = f'{path}{"__" if path else ""}{field.name}'
91
+ self.field_paths.append(field_path)
92
+ step = (hasattr(field, 'decimal_places')
93
+ and self.cast_decimal_places_to_step(field.decimal_places)
94
+ or 1)
95
+ res.append({
96
+ 'name': field_path,
97
+ 'label': str(field.verbose_name),
98
+ 'type': field.get_internal_type(),
99
+ 'choices': field.choices or [],
100
+ 'null': field.null,
101
+ 'step': step
95
102
  })
96
- for annotation in getattr(model, 'annotations', []):
97
- res.update({
98
- f'{path}{"__" if path else ""}_a_{annotation["name"]}': {
99
- 'label': annotation['label'],
100
- 'type': annotation['type'].__name__,
101
- 'choices': [],
102
- 'null': False,
103
- 'step': self.cast_decimal_places_to_step(annotation.get('decimal_places', 1))
104
- }
103
+ if not hasattr(model, 'get_annotations'):
104
+ return res
105
+ for annotation in model.get_annotations():
106
+ field_path = f'{path}{"__" if path else ""}{annotation["name"]}'
107
+ self.field_paths.append(field_path)
108
+ res.append({
109
+ 'name': field_path,
110
+ 'label': str(annotation['annotation'].verbose_name),
111
+ 'type': annotation['annotation'].field.__name__,
112
+ 'choices': [],
113
+ 'null': False,
114
+ 'step': getattr(annotation['annotation'], 'step', '1')
105
115
  })
106
116
  return res
107
117
 
108
118
  def to_html(self):
109
119
  start = time.time()
110
- key = f'filter-terms-{self.model.__module__}.{self.model.__name__}'
120
+ key = f'filter-{self.model.__module__}.{self.model.__name__}-{get_language()}'
111
121
  html = cache.get(key)
112
122
  if html:
113
123
  _logger.info(f'Fetching Filter-Terms from cache in {time.time() - start} seconds')
@@ -115,22 +125,27 @@ class Filter:
115
125
  if not self.fields:
116
126
  self.fields = self.get_fields([self.model], '')
117
127
  html = ''
118
- for k, v in self.fields.items():
119
- html += self.field_params(k, v)
120
- html = mark_safe(html.strip().replace('\n', ''))
128
+ for f in self.fields:
129
+ html += self.field_params(f)
130
+ html = {
131
+ 'params': mark_safe(html.strip().replace('\n', '')),
132
+ 'field_paths': mark_safe(
133
+ self.field_path_selection().strip().replace('\n', '')
134
+ )
135
+ }
121
136
  cache.set(key, html, 60 * 15)
122
137
  _logger.info(f'Generated Filter-Terms in {time.time() - start} seconds')
123
- return html
138
+ return {'params': html['params'], 'field_paths': html['field_paths']}
124
139
 
125
- def field_params(self, key, value):
140
+ def field_params(self, field):
126
141
  params = ''
127
- params += self.params(key, value)
128
- for k, v in value.get('fields', {}).items():
129
- params += self.field_params(k, v)
142
+ params += self.params(field)
143
+ for f in field.get('fields', []):
144
+ params += self.field_params(f)
130
145
  return f"""
131
- <div class="query-param" tabindex="-1" data-param="{key}" data-param-label="{value['label']}">
132
- <p class="px-1 arrow">{value['label']}</p>
133
- <div class="query-params is-hidden" data-param="{key}">
146
+ <div class="query-param" tabindex="-1" data-param="{field['name']}" data-param-label="{field['label']}">
147
+ <p class="px-1 arrow">{field['label']}</p>
148
+ <div class="query-params is-hidden" data-param="{field['name']}">
134
149
  {params}
135
150
  </div>
136
151
  </div>
@@ -158,8 +173,8 @@ class Filter:
158
173
  for choice in choices
159
174
  ])
160
175
 
161
- def params(self, key, value):
162
- return self.field_map().get(value['type'], self.no_param)(key, value)
176
+ def params(self, field):
177
+ return self.field_map().get(field['type'], self.no_param)(field['name'], field)
163
178
 
164
179
  def param(
165
180
  self, key: str, value: dict, param: str, data_type: str,
@@ -195,7 +210,6 @@ class Filter:
195
210
  html += param_div(inverted=True)
196
211
  return html
197
212
 
198
-
199
213
  def char_param(self, key, value):
200
214
  if value.get('choices'):
201
215
  return self.char_choice_param(key, value)
@@ -262,7 +276,10 @@ class Filter:
262
276
  return ''
263
277
 
264
278
  def null_param(self, key, value):
265
- options = self.parse_choices([('true', _('True')), ('false', _('False'))])
279
+ options = self.parse_choices([
280
+ ('true', _('True')),
281
+ ('false', _('False'))
282
+ ])
266
283
  return f"""
267
284
  <div id="filter-id-~{key}__isnull"
268
285
  class="query-param" tabindex="-1" data-type="selection"
@@ -278,3 +295,48 @@ class Filter:
278
295
 
279
296
  def no_param(self, key, value):
280
297
  return ''
298
+
299
+ def field_path_selection(self):
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
305
+ html += f"""
306
+ <label class="checkbox my-1" style="width: 100%">
307
+ <input type="checkbox" name="{field}">
308
+ {label}
309
+ </label>
310
+ """
311
+ return html
312
+
313
+ def get_field_path_label(self, field_path):
314
+ label_parts = []
315
+ model = self.model
316
+ for field_part in field_path.split('__'):
317
+ if field_part.startswith('_a_'):
318
+ field_part = field_part.removeprefix('_a_')
319
+ annotations = {
320
+ annotation['name']: annotation
321
+ for annotation in getattr(model, 'annotations', [])
322
+ }
323
+ if annotations.get(field_part):
324
+ label_parts.append(str(annotations[field_part]['label']))
325
+ return ' / '.join(label_parts)
326
+ return False
327
+
328
+ field = getattr(model, field_part, False)
329
+
330
+ if not field:
331
+ return False
332
+
333
+ if isinstance(field.field, models.ForeignKey):
334
+ model = field.field.related_model
335
+ label_parts.append(str(model._meta.verbose_name))
336
+ continue
337
+
338
+ if not isinstance(field, Annotation):
339
+ field = field.field
340
+
341
+ label_parts.append(str(field.verbose_name))
342
+ return ' / '.join(label_parts)