lino 24.9.4__py3-none-any.whl → 24.10.1__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 (49) hide show
  1. lino/__init__.py +1 -5
  2. lino/api/doctest.py +29 -6
  3. lino/core/__init__.py +0 -1
  4. lino/core/actions.py +16 -9
  5. lino/core/actors.py +99 -30
  6. lino/core/choicelists.py +28 -11
  7. lino/core/constants.py +20 -4
  8. lino/core/dashboard.py +19 -18
  9. lino/core/dbtables.py +49 -60
  10. lino/core/elems.py +26 -33
  11. lino/core/fields.py +13 -7
  12. lino/core/kernel.py +3 -3
  13. lino/core/model.py +5 -2
  14. lino/core/renderer.py +52 -39
  15. lino/core/requests.py +876 -299
  16. lino/core/store.py +17 -17
  17. lino/core/tables.py +46 -32
  18. lino/core/utils.py +134 -0
  19. lino/mixins/dupable.py +1 -1
  20. lino/modlib/bootstrap3/views.py +4 -3
  21. lino/modlib/checkdata/models.py +1 -1
  22. lino/modlib/comments/mixins.py +2 -2
  23. lino/modlib/comments/ui.py +83 -77
  24. lino/modlib/dupable/models.py +1 -1
  25. lino/modlib/export_excel/models.py +0 -3
  26. lino/modlib/extjs/ext_renderer.py +8 -8
  27. lino/modlib/extjs/views.py +8 -17
  28. lino/modlib/forms/views.py +3 -3
  29. lino/modlib/help/management/commands/makehelp.py +1 -1
  30. lino/modlib/notify/models.py +1 -1
  31. lino/modlib/odata/views.py +2 -2
  32. lino/modlib/printing/actions.py +0 -5
  33. lino/modlib/publisher/ui.py +3 -3
  34. lino/modlib/publisher/views.py +14 -11
  35. lino/modlib/search/models.py +2 -2
  36. lino/modlib/system/mixins.py +0 -10
  37. lino/modlib/uploads/mixins.py +6 -3
  38. lino/modlib/uploads/ui.py +2 -2
  39. lino/modlib/users/mixins.py +5 -5
  40. lino/sphinxcontrib/actordoc.py +1 -1
  41. lino/sphinxcontrib/logo/static/linodocs.css +1 -8
  42. lino/utils/choosers.py +1 -1
  43. lino/utils/report.py +4 -11
  44. {lino-24.9.4.dist-info → lino-24.10.1.dist-info}/METADATA +1 -1
  45. {lino-24.9.4.dist-info → lino-24.10.1.dist-info}/RECORD +48 -49
  46. lino/core/tablerequest.py +0 -758
  47. {lino-24.9.4.dist-info → lino-24.10.1.dist-info}/WHEEL +0 -0
  48. {lino-24.9.4.dist-info → lino-24.10.1.dist-info}/licenses/AUTHORS.rst +0 -0
  49. {lino-24.9.4.dist-info → lino-24.10.1.dist-info}/licenses/COPYING +0 -0
lino/core/store.py CHANGED
@@ -20,7 +20,7 @@ Other usages:
20
20
 
21
21
  - render tables as text
22
22
  (:meth:`lino.core.renderer.TextRenderer.show_table` and
23
- :meth:`lino.core.tablerequest.TableRequest.row2text`)
23
+ :meth:`lino.core.requests.ActionRequest.row2text`)
24
24
 
25
25
  """
26
26
 
@@ -45,11 +45,11 @@ from lino.core import fields
45
45
  from lino.core import actors
46
46
  from lino.core import actions
47
47
  from lino.core import frames
48
+ from lino.core.utils import PhantomRow
48
49
  from lino.utils import choosers
49
50
  from lino.utils import curry
50
51
  from lino.utils import iif
51
52
  from lino.utils.format_date import fds
52
- from lino.core.requests import PhantomRow
53
53
  from lino.utils import IncompleteDate
54
54
  from lino.core.utils import DelayedValue
55
55
 
@@ -861,7 +861,7 @@ class FileFieldStoreField(StoreField):
861
861
 
862
862
  class MethodStoreField(StoreField):
863
863
  """Deprecated. See `/blog/2012/0327`.
