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/core/kernel.py CHANGED
@@ -877,12 +877,12 @@ class Kernel(object):
877
877
 
878
878
  if isinstance(h, tables.TableHandle):
879
879
  he = set(h.actor.hidden_columns | h.actor.hidden_elements)
880
- ll = layouts.ColumnsLayout(
880
+ gl = layouts.ColumnsLayout(
881
881
  h.actor.get_column_names(ar), h.actor, hidden_elements=he
882
882
  )
883
- h.list_layout = ll.get_layout_handle()
883
+ h.grid_layout = gl.get_layout_handle()
884
884
  else:
885
- h.list_layout = None
885
+ h.grid_layout = None
886
886
 
887
887
  if h.actor.params_layout:
888
888
  h.params_layout_handle = h.actor.make_params_layout_handle()
lino/core/model.py CHANGED
@@ -937,6 +937,7 @@ LINO_MODEL_ATTRIBS = (
937
937
  "save_watched_instance",
938
938
  "save_existing_instance",
939
939
  "_widget_options",
940
+ "extra_display_modes",
940
941
  "set_widget_options",
941
942
  "get_widget_options",
942
943
  "get_chooser_for_field",
lino/core/renderer.py CHANGED
@@ -197,7 +197,7 @@ class HtmlRenderer(Renderer):
197
197
  import rstgen
198
198
 
199
199
  if display_mode == constants.DISPLAY_MODE_CARDS:
200
- layout = ar.actor.card_layout or actor.list_layout
200
+ layout = ar.actor.card_layout or ar.actor.list_layout
201
201
  lh = layout.get_layout_handle()
202
202
  else:
203
203
  lh = ar.bound_action.get_layout_handel()
@@ -275,14 +275,18 @@ class HtmlRenderer(Renderer):
275
275
  Silently ignores the parameters `stripped` and `header_links`
276
276
  since for HTML these options have no meaning.
277
277
  """
278
- # print("20231227")
279
278
  if display_mode is None:
280
279
  display_mode = ar.actor.get_display_mode()
281
- # yield E.p(display_mode)
282
- if not nosummary:
283
- if display_mode == constants.DISPLAY_MODE_SUMMARY:
284
- yield ar.actor.get_table_summary(ar.master_instance, ar)
285
- return
280
+ if nosummary and display_mode == constants.DISPLAY_MODE_SUMMARY:
281
+ display_mode = constants.DISPLAY_MODE_GRID
282
+ else:
283
+ assert display_mode in ar.actor.extra_display_modes | constants.BASIC_DISPLAY_MODES
284
+ if nosummary:
285
+ raise Exception("Both nosummary and display_mode were specified")
286
+
287
+ if display_mode == constants.DISPLAY_MODE_SUMMARY:
288
+ yield ar.actor.get_table_summary(ar.master_instance, ar)
289
+ return
286
290
 
287
291
  if header_level is not None:
288
292
  k = "h" + str(header_level)
@@ -294,6 +298,10 @@ class HtmlRenderer(Renderer):
294
298
  yield ar.actor.get_table_as_list(ar.master_instance, ar)
295
299
  return
296
300
 
301
+ if display_mode == constants.DISPLAY_MODE_STORY:
302
+ yield ar.actor.get_table_story(ar.master_instance, ar)
303
+ return
304
+
297
305
  yield ar.table2xhtml(**kwargs)
298
306
 
299
307
  # if show_toolbar:
@@ -810,7 +818,7 @@ class TextRenderer(HtmlRenderer):
810
818
  stripped=True,
811
819
  show_urls=False,
812
820
  display_mode=None,
813
- **kwargs,
821
+ **kwargs
814
822
  ):
815
823
  """
816
824
  Render the given table request as reStructuredText to stdout. See
@@ -819,19 +827,24 @@ class TextRenderer(HtmlRenderer):
819
827
  self.show_urls = show_urls
820
828
  if display_mode is None:
821
829
  display_mode = ar.actor.get_display_mode()
822
- # display_mode = [dmi[1] for dmi in ar.actor.display_mode if dmi[0] is None][0]
823
- # if ar.actor.master is not None and not nosummary:
830
+ if nosummary and display_mode == constants.DISPLAY_MODE_SUMMARY:
831
+ display_mode = constants.DISPLAY_MODE_GRID
832
+ else:
833
+ if display_mode not in ar.actor.extra_display_modes | constants.BASIC_DISPLAY_MODES:
834
+ raise Exception(f"Invalid display mode {display_mode} for {ar.actor}")
835
+ if nosummary:
836
+ raise Exception("Both nosummary and display_mode were specified")
837
+ # print(f"20240929 {nosummary} {display_mode} {ar}")
824
838
  # yield "20240506 {}".format(ar)
825
- if not nosummary:
826
- if display_mode == constants.DISPLAY_MODE_SUMMARY:
827
- s = to_rst(
828
- ar.actor.get_table_summary(ar.master_instance, ar),
829
- stripped=stripped,
830
- )
831
- if stripped:
832
- s = s.strip()
833
- yield s
834
- return
839
+ if display_mode == constants.DISPLAY_MODE_SUMMARY:
840
+ s = to_rst(
841
+ ar.actor.get_table_summary(ar.master_instance, ar),
842
+ stripped=stripped,
843
+ )
844
+ if stripped:
845
+ s = s.strip()
846
+ yield s
847
+ return
835
848
 
836
849
  if display_mode == constants.DISPLAY_MODE_CARDS:
837
850
  for row in ar.sliced_data_iterator:
lino/core/requests.py CHANGED
@@ -633,13 +633,16 @@ class BaseRequest(object):
633
633
  """
634
634
  # ~ print 20131003, selected_pks
635
635
  self.selected_rows = []
636
- try:
637
- for pk in selected_pks:
638
- if pk and pk != "-99998" and pk != "-99999":
639
- self.selected_rows.append(self.get_row_by_pk(pk))
640
- except Exception as e:
641
- raise exceptions.BadRequest(
642
- "Invalid primary key {0} for {1} ({2})".format(pk, self.actor, e)) from None
636
+ for pk in selected_pks:
637
+ if pk and pk != "-99998" and pk != "-99999":
638
+ self.selected_rows.append(self.get_row_by_pk(pk))
639
+ # try:
640
+ # for pk in selected_pks:
641
+ # if pk and pk != "-99998" and pk != "-99999":
642
+ # self.selected_rows.append(self.get_row_by_pk(pk))
643
+ # except ObjectDoesNotExist as e:
644
+ # raise exceptions.BadRequest(
645
+ # "Invalid primary key {0} for {1} ({2})".format(pk, self.actor, e)) from None
643
646
  # self.selected_rows = filter(lambda x: x, self.selected_rows)
644
647
  # note: ticket #523 was because the GET contained an empty pk ("&sr=")
645
648
 
@@ -661,11 +664,11 @@ class BaseRequest(object):
661
664
  return False
662
665
  if not self.bound_action.action.select_rows:
663
666
  return True
664
- # raise Exception("20160814 {}".format(self.bound_action))
665
667
  for obj in self.selected_rows:
666
668
  # obj = self.selected_rows[0]
667
669
  state = self.bound_action.actor.get_row_state(obj)
668
670
  if not self.bound_action.get_row_permission(self, obj, state):
671
+ # raise Exception(f"20241001 {self.bound_action}")
669
672
  return False
670
673
  return True
671
674
  # obj = state = None
@@ -1078,17 +1081,16 @@ class BaseRequest(object):
1078
1081
  set to ``((None, DISPLAY_MODE_SUMMARY), )``,
1079
1082
  force rendering it as a table.
1080
1083
 
1081
- :display_mode: override the table's :attr:`display_mode
1082
- <lino.core.tables.AbstractTable.display_mode>`.
1084
+ :display_mode: override the table's default display_mode specified by
1085
+ :attr:`default_display_modes <lino.core.tables.AbstractTable.default_display_modes>`.
1083
1086
 
1084
1087
  Unlike `nosummary` this can be used to ask a summary for
1085
1088
  a table that would not show as summary by default.
1086
1089
  Instead of saying `nosummary=True` you can say
1087
- `display_mode=((None, DISPLAY_MODE_TABLE), )` or
1088
- `display_mode=((None, DISPLAY_MODE_HTML), )` (The
1089
- display modes DISPLAY_MODE_TABLE and DISPLAY_MODE_HTML
1090
- have the same result in a printed document or in a
1091
- tested spec).
1090
+ `display_mode=DISPLAY_MODE_GRID` or
1091
+ `display_mode=DISPLAY_MODE_HTML` (The display
1092
+ modes DISPLAY_MODE_GRID and DISPLAY_MODE_HTML have the
1093
+ same result in a printed document or in a tested spec).
1092
1094
 
1093
1095
  :header_level: show also the table header (using specified level)
1094
1096
 
@@ -1426,9 +1428,7 @@ class BaseRequest(object):
1426
1428
  if ar.actor.parameters:
1427
1429
  rec.update(
1428
1430
  param_values=ar.actor.params_layout.params_store.pv2dict(
1429
- ar, ar.param_values
1430
- )
1431
- )
1431
+ ar, ar.param_values))
1432
1432
 
1433
1433
  return rec
1434
1434
 
lino/core/store.py CHANGED
@@ -1192,7 +1192,7 @@ class Store(BaseStore):
1192
1192
  # temporary dict used by collect_fields and add_field_for
1193
1193
  # self.df2sf = {}
1194
1194
  self.all_fields = []
1195
- self.list_fields = []
1195
+ self.grid_fields = []
1196
1196
  self.detail_fields = []
1197
1197
  self.card_fields = []
1198
1198
  self.item_fields = []
@@ -1202,11 +1202,11 @@ class Store(BaseStore):
1202
1202
  if not isinstance(sf, StoreField):
1203
1203
  raise Exception("20210623 {} is not a StoreField".format(sf))
1204
1204
  self.all_fields.append(sf)
1205
- self.list_fields.append(sf)
1205
+ self.grid_fields.append(sf)
1206
1206
  self.detail_fields.append(sf)
1207
1207
 
1208
1208
  if not issubclass(rh.actor, frames.Frame):
1209
- self.collect_fields(self.list_fields, rh.get_list_layout())
1209
+ self.collect_fields(self.grid_fields, rh.get_grid_layout())
1210
1210
 
1211
1211
  form = rh.actor.detail_layout
1212
1212
  if form:
@@ -1235,7 +1235,7 @@ class Store(BaseStore):
1235
1235
  if self.pk is not None:
1236
1236
  self.pk_index = 0
1237
1237
  found = False
1238
- for fld in self.list_fields:
1238
+ for fld in self.grid_fields:
1239
1239
  """
1240
1240
  Django's Field.__cmp__() does::
1241
1241
 
@@ -1251,8 +1251,8 @@ class Store(BaseStore):
1251
1251
  self.pk_index += fld.list_values_count
1252
1252
  if not found:
1253
1253
  raise Exception(
1254
- "Primary key %r not found in list_fields %s"
1255
- % (self.pk, [f.field for f in self.list_fields])
1254
+ "Primary key %r not found in grid_fields %s"
1255
+ % (self.pk, [f.field for f in self.grid_fields])
1256
1256
  )
1257
1257
 
1258
1258
  actor_editable = True # not rh.actor.hide_editing(None)
@@ -1273,14 +1273,14 @@ class Store(BaseStore):
1273
1273
  f for f in self.all_fields if not isinstance(f, VirtStoreField)
1274
1274
  ] + [f for f in self.all_fields if isinstance(f, VirtStoreField)]
1275
1275
  self.all_fields = tuple(self.all_fields)
1276
- self.list_fields = tuple(self.list_fields)
1276
+ self.grid_fields = tuple(self.grid_fields)
1277
1277
  self.detail_fields = tuple(self.detail_fields)
1278
1278
  self.card_fields = tuple(self.card_fields)
1279
1279
  self.item_fields = tuple(self.item_fields)
1280
1280
 
1281
1281
  def collect_fields(self, fields, *layouts):
1282
1282
  """`fields` is a pointer to either `self.detail_fields` or
1283
- `self.list_fields`. Each of these must contain a primary key
1283
+ `self.grid_fields`. Each of these must contain a primary key
1284
1284
  field.
1285
1285
 
1286
1286
  """
@@ -1370,7 +1370,7 @@ class Store(BaseStore):
1370
1370
 
1371
1371
  def column_names(self):
1372
1372
  l = []
1373
- for fld in self.list_fields:
1373
+ for fld in self.grid_fields:
1374
1374
  l += fld.column_names()
1375
1375
  return l
1376
1376
 
@@ -1390,10 +1390,10 @@ class Store(BaseStore):
1390
1390
  # logger.info("20120107 Store %s row2list(%s)", self.report.model, dd.obj2str(row))
1391
1391
  l = []
1392
1392
  if isinstance(row, PhantomRow):
1393
- for fld in self.list_fields:
1393
+ for fld in self.grid_fields:
1394
1394
  fld.value2list(ar, None, l, row)
1395
1395
  else:
1396
- for fld in self.list_fields:
1396
+ for fld in self.grid_fields:
1397
1397
  if fld.delayed_value:
1398
1398
  # self.actor.collect_extra_fields(fld)
1399
1399
  v = DelayedValue(ar, fld.name, row)
lino/core/tablerequest.py CHANGED
@@ -472,7 +472,7 @@ class TableRequest(ActionRequest):
472
472
  tble.attrib.update(self.renderer.tableattrs)
473
473
  tble.attrib.setdefault("name", self.bound_action.full_name())
474
474
 
475
- grid = ar.ah.list_layout.main
475
+ grid = ar.ah.grid_layout.main
476
476
  # from lino.core.widgets import GridWidget
477
477
  # if not isinstance(grid, GridWidget):
478
478
  # raise Exception("20160529 %r is not a GridElement", grid)
@@ -549,14 +549,14 @@ class TableRequest(ActionRequest):
549
549
  ah = ar.actor.get_handle()
550
550
  for i, cn in enumerate(columns):
551
551
  col = None
552
- for e in ah.list_layout.main.columns:
552
+ for e in ah.grid_layout.main.columns:
553
553
  if e.name == cn:
554
554
  col = e
555
555
  break
556
556
  if col is None:
557
557
  raise Exception(
558
558
  "No column named %r in %s"
559
- % (cn, ar.ah.list_layout.main.columns)
559
+ % (cn, ar.ah.grid_layout.main.columns)
560
560
  )
561
561
  if not hiddens[i]:
562
562
  fields.append(col)
@@ -570,8 +570,8 @@ class TableRequest(ActionRequest):
570
570
  else:
571
571
  ah = ar.actor.get_request_handle(ar)
572
572
 
573
- columns = ah.list_layout.main.columns
574
- # print(20160530, ah, columns, ah.list_layout.main)
573
+ columns = ah.grid_layout.main.columns
574
+ # print(20160530, ah, columns, ah.grid_layout.main)
575
575
 
576
576
  # render them so that babelfields in hidden_languages
577
577
  # get hidden:
lino/core/tables.py CHANGED
@@ -1,5 +1,5 @@
1
1
  # -*- coding: UTF-8 -*-
2
- # Copyright 2009-2023 Rumma & Ko Ltd
2
+ # Copyright 2009-2024 Rumma & Ko Ltd
3
3
  # License: GNU Affero General Public License v3 (see file COPYING for details)
4
4
  """Defines the classes :class:`AbstractTable` and
5
5
  :class:`VirtualTable`.
@@ -15,6 +15,7 @@ from django.db import models
15
15
  from django.db.utils import OperationalError, ProgrammingError
16
16
  from django.conf import settings
17
17
  from django.utils.translation import gettext_lazy as _
18
+ from django.core.exceptions import BadRequest
18
19
 
19
20
  from lino.core import actors
20
21
  from lino.core import actions
@@ -114,7 +115,7 @@ class TableHandle(object):
114
115
  def setup_layouts(self):
115
116
  if self._layouts is not None:
116
117
  return
117
- self._layouts = [self.list_layout]
118
+ self._layouts = [self.grid_layout]
118
119
 
119
120
  def get_actor_url(self, *args, **kw):
120
121
  return settings.SITE.kernel.get_actor_url(self.actor, *args, **kw)
@@ -122,12 +123,12 @@ class TableHandle(object):
122
123
  def submit_elems(self):
123
124
  return []
124
125
 
125
- def get_list_layout(self):
126
+ def get_grid_layout(self):
126
127
  self.setup_layouts()
127
128
  return self._layouts[0]
128
129
 
129
130
  def get_columns(self):
130
- lh = self.get_list_layout()
131
+ lh = self.get_grid_layout()
131
132
  # ~ print 20110315, layout._main.columns
132
133
  return lh.main.columns
133
134
 
@@ -315,32 +316,44 @@ class AbstractTable(actors.Actor):
315
316
  Usually such a warning means that there is something wrong.
316
317
  """
317
318
 
318
- display_mode = (
319
- (70, constants.DISPLAY_MODE_SUMMARY),
320
- (None, constants.DISPLAY_MODE_TABLE),
321
- )
319
+ default_display_modes = {
320
+ 70: constants.DISPLAY_MODE_SUMMARY,
321
+ None: constants.DISPLAY_MODE_GRID
322
+ }
322
323
  """
323
- A tuple of `(width, display_mode)` items used to select a :term:`display
324
- mode` depending on the available width (in characters) on the client.
324
+ Which :term:`display mode` to use in a :term:`slave panel`,
325
+ depending on available width.
325
326
 
326
- The last item should have a width of `None`.
327
-
328
- See also :meth:`get_display_mode` and :ref:`dev.format`.
327
+ See :ref:`dg.table.default_display_modes`.
329
328
  """
330
329
 
331
330
  @classmethod
332
- def get_display_mode(cls, available_width=None):
333
- """Given the available width, return the :term:`display mode` to use."""
334
- current_choice = (0, None)
335
- for dmi in cls.display_mode:
336
- if available_width is not None:
337
- if dmi[0] is None and current_choice[0] == 0:
338
- current_choice = (0, dmi[1])
339
- elif available_width > dmi[0] > current_choice[0]:
340
- current_choice = dmi
341
- elif dmi[0] is None:
342
- return dmi[1]
343
- return current_choice[1]
331
+ # def get_display_mode(cls, available_width=None):
332
+ def get_display_mode(cls):
333
+ """Return the fallback :term:`display mode` to use."""
334
+ return cls.default_display_modes[None]
335
+ # rv = cls.default_display_modes[None]
336
+ # if available_width is not None:
337
+ # found = None
338
+ # for w, v in cls.default_display_modes.items():
339
+ # if w is not None and w <= available_width:
340
+ # if found is None:
341
+ # found = w
342
+ # rv = v
343
+ # elif found > w:
344
+ # found = w
345
+ # rv = v
346
+ # return rv
347
+ # current_choice = (0, None)
348
+ # for dmi in cls.display_mode:
349
+ # if available_width is not None:
350
+ # if dmi[0] is None and current_choice[0] == 0:
351
+ # current_choice = (0, dmi[1])
352
+ # elif available_width > dmi[0] > current_choice[0]:
353
+ # current_choice = dmi
354
+ # elif dmi[0] is None:
355
+ # return dmi[1]
356
+ # return current_choice[1]
344
357
 
345
358
  max_render_depth = 2
346
359
  """
@@ -613,6 +626,7 @@ method in order to sort the rows of the queryset.
613
626
  logger.warning(msg)
614
627
  # raise Exception(msg)
615
628
  # raise PermissionDenied(msg)
629
+ # raise BadRequest(msg)
616
630
  # master_instance = None
617
631
  return # cannot add rows to this table
618
632
  kw[self.master_field.name] = master_instance
lino/mixins/dupable.py CHANGED
@@ -266,7 +266,7 @@ DupableChecker.activate()
266
266
  class SimilarObjects(dd.VirtualTable):
267
267
  """Shows the other objects that are similar to this one."""
268
268
 
269
- display_mode = ((None, constants.DISPLAY_MODE_SUMMARY),)
269
+ default_display_modes = {None: constants.DISPLAY_MODE_SUMMARY}
270
270
  master = dd.Model
271
271
  abstract = True
272
272
 
@@ -180,7 +180,7 @@ class MessagesByOwner(Messages):
180
180
 
181
181
  master_key = "owner"
182
182
  column_names = "message checker user #fixable *"
183
- display_mode = ((None, constants.DISPLAY_MODE_SUMMARY),)
183
+ default_display_modes = {None: constants.DISPLAY_MODE_SUMMARY}
184
184
 
185
185
 
186
186
  # This was the first use case of a slave table with something else than a model
@@ -51,7 +51,7 @@ class AddCommentField(dd.VirtualField):
51
51
  """
52
52
 
53
53
  editable = True
54
- simple_elem = True
54
+ # simple_elem = True
55
55
 
56
56
  def __init__(self, slave_table):
57
57
  t = models.TextField(_("Add comment"), blank=True)
@@ -94,7 +94,7 @@ class Commentable(MemoReferrable):
94
94
 
95
95
  create_comment_template = _("Created new {model} {obj}.")
96
96
 
97
- add_comment = AddCommentField("comments.CommentsByRFC")
97
+ # add_comment = AddCommentField("comments.CommentsByRFC")
98
98
 
99
99
  def on_commented(self, comment, ar, cw):
100
100
  pass