accrete 0.0.36__py3-none-any.whl → 0.0.37__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.
accrete/annotation.py ADDED
@@ -0,0 +1,46 @@
1
+ from django.db.models import Field, QuerySet
2
+ from django.db.models.expressions import Func
3
+ from django.db.models.aggregates import Aggregate
4
+
5
+
6
+ class Annotation:
7
+
8
+ def __init__(
9
+ self,
10
+ verbose_name: str,
11
+ field: type[Field],
12
+ function: type[Func] | type[Aggregate],
13
+ help_text: str = '',
14
+ **kwargs
15
+ ):
16
+ self.verbose_name = verbose_name or self
17
+ self.field = field
18
+ self.function = function
19
+ self.help_text = help_text
20
+ self.__dict__.update(kwargs)
21
+
22
+
23
+ class AnnotationModelMixin:
24
+
25
+ @classmethod
26
+ def get_annotations(cls) -> list[dict]:
27
+ return list({'name': a, 'annotation': getattr(cls, a)} for a in filter(
28
+ lambda a:
29
+ not a.startswith('__')
30
+ and isinstance(getattr(cls, a), Annotation),
31
+ cls.__dict__
32
+ ))
33
+
34
+
35
+ class AnnotationManagerMixin:
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(**{
43
+ annotation['name']: annotation['annotation'].function
44
+ for annotation in model.get_annotations()
45
+ })
46
+
@@ -4,18 +4,15 @@ from .context import (
4
4
  TableContext,
5
5
  ListContext,
6
6
  FormContext,
7
- default_form_actions,
8
- Page,
9
- ListContent,
10
- DetailContent,
11
- FormContent,
12
- get_list_page,
13
- get_detail_page,
7
+ form_actions,
8
+ list_page,
9
+ detail_page,
14
10
  cast_param,
15
11
  prepare_url_params,
16
12
  extract_url_params,
17
13
  exclude_params,
18
- url_param_str
14
+ url_param_str,
15
+ get_table_fields
19
16
  )
