accrete 0.0.143__py3-none-any.whl → 0.0.144__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/contrib/log/admin.py +1 -1
- accrete/contrib/system_mail/tasks.py +4 -5
- accrete/contrib/ui/__init__.py +15 -14
- accrete/contrib/ui/filter.py +22 -13
- accrete/contrib/ui/response.py +540 -0
- accrete/contrib/ui/static/css/accrete.css +69 -12
- accrete/contrib/ui/static/css/accrete.css.map +1 -1
- accrete/contrib/ui/static/css/accrete.scss +370 -300
- accrete/contrib/ui/templates/ui/content.html +0 -0
- accrete/contrib/ui/templates/ui/content_right.html +2 -2
- accrete/contrib/ui/templates/ui/detail.html +11 -0
- accrete/contrib/ui/templates/ui/filter/filter.html +5 -5
- accrete/contrib/ui/templates/ui/filter/query_tags.html +1 -1
- accrete/contrib/ui/templates/ui/layout.html +69 -64
- accrete/contrib/ui/templates/ui/list.html +21 -19
- accrete/contrib/ui/templates/ui/list_update.html +9 -3
- accrete/contrib/ui/templates/ui/message.html +1 -1
- accrete/contrib/ui/templates/ui/modal.html +41 -32
- accrete/contrib/ui/templates/ui/table_row_update.html +2 -2
- accrete/contrib/ui/templates/ui/templatetags/field.html +19 -6
- accrete/contrib/ui/templates/ui/widgets/model_search_select.html +7 -7
- accrete/contrib/ui/templates/ui/widgets/model_search_select_multi.html +3 -3
- accrete/contrib/ui/templatetags/ui.py +24 -2
- accrete/contrib/user/templates/user/password_forgotten.html +1 -0
- accrete/contrib/user/templates/user/user_preferences.html +43 -0
- accrete/contrib/user/views.py +36 -37
- accrete/migrations/0008_alter_member_access_groups_and_more.py +23 -0
- accrete/utils/__init__.py +1 -0
- accrete/utils/forms.py +11 -3
- accrete/utils/views.py +1 -2
- accrete/views.py +2 -3
- {accrete-0.0.143.dist-info → accrete-0.0.144.dist-info}/METADATA +2 -2
- {accrete-0.0.143.dist-info → accrete-0.0.144.dist-info}/RECORD +35 -33
- accrete/contrib/ui/context.py +0 -123
- accrete/contrib/ui/static/css/.sass-cache/15adf1eed05371361b08787c918a7f18fc15be79/accrete.scssc +0 -0
- accrete/contrib/ui/utils.py +0 -88
- accrete/contrib/user/templates/user/user_form.html +0 -58
- {accrete-0.0.143.dist-info → accrete-0.0.144.dist-info}/WHEEL +0 -0
- {accrete-0.0.143.dist-info → accrete-0.0.144.dist-info}/licenses/LICENSE +0 -0
accrete/contrib/log/admin.py
CHANGED
@@ -36,8 +36,7 @@ def run_mail_queue():
|
|
36
36
|
_logger.error(f'Failed to send system mail\n{error_str}')
|
37
37
|
email.error = error_str
|
38
38
|
email.save()
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
email.save()
|
39
|
+
else:
|
40
|
+
email.sent = True
|
41
|
+
email.error = None
|
42
|
+
email.save()
|
accrete/contrib/ui/__init__.py
CHANGED
@@ -1,18 +1,19 @@
|
|
1
1
|
from . import widgets
|
2
2
|
from .filter import Filter
|
3
|
-
from .
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
detail_response,
|
3
|
+
from .response import (
|
4
|
+
Response,
|
5
|
+
WindowResponse,
|
6
|
+
ListResponse,
|
7
|
+
ListEntryResponse,
|
8
|
+
TableResponse,
|
9
|
+
TableRowResponse,
|
10
|
+
DetailResponse,
|
11
|
+
ModalResponse,
|
12
|
+
OobResponse,
|
13
|
+
TriggerResponse,
|
14
|
+
ClientTrigger,
|
16
15
|
search_select_response,
|
17
|
-
|
16
|
+
message_response,
|
17
|
+
add_trigger,
|
18
|
+
update
|
18
19
|
)
|
accrete/contrib/ui/filter.py
CHANGED
@@ -46,12 +46,21 @@ class Filter:
|
|
46
46
|
TYPES_TIME = ['TimeField']
|
47
47
|
|
48
48
|
def __init__(
|
49
|
-
self,
|
49
|
+
self,
|
50
|
+
model,
|
51
|
+
query_dict: QueryDict,
|
52
|
+
default_lookup: str = None,
|
53
|
+
query: Q = None,
|
54
|
+
select_related: list[str] = None,
|
55
|
+
prefetch_related: list[str] = None
|
50
56
|
):
|
51
57
|
self.model = model
|
52
|
-
self.
|
58
|
+
self.query_dict = query_dict
|
53
59
|
self.model_name = f'{self.model._meta.app_label}.{self.model._meta.model_name}'
|
54
|
-
|
60
|
+
self.query = query or Q()
|
61
|
+
self.select_related = select_related
|
62
|
+
self.prefetch_related = prefetch_related
|
63
|
+
seen_models = query_dict.get('models', '').split(',')
|
55
64
|
self.seen_models = [
|
56
65
|
model for model in seen_models
|
57
66
|
if model and model != self.model_name
|
@@ -67,14 +76,14 @@ class Filter:
|
|
67
76
|
) -> paginator.Page:
|
68
77
|
return page_from_querystring(
|
69
78
|
self.model,
|
70
|
-
self.
|
71
|
-
select_related=select_related,
|
72
|
-
prefetch_related=prefetch_related,
|
73
|
-
query=query
|
79
|
+
self.query_dict,
|
80
|
+
select_related=select_related or self.select_related,
|
81
|
+
prefetch_related=prefetch_related or self.prefetch_related,
|
82
|
+
query=query or self.query
|
74
83
|
)
|
75
84
|
|
76
85
|
def get_queryset(self) -> QuerySet:
|
77
|
-
return filter_from_querystring(self.model, self.
|
86
|
+
return filter_from_querystring(self.model, self.query_dict)
|
78
87
|
|
79
88
|
def query_params(self):
|
80
89
|
fields = filter(
|
@@ -82,7 +91,7 @@ class Filter:
|
|
82
91
|
self.model._meta.get_fields()
|
83
92
|
)
|
84
93
|
params = []
|
85
|
-
path = self.
|
94
|
+
path = self.query_dict.get('path', '')
|
86
95
|
for field in fields:
|
87
96
|
field_params = self._get_field_params(field)
|
88
97
|
if not field.is_relation or field_params['model_name'] not in self.seen_models:
|
@@ -115,7 +124,7 @@ class Filter:
|
|
115
124
|
if not lookup:
|
116
125
|
ctx = {
|
117
126
|
'verbose_lookup': str(_('Select an attribute')),
|
118
|
-
'query_dict': self.
|
127
|
+
'query_dict': self.query_dict,
|
119
128
|
'model_name': self.model_name
|
120
129
|
}
|
121
130
|
return render_to_string('ui/filter/query_input.html', ctx)
|
@@ -133,14 +142,14 @@ class Filter:
|
|
133
142
|
'lookup': prefix + lookup,
|
134
143
|
'input': input_params,
|
135
144
|
'lookup_operator': lookup_operator,
|
136
|
-
'query_dict': self.
|
145
|
+
'query_dict': self.query_dict,
|
137
146
|
'model_name': self.model_name
|
138
147
|
}
|
139
148
|
return render_to_string('ui/filter/query_input.html', ctx)
|
140
149
|
|
141
150
|
def query_tags(self, query: list | dict = None, operator: str = None):
|
142
151
|
if not query:
|
143
|
-
query = json.loads(self.
|
152
|
+
query = json.loads(self.query_dict.get('q', '[]'))
|
144
153
|
if isinstance(query, dict):
|
145
154
|
query = [query]
|
146
155
|
operator = operator or '&'
|
@@ -291,7 +300,7 @@ class Filter:
|
|
291
300
|
return str(labels[lookup])
|
292
301
|
|
293
302
|
def _get_field_params(self, field):
|
294
|
-
path = self.
|
303
|
+
path = self.query_dict.get('path', '')
|
295
304
|
label = ''
|
296
305
|
name = field.name
|
297
306
|
if path:
|
@@ -0,0 +1,540 @@
|
|
1
|
+
import re
|
2
|
+
import ast
|
3
|
+
import json
|
4
|
+
from dataclasses import dataclass
|
5
|
+
|
6
|
+
from django.core import paginator
|
7
|
+
from django.db.models import Model
|
8
|
+
from django.http import HttpResponse
|
9
|
+
from django.template.loader import render_to_string
|
10
|
+
from django.utils.translation import gettext_lazy as _
|
11
|
+
from accrete.contrib.ui import Filter
|
12
|
+
|
13
|
+
|
14
|
+
class Response:
|
15
|
+
|
16
|
+
def __init__(self, *, template: str, context: dict):
|
17
|
+
self.template = template
|
18
|
+
self.context = context
|
19
|
+
|
20
|
+
@staticmethod
|
21
|
+
def add_trigger(response):
|
22
|
+
pass
|
23
|
+
|
24
|
+
def render(self, request) -> str:
|
25
|
+
return render_to_string(
|
26
|
+
template_name=self.template, context=self.context, request=request
|
27
|
+
)
|
28
|
+
|
29
|
+
def response(self, request, extra_content: str = None, replace_body: bool = False) -> HttpResponse:
|
30
|
+
extra_content = extra_content or ''
|
31
|
+
res = HttpResponse(content=(
|
32
|
+
self.render(request)
|
33
|
+
+ render_to_string('ui/message.html', request=request)
|
34
|
+
+ extra_content
|
35
|
+
))
|
36
|
+
self.add_trigger(res)
|
37
|
+
if replace_body:
|
38
|
+
res.headers['HX-Retarget'] = 'body'
|
39
|
+
res.headers['HX-Reswap'] = 'innerHTML'
|
40
|
+
res.headers['HX-Push-Url'] = request.path
|
41
|
+
return res
|
42
|
+
|
43
|
+
|
44
|
+
class OobResponse(Response):
|
45
|
+
|
46
|
+
oob_template = 'ui/oob.html'
|
47
|
+
|
48
|
+
def __init__(self, *, template: str, context: dict, swap: str, tag: str = 'div'):
|
49
|
+
super().__init__(template=self.oob_template, context=context)
|
50
|
+
self.context.update({'oob': {
|
51
|
+
'template': template,
|
52
|
+
'swap': swap,
|
53
|
+
'tag': tag
|
54
|
+
}})
|
55
|
+
|
56
|
+
|
57
|
+
class WindowResponse(Response):
|
58
|
+
|
59
|
+
base_template = 'ui/layout.html'
|
60
|
+
|
61
|
+
def __init__(
|
62
|
+
self, *,
|
63
|
+
title: str,
|
64
|
+
context: dict,
|
65
|
+
overview_template: str = None,
|
66
|
+
header_template: str = None,
|
67
|
+
panel_template: str = None,
|
68
|
+
is_centered: bool = False
|
69
|
+
):
|
70
|
+
super().__init__(template=self.base_template, context=context)
|
71
|
+
self.panel_template = panel_template
|
72
|
+
if 'has_panel' not in self.context.keys():
|
73
|
+
self.context.update(has_panel=self._has_panel())
|
74
|
+
self.context.update({
|
75
|
+
'title': title,
|
76
|
+
'overview_template': overview_template,
|
77
|
+
'header_template': header_template,
|
78
|
+
'panel_template': panel_template,
|
79
|
+
'is_centered': is_centered
|
80
|
+
})
|
81
|
+
|
82
|
+
def _has_panel(self):
|
83
|
+
return bool(self.panel_template)
|
84
|
+
|
85
|
+
def response(self, request, extra_content: str = None, replace_body: bool = True) -> HttpResponse:
|
86
|
+
return super().response(request, extra_content, replace_body)
|
87
|
+
|
88
|
+
|
89
|
+
class ListResponse(WindowResponse):
|
90
|
+
|
91
|
+
def __init__(
|
92
|
+
self, *,
|
93
|
+
title: str,
|
94
|
+
context: dict,
|
95
|
+
list_entry_template: str = None,
|
96
|
+
page: paginator.Page = None,
|
97
|
+
ui_filter: Filter = None,
|
98
|
+
endless_scroll: bool = True,
|
99
|
+
header_template: str = None,
|
100
|
+
panel_template: str = None,
|
101
|
+
column_count: int = 1,
|
102
|
+
column_height: str = '150px',
|
103
|
+
overview_template: str = 'ui/list.html'
|
104
|
+
):
|
105
|
+
assert page is not None or ui_filter is not None, _(
|
106
|
+
'Argument page or ui_filter must be supplied'
|
107
|
+
)
|
108
|
+
self.ui_filter = ui_filter
|
109
|
+
self.overview_template = overview_template
|
110
|
+
super().__init__(
|
111
|
+
title=title,
|
112
|
+
context=context,
|
113
|
+
overview_template=self.overview_template,
|
114
|
+
header_template=header_template,
|
115
|
+
panel_template=panel_template,
|
116
|
+
)
|
117
|
+
if ui_filter and not page:
|
118
|
+
page = ui_filter.get_page()
|
119
|
+
self.context.update({
|
120
|
+
'list_entry_template': list_entry_template,
|
121
|
+
'page': page,
|
122
|
+
'ui_filter': ui_filter,
|
123
|
+
'endless_scroll': endless_scroll,
|
124
|
+
'column_count': column_count,
|
125
|
+
'column_height': column_height
|
126
|
+
})
|
127
|
+
|
128
|
+
def _has_panel(self):
|
129
|
+
return bool(self.panel_template or self.ui_filter)
|
130
|
+
|
131
|
+
def response(self, request, extra_content: str = None, replace_body: bool = False) -> HttpResponse:
|
132
|
+
return super().response(request, extra_content, replace_body)
|
133
|
+
|
134
|
+
|
135
|
+
class ListEntryResponse(Response):
|
136
|
+
|
137
|
+
base_template = 'ui/list_update.html'
|
138
|
+
|
139
|
+
def __init__(
|
140
|
+
self, *,
|
141
|
+
instance: Model,
|
142
|
+
list_entry_template: str,
|
143
|
+
context: dict = None,
|
144
|
+
page: paginator.Page,
|
145
|
+
is_new: bool,
|
146
|
+
column_count: int = 1,
|
147
|
+
column_height: str = '150px',
|
148
|
+
):
|
149
|
+
self.page = page
|
150
|
+
super().__init__(template=self.base_template, context=context or {})
|
151
|
+
self.context.update({
|
152
|
+
'instance': instance,
|
153
|
+
'list_entry_template': list_entry_template,
|
154
|
+
'is_new': is_new,
|
155
|
+
'column_count': column_count,
|
156
|
+
'column_height': column_height
|
157
|
+
})
|
158
|
+
|
159
|
+
def render(self, request) -> str:
|
160
|
+
res = super().render(request)
|
161
|
+
if self.page:
|
162
|
+
pagination_update = OobResponse(
|
163
|
+
template='ui/layout.html#pagination',
|
164
|
+
swap='innerHTML:#pagination',
|
165
|
+
context=dict(page=self.page)
|
166
|
+
).render(request)
|
167
|
+
res += pagination_update
|
168
|
+
return res
|
169
|
+
|
170
|
+
|
171
|
+
class TableResponse(WindowResponse):
|
172
|
+
|
173
|
+
def __init__(
|
174
|
+
self, *,
|
175
|
+
title: str,
|
176
|
+
context: dict,
|
177
|
+
object_label: str,
|
178
|
+
fields: list[str],
|
179
|
+
footer: dict = None,
|
180
|
+
page: paginator.Page = None,
|
181
|
+
ui_filter: Filter = None,
|
182
|
+
endless_scroll: bool = True,
|
183
|
+
header_template: str = None,
|
184
|
+
panel_template: str = None,
|
185
|
+
overview_template: str = 'ui/table.html'
|
186
|
+
):
|
187
|
+
assert page is not None or ui_filter is not None, _(
|
188
|
+
'Argument page or ui_filter must be supplied'
|
189
|
+
)
|
190
|
+
self.ui_filter = ui_filter
|
191
|
+
self.overview_template = overview_template
|
192
|
+
super().__init__(
|
193
|
+
title=title,
|
194
|
+
context=context,
|
195
|
+
overview_template=self.overview_template,
|
196
|
+
header_template=header_template,
|
197
|
+
panel_template=panel_template
|
198
|
+
)
|
199
|
+
if ui_filter and not page:
|
200
|
+
page = ui_filter.get_page()
|
201
|
+
self.context.update({
|
202
|
+
'page': page,
|
203
|
+
'ui_filter': ui_filter,
|
204
|
+
'endless_scroll': endless_scroll,
|
205
|
+
'fields': fields,
|
206
|
+
'object_label': object_label,
|
207
|
+
'footer': footer
|
208
|
+
})
|
209
|
+
|
210
|
+
def _has_panel(self):
|
211
|
+
return bool(self.panel_template or self.ui_filter)
|
212
|
+
|
213
|
+
def response(self, request, extra_content: str = None, replace_body: bool = False) -> HttpResponse:
|
214
|
+
return super().response(request, extra_content, replace_body)
|
215
|
+
|
216
|
+
|
217
|
+
class TableRowResponse(Response):
|
218
|
+
|
219
|
+
base_template = 'ui/table_row_update.html'
|
220
|
+
|
221
|
+
def __init__(
|
222
|
+
self, *,
|
223
|
+
instance: Model,
|
224
|
+
fields: list[str],
|
225
|
+
footer: dict = None,
|
226
|
+
page: paginator.Page = None,
|
227
|
+
):
|
228
|
+
self.page = page
|
229
|
+
context = {
|
230
|
+
'instance': instance,
|
231
|
+
'fields': fields,
|
232
|
+
'footer': footer,
|
233
|
+
}
|
234
|
+
super().__init__(template=self.base_template, context=context)
|
235
|
+
|
236
|
+
def render(self, request) -> str:
|
237
|
+
res = super().render(request)
|
238
|
+
if self.page:
|
239
|
+
pagination_update = OobResponse(
|
240
|
+
template='ui/layout.html#pagination',
|
241
|
+
swap='innerHTML:#pagination',
|
242
|
+
context=dict(page=self.page)
|
243
|
+
).render(request)
|
244
|
+
res += pagination_update
|
245
|
+
return res
|
246
|
+
|
247
|
+
|
248
|
+
class DetailResponse(Response):
|
249
|
+
|
250
|
+
base_template = 'ui/detail.html'
|
251
|
+
|
252
|
+
def __init__(
|
253
|
+
self, *,
|
254
|
+
context: dict,
|
255
|
+
detail_header_template: str = None,
|
256
|
+
detail_data_template: str = None
|
257
|
+
):
|
258
|
+
super().__init__(template=self.base_template, context=context)
|
259
|
+
self.context.update({
|
260
|
+
'detail_header_template': detail_header_template,
|
261
|
+
'detail_data_template': detail_data_template
|
262
|
+
})
|
263
|
+
|
264
|
+
@staticmethod
|
265
|
+
def add_trigger(response):
|
266
|
+
add_trigger(response, 'activate-detail')
|
267
|
+
|
268
|
+
|
269
|
+
class ModalResponse(Response):
|
270
|
+
|
271
|
+
def __init__(
|
272
|
+
self, *,
|
273
|
+
modal_id: str,
|
274
|
+
template: str,
|
275
|
+
context: dict,
|
276
|
+
title: str = None,
|
277
|
+
is_update: bool = False,
|
278
|
+
is_blocking: bool = False,
|
279
|
+
modal_width: str = None
|
280
|
+
|
281
|
+
):
|
282
|
+
super().__init__(template=template, context=context)
|
283
|
+
self.context.update({
|
284
|
+
'title': title,
|
285
|
+
'modal_id': re.sub(r'[^A-Za-z-]+', '', modal_id).strip('-'),
|
286
|
+
'is_update': is_update,
|
287
|
+
'is_blocking': is_blocking,
|
288
|
+
'modal_width': modal_width
|
289
|
+
})
|
290
|
+
|
291
|
+
|
292
|
+
@dataclass
|
293
|
+
class ClientTrigger:
|
294
|
+
|
295
|
+
trigger: dict | str
|
296
|
+
header: str = 'HX-Trigger'
|
297
|
+
|
298
|
+
|
299
|
+
class TriggerResponse:
|
300
|
+
|
301
|
+
def __init__(self, trigger: list[ClientTrigger]):
|
302
|
+
self.trigger = trigger
|
303
|
+
|
304
|
+
def response(self):
|
305
|
+
res = HttpResponse()
|
306
|
+
res.headers['HX-Reswap'] = 'None'
|
307
|
+
for trigger in self.trigger:
|
308
|
+
add_trigger(res, trigger.trigger, trigger.header)
|
309
|
+
return res
|
310
|
+
|
311
|
+
|
312
|
+
# @dataclass(kw_only=True)
|
313
|
+
# class Response:
|
314
|
+
#
|
315
|
+
# template: str
|
316
|
+
# context: dict = field(default_factory=dict)
|
317
|
+
#
|
318
|
+
# base_template = None
|
319
|
+
#
|
320
|
+
# def __post_init__(self):
|
321
|
+
# for key, value in self.context.items():
|
322
|
+
# setattr(self, key, value)
|
323
|
+
# if self.base_template is None:
|
324
|
+
# self.base_template = self.template
|
325
|
+
#
|
326
|
+
# def dict(self):
|
327
|
+
# return {
|
328
|
+
# attr: getattr(self, attr, None) for attr
|
329
|
+
# in filter(lambda x: not x.startswith('_'), self.__dict__)
|
330
|
+
# }
|
331
|
+
#
|
332
|
+
# @staticmethod
|
333
|
+
# def add_trigger(response):
|
334
|
+
# pass
|
335
|
+
#
|
336
|
+
# def render(self, request) -> str:
|
337
|
+
# return render_to_string(
|
338
|
+
# template_name=self.base_template, context=self.dict(), request=request
|
339
|
+
# )
|
340
|
+
#
|
341
|
+
# def response(self, request, extra_content: str = None) -> HttpResponse:
|
342
|
+
# extra_content = extra_content or ''
|
343
|
+
# res = HttpResponse(content=(
|
344
|
+
# self.render(request)
|
345
|
+
# + render_to_string('ui/message.html', self.dict(), request)
|
346
|
+
# + extra_content
|
347
|
+
# ))
|
348
|
+
# self.add_trigger(res)
|
349
|
+
# return res
|
350
|
+
#
|
351
|
+
#
|
352
|
+
# @dataclass(kw_only=True)
|
353
|
+
# class OobResponse(Response):
|
354
|
+
#
|
355
|
+
# swap: str
|
356
|
+
# tag: str = 'div'
|
357
|
+
# id: str = None
|
358
|
+
#
|
359
|
+
# base_template = 'ui/oob.html'
|
360
|
+
#
|
361
|
+
# def dict(self) -> dict:
|
362
|
+
# attributes = filter(
|
363
|
+
# lambda x: not x.startswith('_') and x not in ['template', 'swap', 'tag'],
|
364
|
+
# self.__dict__
|
365
|
+
# )
|
366
|
+
# res = {attr: getattr(self, attr, None) for attr in attributes}
|
367
|
+
# res.update({'oob': {
|
368
|
+
# 'template': self.template,
|
369
|
+
# 'id': self.id,
|
370
|
+
# 'swap': self.swap,
|
371
|
+
# 'tag': self.tag
|
372
|
+
# }})
|
373
|
+
# return res
|
374
|
+
#
|
375
|
+
# # def render(self, request) -> str:
|
376
|
+
# # return render_to_string(
|
377
|
+
# # template_name='ui/oob.html', context=self.dict(), request=request
|
378
|
+
# # )
|
379
|
+
#
|
380
|
+
#
|
381
|
+
# @dataclass(kw_only=True)
|
382
|
+
# class WindowResponse(Response):
|
383
|
+
#
|
384
|
+
# title: str = ''
|
385
|
+
# panel_template: str = None
|
386
|
+
# overview_header_template: str = None
|
387
|
+
# overview_data_template: str = None
|
388
|
+
#
|
389
|
+
# base_template = 'ui/layout.html'
|
390
|
+
#
|
391
|
+
# def __post_init__(self):
|
392
|
+
# if 'has_panel' not in self.context.keys():
|
393
|
+
# self.context.update(has_panel=self._has_panel())
|
394
|
+
# super().__post_init__()
|
395
|
+
# if self.template:
|
396
|
+
# self.base_template = self.template
|
397
|
+
#
|
398
|
+
# def _has_panel(self) -> bool:
|
399
|
+
# return bool(self.panel_template)
|
400
|
+
#
|
401
|
+
#
|
402
|
+
# @dataclass(kw_only=True)
|
403
|
+
# class PageResponse(WindowResponse):
|
404
|
+
#
|
405
|
+
# page: paginator.Page = None
|
406
|
+
# ui_filter: Filter = None
|
407
|
+
# endless_scroll: bool = True
|
408
|
+
#
|
409
|
+
# def __post_init__(self):
|
410
|
+
# assert self.page is not None or self.ui_filter is not None, _(
|
411
|
+
# 'Argument page or ui_filter must be supplied'
|
412
|
+
# )
|
413
|
+
# if self.ui_filter and not self.page:
|
414
|
+
# self.page = self.ui_filter.get_page()
|
415
|
+
# super().__post_init__()
|
416
|
+
#
|
417
|
+
# def _has_panel(self) -> bool:
|
418
|
+
# return bool(self.panel_template or self.ui_filter)
|
419
|
+
#
|
420
|
+
#
|
421
|
+
# @dataclass(kw_only=True)
|
422
|
+
# class ListResponse(PageResponse):
|
423
|
+
#
|
424
|
+
# list_entry_template: str = None
|
425
|
+
# column_count: int = 1
|
426
|
+
# column_height: str = '150px'
|
427
|
+
# overview_data_template:str = 'ui/list.html'
|
428
|
+
#
|
429
|
+
#
|
430
|
+
# @dataclass(kw_only=True)
|
431
|
+
# class ListEntryResponse(Response):
|
432
|
+
#
|
433
|
+
# object: Model
|
434
|
+
# list_entry_template: str
|
435
|
+
# page: paginator.Page = None
|
436
|
+
# new_entry: bool = False
|
437
|
+
# template: str = 'ui/list_update.html'
|
438
|
+
#
|
439
|
+
# def render(self, request) -> str:
|
440
|
+
# res = super().render(request)
|
441
|
+
# if self.page:
|
442
|
+
# pagination_update = OobResponse(
|
443
|
+
# template='ui/layout.html#pagination',
|
444
|
+
# swap='innerHTML:#pagination',
|
445
|
+
# context=dict(page=self.page)
|
446
|
+
# ).render(request)
|
447
|
+
# res += pagination_update
|
448
|
+
# return res
|
449
|
+
#
|
450
|
+
#
|
451
|
+
# @dataclass(kw_only=True)
|
452
|
+
# class TableResponse(PageResponse):
|
453
|
+
#
|
454
|
+
# object_label: str
|
455
|
+
# fields: list[str]
|
456
|
+
# footer: dict = field(default_factory=dict)
|
457
|
+
# template = 'ui/table.html'
|
458
|
+
#
|
459
|
+
#
|
460
|
+
# @dataclass(kw_only=True)
|
461
|
+
# class TableRowResponse(Response):
|
462
|
+
#
|
463
|
+
# object: Model
|
464
|
+
# fields: list[str]
|
465
|
+
# footer: dict = field(default_factory=dict)
|
466
|
+
# page: paginator.Page = None
|
467
|
+
#
|
468
|
+
# base_template = 'ui/table_row_update.html'
|
469
|
+
#
|
470
|
+
#
|
471
|
+
# @dataclass(kw_only=True)
|
472
|
+
# class DetailResponse(Response):
|
473
|
+
#
|
474
|
+
# detail_data_template: str = None
|
475
|
+
# detail_header_template: str = None
|
476
|
+
#
|
477
|
+
# base_template = 'ui/detail.html'
|
478
|
+
#
|
479
|
+
# @staticmethod
|
480
|
+
# def add_trigger(response):
|
481
|
+
# add_trigger(response, 'activate-detail')
|
482
|
+
#
|
483
|
+
#
|
484
|
+
# @dataclass(kw_only=True)
|
485
|
+
# class ModalResponse(Response):
|
486
|
+
#
|
487
|
+
# title: str
|
488
|
+
# modal_id: str
|
489
|
+
# template: str
|
490
|
+
# modal_update: bool = False
|
491
|
+
# blocking: bool = False
|
492
|
+
# modal_width: str = None
|
493
|
+
#
|
494
|
+
# def __post_init__(self):
|
495
|
+
# super().__post_init__()
|
496
|
+
# self.modal_id = re.sub(r'[^A-Za-z-]+', '', self.modal_id).strip('-')
|
497
|
+
|
498
|
+
|
499
|
+
def search_select_response(queryset) -> HttpResponse:
|
500
|
+
return HttpResponse(render_to_string(
|
501
|
+
'ui/widgets/model_search_select_options.html',
|
502
|
+
{'options': queryset}
|
503
|
+
))
|
504
|
+
|
505
|
+
|
506
|
+
def message_response(request, persistent: bool = False):
|
507
|
+
return HttpResponse(content=(render_to_string(
|
508
|
+
'ui/message.html', context={'persistent': persistent}, request=request
|
509
|
+
)))
|
510
|
+
|
511
|
+
|
512
|
+
def add_trigger(
|
513
|
+
response: HttpResponse,
|
514
|
+
trigger: dict | str,
|
515
|
+
header: str = 'HX-Trigger'
|
516
|
+
) -> HttpResponse:
|
517
|
+
if isinstance(trigger, str):
|
518
|
+
trigger = {trigger: ''}
|
519
|
+
res_trigger = response.headers.get(header)
|
520
|
+
if not res_trigger:
|
521
|
+
response.headers[header] = json.dumps(trigger)
|
522
|
+
return response
|
523
|
+
try:
|
524
|
+
res_trigger = ast.literal_eval(response.headers.get(header, '{}'))
|
525
|
+
except SyntaxError:
|
526
|
+
res_trigger = {response.headers[header]: ''}
|
527
|
+
res_trigger.update(trigger)
|
528
|
+
response.headers[header] = json.dumps(res_trigger)
|
529
|
+
return response
|
530
|
+
|
531
|
+
|
532
|
+
def update(request, ui_responses: list[Response]) -> HttpResponse:
|
533
|
+
response = HttpResponse()
|
534
|
+
content = ''
|
535
|
+
for res in ui_responses:
|
536
|
+
content += res.render(request)
|
537
|
+
res.add_trigger(response)
|
538
|
+
content += render_to_string('ui/message.html', request=request)
|
539
|
+
response.content = content
|
540
|
+
return response
|