lino 25.5.1__py3-none-any.whl → 25.5.3__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.
- lino/__init__.py +1 -1
- lino/api/dd.py +5 -3
- lino/api/doctest.py +2 -2
- lino/core/__init__.py +3 -3
- lino/core/actions.py +60 -582
- lino/core/actors.py +66 -32
- lino/core/atomizer.py +355 -0
- lino/core/boundaction.py +8 -4
- lino/core/constants.py +3 -1
- lino/core/dbtables.py +4 -3
- lino/core/elems.py +33 -21
- lino/core/fields.py +40 -210
- lino/core/kernel.py +18 -13
- lino/core/layouts.py +30 -57
- lino/core/model.py +6 -4
- lino/core/permissions.py +18 -0
- lino/core/renderer.py +5 -1
- lino/core/requests.py +13 -7
- lino/core/signals.py +1 -1
- lino/core/site.py +7 -8
- lino/core/store.py +13 -156
- lino/core/tables.py +10 -1
- lino/core/utils.py +124 -1
- lino/locale/bn/LC_MESSAGES/django.po +1034 -868
- lino/locale/de/LC_MESSAGES/django.mo +0 -0
- lino/locale/de/LC_MESSAGES/django.po +996 -892
- lino/locale/django.pot +968 -869
- lino/locale/es/LC_MESSAGES/django.po +1032 -869
- lino/locale/et/LC_MESSAGES/django.po +1032 -866
- lino/locale/fr/LC_MESSAGES/django.po +1034 -866
- lino/locale/nl/LC_MESSAGES/django.po +1040 -868
- lino/locale/pt_BR/LC_MESSAGES/django.po +1029 -868
- lino/locale/zh_Hant/LC_MESSAGES/django.po +1029 -868
- lino/mixins/duplicable.py +8 -2
- lino/mixins/registrable.py +1 -1
- lino/modlib/changes/utils.py +4 -3
- lino/modlib/export_excel/models.py +7 -3
- lino/modlib/extjs/ext_renderer.py +1 -1
- lino/modlib/extjs/views.py +5 -0
- lino/modlib/linod/mixins.py +10 -11
- lino/modlib/memo/mixins.py +1 -3
- lino/modlib/summaries/__init__.py +2 -2
- lino/modlib/uploads/ui.py +6 -8
- lino/modlib/users/fixtures/demo_users.py +16 -13
- lino/utils/choosers.py +11 -1
- lino/utils/diag.py +6 -4
- lino/utils/fieldutils.py +14 -11
- lino/utils/instantiator.py +4 -2
- lino/utils/report.py +5 -3
- {lino-25.5.1.dist-info → lino-25.5.3.dist-info}/METADATA +1 -1
- {lino-25.5.1.dist-info → lino-25.5.3.dist-info}/RECORD +54 -53
- {lino-25.5.1.dist-info → lino-25.5.3.dist-info}/WHEEL +0 -0
- {lino-25.5.1.dist-info → lino-25.5.3.dist-info}/licenses/AUTHORS.rst +0 -0
- {lino-25.5.1.dist-info → lino-25.5.3.dist-info}/licenses/COPYING +0 -0
lino/core/layouts.py
CHANGED
@@ -14,8 +14,11 @@ from django.db.models.fields.related import ForeignObject
|
|
14
14
|
# from django.contrib.contenttypes.fields import GenericRelation
|
15
15
|
|
16
16
|
from lino.core import constants
|
17
|
-
from lino.core
|
17
|
+
from lino.core import atomizer
|
18
|
+
from lino.core import fields
|
19
|
+
from lino.core import store
|
18
20
|
from lino.core.plugin import Plugin
|
21
|
+
from lino.core.utils import Panel
|
19
22
|
from lino.modlib.users.utils import get_user_profile
|
20
23
|
|
21
24
|
|
@@ -36,46 +39,16 @@ def DEBUG_LAYOUTS(lo):
|
|
36
39
|
return False
|
37
40
|
|
38
41
|
|
39
|
-
class DummyPanel
|
42
|
+
class DummyPanel:
|
40
43
|
"""
|
41
|
-
A layout panel
|
44
|
+
A layout panel that does not exist in the current configuration
|
42
45
|
but might exist as a real panel in some other configuration.
|
43
46
|
"""
|
44
47
|
|
45
48
|
pass
|
46
49
|
|
47
50
|
|
48
|
-
class
|
49
|
-
"""
|
50
|
-
To be used when a panel cannot be expressed using a simple
|
51
|
-
template string because it requires one or more options. These
|
52
|
-
`options` parameters can be:
|
53
|
-
|
54
|
-
- label
|
55
|
-
- required_roles
|
56
|
-
- window_size
|
57
|
-
- label_align
|
58
|
-
|
59
|
-
Unlike a :class:`BaseLayout` it cannot have any child panels
|
60
|
-
and cannot become a tabbed panel.
|
61
|
-
"""
|
62
|
-
|
63
|
-
def __init__(self, desc, label=None, **options):
|
64
|
-
# assert not 'required' in options
|
65
|
-
self.desc = desc
|
66
|
-
if label is not None:
|
67
|
-
options.update(label=label)
|
68
|
-
self.options = options
|
69
|
-
|
70
|
-
def replace(self, *args, **kw):
|
71
|
-
"""
|
72
|
-
Calls the standard :meth:`string.replace`
|
73
|
-
method on this Panel's template.
|
74
|
-
"""
|
75
|
-
self.desc = self.desc.replace(*args, **kw)
|
76
|
-
|
77
|
-
|
78
|
-
class LayoutHandle(object):
|
51
|
+
class LayoutHandle:
|
79
52
|
"""
|
80
53
|
A layout handle analyzes a layout (an instance of :class:`BaseLayout` or a some
|
81
54
|
subclass thereof) and holds the resulting metadata, especially the layout
|
@@ -109,8 +82,8 @@ class LayoutHandle(object):
|
|
109
82
|
# main = layout.main_m or layout.main
|
110
83
|
# else:
|
111
84
|
# main = layout.main
|
112
|
-
|
113
|
-
self.define_panel("main", main)
|
85
|
+
|
86
|
+
self.define_panel("main", layout.main)
|
114
87
|
|
115
88
|
self.main = self._names.get("main")
|
116
89
|
if self.main is None:
|
@@ -120,8 +93,6 @@ class LayoutHandle(object):
|
|
120
93
|
|
121
94
|
self.width = self.main.width
|
122
95
|
self.height = self.main.height
|
123
|
-
|
124
|
-
self.layout.setup_handle(self)
|
125
96
|
for k, v in self.layout._labels.items():
|
126
97
|
if k not in self._names:
|
127
98
|
raise Exception(
|
@@ -182,8 +153,8 @@ class LayoutHandle(object):
|
|
182
153
|
raise Exception("Invalid remote wildcard %s" % rw)
|
183
154
|
rwmodel = fld.remote_field.model
|
184
155
|
rwnames = []
|
185
|
-
for de in wildcard_data_elems(rwmodel):
|
186
|
-
if isinstance(de, (VirtualField, ForeignObject)):
|
156
|
+
for de in fields.wildcard_data_elems(rwmodel):
|
157
|
+
if isinstance(de, (fields.VirtualField, ForeignObject)):
|
187
158
|
continue
|
188
159
|
if self.use_as_wildcard(de):
|
189
160
|
k = rwprefix + de.name
|
@@ -200,7 +171,7 @@ class LayoutHandle(object):
|
|
200
171
|
if de.name not in explicit_specs:
|
201
172
|
if self.use_as_wildcard(de):
|
202
173
|
wildcard_names.append(de.name)
|
203
|
-
if len(explicit_specs) or isinstance(de, VirtualField):
|
174
|
+
if len(explicit_specs) or isinstance(de, fields.VirtualField):
|
204
175
|
self.hidden_elements.add(de.name)
|
205
176
|
wildcard_str = self.layout.join_str.join(wildcard_names)
|
206
177
|
desc = desc.replace("*", wildcard_str)
|
@@ -387,7 +358,7 @@ class LayoutHandle(object):
|
|
387
358
|
)
|
388
359
|
|
389
360
|
|
390
|
-
class BaseLayout
|
361
|
+
class BaseLayout:
|
391
362
|
"""
|
392
363
|
Base class for all Layouts (:class:`FormLayout`, :class:`ColumnsLayout`
|
393
364
|
and :class:`ParamsLayout`).
|
@@ -482,7 +453,8 @@ class BaseLayout(object):
|
|
482
453
|
self._datasource = ds
|
483
454
|
if ds is not None:
|
484
455
|
if isinstance(self.hidden_elements, str):
|
485
|
-
self.hidden_elements = set(
|
456
|
+
self.hidden_elements = set(
|
457
|
+
atomizer.fields_list(ds, self.hidden_elements))
|
486
458
|
self.hidden_elements |= ds.hidden_elements
|
487
459
|
# self.editable = not ds.hide_editing(user_type)
|
488
460
|
# ~ if str(ds).endswith('Partners'):
|
@@ -515,9 +487,6 @@ class BaseLayout(object):
|
|
515
487
|
for name in args:
|
516
488
|
self.main = self.main.replace(name, "")
|
517
489
|
|
518
|
-
def setup_handle(self, lh):
|
519
|
-
pass
|
520
|
-
|
521
490
|
def setup_element(self, lh, e):
|
522
491
|
pass
|
523
492
|
|
@@ -626,7 +595,7 @@ add_tabpanel() on %s horizontal 'main' panel %r."""
|
|
626
595
|
# ~ if kw:
|
627
596
|
# ~ print 20120525, self, self.detail_layout._element_options
|
628
597
|
|
629
|
-
def get_layout_handle(self, front_end=None):
|
598
|
+
def get_layout_handle(self): # , front_end=None):
|
630
599
|
"""
|
631
600
|
Return the LayoutHandle for this layout.
|
632
601
|
Create a LayoutHandle instance if this is the first call.
|
@@ -638,11 +607,11 @@ add_tabpanel() on %s horizontal 'main' panel %r."""
|
|
638
607
|
changed my mind.
|
639
608
|
|
640
609
|
"""
|
641
|
-
if front_end is None:
|
642
|
-
|
610
|
+
# if front_end is None:
|
611
|
+
front_end = settings.SITE.kernel.editing_front_end
|
643
612
|
hname = front_end.ui_handle_attr_name
|
644
613
|
if hname is None:
|
645
|
-
raise Exception("{
|
614
|
+
raise Exception(f"{front_end} has no `ui_handle_attr_name`!")
|
646
615
|
|
647
616
|
# we do not want any inherited handle
|
648
617
|
h = self.__dict__.get(hname, None)
|
@@ -800,7 +769,7 @@ class ParamsLayout(BaseLayout):
|
|
800
769
|
|
801
770
|
join_str = " "
|
802
771
|
url_param_name = constants.URL_PARAM_PARAM_VALUES
|
803
|
-
|
772
|
+
_params_store = None
|
804
773
|
|
805
774
|
def get_data_elem(self, name):
|
806
775
|
de = self._datasource.get_param_elem(name)
|
@@ -813,12 +782,16 @@ class ParamsLayout(BaseLayout):
|
|
813
782
|
# print("20200425 ParamsLayout.get_data_elem()", name, de)
|
814
783
|
return de
|
815
784
|
|
816
|
-
|
817
|
-
|
818
|
-
|
819
|
-
|
820
|
-
|
821
|
-
|
785
|
+
@property
|
786
|
+
def params_store(self):
|
787
|
+
if self._params_store is None:
|
788
|
+
k = settings.SITE.kernel.editing_front_end.ui_handle_attr_name
|
789
|
+
lh = getattr(self, k, None)
|
790
|
+
if lh is None:
|
791
|
+
# raise Exception(f"20250523 {self} has no lh")
|
792
|
+
return None
|
793
|
+
self._params_store = store.ParameterStore(lh, self.url_param_name)
|
794
|
+
return self._params_store
|
822
795
|
|
823
796
|
|
824
797
|
class ActionParamsLayout(ParamsLayout):
|
lino/core/model.py
CHANGED
@@ -23,11 +23,13 @@ from lino.utils.html import E, forcetext, tostring, join_elems
|
|
23
23
|
from lino.utils.soup import sanitize
|
24
24
|
|
25
25
|
from lino.core import fields
|
26
|
+
from lino.core import atomizer
|
26
27
|
from lino.core import signals
|
27
28
|
from lino.core import actions
|
28
29
|
from lino.core import inject
|
29
30
|
|
30
|
-
from .
|
31
|
+
from lino.core.atomizer import make_remote_field
|
32
|
+
from .fields import RichTextField, displayfield
|
31
33
|
from .utils import error2str
|
32
34
|
from .utils import obj2str
|
33
35
|
from .diff import ChangeWatcher
|
@@ -246,8 +248,8 @@ class Model(models.Model, fields.TableRow):
|
|
246
248
|
def add_active_field(cls, names):
|
247
249
|
if isinstance(cls.active_fields, str):
|
248
250
|
cls.active_fields = frozenset(
|
249
|
-
|
250
|
-
cls.active_fields = cls.active_fields |
|
251
|
+
atomizer.fields_list(cls, cls.active_fields))
|
252
|
+
cls.active_fields = cls.active_fields | atomizer.fields_list(cls, names)
|
251
253
|
|
252
254
|
@classmethod
|
253
255
|
def hide_elements(self, *names):
|
@@ -491,7 +493,7 @@ class Model(models.Model, fields.TableRow):
|
|
491
493
|
self.delete()
|
492
494
|
|
493
495
|
def get_row_permission(self, ar, state, ba):
|
494
|
-
"""Returns True or False whether this database
|
496
|
+
"""Returns True or False whether this database row gives permission
|
495
497
|
to the ActionRequest `ar` to run the specified action.
|
496
498
|
|
497
499
|
"""
|
lino/core/permissions.py
CHANGED
@@ -12,6 +12,7 @@ from lino import logger
|
|
12
12
|
from django.conf import settings
|
13
13
|
|
14
14
|
from lino.core.utils import obj2str
|
15
|
+
from lino.modlib.users.utils import get_user_profile
|
15
16
|
|
16
17
|
from .roles import check_required_roles
|
17
18
|
from .exceptions import ChangedAPI
|
@@ -242,3 +243,20 @@ def make_permission_handler_(
|
|
242
243
|
return v
|
243
244
|
|
244
245
|
return allow
|
246
|
+
|
247
|
+
|
248
|
+
def get_view_permission(e):
|
249
|
+
if isinstance(e, Permittable) and not e.get_view_permission(get_user_profile()):
|
250
|
+
return False
|
251
|
+
# e.g. pcsw.ClientDetail has a tab "Other", visible only to system
|
252
|
+
# admins but the "Other" contains a GridElement RolesByPerson
|
253
|
+
# which is not per se reserved for system admins. js of normal
|
254
|
+
# users should not try to call on_master_changed() on it
|
255
|
+
parent = e.parent
|
256
|
+
while parent is not None:
|
257
|
+
if isinstance(parent, Permittable) and not parent.get_view_permission(
|
258
|
+
get_user_profile()
|
259
|
+
):
|
260
|
+
return False # bug 3 (bcss_summary) blog/2012/0927
|
261
|
+
parent = parent.parent
|
262
|
+
return True
|
lino/core/renderer.py
CHANGED
@@ -300,10 +300,14 @@ class HtmlRenderer(Renderer):
|
|
300
300
|
yield ar.actor.get_table_as_list(ar.master_instance, ar)
|
301
301
|
return
|
302
302
|
|
303
|
-
|
303
|
+
elif display_mode == constants.DISPLAY_MODE_STORY:
|
304
304
|
yield ar.actor.get_table_story(ar.master_instance, ar)
|
305
305
|
return
|
306
306
|
|
307
|
+
elif display_mode == constants.DISPLAY_MODE_TILES:
|
308
|
+
yield ar.actor.get_table_as_tiles(ar.master_instance, ar)
|
309
|
+
return
|
310
|
+
|
307
311
|
yield ar.table2xhtml(**kwargs)
|
308
312
|
|
309
313
|
# if show_toolbar:
|
lino/core/requests.py
CHANGED
@@ -50,7 +50,7 @@ from lino.core.utils import obj2unicode
|
|
50
50
|
from lino.core.utils import obj2str
|
51
51
|
from lino.core.utils import format_request
|
52
52
|
from lino.core.utils import PhantomRow
|
53
|
-
from lino.core.
|
53
|
+
from lino.core.atomizer import get_atomizer
|
54
54
|
|
55
55
|
# try:
|
56
56
|
# from django.contrib.contenttypes.models import ContentType
|
@@ -62,7 +62,7 @@ if settings.SITE.is_installed("contenttypes"):
|
|
62
62
|
else:
|
63
63
|
ContentType = None
|
64
64
|
|
65
|
-
from .fields import
|
65
|
+
from lino.core.fields import FakeField, TableRow, RemoteField
|
66
66
|
|
67
67
|
|
68
68
|
CATCHED_AJAX_EXCEPTIONS = (Warning, exceptions.ValidationError)
|
@@ -1668,7 +1668,7 @@ class BaseRequest:
|
|
1668
1668
|
def get_breadcrumbs(self, elem=None):
|
1669
1669
|
list_title = self.get_title()
|
1670
1670
|
# TODO: make it clickable so that we can return from detail to list view
|
1671
|
-
if elem is None:
|
1671
|
+
if elem is None or self.actor.default_record_id is not None:
|
1672
1672
|
return list_title
|
1673
1673
|
else:
|
1674
1674
|
# print("20190703", self.actor, self.actor.default_action)
|
@@ -1769,6 +1769,7 @@ class ActionRequest(BaseRequest):
|
|
1769
1769
|
_data_iterator = None
|
1770
1770
|
_sliced_data_iterator = None
|
1771
1771
|
_insert_sar = None
|
1772
|
+
_ah = None
|
1772
1773
|
|
1773
1774
|
def __init__(
|
1774
1775
|
self,
|
@@ -1782,13 +1783,17 @@ class ActionRequest(BaseRequest):
|
|
1782
1783
|
self.rqdata = rqdata
|
1783
1784
|
self.bound_action = action or actor.default_action
|
1784
1785
|
super().__init__(**kw)
|
1785
|
-
|
1786
|
-
|
1786
|
+
|
1787
|
+
@property
|
1788
|
+
def ah(self):
|
1789
|
+
if self._ah is None and not self.actor.is_abstract():
|
1790
|
+
self._ah = self.actor.get_request_handle(self)
|
1787
1791
|
# if self.ah.store is None:
|
1788
1792
|
# raise Exception("20240530 {} has no store!?".format(self.ah))
|
1793
|
+
return self._ah
|
1789
1794
|
|
1790
1795
|
def parse_req(self, request, rqdata, **kw):
|
1791
|
-
if
|
1796
|
+
if "selected_pks" not in kw and "selected_rows" not in kw:
|
1792
1797
|
selected = rqdata.getlist(constants.URL_PARAM_SELECTED)
|
1793
1798
|
kw.update(selected_pks=selected)
|
1794
1799
|
return super().parse_req(request, rqdata, **kw)
|
@@ -1897,6 +1902,7 @@ class ActionRequest(BaseRequest):
|
|
1897
1902
|
# msg = "20170116 selected_rows is {} for {!r}".format(
|
1898
1903
|
# self.selected_rows, action)
|
1899
1904
|
# raise Exception(msg)
|
1905
|
+
# print(f"20250523 {self.bound_action} {request}")
|
1900
1906
|
if request is not None:
|
1901
1907
|
apv.update(
|
1902
1908
|
action.params_layout.params_store.parse_params(request))
|
@@ -2203,7 +2209,7 @@ class ActionRequest(BaseRequest):
|
|
2203
2209
|
# print("20230331 712 get_status() {}".format(self.subst_user))
|
2204
2210
|
if self._status is not None and not kw:
|
2205
2211
|
return self._status
|
2206
|
-
if self.actor.parameters:
|
2212
|
+
if self.actor.parameters and self.actor.params_layout.params_store is not None:
|
2207
2213
|
pv = self.actor.params_layout.params_store.pv2dict(
|
2208
2214
|
self, self.param_values)
|
2209
2215
|
# print(f"20250121c {self}\n{self.actor.params_layout.params_store.__class__}\n{self.actor.params_layout.params_store.param_fields}")
|
lino/core/signals.py
CHANGED
lino/core/site.py
CHANGED
@@ -247,7 +247,7 @@ class Site(object):
|
|
247
247
|
|
248
248
|
legacy_data_path = None
|
249
249
|
"""
|
250
|
-
|
250
|
+
Deprecated. Was used by tim2lino and pp2lino to import data from some legacy
|
251
251
|
database.
|
252
252
|
|
253
253
|
"""
|
@@ -1369,17 +1369,16 @@ class Site(object):
|
|
1369
1369
|
|
1370
1370
|
def today(self, *args, **kwargs):
|
1371
1371
|
if self.site_config is None:
|
1372
|
-
base = self.the_demo_date
|
1372
|
+
base = self.the_demo_date
|
1373
1373
|
else:
|
1374
|
-
base =
|
1375
|
-
|
1376
|
-
|
1377
|
-
|
1378
|
-
)
|
1374
|
+
base = self.site_config.simulate_today or self.the_demo_date
|
1375
|
+
if base is None:
|
1376
|
+
# base = datetime.date.today()
|
1377
|
+
base = timezone.now().date()
|
1379
1378
|
return date_offset(base, *args, **kwargs)
|
1380
1379
|
|
1381
1380
|
def now(self, *args, **kwargs):
|
1382
|
-
t = self.today()
|
1381
|
+
t = self.today(*args, **kwargs)
|
1383
1382
|
now = timezone.now()
|
1384
1383
|
return now.replace(year=t.year, month=t.month, day=t.day)
|
1385
1384
|
|
lino/core/store.py
CHANGED
@@ -30,11 +30,11 @@ import datetime
|
|
30
30
|
|
31
31
|
from django.conf import settings
|
32
32
|
from django.db import models
|
33
|
-
from django.db.models.fields import Field
|
33
|
+
# from django.db.models.fields import Field
|
34
34
|
from django.core import exceptions
|
35
35
|
from django.utils.translation import gettext_lazy as _
|
36
36
|
from django.utils.encoding import force_str
|
37
|
-
from lino import AFTER17
|
37
|
+
# from lino import AFTER17
|
38
38
|
|
39
39
|
from lino.utils.jsgen import py2js
|
40
40
|
from lino.utils.quantities import parse_decimal
|
@@ -42,11 +42,12 @@ from lino.core.utils import getrqdata
|
|
42
42
|
from lino.core.gfks import GenericForeignKey
|
43
43
|
from lino.core import constants
|
44
44
|
from lino.core import fields
|
45
|
-
from lino.core import actors
|
45
|
+
# from lino.core import actors
|
46
46
|
from lino.core import actions
|
47
47
|
from lino.core import frames
|
48
|
+
from lino.core import atomizer
|
48
49
|
from lino.core.utils import PhantomRow
|
49
|
-
from lino.utils import choosers
|
50
|
+
# from lino.utils import choosers
|
50
51
|
from lino.utils import curry
|
51
52
|
from lino.utils import iif
|
52
53
|
from lino.utils.format_date import fds
|
@@ -291,8 +292,12 @@ class ComboStoreField(StoreField):
|
|
291
292
|
# v = self.full_value_from_object(None,obj)
|
292
293
|
if v is None or v == "":
|
293
294
|
return (None, None)
|
295
|
+
# print("20250518", obj, obj.__class__, ar.actor, self.field.name)
|
294
296
|
if obj is not None:
|
295
|
-
ch = obj.__class__.get_chooser_for_field(self.field.name)
|
297
|
+
# ch = obj.__class__.get_chooser_for_field(self.field.name)
|
298
|
+
# if ch is not None:
|
299
|
+
# return (v, ch.get_text_for_value(v, obj))
|
300
|
+
ch = ar.actor.get_chooser_for_field(self.field.name)
|
296
301
|
if ch is not None:
|
297
302
|
return (v, ch.get_text_for_value(v, obj))
|
298
303
|
for i in self.field.choices:
|
@@ -925,154 +930,6 @@ class OneToOneRelStoreField(StoreField):
|
|
925
930
|
return None
|
926
931
|
|
927
932
|
|
928
|
-
def get_atomizer(holder, fld, name):
|
929
|
-
"""
|
930
|
-
Return the :term:`atomizer` for this database field.
|
931
|
-
|
932
|
-
An atomizer is an instance of a subclass of :class:`StoreField`.
|
933
|
-
|
934
|
-
"""
|
935
|
-
sf = getattr(fld, "_lino_atomizer", None)
|
936
|
-
if sf is None:
|
937
|
-
sf = create_atomizer(holder, fld, name)
|
938
|
-
if sf is None:
|
939
|
-
# print("20210623 {} on {} has no StoreField".format(name, holder))
|
940
|
-
return
|
941
|
-
assert isinstance(sf, StoreField)
|
942
|
-
# if not isinstance(sf, StoreField):
|
943
|
-
# raise Exception("{} is not a StoreField".format(sf))
|
944
|
-
if isinstance(fld, type):
|
945
|
-
raise Exception("20240913 trying to set class attribute")
|
946
|
-
setattr(fld, "_lino_atomizer", sf)
|
947
|
-
return sf
|
948
|
-
|
949
|
-
|
950
|
-
def create_atomizer(holder, fld, name):
|
951
|
-
"""
|
952
|
-
The holder is where the (potential) choices come from. It can be
|
953
|
-
a model, an actor or an action. `fld` is a data element.
|
954
|
-
"""
|
955
|
-
if name is None:
|
956
|
-
# print("20181023 create_atomizer() no name {}".format(fld))
|
957
|
-
return
|
958
|
-
# raise Exception("20181023 create_atomizer() {}".format(fld))
|
959
|
-
if isinstance(fld, fields.RemoteField):
|
960
|
-
"""
|
961
|
-
Hack: we create a StoreField based on the remote field,
|
962
|
-
then modify some of its behaviour.
|
963
|
-
"""
|
964
|
-
sf = create_atomizer(holder, fld.field, fld.name)
|
965
|
-
|
966
|
-
# print("20180712 create_atomizer {} from {}".format(sf, fld.field))
|
967
|
-
|
968
|
-
def value_from_object(unused, obj, ar=None):
|
969
|
-
return fld.func(obj, ar)
|
970
|
-
|
971
|
-
def full_value_from_object(unused, obj, ar=None):
|
972
|
-
return fld.func(obj, ar)
|
973
|
-
|
974
|
-
def set_value_in_object(sf, ar, instance, v):
|
975
|
-
# print("20180712 {}.set_value_in_object({}, {})".format(
|
976
|
-
# sf, instance, v))
|
977
|
-
old_value = sf.value_from_object(instance, ar.request)
|
978
|
-
if old_value != v:
|
979
|
-
return fld.setter(instance, v)
|
980
|
-
|
981
|
-
sf.value_from_object = curry(value_from_object, sf)
|
982
|
-
sf.full_value_from_object = curry(full_value_from_object, sf)
|
983
|
-
sf.set_value_in_object = curry(set_value_in_object, sf)
|
984
|
-
return sf
|
985
|
-
|
986
|
-
meth = getattr(fld, "_return_type_for_method", None)
|
987
|
-
if meth is not None:
|
988
|
-
# uh, this is tricky...
|
989
|
-
return MethodStoreField(fld, name)
|
990
|
-
|
991
|
-
# sf_class = getattr(fld, 'lino_atomizer_class', None)
|
992
|
-
# if sf_class is not None:
|
993
|
-
# return sf_class(fld, name)
|
994
|
-
|
995
|
-
if isinstance(fld, fields.DummyField):
|
996
|
-
return None
|
997
|
-
if isinstance(fld, fields.RequestField):
|
998
|
-
delegate = create_atomizer(holder, fld.return_type, fld.name)
|
999
|
-
return RequestStoreField(fld, delegate, name)
|
1000
|
-
if isinstance(fld, type) and issubclass(fld, actors.Actor):
|
1001
|
-
return
|
1002
|
-
# if isinstance(fld, type) and issubclass(fld, actors.Actor):
|
1003
|
-
# raise Exception("20230219")
|
1004
|
-
# # 20210618 & 20230218
|
1005
|
-
# if settings.SITE.kernel.default_ui.support_async:
|
1006
|
-
# return SlaveTableStoreField(fld, name)
|
1007
|
-
# return DisplayStoreField(fld, name)
|
1008
|
-
|
1009
|
-
if isinstance(fld, fields.VirtualField):
|
1010
|
-
delegate = create_atomizer(holder, fld.return_type, fld.name)
|
1011
|
-
if delegate is None:
|
1012
|
-
# e.g. VirtualField with DummyField as return_type
|
1013
|
-
return None
|
1014
|
-
# raise Exception("No atomizer for %s %s %s" % (
|
1015
|
-
# holder, fld.return_type, fld.name))
|
1016
|
-
return VirtStoreField(fld, delegate, name)
|
1017
|
-
if isinstance(fld, models.FileField):
|
1018
|
-
return FileFieldStoreField(fld, name)
|
1019
|
-
if isinstance(fld, models.ManyToManyField):
|
1020
|
-
return StoreField(fld, name)
|
1021
|
-
if isinstance(fld, fields.PasswordField):
|
1022
|
-
return PasswordStoreField(fld, name)
|
1023
|
-
if isinstance(fld, models.OneToOneField):
|
1024
|
-
return OneToOneStoreField(fld, name)
|
1025
|
-
if isinstance(fld, models.OneToOneRel):
|
1026
|
-
return OneToOneRelStoreField(fld, name)
|
1027
|
-
|
1028
|
-
if settings.SITE.is_installed("contenttypes"):
|
1029
|
-
from lino.core.gfks import GenericForeignKey, GenericRel
|
1030
|
-
from lino.modlib.gfks.fields import GenericForeignKeyIdField
|
1031
|
-
|
1032
|
-
if isinstance(fld, GenericForeignKey):
|
1033
|
-
return GenericForeignKeyField(fld, name)
|
1034
|
-
if isinstance(fld, GenericRel):
|
1035
|
-
return GenericRelField(fld, name)
|
1036
|
-
if isinstance(fld, GenericForeignKeyIdField):
|
1037
|
-
return ComboStoreField(fld, name)
|
1038
|
-
|
1039
|
-
if isinstance(fld, models.ForeignKey):
|
1040
|
-
return ForeignKeyStoreField(fld, name)
|
1041
|
-
if isinstance(fld, models.TimeField):
|
1042
|
-
return TimeStoreField(fld, name)
|
1043
|
-
if isinstance(fld, models.DateTimeField):
|
1044
|
-
return DateTimeStoreField(fld, name)
|
1045
|
-
if isinstance(fld, fields.IncompleteDateField):
|
1046
|
-
return IncompleteDateStoreField(fld, name)
|
1047
|
-
if isinstance(fld, models.DateField):
|
1048
|
-
return DateStoreField(fld, name)
|
1049
|
-
if isinstance(fld, models.BooleanField):
|
1050
|
-
return BooleanStoreField(fld, name)
|
1051
|
-
if isinstance(fld, models.DecimalField):
|
1052
|
-
return DecimalStoreField(fld, name)
|
1053
|
-
if isinstance(fld, models.AutoField):
|
1054
|
-
return AutoStoreField(fld, name)
|
1055
|
-
# kw.update(type='int')
|
1056
|
-
if isinstance(fld, models.SmallIntegerField):
|
1057
|
-
return IntegerStoreField(fld, name)
|
1058
|
-
if isinstance(fld, fields.DisplayField):
|
1059
|
-
return DisplayStoreField(fld, name)
|
1060
|
-
if isinstance(fld, models.IntegerField):
|
1061
|
-
return IntegerStoreField(fld, name)
|
1062
|
-
if isinstance(fld, fields.PreviewTextField):
|
1063
|
-
return PreviewTextStoreField(fld, name)
|
1064
|
-
if isinstance(fld, models.ManyToOneRel):
|
1065
|
-
# raise Exception("20190625 {} {} {}".format(holder, fld, name))
|
1066
|
-
return
|
1067
|
-
if (sft := FIELD_TYPES.get(fld.__class__, None)) is not None:
|
1068
|
-
return sft(fld, name)
|
1069
|
-
kw = {}
|
1070
|
-
if choosers.uses_simple_values(holder, fld):
|
1071
|
-
return StoreField(fld, name, **kw)
|
1072
|
-
else:
|
1073
|
-
return ComboStoreField(fld, name, **kw)
|
1074
|
-
|
1075
|
-
|
1076
933
|
class BaseStore(object):
|
1077
934
|
pass
|
1078
935
|
|
@@ -1086,7 +943,7 @@ class ParameterStore(BaseStore):
|
|
1086
943
|
# print(f"20250121e {holder}")
|
1087
944
|
# debug = False
|
1088
945
|
for pf in params_layout_handle._data_elems:
|
1089
|
-
sf = create_atomizer(holder, pf, pf.name)
|
946
|
+
sf = atomizer.create_atomizer(holder, pf, pf.name)
|
1090
947
|
if sf is not None:
|
1091
948
|
# if "__" in pf.name:
|
1092
949
|
# print("20200423 ParameterStore", pf.name, sf)
|
@@ -1221,7 +1078,7 @@ class Store(BaseStore):
|
|
1221
1078
|
form = rh.actor.insert_layout
|
1222
1079
|
if form:
|
1223
1080
|
if isinstance(form, str):
|
1224
|
-
raise Exception(f"20250306 insert_layout {repr(rh.actor)}")
|
1081
|
+
raise Exception(f"20250306 insert_layout {repr(rh.actor)} is a str!")
|
1225
1082
|
dh = form.get_layout_handle()
|
1226
1083
|
self.collect_fields(self.detail_fields, dh)
|
1227
1084
|
|
@@ -1305,7 +1162,7 @@ class Store(BaseStore):
|
|
1305
1162
|
self.add_field_for(fields, self.pk)
|
1306
1163
|
|
1307
1164
|
def add_field_for(self, field_list, df):
|
1308
|
-
sf = get_atomizer(self.actor, df, df.name)
|
1165
|
+
sf = atomizer.get_atomizer(self.actor, df, df.name)
|
1309
1166
|
# if df.name == 'humanlinks_LinksByHuman':
|
1310
1167
|
# raise Exception("20181023 {} ({}) {}".format(
|
1311
1168
|
# self, df, sf))
|
lino/core/tables.py
CHANGED
@@ -16,10 +16,11 @@ from django.utils.translation import gettext_lazy as _
|
|
16
16
|
# from django.core.exceptions import BadRequest
|
17
17
|
|
18
18
|
from lino import logger
|
19
|
+
from lino.core import constants
|
19
20
|
from lino.core import actors
|
20
21
|
from lino.core import actions
|
21
22
|
from lino.core import fields
|
22
|
-
from lino.core import
|
23
|
+
from lino.core import layouts
|
23
24
|
from lino.core.utils import resolve_fields_list
|
24
25
|
|
25
26
|
# class InvalidRequest(Exception):
|
@@ -159,6 +160,8 @@ class AbstractTable(actors.Actor):
|
|
159
160
|
abstract = True
|
160
161
|
|
161
162
|
_handle_class = TableHandle
|
163
|
+
_detail_action_class = actions.ShowDetail
|
164
|
+
_params_layout_class = layouts.ParamsLayout
|
162
165
|
|
163
166
|
hide_zero_rows = False
|
164
167
|
"""Set this to `True` if you want to remove rows which contain no
|
@@ -312,6 +315,12 @@ class AbstractTable(actors.Actor):
|
|
312
315
|
Usually such a warning means that there is something wrong.
|
313
316
|
"""
|
314
317
|
|
318
|
+
table_as_calendar = False
|
319
|
+
"""Whether the primereact Datatable is used to display a calendar view.
|
320
|
+
|
321
|
+
Used only in :term:`React front end`.
|
322
|
+
"""
|
323
|
+
|
315
324
|
default_display_modes = {
|
316
325
|
70: constants.DISPLAY_MODE_SUMMARY,
|
317
326
|
None: constants.DISPLAY_MODE_GRID
|