lino 25.5.2__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.
Files changed (51) hide show
  1. lino/__init__.py +1 -1
  2. lino/api/dd.py +5 -3
  3. lino/api/doctest.py +2 -2
  4. lino/core/__init__.py +3 -3
  5. lino/core/actions.py +60 -582
  6. lino/core/actors.py +66 -32
  7. lino/core/atomizer.py +355 -0
  8. lino/core/boundaction.py +8 -4
  9. lino/core/constants.py +3 -1
  10. lino/core/dbtables.py +4 -3
  11. lino/core/elems.py +33 -21
  12. lino/core/fields.py +40 -210
  13. lino/core/kernel.py +18 -13
  14. lino/core/layouts.py +30 -57
  15. lino/core/model.py +6 -4
  16. lino/core/permissions.py +18 -0
  17. lino/core/renderer.py +5 -1
  18. lino/core/requests.py +13 -7
  19. lino/core/signals.py +1 -1
  20. lino/core/site.py +1 -1
  21. lino/core/store.py +13 -156
  22. lino/core/tables.py +5 -2
  23. lino/core/utils.py +124 -1
  24. lino/locale/bn/LC_MESSAGES/django.po +1034 -868
  25. lino/locale/de/LC_MESSAGES/django.mo +0 -0
  26. lino/locale/de/LC_MESSAGES/django.po +996 -892
  27. lino/locale/django.pot +968 -869
  28. lino/locale/es/LC_MESSAGES/django.po +1032 -869
  29. lino/locale/et/LC_MESSAGES/django.po +1032 -866
  30. lino/locale/fr/LC_MESSAGES/django.po +1034 -866
  31. lino/locale/nl/LC_MESSAGES/django.po +1040 -868
  32. lino/locale/pt_BR/LC_MESSAGES/django.po +1029 -868
  33. lino/locale/zh_Hant/LC_MESSAGES/django.po +1029 -868
  34. lino/mixins/duplicable.py +8 -2
  35. lino/mixins/registrable.py +1 -1
  36. lino/modlib/changes/utils.py +4 -3
  37. lino/modlib/extjs/ext_renderer.py +1 -1
  38. lino/modlib/extjs/views.py +5 -0
  39. lino/modlib/memo/mixins.py +1 -3
  40. lino/modlib/uploads/ui.py +6 -8
  41. lino/modlib/users/fixtures/demo_users.py +16 -13
  42. lino/utils/choosers.py +11 -1
  43. lino/utils/diag.py +6 -4
  44. lino/utils/fieldutils.py +14 -11
  45. lino/utils/instantiator.py +4 -2
  46. lino/utils/report.py +5 -3
  47. {lino-25.5.2.dist-info → lino-25.5.3.dist-info}/METADATA +1 -1
  48. {lino-25.5.2.dist-info → lino-25.5.3.dist-info}/RECORD +51 -50
  49. {lino-25.5.2.dist-info → lino-25.5.3.dist-info}/WHEEL +0 -0
  50. {lino-25.5.2.dist-info → lino-25.5.3.dist-info}/licenses/AUTHORS.rst +0 -0
  51. {lino-25.5.2.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.fields import fields_list, VirtualField, wildcard_data_elems
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(object):
42
+ class DummyPanel:
40
43
  """
41
- A layout panel which does not exist in the current configuration
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 Panel(object):
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
- main = layout.main
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(object):
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(fields_list(ds, self.hidden_elements))
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
- front_end = settings.SITE.kernel.editing_front_end
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("{0} has no `ui_handle_attr_name`!".format(front_end))
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
- params_store = None
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
- def setup_handle(self, lh):
817
- # if str(self._datasource) == 'courses.Pupils':
818
- # print("20160329 setup_handle")
819
- from lino.core.store import ParameterStore
820
-
821
- self.params_store = ParameterStore(lh, self.url_param_name)
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 .fields import make_remote_field, RichTextField, displayfield
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
- fields.fields_list(cls, cls.active_fields))
250
- cls.active_fields = cls.active_fields | fields.fields_list(cls, names)
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 object gives permission
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
- if display_mode == constants.DISPLAY_MODE_STORY:
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.store import get_atomizer
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 RemoteField, FakeField, TableRow
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
- if not actor.is_abstract():
1786
- self.ah = actor.get_request_handle(self)
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 not "selected_pks" in kw and not "selected_rows" in kw:
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
@@ -24,5 +24,5 @@ pre_ui_save = Signal() # ['instance', 'ar'])
24
24
  post_ui_save = Signal() # ['instance', 'ar'])
25
25
  pre_ui_delete = Signal() # ['request'])
26
26
  pre_ui_build = Signal()
27
- post_ui_build = Signal()
27
+ # post_ui_build = Signal()
28
28
  # database_connected = Signal()
lino/core/site.py CHANGED
@@ -247,7 +247,7 @@ class Site(object):
247
247
 
248
248
  legacy_data_path = None
249
249
  """
250
- Used by custom fixtures that import data from some legacy
250
+ Deprecated. Was used by tim2lino and pp2lino to import data from some legacy
251
251
  database.
252
252
 
253
253
  """
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 constants
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
@@ -314,7 +317,7 @@ class AbstractTable(actors.Actor):
314
317
 
315
318
  table_as_calendar = False
316
319
  """Whether the primereact Datatable is used to display a calendar view.
317
-
320
+
318
321
  Used only in :term:`React front end`.
319
322
  """
320
323