864
- Still used for display_mode 'html'.
864
+ Still used for DISPLAY_MODE_HTML.
865
865
  """
866
866
 
867
867
  def full_value_from_object(self, obj, ar=None):
@@ -870,7 +870,7 @@ class MethodStoreField(StoreField):
870
870
  self.name,
871
871
  unbound_meth.__code__.co_varnames,
872
872
  )
873
- # print self.field.name
873
+ # print(f"20241003 {self.field.name} {ar} full_value_from_object()")
874
874
  return unbound_meth(obj, ar)
875
875
 
876
876
  def value_from_object(self, obj, request=None):
@@ -879,7 +879,7 @@ class MethodStoreField(StoreField):
879
879
  self.name,
880
880
  unbound_meth.__code__.co_varnames,
881
881
  )
882
- # print self.field.name
882
+ # print(f"20241003 {self.field.name} value_from_object()")
883
883
  return unbound_meth(obj, request)
884
884
 
885
885
  # def obj2list(self,request,obj):
@@ -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)
@@ -1413,7 +1413,7 @@ class Store(BaseStore):
1413
1413
  for fld in fields:
1414
1414
  if fld is None:
1415
1415
  continue
1416
- # logger.info("20140429 Store.row2dict %s", fld)
1416
+ # logger.info("20241003 Store.row2dict %s", fld)
1417
1417
  if fld.delayed_value:
1418
1418
  # self.actor.collect_extra_fields(fld)
1419
1419
  v = DelayedValue(ar, fld.name, row)
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,13 +15,13 @@ 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
21
22
  from lino.core import fields
22
23
  from lino.core import constants
23
24
  from lino.core.utils import resolve_fields_list
24
- from lino.core.tablerequest import TableRequest
25
25
 
26
26
  # class InvalidRequest(Exception):
27
27
  # pass
@@ -114,7 +114,7 @@ class TableHandle(object):
114
114
  def setup_layouts(self):
115
115
  if self._layouts is not None:
116
116
  return
117
- self._layouts = [self.list_layout]
117
+ self._layouts = [self.grid_layout]
118
118
 
119
119
  def get_actor_url(self, *args, **kw):
120
120
  return settings.SITE.kernel.get_actor_url(self.actor, *args, **kw)
@@ -122,12 +122,12 @@ class TableHandle(object):
122
122
  def submit_elems(self):
123
123
  return []
124
124
 
125
- def get_list_layout(self):
125
+ def get_grid_layout(self):
126
126
  self.setup_layouts()
127
127
  return self._layouts[0]
128
128
 
129
129
  def get_columns(self):
130
- lh = self.get_list_layout()
130
+ lh = self.get_grid_layout()
131
131
  # ~ print 20110315, layout._main.columns
132
132
  return lh.main.columns
133
133
 
@@ -229,7 +229,7 @@ class AbstractTable(actors.Actor):
229
229
  it. e.g. in case they need local filtering.
230
230
 
231
231
  This will be called with a
232
- :class:`lino.core.requests.TableRequest` object and is expected to
232
+ :class:`lino.core.requests.ActionRequest` object and is expected to
233
233
  return or yield the list of "rows"::
234
234
 
235
235
  @classmethod
@@ -315,32 +315,44 @@ class AbstractTable(actors.Actor):
315
315
  Usually such a warning means that there is something wrong.
316
316
  """
317
317
 
318
- display_mode = (
319
- (70, constants.DISPLAY_MODE_SUMMARY),
320
- (None, constants.DISPLAY_MODE_TABLE),
321
- )
318
+ default_display_modes = {
319
+ 70: constants.DISPLAY_MODE_SUMMARY,
320
+ None: constants.DISPLAY_MODE_GRID
321
+ }
322
322
  """
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.
323
+ Which :term:`display mode` to use in a :term:`slave panel`,
324
+ depending on available width.
325
325
 
326
- The last item should have a width of `None`.
327
-
328
- See also :meth:`get_display_mode` and :ref:`dev.format`.
326
+ See :ref:`dg.table.default_display_modes`.
329
327
  """
330
328
 
331
329
  @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]
