lino 24.9.4__py3-none-any.whl → 24.10.0__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 (43) hide show
  1. lino/__init__.py +1 -5
  2. lino/api/doctest.py +18 -1
  3. lino/core/actions.py +15 -8
  4. lino/core/actors.py +82 -8
  5. lino/core/constants.py +20 -4
  6. lino/core/dashboard.py +1 -3
  7. lino/core/dbtables.py +33 -50
  8. lino/core/elems.py +26 -33
  9. lino/core/fields.py +13 -7
  10. lino/core/kernel.py +3 -3
  11. lino/core/model.py +1 -0
  12. lino/core/renderer.py +33 -20
  13. lino/core/requests.py +18 -18
  14. lino/core/store.py +11 -11
  15. lino/core/tablerequest.py +5 -5
  16. lino/core/tables.py +39 -25
  17. lino/mixins/dupable.py +1 -1
  18. lino/modlib/checkdata/models.py +1 -1
  19. lino/modlib/comments/mixins.py +2 -2
  20. lino/modlib/comments/ui.py +83 -77
  21. lino/modlib/dupable/models.py +1 -1
  22. lino/modlib/export_excel/models.py +0 -3
  23. lino/modlib/extjs/ext_renderer.py +8 -8
  24. lino/modlib/extjs/views.py +6 -16
  25. lino/modlib/help/management/commands/makehelp.py +1 -1
  26. lino/modlib/notify/models.py +1 -1
  27. lino/modlib/printing/actions.py +0 -5
  28. lino/modlib/publisher/ui.py +3 -3
  29. lino/modlib/publisher/views.py +10 -10
  30. lino/modlib/search/models.py +2 -2
  31. lino/modlib/system/mixins.py +0 -10
  32. lino/modlib/uploads/mixins.py +6 -3
  33. lino/modlib/uploads/ui.py +2 -2
  34. lino/modlib/users/mixins.py +5 -5
  35. lino/sphinxcontrib/actordoc.py +1 -1
  36. lino/sphinxcontrib/logo/static/linodocs.css +1 -8
  37. lino/utils/choosers.py +1 -1
  38. lino/utils/report.py +1 -1
  39. {lino-24.9.4.dist-info → lino-24.10.0.dist-info}/METADATA +1 -1
  40. {lino-24.9.4.dist-info → lino-24.10.0.dist-info}/RECORD +43 -43
  41. {lino-24.9.4.dist-info → lino-24.10.0.dist-info}/WHEEL +0 -0
  42. {lino-24.9.4.dist-info → lino-24.10.0.dist-info}/licenses/AUTHORS.rst +0 -0
  43. {lino-24.9.4.dist-info → lino-24.10.0.dist-info}/licenses/COPYING +0 -0
