accrete 0.0.35__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 +46 -0
- accrete/contrib/ui/__init__.py +12 -3
- accrete/contrib/ui/context.py +184 -257
- accrete/contrib/ui/elements.py +78 -2
- accrete/contrib/ui/filter.py +110 -44
- accrete/contrib/ui/static/css/accrete.css +128 -128
- accrete/contrib/ui/static/css/accrete.css.map +1 -1
- accrete/contrib/ui/static/css/accrete.scss +9 -9
- accrete/contrib/ui/static/js/filter.js +24 -0
- accrete/contrib/ui/static/js/htmx.min.js +1 -0
- accrete/contrib/ui/templates/ui/layout.html +134 -129
- accrete/contrib/ui/templates/ui/list.html +3 -3
- accrete/contrib/ui/templates/ui/partials/filter.html +29 -5
- accrete/contrib/ui/templates/ui/partials/header.html +7 -7
- accrete/contrib/ui/templates/ui/partials/pagination_detail.html +3 -3
- accrete/contrib/ui/templates/ui/partials/pagination_list.html +4 -4
- accrete/contrib/ui/templates/ui/table.html +18 -13
- accrete/contrib/ui/templatetags/accrete_ui.py +12 -1
- accrete/contrib/user/forms.py +0 -4
- accrete/contrib/user/templates/user/login.html +6 -12
- accrete/contrib/user/views.py +31 -21
- accrete/middleware.py +15 -0
- accrete/models.py +9 -7
- accrete/querystring.py +11 -8
- accrete/utils/models.py +14 -0
- {accrete-0.0.35.dist-info → accrete-0.0.37.dist-info}/METADATA +1 -1
- {accrete-0.0.35.dist-info → accrete-0.0.37.dist-info}/RECORD +29 -28
- accrete/contrib/ui/components.py +0 -96
- accrete/contrib/ui/querystring.py +0 -19
- {accrete-0.0.35.dist-info → accrete-0.0.37.dist-info}/WHEEL +0 -0
- {accrete-0.0.35.dist-info → accrete-0.0.37.dist-info}/licenses/LICENSE +0 -0
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
|
+
|
accrete/contrib/ui/__init__.py
CHANGED
@@ -1,10 +1,20 @@
|
|
1
1
|
from .filter import Filter
|
2
2
|
from .context import (
|
3
|
-
ListContext,
|
4
3
|
DetailContext,
|
4
|
+
TableContext,
|
5
|
+
ListContext,
|
5
6
|
FormContext,
|
7
|
+
form_actions,
|
8
|
+
list_page,
|
9
|
+
detail_page,
|
10
|
+
cast_param,
|
11
|
+
prepare_url_params,
|
12
|
+
extract_url_params,
|
13
|
+
exclude_params,
|
14
|
+
url_param_str,
|
15
|
+
get_table_fields
|
6
16
|
)
|
7
|
-
from .
|
17
|
+
from .elements import (
|
8
18
|
ClientAction,
|
9
19
|
ActionMethod,
|
10
20
|
BreadCrumb,
|
@@ -13,4 +23,3 @@ from .components import (
|
|
13
23
|
TableFieldType,
|
14
24
|
Icon
|
15
25
|
)
|
16
|
-
from .querystring import load_querystring, build_querystring
|
accrete/contrib/ui/context.py
CHANGED
@@ -1,291 +1,218 @@
|
|
1
1
|
import logging
|
2
2
|
from dataclasses import dataclass, field
|
3
|
-
|
4
|
-
from django.utils.translation import gettext_lazy as
|
5
|
-
from django.db.models import Model, QuerySet
|
3
|
+
from typing import TypedDict
|
4
|
+
from django.utils.translation import gettext_lazy as _t
|
5
|
+
from django.db.models import Model, QuerySet
|
6
6
|
from django.core.paginator import Paginator
|
7
|
+
from django.core import paginator
|
7
8
|
from django.forms import Form, ModelForm
|
8
|
-
from accrete.
|
9
|
-
|
10
|
-
from .querystring import load_querystring, build_querystring
|
11
|
-
from .components import ClientAction, BreadCrumb, TableField
|
9
|
+
from accrete.utils.models import get_related_model
|
10
|
+
from .elements import ClientAction, BreadCrumb, TableField, TableFieldType
|
12
11
|
from .filter import Filter
|
12
|
+
from ...annotation import Annotation
|
13
13
|
|
14
14
|
_logger = logging.getLogger(__name__)
|
15
15
|
|
16
16
|
DEFAULT_PAGINATE_BY = 40
|
17
17
|
|
18
18
|
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
title: str = None
|
25
|
-
context: dict = field(default_factory=dict)
|
26
|
-
queryset: QuerySet = None
|
27
|
-
select_related: list[str] = field(default_factory=list)
|
28
|
-
prefetch_related: list[str] = field(default_factory=list)
|
29
|
-
annotate: dict = field(default_factory=dict)
|
30
|
-
paginate_by: int = DEFAULT_PAGINATE_BY
|
31
|
-
order_by: list[str] = field(default_factory=list)
|
32
|
-
filter_relation_depth: int = 4
|
33
|
-
default_filter_term: str = ''
|
34
|
-
actions: list[ClientAction] = field(default_factory=list)
|
35
|
-
breadcrumbs: list[BreadCrumb] = field(default_factory=list)
|
36
|
-
object_label: str = None
|
37
|
-
fields: list[TableField] = field(default_factory=list)
|
38
|
-
unselect_button: bool = False
|
39
|
-
endless_scroll: bool = True
|
19
|
+
class DetailPagination(TypedDict):
|
20
|
+
previous_object_url: str
|
21
|
+
next_object_url: str
|
22
|
+
current_object_idx: int
|
23
|
+
total_objects: int
|
40
24
|
|
41
|
-
def get_queryset(self):
|
42
|
-
if self.queryset:
|
43
|
-
return self.queryset
|
44
|
-
|
45
|
-
order = self.order_by or self.model._meta.ordering
|
46
|
-
|
47
|
-
# pks = self.model.objects.filter(
|
48
|
-
# parse_querystring(self.model, self.get_params.get('q', '[]'))
|
49
|
-
# ).distinct().values_list('pk', flat=True)
|
50
|
-
|
51
|
-
queryset = self.model.objects.select_related(
|
52
|
-
*self.select_related
|
53
|
-
).prefetch_related(
|
54
|
-
*self.prefetch_related
|
55
|
-
).filter(
|
56
|
-
parse_querystring(self.model, self.get_params.get('q', '[]'))
|
57
|
-
).annotate(
|
58
|
-
**self.get_annotations()
|
59
|
-
).order_by(
|
60
|
-
*order
|
61
|
-
).distinct()
|
62
|
-
|
63
|
-
return queryset
|
64
|
-
|
65
|
-
def get_annotations(self):
|
66
|
-
annotations = {
|
67
|
-
annotation['name']: annotation['func']
|
68
|
-
for annotation in getattr(self.model, 'annotations', [])
|
69
|
-
}
|
70
|
-
if self.annotate:
|
71
|
-
annotations.update(self.annotate)
|
72
|
-
return annotations
|
73
25
|
|
74
|
-
|
75
|
-
|
26
|
+
@dataclass(kw_only=True)
|
27
|
+
class Context:
|
76
28
|
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
page_number = paginator.num_pages
|
86
|
-
return page_number
|
87
|
-
|
88
|
-
def get_paginate_by(self):
|
89
|
-
paginate_by = self.get_params.get('paginate_by', self.paginate_by)
|
90
|
-
try:
|
91
|
-
paginate_by = int(paginate_by)
|
92
|
-
except ValueError:
|
93
|
-
paginate_by = self.paginate_by
|
94
|
-
return paginate_by
|
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)
|
95
37
|
|
96
38
|
def dict(self):
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
page = paginator.page(self.get_page_number(paginator))
|
101
|
-
context = {
|
102
|
-
'queryset': queryset,
|
103
|
-
'paginate_by': paginate_by,
|
104
|
-
'order_by': self.get_params.get('order_by', self.model._meta.ordering),
|
105
|
-
'paginator': paginator,
|
106
|
-
'page': page,
|
107
|
-
'list_pagination': True,
|
108
|
-
'title': self.title or self.model._meta.verbose_name_plural,
|
109
|
-
'object_label': self.object_label or self.model._meta.verbose_name or _('Name'),
|
110
|
-
'filter': Filter(self.model, self.filter_relation_depth),
|
111
|
-
'default_filter_term': self.default_filter_term,
|
112
|
-
'breadcrumbs': self.breadcrumbs,
|
113
|
-
'querystring': load_querystring(self.get_params),
|
114
|
-
'url_params': build_querystring(self.get_params),
|
115
|
-
'actions': self.actions,
|
116
|
-
'fields': self.fields,
|
117
|
-
'endless_scroll': self.endless_scroll
|
39
|
+
return {
|
40
|
+
attr: getattr(self, attr, '') for attr
|
41
|
+
in filter(lambda x: not x.startswith('_'), self.__dict__)
|
118
42
|
}
|
119
|
-
context.update(self.context)
|
120
|
-
return context
|
121
43
|
|
122
44
|
|
123
45
|
@dataclass
|
124
|
-
class
|
125
|
-
|
126
|
-
obj: Model | type[Model]
|
127
|
-
get_params: dict
|
128
|
-
order_by: str = None
|
129
|
-
paginate_by: int = DEFAULT_PAGINATE_BY
|
130
|
-
title: str = None
|
131
|
-
queryset: type[QuerySet] = None
|
132
|
-
select_related: list[str] = field(default_factory=list)
|
133
|
-
prefetch_related: list[str] = field(default_factory=list)
|
134
|
-
annotate: dict = field(default_factory=dict)
|
135
|
-
actions: list[ClientAction] = field(default_factory=list)
|
136
|
-
breadcrumbs: list[BreadCrumb] = field(default_factory=list)
|
137
|
-
context: dict = field(default_factory=dict)
|
138
|
-
|
139
|
-
def get_queryset(self):
|
140
|
-
if self.queryset:
|
141
|
-
return self.queryset
|
142
|
-
|
143
|
-
order = self.order_by or self.obj._meta.model._meta.ordering
|
144
|
-
|
145
|
-
pks = self.obj._meta.model.objects.filter(
|
146
|
-
parse_querystring(self.obj._meta.model, self.get_params.get('q', '[]'))
|
147
|
-
).distinct().values_list('pk', flat=True)
|
148
|
-
|
149
|
-
queryset = self.obj._meta.model.objects.select_related(
|
150
|
-
*self.select_related
|
151
|
-
).prefetch_related(
|
152
|
-
*self.prefetch_related
|
153
|
-
).filter(
|
154
|
-
Q(pk__in=pks)
|
155
|
-
).annotate(
|
156
|
-
**self.get_annotations()
|
157
|
-
).order_by(
|
158
|
-
*order
|
159
|
-
).distinct()
|
160
|
-
|
161
|
-
return queryset
|
162
|
-
|
163
|
-
def get_annotations(self):
|
164
|
-
annotations = {
|
165
|
-
annotation['name']: annotation['func']
|
166
|
-
for annotation in getattr(self.obj._meta.model, 'annotations', [])
|
167
|
-
}
|
168
|
-
if self.annotate:
|
169
|
-
annotations.update(self.annotate)
|
170
|
-
return annotations
|
46
|
+
class TableContext(Context):
|
171
47
|
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
48
|
+
object_label: str
|
49
|
+
fields: list[TableField]
|
50
|
+
list_page: paginator.Page
|
51
|
+
pagination_param_str: str
|
52
|
+
endless_scroll: bool
|
53
|
+
filter: Filter
|
54
|
+
object_param_str: str = field(default='', kw_only=True)
|
55
|
+
|
56
|
+
|
57
|
+
@dataclass
|
58
|
+
class ListContext(Context):
|
59
|
+
|
60
|
+
object_label: str
|
61
|
+
object_param_str: str
|
62
|
+
list_page: paginator.Page
|
63
|
+
pagination_param_str: str
|
64
|
+
filter: Filter
|
65
|
+
endless_scroll: bool = True
|
66
|
+
column_height: int = 4
|
67
|
+
column_width: int = 12
|
179
68
|
|
180
|
-
def
|
181
|
-
|
69
|
+
def __post_init__(self):
|
70
|
+
super().__post_init__()
|
71
|
+
if self.column_width not in range(1, 13):
|
182
72
|
_logger.warning(
|
183
|
-
'
|
184
|
-
'get_absolute_url attribute. Set paginate_by to 0 to '
|
185
|
-
'deactivate pagination.'
|
73
|
+
'ListContext parameter column_width should be in range 1 - 12'
|
186
74
|
)
|
187
|
-
return {}
|
188
|
-
queryset = self.get_queryset()
|
189
|
-
idx = (*queryset,).index(self.obj)
|
190
|
-
previous_object_url = (
|
191
|
-
queryset[idx - 1] if idx - 1 >= 0 else queryset.last()
|
192
|
-
).get_absolute_url()
|
193
|
-
next_object_url = (
|
194
|
-
queryset[idx + 1] if idx + 1 <= queryset.count() - 1 else queryset.first()
|
195
|
-
).get_absolute_url()
|
196
|
-
ctx = {
|
197
|
-
'previous_object_url': previous_object_url,
|
198
|
-
'next_object_url': next_object_url,
|
199
|
-
'current_object_idx': idx + 1,
|
200
|
-
'total_objects': queryset.count(),
|
201
|
-
'detail_pagination': True
|
202
|
-
}
|
203
|
-
return ctx
|
204
75
|
|
205
|
-
def dict(self):
|
206
|
-
paginate_by = self.get_paginate_by()
|
207
|
-
ctx = {
|
208
|
-
'object': self.get_queryset().get(pk=self.obj.pk),
|
209
|
-
'title': self.title or self.obj,
|
210
|
-
'order_by': self.get_params.get('order_by', self.obj._meta.model._meta.ordering),
|
211
|
-
'paginate_by': paginate_by,
|
212
|
-
'detail_pagination': False,
|
213
|
-
'breadcrumbs': self.breadcrumbs,
|
214
|
-
'url_params': build_querystring(self.get_params, ['page']),
|
215
|
-
'actions': self.actions
|
216
|
-
}
|
217
|
-
if self.paginate_by > 0:
|
218
|
-
ctx.update(self.get_pagination_context())
|
219
|
-
ctx.update(self.context)
|
220
|
-
return ctx
|
221
76
|
|
77
|
+
class DetailContext(TypedDict, total=False):
|
222
78
|
|
223
|
-
|
224
|
-
|
79
|
+
title: str
|
80
|
+
object: Model
|
81
|
+
breadcrumbs: list[BreadCrumb]
|
82
|
+
detail_page: DetailPagination
|
83
|
+
pagination_param_str: str
|
84
|
+
actions: list[ClientAction]
|
85
|
+
|
86
|
+
|
87
|
+
class FormContext(TypedDict, total=False):
|
225
88
|
|
226
|
-
|
89
|
+
title: str
|
90
|
+
breadcrumbs: list[BreadCrumb]
|
227
91
|
form: Form | ModelForm
|
228
|
-
|
229
|
-
|
230
|
-
context: dict = field(default_factory=dict)
|
231
|
-
form_id: str = 'form'
|
232
|
-
add_default_actions: bool = True
|
233
|
-
discard_url: str = None
|
234
|
-
actions: list[ClientAction] = field(default_factory=list)
|
235
|
-
breadcrumbs: list[BreadCrumb] = field(default_factory=list)
|
92
|
+
form_id: str
|
93
|
+
actions: list[ClientAction]
|
236
94
|
|
237
|
-
def get_default_form_actions(self):
|
238
|
-
actions = [
|
239
|
-
ClientAction(
|
240
|
-
name=_('Save'),
|
241
|
-
submit=True,
|
242
|
-
class_list=['is-success'],
|
243
|
-
form_id=self.form_id
|
244
|
-
)
|
245
|
-
]
|
246
|
-
try:
|
247
|
-
url = self.discard_url or (self.model.pk and self.model.get_absolute_url())
|
248
|
-
except TypeError:
|
249
|
-
raise TypeError(
|
250
|
-
'Supply the discard_url parameter if FormContext is called '
|
251
|
-
'with a model class instead of an instance.'
|
252
|
-
)
|
253
|
-
except AttributeError as e:
|
254
|
-
_logger.error(
|
255
|
-
'Supply the discard_url parameter if FormContext is '
|
256
|
-
'called with a model instance that has the get_absolute_url '
|
257
|
-
'method not defined.'
|
258
|
-
)
|
259
|
-
raise e
|
260
95
|
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
96
|
+
def cast_param(params: dict, param: str, cast_to: callable, default):
|
97
|
+
if param not in params:
|
98
|
+
return default
|
99
|
+
try:
|
100
|
+
return cast_to(params.get(param, default))
|
101
|
+
except Exception as e:
|
102
|
+
_logger.exception(e)
|
103
|
+
return default
|
104
|
+
|
105
|
+
|
106
|
+
def prepare_url_params(get_params: dict) -> dict:
|
107
|
+
return {key: f'&{key}={value}' for key, value in get_params.items()}
|
108
|
+
|
109
|
+
|
110
|
+
def url_param_str(params: dict, extract: list[str] = None) -> str:
|
111
|
+
"""
|
112
|
+
Return a URL Querystring from the given parameters
|
113
|
+
If extract is supplied, extract the value from the dictionary and prepare
|
114
|
+
them, so that each value is formatted e.g. {'page': '&page=1'}
|
115
|
+
"""
|
116
|
+
if extract:
|
117
|
+
params = prepare_url_params(extract_url_params(params, extract))
|
118
|
+
param_str = (
|
119
|
+
"".join(str(value) for value in params.values())
|
120
|
+
.replace('&&', '&')
|
121
|
+
.replace('?&', '?')
|
122
|
+
.strip('?&')
|
123
|
+
)
|
124
|
+
return f'?{param_str}'
|
125
|
+
|
268
126
|
|
269
|
-
|
270
|
-
|
271
|
-
|
127
|
+
def extract_url_params(params: dict, keys: list[str]) -> dict:
|
128
|
+
return {key: params[key] for key in keys if key in params}
|
129
|
+
|
130
|
+
|
131
|
+
def exclude_params(params: dict, keys: list[str]) -> dict:
|
132
|
+
return {key: val for key, val in params.items() if key not in keys}
|
133
|
+
|
134
|
+
|
135
|
+
def list_page(queryset: QuerySet, paginate_by: int, page_number: int) -> paginator.Page:
|
136
|
+
pages = Paginator(queryset, per_page=paginate_by)
|
137
|
+
return pages.page(page_number <= pages.num_pages and page_number or pages.num_pages)
|
138
|
+
|
139
|
+
|
140
|
+
def detail_page(queryset: QuerySet, obj: Model) -> dict:
|
141
|
+
if not hasattr(obj, 'get_absolute_url'):
|
142
|
+
_logger.warning(
|
143
|
+
'Detail pagination disabled for models without the '
|
144
|
+
'get_absolute_url attribute. Set paginate_by to 0 to '
|
145
|
+
'deactivate pagination.'
|
146
|
+
)
|
147
|
+
return {}
|
148
|
+
idx = (*queryset,).index(obj)
|
149
|
+
previous_object_url = (
|
150
|
+
queryset[idx - 1] if idx - 1 >= 0 else queryset.last()
|
151
|
+
).get_absolute_url()
|
152
|
+
next_object_url = (
|
153
|
+
queryset[idx + 1] if idx + 1 <= queryset.count() - 1 else queryset.first()
|
154
|
+
).get_absolute_url()
|
155
|
+
return {
|
156
|
+
'previous_object_url': previous_object_url,
|
157
|
+
'next_object_url': next_object_url,
|
158
|
+
'current_object_idx': idx + 1,
|
159
|
+
'total_objects': queryset.count()
|
160
|
+
}
|
161
|
+
|
162
|
+
|
163
|
+
def form_actions(discard_url: str, form_id: str = 'form') -> list[ClientAction]:
|
164
|
+
return [
|
165
|
+
ClientAction(
|
166
|
+
name=_t('Save'),
|
167
|
+
submit=True,
|
168
|
+
class_list=['is-success'],
|
169
|
+
form_id=form_id,
|
170
|
+
),
|
171
|
+
ClientAction(
|
172
|
+
name=_t('Discard'),
|
173
|
+
url=discard_url
|
174
|
+
)
|
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)
|
272
196
|
try:
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
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
|
+
)
|
277
210
|
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
}
|
287
|
-
if self.add_default_actions:
|
288
|
-
ctx.update({'actions': self.get_default_form_actions()})
|
289
|
-
ctx['actions'].extend(self.actions)
|
290
|
-
ctx.update(self.context)
|
291
|
-
return ctx
|
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
|
accrete/contrib/ui/elements.py
CHANGED
@@ -1,2 +1,78 @@
|
|
1
|
-
from
|
2
|
-
from
|
1
|
+
from enum import Enum
|
2
|
+
from dataclasses import dataclass, field
|
3
|
+
|
4
|
+
|
5
|
+
class Icon(Enum):
|
6
|
+
|
7
|
+
ADD = 'icon-add'
|
8
|
+
EDIT = 'icon-edit'
|
9
|
+
LIST = 'icon-list'
|
10
|
+
OPEN_RELATED = 'icon-open-related'
|
11
|
+
ENVELOPE = 'icon-envelope'
|
12
|
+
CLEAR = 'icon-clear'
|
13
|
+
BACKSPACE = 'icon-backspace'
|
14
|
+
FILTER = 'icon-filter'
|
15
|
+
DELETE_FILTER = 'icon-delete-filter'
|
16
|
+
SELECT = 'icon-select'
|
17
|
+
|
18
|
+
|
19
|
+
class ActionMethod(Enum):
|
20
|
+
|
21
|
+
HREF = 'href'
|
22
|
+
GET = 'hx-get'
|
23
|
+
POST = 'hx-post'
|
24
|
+
PUT = 'hx-put'
|
25
|
+
DELETE = 'hx-delete'
|
26
|
+
|
27
|
+
|
28
|
+
@dataclass
|
29
|
+
class ClientAction:
|
30
|
+
|
31
|
+
name: str
|
32
|
+
url: str = ''
|
33
|
+
method: ActionMethod = ActionMethod.HREF
|
34
|
+
attrs: list[tuple[str, str]] = field(default_factory=list)
|
35
|
+
submit: bool = False
|
36
|
+
form_id: str = 'form'
|
37
|
+
class_list: list[str] = field(default_factory=list)
|
38
|
+
icon: Icon | type[Enum] = None
|
39
|
+
|
40
|
+
def attrs_str(self):
|
41
|
+
return ' '.join([f'{str(attr[0])}={str(attr[1])}' for attr in self.attrs])
|
42
|
+
|
43
|
+
|
44
|
+
class TableFieldAlignment(Enum):
|
45
|
+
|
46
|
+
LEFT = 'left'
|
47
|
+
CENTER = 'center'
|
48
|
+
RIGHT = 'right'
|
49
|
+
|
50
|
+
|
51
|
+
class TableFieldType(Enum):
|
52
|
+
|
53
|
+
NONE = ''
|
54
|
+
STRING = '_string'
|
55
|
+
MONETARY = '_monetary'
|
56
|
+
FLOAT = '_float'
|
57
|
+
|
58
|
+
|
59
|
+
@dataclass
|
60
|
+
class TableField:
|
61
|
+
|
62
|
+
label: str
|
63
|
+
name: str
|
64
|
+
alignment: TableFieldAlignment | Enum = TableFieldAlignment.LEFT
|
65
|
+
header_alignment: TableFieldAlignment | Enum = None
|
66
|
+
header_info: str = None
|
67
|
+
field_type: TableFieldType | Enum = TableFieldType.NONE
|
68
|
+
prefix: str = ''
|
69
|
+
suffix: str = ''
|
70
|
+
truncate_after: int = 0
|
71
|
+
template: str = None
|
72
|
+
|
73
|
+
|
74
|
+
@dataclass
|
75
|
+
class BreadCrumb:
|
76
|
+
|
77
|
+
name: str
|
78
|
+
url: str
|