330
+ # def get_display_mode(cls, available_width=None):
331
+ def get_display_mode(cls):
332
+ """Return the fallback :term:`display mode` to use."""
333
+ return cls.default_display_modes[None]
334
+ # rv = cls.default_display_modes[None]
335
+ # if available_width is not None:
336
+ # found = None
337
+ # for w, v in cls.default_display_modes.items():
338
+ # if w is not None and w <= available_width:
339
+ # if found is None:
340
+ # found = w
341
+ # rv = v
342
+ # elif found > w:
343
+ # found = w
344
+ # rv = v
345
+ # return rv
346
+ # current_choice = (0, None)
347
+ # for dmi in cls.display_mode:
348
+ # if available_width is not None:
349
+ # if dmi[0] is None and current_choice[0] == 0:
350
+ # current_choice = (0, dmi[1])
351
+ # elif available_width > dmi[0] > current_choice[0]:
352
+ # current_choice = dmi
353
+ # elif dmi[0] is None:
354
+ # return dmi[1]
355
+ # return current_choice[1]
344
356
 
345
357
  max_render_depth = 2
346
358
  """
@@ -613,6 +625,7 @@ method in order to sort the rows of the queryset.
613
625
  logger.warning(msg)
614
626
  # raise Exception(msg)
615
627
  # raise PermissionDenied(msg)
628
+ # raise BadRequest(msg)
616
629
  # master_instance = None
617
630
  return # cannot add rows to this table
618
631
  kw[self.master_field.name] = master_instance
@@ -628,19 +641,20 @@ method in order to sort the rows of the queryset.
628
641
 
629
642
  @classmethod
630
643
  def request(self, master_instance=None, **kw):
631
- """Return a new :class:`TableRequest
632
- <lino.core.tablerequest.TableRequest>` on this table.
644
+ """Return a new :class:`ActionRequest
645
+ <lino.core.requests.ActionRequest>` on this table.
633
646
 
634
- If this is a slave table, the :attr:`master_instance
635
- <lino.core.tablerequest.TableRequest.master_instance>` can be
647
+ The :attr:`master_instance
648
+ <lino.core.requests.ActionRequest.master_instance>` can be
636
649
  specified as optional first positional argument.
637
650
 
638
651
  """
652
+ from lino.core.requests import ActionRequest
639
653
  kw.update(actor=self)
640
654
  if master_instance is not None:
641
655
  kw.update(master_instance=master_instance)
642
656
  kw.setdefault("action", self.default_action)
643
- return TableRequest(**kw)
657
+ return ActionRequest(**kw)
644
658
 
645
659
  @classmethod
646
660
  def run_action_from_console(self, pk=None, an=None):
lino/core/utils.py CHANGED
@@ -953,3 +953,137 @@ def DelayedValue(ar, fieldname, obj):
953
953
  #
954
954
  # def __repr__(self):
955
955
  # return "DelayedValue({})".format(self.url)