lino/__init__.py CHANGED
@@ -26,11 +26,7 @@ defines no models, some template files, a series of :term:`django-admin commands
26
26
 
27
27
  """
28
28
 
29
- __version__ = '24.9.4'
30
-
31
- # from __future__ import unicode_literals
32
- # from __future__ import absolute_import
33
- # from builtins import str
29
+ __version__ = '24.10.0'
34
30
 
35
31
  # import setuptools # avoid UserWarning "Distutils was imported before Setuptools"?
36
32
 
lino/api/doctest.py CHANGED
@@ -314,7 +314,7 @@ def get_fields(model, fieldnames=None, columns=None):
314
314
  get_field = model.get_data_elem
315
315
  if fieldnames is None:
316
316
  fieldnames = model.column_names
317
- # get_handle().list_layout.main.columns
317
+ # get_handle().grid_layout.main.columns
318
318
  else:
319
319
  get_field = model.parameters.get
320
320
  if fieldnames is None:
@@ -773,3 +773,20 @@ def show_change_watchers():
773
773
  [full_model_name(m), ws.master_key, " ".join(sorted(ws.ignored_fields))]
774
774
  )
775
775
  print(table(headers, rows, max_width=40))
776
+
777
+ def show_display_modes():
778
+ """
779
+ Show the availble display modes per actor.
780
+ """
781
+ dml = sorted(constants.DISPLAY_MODES - constants.BASIC_DISPLAY_MODES)
782
+ headers = ["actor"]
783
+ headers += dml
784
+ rows = []
785
+ for a in sorted(actors.actors_list, key=str):
786
+ if not a.is_abstract():
787
+ rows.append(
788
+ [str(a)] + [
789
+ ("x" if dm in a.extra_display_modes else "")
790
+ for dm in dml]
791
+ )
792
+ print(table(headers, rows))
lino/core/actions.py CHANGED
@@ -85,7 +85,10 @@ def resolve_layout(cls, k, spec, layout_class, **options):
85
85
  def install_layout(cls, k, layout_class, **options):
86
86
  """
87
87
  - `cls` is the actor (a class object)
88
- - `k` is one of 'detail_layout', 'insert_layout', 'params_layout', 'card_layout', 'list_layout'
88
+
89
+ - `k` is one of 'grid_layout', 'detail_layout', 'insert_layout',
90
+ 'params_layout', 'card_layout', 'list_layout'
91
+
89
92
  - `layout_class`
90
93
 
91
94
  """
@@ -437,6 +440,8 @@ class Action(Parametrizable, Permittable):
437
440
  On actions that opens_a_window this must be a unique one-letter
438
441
  string expressing the window type.
439
442
 
443
+ See `constants.WINDOW_TYPES`.
444
+
440
445
  Allowed values are:
441
446
 
442
447
  - None : opens_a_window is False
@@ -486,6 +491,12 @@ class Action(Parametrizable, Permittable):
486
491
 
487
492
  register_params(self)
488
493
 
494
+ if self.callable_from is not None:
495
+ for c in self.callable_from:
496
+ if not c in constants.WINDOW_TYPES:
497
+ raise Exception(f"Invalid window_type spec {c} in {self}")
498
+
499
+
489
500
  def __get__(self, instance, owner):
490
501
  """
491
502
  When a model has an action "foo", then getting an attribute
@@ -785,7 +796,7 @@ class ShowTable(TableAction):
785
796
  use_param_panel = True
786
797
  show_in_workflow = False
787
798
  opens_a_window = True
788
- window_type = "t"
799
+ window_type = constants.WINDOW_TYPE_TABLE
789
800
  action_name = "grid"
790
801
  select_rows = False
791
802
  callable_from = None
@@ -809,7 +820,7 @@ class ShowDetail(Action):
809
820
  icon_name = "application_form"
810
821
  ui5_icon_name = "sap-icon://detail-view"
811
822
  opens_a_window = True
812
- window_type = "d"
823
+ window_type = constants.WINDOW_TYPE_DETAIL
813
824
  show_in_workflow = False
814
825
  save_action_name = "submit_detail"
815
826
  callable_from = "t"
@@ -893,7 +904,7 @@ class ShowInsert(TableAction):
893
904
  show_in_workflow = False
894
905
  show_in_side_toolbar = True
895
906
  opens_a_window = True
896
- window_type = "i"
907
+ window_type = constants.WINDOW_TYPE_INSERT
897
908
  hide_navigator = True # 20210509
898
909
  hide_top_toolbar = True # 20210509
899
910
  sort_index = 10
@@ -1342,11 +1353,7 @@ class DeleteSelected(MultipleRowAction):
1342
1353
  sort_index = 30
1343
1354
  readonly = False
1344
1355
  show_in_workflow = False
1345
- # required_roles = set([SiteUser])
1346
- # ~ callable_from = (ShowTable,ShowDetail)
1347
- # ~ needs_selection = True
1348
1356
  label = _("Delete")
1349
- # ~ url_action_name = 'delete'
1350
1357
  hotkey = keyboard.DELETE # (ctrl=True)
1351
1358
 
1352
1359
  # ~ client_side = True
lino/core/actors.py CHANGED
@@ -27,10 +27,11 @@ from lino.utils import MissingRow
27
27
  from lino.core import fields
28
28
  from lino.core import actions
29
29
  from lino.core import layouts
30
+ from lino.core import constants
30
31
  from lino.core.requests import ActionRequest
31
32
  from lino.core.boundaction import BoundAction
32
33
  from lino.core.exceptions import ChangedAPI
33
- from lino.core.constants import _handle_attr_name, CHOICES_BLANK_FILTER_VALUE
34
+ from lino.core.constants import _handle_attr_name
34
35
  from lino.core.permissions import add_requirements, Permittable
35
36
  from lino.core.utils import resolve_model
36
37
  from lino.core.utils import error2str
@@ -291,7 +292,17 @@ class Actor(actions.Parametrizable, Permittable, metaclass=ActorMetaClass):
291
292
  # """
292
293
 
293
294
  only_fields = None
294
- display_mode = None
295
+
296
+ default_display_modes = None
297
+
298
+ # extra_display_modes = set()
299
+ # extra_display_modes = {constants.DISPLAY_MODE_SUMMARY}
300
+ extra_display_modes = {constants.DISPLAY_MODE_HTML}
301
+ """
302
+ A set of additional display modes to make available when rendering this table.
303
+
304
+ See :ref:`dg.dd.table.extra_display_modes`.
305
+ """
295
306
 
296
307
  app_label = None
297
308
  """
@@ -428,6 +439,7 @@ class Actor(actions.Parametrizable, Permittable, metaclass=ActorMetaClass):
428
439
  default_action = None
429
440
  actor_id = None
430
441
 
442
+ grid_layout = None
431
443
  detail_layout = None
432
444
  insert_layout = None
433
445
  card_layout = None
@@ -730,8 +742,12 @@ class Actor(actions.Parametrizable, Permittable, metaclass=ActorMetaClass):
730
742
 
731
743
  if hasattr(cls, "required"):
732
744
  raise ChangedAPI(
733
- "{0} must convert `required` to `required_roles`".format(cls)
745
+ f"{cls} must convert `required` to `required_roles`"
734
746
  )
747
+ if hasattr(cls, "display_mode"):
748
+ # cls.default_display_modes = {k:v for k, v in cls.display_mode}
749
+ # logger.info(f"{cls} uses deprecated `display_mode`, please convert to `default_display_modes`.")
750
+ raise ChangedAPI(f"{cls} must convert `display_mode` to `default_display_modes`")
735
751
 
736
752
  master = getattr(cls, "master", None)
737
753
  if isinstance(master, str):
@@ -760,6 +776,45 @@ class Actor(actions.Parametrizable, Permittable, metaclass=ActorMetaClass):
760
776
  cls.virtual_fields["detail_link"] = de
761
777
  # cls.add_virtual_field('detail_link', de)
762
778
 
779
+ if cls.abstract:
780
+ return
781
+
782
+ edm = set() # extra display modes to add automatically
783
+ if cls.default_display_modes is not None:
784
+ for v in cls.default_display_modes.values():
785
+ if v not in constants.DISPLAY_MODES:
786
+ raise Exception(f"Invalid display mode {v} in {cls}")
787
+ if v not in constants.BASIC_DISPLAY_MODES:
788
+ # be sure to have our copy the extra_display_modes set
789
+ # if str(cls) == "comments.Comments":
790
+ # print(f"20240929 extra_display_modes is {cls.extra_display_modes}, we add {v}")
791
+ # cls.extra_display_modes = cls.extra_display_modes | {v}
792
+ edm.add(v)
793
+
794
+ if cls.card_layout is not None:
795
+ edm.add(constants.DISPLAY_MODE_CARDS)
796
+ if 'row_as_paragraph' in cls.__dict__:
797
+ edm.add(constants.DISPLAY_MODE_LIST)
798
+ # if 'row_as_page' in cls.__dict__:
799
+ # edm.add(constants.DISPLAY_MODE_STORY)
800
+ if model is not None:
801
+ if model.extra_display_modes is not None:
802
+ for v in model.extra_display_modes:
803
+ if v not in constants.DISPLAY_MODES:
804
+ raise Exception(f"Invalid display mode {v} in {model}.extra_display_modes")
805
+ edm |= model.extra_display_modes
806
+ if 'as_paragraph' in model.__dict__:
807
+ edm.add(constants.DISPLAY_MODE_LIST)
808
+ if 'as_page' in model.__dict__:
809
+ edm.add(constants.DISPLAY_MODE_STORY)
810
+ # no need to automatically add summary because it's in default_display_modes of every table
811
+ # if 'as_summary_item' in model.__dict__:
812
+ # edm.add(constants.DISPLAY_MODE_SUMMARY)
813
+ if len(edm) > 0:
814
+ # if str(cls) == "comments.Comments":
815
+ # print(f"20240929 {cls} extra_display_modes add {edm}")
816
+ cls.extra_display_modes = cls.extra_display_modes | edm
817
+
763
818
  @classmethod
764
819
  def init_layouts(cls):
765
820
  # 20200430 this was previously part of class_init, but is now called in
@@ -1238,7 +1293,7 @@ class Actor(actions.Parametrizable, Permittable, metaclass=ActorMetaClass):
1238
1293
  yield t
1239
1294
  for k in self.simple_parameters:
1240
1295
  v = getattr(ar.param_values, k)
1241
- if v and v != CHOICES_BLANK_FILTER_VALUE:
1296
+ if v and v != constants.CHOICES_BLANK_FILTER_VALUE:
1242
1297
  if v is True:
1243
1298
  # For BooleanField no need to add "True" in the title
1244
1299
  yield str(self.parameters[k].verbose_name)
@@ -1605,10 +1660,10 @@ class Actor(actions.Parametrizable, Permittable, metaclass=ActorMetaClass):
1605
1660
  return wl.get_layout_handle()
1606
1661
 
1607
1662
  @classmethod
1608
- def get_list_layout(cls, *args):
1663
+ def get_grid_layout(cls, *args):
1609
1664
  assert cls.default_action is not None
1610
1665
  ah = cls.get_handle()
1611
- return ah.get_list_layout()
1666
+ return ah.get_grid_layout()
1612
1667
 
1613
1668
  @classmethod
1614
1669
  def get_detail_elems(cls):
@@ -1831,6 +1886,25 @@ class Actor(actions.Parametrizable, Permittable, metaclass=ActorMetaClass):
1831
1886
  # assert_safe(html_text) # temporary 20240506
1832
1887
  return format_html(DIVTPL, html_text)
1833
1888
 
1889
+ @classmethod
1890
+ def get_table_story(cls, obj, ar):
1891
+ sar = cls.request(parent=ar, master_instance=obj, is_on_main_actor=False)
1892
+ html = mark_safe("")
1893
+ for i, obj in enumerate(sar.data_iterator):
1894
+ if i == cls.preview_limit:
1895
+ break
1896
+ s = obj.as_story_item(sar)
1897
+ # assert_safe(s) # temporary 20240506
1898
+ html += s
1899
+ if cls.insert_action is not None:
1900
+ if not cls.editable:
1901
+ return html
1902
+ ir = cls.insert_action.request_from(sar)
1903
+ if ir.get_permission():
1904
+ html = tostring(ir.ar2button()) + html
1905
+ # assert_safe(html) # temporary 20240506
1906
+ return html
1907
+
1834
1908
  @classmethod
1835
1909
  def get_table_summary(cls, obj, ar):
1836
1910
  """
@@ -1892,7 +1966,7 @@ class Actor(actions.Parametrizable, Permittable, metaclass=ActorMetaClass):
1892
1966
  if ar is None:
1893
1967
  return str(self)
1894
1968
  # cols = ar.actor.get_detail_layout().main
1895
- cols = cls.get_list_layout().main.columns
1969
+ cols = cls.get_grid_layout().main.columns
1896
1970
  cols = [c for c in cols if not c.value.get("hidden")]
1897
1971
  if fmt is None:
1898
1972
 
@@ -1914,7 +1988,7 @@ class Actor(actions.Parametrizable, Permittable, metaclass=ActorMetaClass):
1914
1988
  values = [v for v in values if v is not None]
1915
1989
  s = ", ".join(values)
1916
1990
  # s = str(ar.actor)
1917
- # print("20231218", s) # self, ar.actor.get_list_layout().main.columns)
1991
+ # print("20231218", s) # self, ar.actor.get_grid_layout().main.columns)
1918
1992
  return s
1919
1993
 
1920
1994
  @classmethod
lino/core/constants.py CHANGED
@@ -47,22 +47,38 @@ URL_PARAM_WINDOW_TYPE = "wt"
47
47
 
48
48
  WINDOW_TYPE_TABLE = "t"
49
49
  WINDOW_TYPE_DETAIL = "d"
50
- WINDOW_TYPE_CARDS = "c"
51
- WINDOW_TYPE_GALLERIA = "g"
52
50
  WINDOW_TYPE_INSERT = "i"
53
51
  WINDOW_TYPE_PARAMS = "p"
54
52
 
53
+ WINDOW_TYPES = {
54
+ WINDOW_TYPE_TABLE, WINDOW_TYPE_DETAIL, WINDOW_TYPE_INSERT,
55
+ WINDOW_TYPE_PARAMS
56
+ }
57
+
55
58
  URL_PARAM_DISPLAY_MODE = "dm"
56
59
 
57
- DISPLAY_MODE_TABLE = "grid" # deprecated
58
60
  DISPLAY_MODE_GRID = "grid"
59
61
  DISPLAY_MODE_DETAIL = "detail"
62
+ DISPLAY_MODE_HTML = "html"
60
63
  DISPLAY_MODE_SUMMARY = "summary"
61
64
  DISPLAY_MODE_LIST = "list"
62
65
  DISPLAY_MODE_CARDS = "cards"
63
66
  DISPLAY_MODE_GALLERY = "gallery"
64
67
  DISPLAY_MODE_STORY = "story"
65
- DISPLAY_MODE_HTML = "html"
68
+
69
+ DISPLAY_MODES = {
70
+ DISPLAY_MODE_GRID,
71
+ DISPLAY_MODE_DETAIL,
72
+ DISPLAY_MODE_HTML,
73
+ DISPLAY_MODE_SUMMARY,
74
+ DISPLAY_MODE_LIST,
75
+ DISPLAY_MODE_CARDS,
76
+ DISPLAY_MODE_GALLERY,
77
+ DISPLAY_MODE_STORY}
78
+
79
+ BASIC_DISPLAY_MODES = {
80
+ DISPLAY_MODE_GRID,
81
+ DISPLAY_MODE_DETAIL}
66
82
 
67
83
  # ~ URL_PARAM_EUSER = 'euser'
68
84
  # ~ URL_PARAM_EUSER = 'su'
lino/core/dashboard.py CHANGED
@@ -74,9 +74,7 @@ class DashboardItem(Permittable):
74
74
  assert sar.renderer is not None
75
75
  if isinstance(sar, TableRequest):
76
76
  # TODO 20220930 until now, dashboard was always acting as if
77
- # display_mode was DISPLAY_MODE_TABLE
78
- # if sar.actor.display_mode == constants.DISPLAY_MODE_TABLE
79
-
77
+ # display_mode was DISPLAY_MODE_GRID
80
78
  for e in sar.renderer.table2story(sar, **kwargs):
81
79
  # assert_safe(tostring(e))
82
80
  yield tostring(e)
lino/core/dbtables.py CHANGED
@@ -212,7 +212,6 @@ class Table(AbstractTable):
212
212
 
213
213
  @classmethod
214
214
  def init_layouts(cls):
215
- # if cls.list_layout is None:
216
215
  if cls.model is not None and issubclass(cls.model, Model):
217
216
  if not cls.abstract:
218
217
  for tbl in cls.__mro__:
@@ -222,7 +221,7 @@ class Table(AbstractTable):
222
221
  if issubclass(tbl, actors.Actor):
223
222
  cls.list_layout = "list_item"
224
223
  break
225
- super(Table, cls).init_layouts()
224
+ super().init_layouts()
226
225
 
227
226
  @classmethod
228
227
  def add_quick_search_filter(cls, qs, search_text):
@@ -237,7 +236,7 @@ class Table(AbstractTable):
237
236
 
238
237
  @classmethod
239
238
  def get_chooser_for_field(self, fieldname):
240
- ch = super(Table, self).get_chooser_for_field(fieldname)
239
+ ch = super().get_chooser_for_field(fieldname)
241
240
  if ch is not None:
242
241
  return ch
243
242
  if self.model is not None:
@@ -292,7 +291,7 @@ class Table(AbstractTable):
292
291
  for ds in yield_model_detail_sets(self.model):
293
292
  yield ds
294
293
 
295
- for s in super(Table, self).get_detail_sets():
294
+ for s in super().get_detail_sets():
296
295
  yield s
297
296
 
298
297
  # @classmethod
@@ -302,25 +301,6 @@ class Table(AbstractTable):
302
301
  # return vf
303
302
  # return cls.model._meta.get_field(name)
304
303
 
305
- @classmethod
306
- def get_table_story(cls, obj, ar):
307
- sar = cls.request(parent=ar, master_instance=obj, is_on_main_actor=False)
308
- html = mark_safe("")
309
- for i, obj in enumerate(sar.data_iterator):
310
- if i == cls.preview_limit:
311
- break
312
- s = obj.as_story_item(sar)
313
- # assert_safe(s) # temporary 20240506
314
- html += s
315
- if cls.insert_action is not None:
316
- if not cls.editable:
317
- return html
318
- ir = cls.insert_action.request_from(sar)
319
- if ir.get_permission():
320
- html = tostring(ir.ar2button()) + html
321
- # assert_safe(html) # temporary 20240506
322
- return html
323
-
324
304
  @classmethod
325
305
  def get_pk_field(self):
326
306
  return self.model._meta.pk
@@ -334,18 +314,22 @@ class Table(AbstractTable):
334
314
  Note: `ar` may not be None.
335
315
 
336
316
  """
337
- return self.model.objects.get(pk=pk)
317
+ # return self.model.objects.get(pk=pk)
318
+ # raise Exception("20240929")
319
+ qs = self.get_request_queryset(ar)
338
320
  # qs = self.model.get_request_queryset(ar)
321
+
322
+ return qs.get(pk=pk)
339
323
  # try:
340
324
  # return qs.get(pk=pk)
341
325
  # # return self.get_queryset(ar).get(pk=pk)
342
326
  # except ValueError:
343
327
  # return None
344
328
  # except self.model.DoesNotExist:
345
- # # import sqlparse
346
- # # sql = str(qs.query).replace('"', '')
347
- # # sql = sqlparse.format(sql, reindent=True, keyword_case='upper')
348
- # # raise Exception("20240324 {}, {}".format(ar.param_values, sql))
329
+ # import sqlparse
330
+ # sql = str(qs.query).replace('"', '')
331
+ # sql = sqlparse.format(sql, reindent=True, keyword_case='upper')
332
+ # raise Exception("20240324 {}, {}".format(ar.param_values, sql))
349
333
  # return None
350
334
 
351
335
  # @classmethod
@@ -520,7 +504,7 @@ class Table(AbstractTable):
520
504
  self.master_field = fk
521
505
  # self.hidden_columns |= set([fk.name])
522
506
 
523
- super(Table, self).class_init()
507
+ super().class_init()
524
508
 
525
509
  if self.order_by is not None:
526
510
  if not isinstance(self.order_by, (list, tuple)):
@@ -547,7 +531,7 @@ class Table(AbstractTable):
547
531
 
548
532
  @classmethod
549
533
  def do_setup(self):
550
- super(Table, self).do_setup()
534
+ super().do_setup()
551
535
  # AbstractTable.do_setup(self)
552
536
  if self.model is None:
553
537
  return
@@ -567,7 +551,7 @@ class Table(AbstractTable):
567
551
 
568
552
  @classmethod
569
553
  def make_disabled_fields(cls, obj, ar):
570
- s = super(Table, cls).make_disabled_fields(obj, ar)
554
+ s = super().make_disabled_fields(obj, ar)
571
555
 
572
556
  if obj is not None and ar is not None:
573
557
  s |= obj.disabled_fields(ar)
@@ -605,8 +589,7 @@ class Table(AbstractTable):
605
589
  if self.delete_action is None:
606
590
  return "No delete_action"
607
591
  if not self.get_row_permission(
608
- obj, ar, self.get_row_state(obj), self.delete_action
609
- ):
592
+ obj, ar, self.get_row_state(obj), self.delete_action):
610
593
  # print "20130222 ar is %r" % ar
611
594
  # logger.info("20130225 dbtables.disable_delete no permission")
612
595
  return _("You have no permission to delete this row.")
@@ -634,23 +617,23 @@ class Table(AbstractTable):
634
617
  qs = qs.exclude(**ar.exclude)
635
618
  # qs = qs.exclude(ar.exclude)
636
619
 
637
- # 20200425
638
- spv = dict()
639
- for k in self.simple_parameters:
640
- v = getattr(ar.param_values, k)
641
- # if "room" in k:
642
- # print("20200423", k, v, self.simple_parameters, ar.param_values)
643
- if v == constants.CHOICES_BLANK_FILTER_VALUE:
644
- spv[k + "__isnull"] = True
645
- elif v == constants.CHOICES_NOT_BLANK_FILTER_VALUE:
646
- spv[k + "__isnull"] = False
647
- elif v is not None:
648
- spv[k] = v
649
- # print("20240506 {} = {}".format(k, v))
650
- ar.known_values[k] = v # make it an obvious field
651
-
652
- qs = self.model.add_param_filter(qs, **spv)
653
- # qs = self.model.add_param_filter(qs, **ar.param_values)
620
+ if ar.param_values is not None:
621
+ spv = dict()
622
+ for k in self.simple_parameters:
623
+ v = getattr(ar.param_values, k)
624
+ # if "room" in k:
625
+ # print("20200423", k, v, self.simple_parameters, ar.param_values)
626
+ if v == constants.CHOICES_BLANK_FILTER_VALUE:
627
+ spv[k + "__isnull"] = True
628
+ elif v == constants.CHOICES_NOT_BLANK_FILTER_VALUE:
629
+ spv[k + "__isnull"] = False
630
+ elif v is not None:
631
+ spv[k] = v
632
+ # print("20240506 {} = {}".format(k, v))
633
+ ar.known_values[k] = v # make it an obvious field
634
+
635
+ qs = self.model.add_param_filter(qs, **spv)
636
+ # qs = self.model.add_param_filter(qs, **ar.param_values)
654
637
 
655
638
  if self.filter:
656
639
  qs = qs.filter(self.filter)
lino/core/elems.py CHANGED
@@ -854,11 +854,11 @@ class TextFieldElement(FieldElement):
854
854
 
855
855
  def get_field_options(self, **kw):
856
856
  kw = super().get_field_options(**kw)
857
- if isinstance(self.field, fields.VirtualField) and self.field.simple_elem:
858
- kw["virtualField"] = True
859
- kw["noHeaderDecore"] = True
860
- kw["noEditorHeader"] = True
861
- kw["alwaysEditable"] = True
857
+ # if isinstance(self.field, fields.VirtualField) and self.field.simple_elem:
858
+ # kw["virtualField"] = True
859
+ # kw["noHeaderDecore"] = True
860
+ # kw["noEditorHeader"] = True
861
+ # kw["alwaysEditable"] = True
862
862
  kw["format"] = self.format
863
863
  return kw
864
864
 
@@ -2311,8 +2311,8 @@ class Panel(Container):
2311
2311
 
2312
2312
 
2313
2313
  class GridElement(Container):
2314
- """Represents a Lino.GridPanel, i.e. the widget used to represent a
2315
- table or a slave table.
2314
+ """Represents a Lino.GridPanel, i.e. the widget used to render a
2315
+ table in :term:`grid mode`.
2316
2316
 
2317
2317
  """
2318
2318
 
@@ -2343,14 +2343,13 @@ class GridElement(Container):
2343
2343
  self.actor = rpt
2344
2344
  if len(columns) == 0:
2345
2345
  self.rh = rpt.get_handle()
2346
- if not hasattr(self.rh, "list_layout"):
2346
+ if not hasattr(self.rh, "grid_layout"):
2347
2347
  raise Exception(
2348
- "Handle for {0} (model {1}) has no list_layout".format(
2348
+ "Handle for {0} (model {1}) has no grid_layout".format(
2349
2349
  rpt, rpt.model
2350
2350
  )
2351
2351
  )
2352
- columns = self.rh.list_layout.main.columns
2353
- # columns = self.rh.list_layout._main.elements
2352
+ columns = self.rh.grid_layout.main.columns
2354
2353
  w = 0
2355
2354
  for e in columns:
2356
2355
  w += e.width or e.preferred_width
@@ -2433,20 +2432,15 @@ class SlaveContainer(GridElement):
2433
2432
  slaves = None
2434
2433
 
2435
2434
  def __init__(self, layout_handle, name, rpt, *columns, **kw):
2436
- dms = [dm[1] for dm in rpt.display_mode]
2437
2435
  slaves = dict()
2438
- if constants.DISPLAY_MODE_STORY in dms:
2436
+ if constants.DISPLAY_MODE_STORY in rpt.extra_display_modes:
2439
2437
  slaves["story"] = get_story_element(layout_handle, rpt, name, **kw)
2440
- if constants.DISPLAY_MODE_LIST in dms:
2438
+ if constants.DISPLAY_MODE_LIST in rpt.extra_display_modes:
2441
2439
  slaves["list"] = get_list_element(layout_handle, rpt, name, **kw)
2442
- # if constants.DISPLAY_MODE_HTML in dms:
2443
- # slaves["html"] = get_htmlbox_element(layout_handle, rpt, name, **kw)
2444
- # if constants.DISPLAY_MODE_SUMMARY in dms:
2445
- # slaves["summary"] = get_summary_element(layout_handle, rpt, name, **kw)
2446
- # slaves["story"] = get_story_element(layout_handle, rpt, name, **kw)
2447
- # slaves["list"] = get_list_element(layout_handle, rpt, name, **kw)
2448
- slaves["html"] = get_htmlbox_element(layout_handle, rpt, name, **kw)
2449
- slaves["summary"] = get_summary_element(layout_handle, rpt, name, **kw)
2440
+ if constants.DISPLAY_MODE_SUMMARY in rpt.extra_display_modes:
2441
+ slaves["summary"] = get_summary_element(layout_handle, rpt, name, **kw)
2442
+ if constants.DISPLAY_MODE_HTML in rpt.extra_display_modes:
2443
+ slaves["html"] = get_htmlbox_element(layout_handle, rpt, name, **kw)
2450
2444
  if slaves:
2451
2445
  self.slaves = slaves
2452
2446
  super().__init__(layout_handle, name, rpt, *columns, **kw)
@@ -2845,13 +2839,11 @@ def create_layout_element(lh, name, **kw):
2845
2839
 
2846
2840
  # print("20240317", de, lh)
2847
2841
  if isinstance(lh.layout, FormLayout):
2848
- # When a table is specified in the layout of a
2849
- # DetailWindow, then it will be rendered as a panel that
2850
- # displays a "summary" of that table. The panel will have
2851
- # a tool button to "open that table in its own
2852
- # window". The format of that summary is defined by the
2853
- # `display_mode` of the table.
2854
-
2842
+ # When a table is specified in the layout of a DetailWindow, then it
2843
+ # will be rendered as a :term:`slave panel`. The panel will have a
2844
+ # tool button to "open that table in its own window". The display
2845
+ # mode of that summary is defined by the `default_display_modes` of
2846
+ # the table.
2855
2847
  if lh.ui.renderer.extjs_version is not None:
2856
2848
  if de.label is not None:
2857
2849
  js = (
@@ -2871,17 +2863,18 @@ def create_layout_element(lh, name, **kw):
2871
2863
  kw.update(label="{} {}".format(de.get_label(), tostring(btn)))
2872
2864
 
2873
2865
  if (
2874
- len(de.display_mode) > 1
2866
+ len(de.default_display_modes) > 1
2875
2867
  and lh.ui.renderer.front_end.media_name == "react"
2876
2868
  ):
2869
+ # print(f"20240928 {de} in {lh} gets SlaveContainer")
2877
2870
  return SlaveContainer(lh, name, de, **kw)
2878
2871
 
2879
2872
  dm = de.get_display_mode()
2880
- if dm in [
2881
- constants.DISPLAY_MODE_TABLE,
2873
+ if dm in {
2874
+ constants.DISPLAY_MODE_GRID,
2882
2875
  constants.DISPLAY_MODE_CARDS,
2883
2876
  constants.DISPLAY_MODE_GALLERY
2884
- ]:
2877
+ }:
2885
2878
  kw.update(hide_top_toolbar=True)
2886
2879
  if de.preview_limit is not None:
2887
2880
  kw.update(preview_limit=de.preview_limit)
lino/core/fields.py CHANGED
@@ -322,7 +322,7 @@ class FakeField(object):
322
322
  setattr(self, k, v)
323
323
 
324
324
  def __repr__(self):
325
- # copied from django Field
325
+ # copied from django Field
326
326
  path = "%s.%s" % (self.__class__.__module__, self.__class__.__qualname__)
327
327
  name = getattr(self, "name", None)
328
328
  if name is not None:
@@ -561,10 +561,10 @@ class VirtualField(FakeField):
561
561
  (because they inherit from that class).
562
562
  """
563
563
 
564
- simple_elem = False
565
- """
566
- Used in :meth:`get_field_options` to set :term:`front end` rendering options.
567
- """
564
+ # simple_elem = False
565
+ # """
566
+ # Used in :meth:`get_field_options` to set :term:`front end` rendering options.
567
+ # """
568
568
 
569
569
  def __init__(self, return_type, get=return_none, **kwargs):
570
570
  """
@@ -579,7 +579,7 @@ class VirtualField(FakeField):
579
579
  self.return_type = return_type # a Django Field instance
580
580
  self.get = get
581
581
 
582
- self.simple_elem = kwargs.get("simple_elem", self.simple_elem)
582
+ # self.simple_elem = kwargs.get("simple_elem", self.simple_elem)
583
583
 
584
584
  # if isinstance(return_type, FakeField):
585
585
  # sortable_by = return_type.sortable_by
@@ -1217,6 +1217,12 @@ class TableRow(object):
1217
1217
 
1218
1218
  _lino_default_table = None
1219
1219
  _widget_options = {}
1220
+ extra_display_modes = None
1221
+ """
1222
+ A set of extra display modes to make available on actors that use this model.
1223
+
1224
+ See :ref:`dg.dd.table.extra_display_modes`.
1225
+ """
1220
1226
 
1221
1227
  pk = None
1222
1228
 
@@ -1363,7 +1369,7 @@ class TableRow(object):
1363
1369
  return tostring(self.as_summary_item(ar, **kwargs))
1364
1370
 
1365
1371
  def as_story_item(self, ar, **kwargs):
1366
- kwargs.update(display_mode="story")
1372
+ kwargs.update(display_mode=constants.DISPLAY_MODE_STORY)
1367
1373
  return mark_safe("".join(self.as_page(ar, **kwargs)))
1368
1374
 
1369
1375
  def as_page(self, ar, **kwargs):