20
17
  from .elements import (
21
18
  ClientAction,
@@ -26,7 +23,3 @@ from .elements import (
26
23
  TableFieldType,
27
24
  Icon
28
25
  )
29
- from .querystring import (
30
- load_querystring,
31
- build_querystring,
32
- )
@@ -1,14 +1,15 @@
1
1
  import logging
2
- from typing import TypedDict
3
2
  from dataclasses import dataclass, field
4
- from django.utils.translation import gettext_lazy as _
5
- from django.shortcuts import resolve_url
3
+ from typing import TypedDict
4
+ from django.utils.translation import gettext_lazy as _t
6
5
  from django.db.models import Model, QuerySet
7
6
  from django.core.paginator import Paginator
8
7
  from django.core import paginator
9
8
  from django.forms import Form, ModelForm
10
- from .elements import ClientAction, BreadCrumb, TableField
9
+ from accrete.utils.models import get_related_model
10
+ from .elements import ClientAction, BreadCrumb, TableField, TableFieldType
11
11
  from .filter import Filter
12
+ from ...annotation import Annotation
12
13
 
13
14
  _logger = logging.getLogger(__name__)
14
15
 
@@ -22,31 +23,55 @@ class DetailPagination(TypedDict):
22
23
  total_objects: int
23
24
 
24
25
 
25
- class TableContext(TypedDict, total=False):
26
+ @dataclass(kw_only=True)
27
+ class Context:
28
+
29
+ title: str = ''
30
+ breadcrumbs: list[BreadCrumb] = field(default_factory=list)
31
+ actions: list[ClientAction] = field(default_factory=list)
32
+ kwargs: dict = field(default_factory=dict)
33
+
34
+ def __post_init__(self):
35
+ for key, value in self.kwargs.items():
36
+ setattr(self, key, value)
37
+
38
+ def dict(self):
39
+ return {
40
+ attr: getattr(self, attr, '') for attr
41
+ in filter(lambda x: not x.startswith('_'), self.__dict__)
42
+ }
43
+
44
+
45
+ @dataclass
46
+ class TableContext(Context):
26
47
 
27
- title: str
28
48
  object_label: str
29
- object_param_str: str
30
49
  fields: list[TableField]
31
- breadcrumbs: list[BreadCrumb]
32
- actions: list[ClientAction]
33
50
  list_page: paginator.Page
34
51
  pagination_param_str: str
35
52
  endless_scroll: bool
36
53
  filter: Filter
54
+ object_param_str: str = field(default='', kw_only=True)
37
55
 
38
56
 
39
- class ListContext(TypedDict):
57
+ @dataclass
58
+ class ListContext(Context):
40
59
 
41
- title: str
42
60
  object_label: str
43
61
  object_param_str: str
44
- breadcrumbs: list[BreadCrumb]
45
- actions: list[ClientAction]
46
62
  list_page: paginator.Page
47
63
  pagination_param_str: str
48
- endless_scroll: bool
49
64
  filter: Filter
65
+ endless_scroll: bool = True
66
+ column_height: int = 4
67
+ column_width: int = 12
68
+
69
+ def __post_init__(self):
70
+ super().__post_init__()
71
+ if self.column_width not in range(1, 13):
72
+ _logger.warning(
73
+ 'ListContext parameter column_width should be in range 1 - 12'
74
+ )
50
75
 
51
76
 
52
77
  class DetailContext(TypedDict, total=False):
@@ -59,7 +84,7 @@ class DetailContext(TypedDict, total=False):
59
84
  actions: list[ClientAction]
60
85
 
61
86
 
62
- class FormContext(TypedDict):
87
+ class FormContext(TypedDict, total=False):
63
88
 
64
89
  title: str
65
90
  breadcrumbs: list[BreadCrumb]
@@ -68,204 +93,9 @@ class FormContext(TypedDict):
68
93
  actions: list[ClientAction]
69
94
 
70
95
 
71
- # @dataclass
72
- # class ListContext:
73
- #
74
- # title: str
75
- # actions: list[ClientAction] = field(default_factory=list)
76
- # object_label: str = ''
77
- # fields: list[TableField] = field(default_factory=list)
78
- # page: paginator.Page = None
79
- # list_pagination: bool = True
80
- # endless_scroll: bool = True
81
- # params: dict = field(default_factory=dict)
82
- # filter: Filter = None
83
- #
84
- # def dict(self):
85
- # return {
86
- # "title": self.title,
87
- # "actions": self.actions,
88
- # "object_label": self.object_label,
89
- # "fields": self.fields,
90
- # "page": self.page,
91
- # "list_pagination": self.list_pagination,
92
- # "endless_scroll": self.endless_scroll,
93
- # "params": self.params,
94
- # "filter": self.filter
95
- # }
96
-
97
-
98
- class GenericContent:
99
-
100
- def __init__(self, *args, **kwargs):
101
- self.page: Page | None = None
102
-
103
- def dict(self):
104
- return {}
105
-
106
-
107
- class ListContent:
108
-
109
- def __init__(
110
- self, queryset: QuerySet, paginate_by: int = DEFAULT_PAGINATE_BY,
111
- page_number: int = 1, filter_obj: Filter = None,
112
- endless_scroll: bool = True, fields: list[TableField] = None,
113
- object_label: str = None, object_url_params: dict = None
114
- ):
115
- self.queryset = queryset
116
- self.paginate_by = paginate_by or DEFAULT_PAGINATE_BY
117
- self.page_number = page_number
118
- self.filter = filter_obj
119
- self.endless_scroll = endless_scroll
120
- self.fields = fields or []
121
- self.object_label = object_label or _('Name')
122
- self.object_url_params = object_url_params or {}
123
- self.page: Page | None = None
124
-
125
- def get_page_number(self, paginator):
126
- if self.page_number < 1:
127
- return 1
128
- if self.page_number > paginator.num_pages:
129
- return paginator.num_pages
130
- return self.page_number
131
-
132
- def dict(self):
133
- paginator = Paginator(self.queryset, self.paginate_by)
134
- page = paginator.page(self.get_page_number(paginator))
135
- context = {
136
- 'page': page,
137
- 'object_label': self.object_label,
138
- 'object_url_params': url_param_str(prepare_url_params(self.object_url_params)),
139
- 'fields': self.fields,
140
- 'list_pagination': True if self.paginate_by > 0 else False,
141
- 'filter': self.filter,
142
- 'endless_scroll': self.endless_scroll
143
- }
144
- return context
145
-
146
-
147
- class DetailContent:
148
-
149
- def __init__(
150
- self, obj: Model | type[Model], queryset: QuerySet = None,
151
- ):
152
- self.obj = obj
153
- self.queryset = queryset
154
- self.page: Page | None = None
155
-
156
- def get_detail_pagination(self):
157
- return detail_pagination(self.queryset, self.obj)
158
-
159
- def dict(self):
160
- ctx = {
161
- 'object': self.obj,
162
- 'detail_pagination': False,
163
- }
164
- if self.queryset:
165
- ctx.update(self.get_detail_pagination())
166
- return ctx
167
-
168
-
169
- class FormContent:
170
-
171
- def __init__(
172
- self, model: Model | type[Model], form: Form | ModelForm,
173
- form_id: str = 'form', add_default_actions: bool = True,
174
- discard_url: str = None
175
- ):
176
- self.model = model
177
- self.form = form
178
- self.form_id = form_id
179
- self.add_default_actions = add_default_actions
180
- self.discard_url = discard_url
181
- self.page: Page | None = None
182
-
183
- def add_default_form_actions(self):
184
- actions = [
185
- ClientAction(
186
- name=_('Save'),
187
- submit=True,
188
- class_list=['is-success'],
189
- form_id=self.form_id
190
- )
191
- ]
192
- try:
193
- url = self.discard_url or (self.model.pk and self.model.get_absolute_url())
194
- except TypeError:
195
- raise TypeError(
196
- 'Supply the discard_url parameter if Form is called '
197
- 'with a model class instead of an instance.'
198
- )
199
- except AttributeError as e:
200
- _logger.error(
201
- 'Supply the discard_url parameter if Form is '
202
- 'called with a model instance that has the get_absolute_url '
203
- 'method not defined.'
204
- )
205
- raise e
206
-
207
- actions.append(
208
- ClientAction(
209
- name=_('Discard'),
210
- url=url,
211
- )
212
- )
213
- if self.page:
214
- self.page.actions = actions.extend(self.page.actions)
215
-
216
- def get_title(self):
217
- try:
218
- int(self.model.pk)
219
- return _('Edit')
220
- except TypeError:
221
- return _('Add')
222
- except Exception as e:
223
- _logger.exception(e)
224
- return ''
225
-
226
- def dict(self):
227
- ctx = {
228
- 'form': self.form,
229
- 'form_id': self.form_id,
230
- }
231
- if self.add_default_actions:
232
- self.add_default_form_actions()
233
- if self.page and not self.page.title:
234
- self.page.title = self.get_title()
235
- return ctx
236
-
237
-
238
- class Page:
239
-
240
- def __init__(
241
- self, *, title: str = None,
242
- content: GenericContent | ListContent | DetailContent | FormContent = None,
243
- breadcrumbs: list[BreadCrumb] = None, get_params: dict = None,
244
- actions: list[ClientAction] = None,
245
- ):
246
- self.title = title or ''
247
- self.content = content
248
- self.breadcrumbs = breadcrumbs or []
249
- self.actions = actions or []
250
- self.get_params = get_params or {}
251
- if self.content:
252
- self.content.page = self
253
-
254
- def dict(self):
255
- url_params = prepare_url_params(self.get_params)
256
- ctx = {
257
- 'title': self.title,
258
- 'breadcrumbs': self.breadcrumbs,
259
- 'actions': self.actions,
260
- 'url_params': url_params,
261
- 'url_params_str': url_param_str(url_params)
262
- }
263
- if self.content:
264
- ctx.update(self.content.dict())
265
- return ctx
266
-
267
-
268
96
  def cast_param(params: dict, param: str, cast_to: callable, default):
97
+ if param not in params:
98
+ return default
269
99
  try:
270
100
  return cast_to(params.get(param, default))
271
101
  except Exception as e:
@@ -281,7 +111,7 @@ def url_param_str(params: dict, extract: list[str] = None) -> str:
281
111
  """
282
112
  Return a URL Querystring from the given parameters
283
113
  If extract is supplied, extract the value from the dictionary and prepare
284
- them, so that each value is formatted eg. {'page': '&page=1'}
114
+ them, so that each value is formatted e.g. {'page': '&page=1'}
285
115
  """
286
116
  if extract:
287
117
  params = prepare_url_params(extract_url_params(params, extract))
@@ -302,12 +132,12 @@ def exclude_params(params: dict, keys: list[str]) -> dict:
302
132
  return {key: val for key, val in params.items() if key not in keys}
303
133
 
304
134
 
305
- def get_list_page(queryset: QuerySet, paginate_by: int, page_number: int) -> paginator.Page:
135
+ def list_page(queryset: QuerySet, paginate_by: int, page_number: int) -> paginator.Page:
306
136
  pages = Paginator(queryset, per_page=paginate_by)
307
137
  return pages.page(page_number <= pages.num_pages and page_number or pages.num_pages)
308
138
 
309
139
 
310
- def get_detail_page(queryset: QuerySet, obj: Model) -> dict:
140
+ def detail_page(queryset: QuerySet, obj: Model) -> dict:
311
141
  if not hasattr(obj, 'get_absolute_url'):
312
142
  _logger.warning(
313
143
  'Detail pagination disabled for models without the '
@@ -330,16 +160,59 @@ def get_detail_page(queryset: QuerySet, obj: Model) -> dict:
330
160
  }
331
161
 
332
162
 
333
- def default_form_actions(discard_url: str, form_id: str = 'form') -> list[ClientAction]:
163
+ def form_actions(discard_url: str, form_id: str = 'form') -> list[ClientAction]:
334
164
  return [
335
165
  ClientAction(
336
- name=_('Save'),
166
+ name=_t('Save'),
337
167
  submit=True,
338
168
  class_list=['is-success'],
339
169
  form_id=form_id,
340
170
  ),
341
171
  ClientAction(
342
- name=_('Discard'),
172
+ name=_t('Discard'),
343
173
  url=discard_url
344
174
  )
345
175
  ]
176
+
177
+
178
+ def get_table_fields(
179
+ fields: list[str],
180
+ model: type[Model],
181
+ field_definition: dict[str | TableField] = None
182
+ ) -> list[TableField]:
183
+ print(fields, model, field_definition)
184
+
185
+ if field_definition is None:
186
+ field_definition = {}
187
+
188
+ def get_field_definition(f_name: str) -> TableField:
189
+ if definition := field_definition.get(f_name):
190
+ return definition
191
+ if len(f_name.split('__')) == 1:
192
+ rel_model = model
193
+ else:
194
+ rel_model = get_related_model(model, f_name)
195
+ print(rel_model)
196
+ try:
197
+ return rel_model.table_field_definition[f_name]
198
+ except (AttributeError, KeyError):
199
+ attr = getattr(rel_model, f_name)
200
+ if isinstance(attr, Annotation):
201
+ attr = attr
202
+ elif hasattr(attr, 'field'):
203
+ attr = attr.field
204
+ return TableField(
205
+ label=str(attr.verbose_name),
206
+ name=f_name,
207
+ header_info=str(attr.help_text),
208
+ truncate_after=50
209
+ )
210
+
211
+ table_fields = []
212
+ for field_name in fields:
213
+ try:
214
+ table_fields.append(get_field_definition(field_name))
215
+ except AttributeError as e:
216
+ print(e)
217
+ pass
218
+ return table_fields