956
+
957
+
958
+ class InstanceAction:
959
+ """
960
+ Volatile object that wraps a given action to be run on a given
961
+ model instance.
962
+
963
+ .. attribute:: bound_action
964
+
965
+ The bound action that will run.
966
+
967
+ .. attribute:: instance
968
+
969
+ The database object on which the action will run.
970
+
971
+ .. attribute:: owner
972
+
973
+
974
+ """
975
+
976
+ def __init__(self, action, actor, instance, owner):
977
+ # ~ print "Bar"
978
+ # ~ self.action = action
979
+ if actor is None:
980
+ actor = instance.get_default_table()
981
+ self.bound_action = actor.get_action_by_name(action.action_name)
982
+ if self.bound_action is None:
983
+ raise Exception("%s has not action %r" % (actor, action))
984
+ # Happened 20131020 from lino_xl.lib.beid.eid_info() :
985
+ # When `use_eid_jslib` was False, then
986
+ # `Action.attach_to_actor` returned False.
987
+ self.instance = instance
988
+ self.owner = owner
989
+
990
+ def __str__(self):
991
+ return "{0} on {1}".format(self.bound_action, obj2str(self.instance))
992
+
993
+ def run_from_code(self, ar, *args, **kw):
994
+ """
995
+ Probably to be deprecated.
996
+ Run this action on this instance in the given session, updating
997
+ the response of the session. Returns the return value of the
998
+ action.
999
+ """
1000
+ # raise Exception("20170129 is this still used?")
1001
+ ar.selected_rows = [self.instance]
1002
+ return self.bound_action.action.run_from_code(ar, *args, **kw)
1003
+
1004
+ def run_from_ui(self, ar, *args, **kwargs):
1005
+ """
1006
+ Run this action on this instance in the given session, updating
1007
+ the response of the session. Returns nothing.
1008
+ """
1009
+ # raise Exception("20170129 is this still used?")
1010
+ # kw.update(selected_rows=[self.instance])
1011
+ ar.selected_rows = [self.instance]
1012
+ self.bound_action.action.run_from_ui(ar, *args, **kwargs)
1013
+
1014
+ def request_from(self, ses, **kwargs):
1015
+ """
1016
+ Create an action request on this instance action without running
1017
+ the action.
1018
+ """
1019
+ kwargs.update(selected_rows=[self.instance])
1020
+ kwargs.update(parent=ses)
1021
+ ar = self.bound_action.request(**kwargs)
1022
+ return ar
1023
+
1024
+ def run_from_session(self, ses, **kwargs):
1025
+ """
1026
+ Run this instance action in a child request of given session.
1027
+
1028
+ Additional arguments are forwarded to the action.
1029
+ Returns the response of the child request.
1030
+ Doesn't modify response of parent request.
1031
+ """
1032
+ ar = self.request_from(ses, **kwargs)
1033
+ self.bound_action.action.run_from_code(ar)
1034
+ return ar.response
1035
+
1036
+ def __call__(self, *args, **kwargs):
1037
+ """
1038
+ Run this instance action in an anonymous base request.
1039
+
1040
+ Additional arguments are forwarded to the action.
1041
+ Returns the response of the base request.
1042
+ """
1043
+ if len(args) and isinstance(args[0], BaseRequest):
1044
+ raise ChangedAPI("20181004")
1045
+ ar = self.bound_action.request()
1046
+ self.run_from_code(ar, *args, **kwargs)
1047
+ return ar.response
1048
+
1049
+ def as_button_elem(self, ar, label=None, **kwargs):
1050
+ return settings.SITE.kernel.row_action_button(
1051
+ self.instance, ar, self.bound_action, label, **kwargs
1052
+ )
1053
+
1054
+ def as_button(self, *args, **kwargs):
1055
+ """Return a HTML chunk with a "button" which, when clicked, will
1056
+ execute this action on this instance. This is being used in
1057
+ the :ref:`lino.tutorial.polls`.
1058
+
1059
+ """
1060
+ return tostring(self.as_button_elem(*args, **kwargs))
1061
+
1062
+ def get_row_permission(self, ar):
1063
+ state = self.bound_action.actor.get_row_state(self.instance)
1064
+ # logger.info("20150202 ia.get_row_permission() %s using %s",
1065
+ # self, state)
1066
+ return self.bound_action.get_row_permission(ar, self.instance, state)
1067
+
1068
+
1069
+ class VirtualRow(object):
1070
+ def __init__(self, **kw):
1071
+ self.update(**kw)
1072
+
1073
+ def update(self, **kw):
1074
+ for k, v in list(kw.items()):
1075
+ setattr(self, k, v)
1076
+
1077
+ def get_row_permission(self, ar, state, ba):
1078
+ if ba.action.readonly:
1079
+ return True
1080
+ return False
1081
+
1082
+
1083
+ class PhantomRow(VirtualRow):
1084
+ def __init__(self, request, **kw):
1085
+ self._ar = request
1086
+ VirtualRow.__init__(self, **kw)
1087
+
1088
+ def __str__(self):
1089
+ return str(self._ar.get_action_title())
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
 
@@ -23,7 +23,7 @@ from lino.core import constants
23
23
 
24
24
  # from lino.core import auth
25
25
  from lino.core.requests import BaseRequest
26
- from lino.core.tablerequest import TableRequest
26
+ from lino.core.tables import AbstractTable
27
27
  from lino.core.views import action_request
28
28
  from lino.core.utils import navinfo
29
29
  from lino.utils.html import E, tostring
@@ -82,7 +82,7 @@ def table2html(ar, as_main=True):
82
82
  """Represent the given table request as an HTML table.
83
83
 
84
84
  `ar` is the request to be rendered, an instance of
85
- :class:`lino.core.tablerequest.TableRequest`.
85
+ :class:`lino.core.requests.ActionRequest`.
86
86
 
87
87
  The returned HTML enclosed in a ``<div>`` tag and generated using
88
88
  :mod:`etgen.html`.
@@ -191,7 +191,8 @@ class List(View):
191
191
  heading=ar.get_title(),
192
192
  )
193
193
 
194
- if isinstance(ar, TableRequest):
194
+ # if isinstance(ar, ActionRequest):
195
+ if ar .actor is not None and issubclass(ar.actor, AbstractTable):
195
196
  context.update(main=table2html(ar))
196
197
  else:
197
198
  context.update(main=layout2html(ar, None))
@@ -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