lino 25.7.0__py3-none-any.whl → 25.7.2__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 (46) hide show
  1. lino/__init__.py +1 -1
  2. lino/api/dd.py +1 -0
  3. lino/api/doctest.py +13 -10
  4. lino/core/__init__.py +0 -2
  5. lino/core/actions.py +24 -5
  6. lino/core/actors.py +22 -177
  7. lino/core/dashboard.py +3 -2
  8. lino/core/dbtables.py +1 -1
  9. lino/core/elems.py +1 -1
  10. lino/core/fields.py +3 -0
  11. lino/core/kernel.py +6 -0
  12. lino/core/layouts.py +5 -7
  13. lino/core/model.py +1 -0
  14. lino/core/plugin.py +1 -1
  15. lino/core/renderer.py +7 -5
  16. lino/core/requests.py +4 -5
  17. lino/core/site.py +1 -1
  18. lino/core/utils.py +6 -4
  19. lino/help_texts.py +2 -1
  20. lino/mixins/registrable.py +4 -2
  21. lino/modlib/checkdata/management/commands/checkdata.py +3 -3
  22. lino/modlib/extjs/views.py +7 -0
  23. lino/modlib/help/models.py +3 -1
  24. lino/modlib/linod/__init__.py +1 -1
  25. lino/modlib/memo/__init__.py +1 -2
  26. lino/modlib/office/roles.py +0 -1
  27. lino/modlib/printing/actions.py +2 -6
  28. lino/modlib/printing/choicelists.py +6 -6
  29. lino/modlib/printing/mixins.py +2 -2
  30. lino/modlib/publisher/__init__.py +21 -30
  31. lino/modlib/publisher/models.py +3 -1
  32. lino/modlib/publisher/renderer.py +2 -5
  33. lino/modlib/publisher/views.py +4 -11
  34. lino/modlib/weasyprint/__init__.py +9 -0
  35. lino/modlib/weasyprint/choicelists.py +14 -9
  36. lino/modlib/weasyprint/config/weasyprint/base.weasy.html +15 -13
  37. lino/utils/jsgen.py +2 -1
  38. {lino-25.7.0.dist-info → lino-25.7.2.dist-info}/METADATA +1 -1
  39. {lino-25.7.0.dist-info → lino-25.7.2.dist-info}/RECORD +42 -46
  40. lino/modlib/forms/__init__.py +0 -51
  41. lino/modlib/forms/models.py +0 -0
  42. lino/modlib/forms/renderer.py +0 -74
  43. lino/modlib/forms/views.py +0 -311
  44. {lino-25.7.0.dist-info → lino-25.7.2.dist-info}/WHEEL +0 -0
  45. {lino-25.7.0.dist-info → lino-25.7.2.dist-info}/licenses/AUTHORS.rst +0 -0
  46. {lino-25.7.0.dist-info → lino-25.7.2.dist-info}/licenses/COPYING +0 -0
lino/__init__.py CHANGED
@@ -31,7 +31,7 @@ from django import VERSION
31
31
  from django.apps import AppConfig
32
32
  from django.conf import settings
33
33
  import warnings
34
- __version__ = '25.7.0'
34
+ __version__ = '25.7.2'
35
35
 
36
36
  # import setuptools # avoid UserWarning "Distutils was imported before Setuptools"?
37
37
 
lino/api/dd.py CHANGED
@@ -62,6 +62,7 @@ from lino.core.actions import ShowSlaveTable
62
62
  from lino.core.actions import ShowTable, ShowDetail
63
63
  from lino.core.actions import ShowInsert, DeleteSelected
64
64
  from lino.core.actions import SubmitDetail, SubmitInsert
65
+ from lino.core.actions import ShowEditor
65
66
 
66
67
  from lino.core.choicelists import ChoiceList, Choice
67
68
  from lino.core.workflows import State, Workflow, ChangeStateAction
lino/api/doctest.py CHANGED
@@ -83,6 +83,8 @@ HttpQuery = collections.namedtuple(
83
83
 
84
84
  # settings.SITE.is_testing = True
85
85
 
86
+ ADMIN_FRONT_END = settings.SITE.kernel.editing_front_end
87
+
86
88
 
87
89
  def get_json_dict(username, uri, an="detail", **kwargs):
88
90
  url = "/api/{0}?fmt=json&an={1}".format(uri, an)
@@ -381,27 +383,28 @@ def walk_menu_items(username=None, severe=True):
381
383
  mnu = settings.SITE.get_site_menu(user_type)
382
384
  items = []
383
385
  for mi in mnu.walk_items():
384
- if mi.bound_action:
386
+ if ba := mi.bound_action:
385
387
  item = menuselection_text(mi)
386
388
  # item += " ({})".format(mi)
387
389
  item += " : "
388
- if isinstance(mi.bound_action.action, ShowTable) and not mi.params:
390
+ if isinstance(ba.action, ShowTable) and not mi.params:
389
391
  # url = settings.SITE.kernel.default_ui.renderer.request_handler()
390
- # sar = mi.bound_action.request(parent=ar)
392
+ # sar = ba.request(parent=ar)
391
393
  if False:
392
- url = ar.get_permalink(mi.bound_action, mi.instance)
394
+ url = ar.get_permalink(ba, mi.instance)
393
395
  else:
394
- mt = mi.bound_action.actor
396
+ mt = ba.actor
395
397
  baseurl = "api/{}/{}".format(mt.app_label, mt.__name__)
396
398
  kwargs = dict(fmt="json")
397
- url = settings.SITE.buildurl(baseurl, **kwargs)
399
+ # url = settings.SITE.buildurl(baseurl, **kwargs)
400
+ url = ADMIN_FRONT_END.build_plain_url(baseurl, **kwargs)
401
+
398
402
  try:
399
403
  response = test_client.get(url)
400
404
  # response = test_client.get(url,
401
405
  # REMOTE_USER=str(username))
402
406
  result = check_json_result(
403
- response, None, "GET %s for user %s" % (url, username)
404
- )
407
+ response, None, f"GET {url} for user {username}")
405
408
  item += str(result["count"])
406
409
  # Also ask in display_mode "list" to cover assert_safe() bugs.
407
410
  # But not e.g. UserRoles because it's not on a model and doesn't have a list mode
@@ -409,10 +412,10 @@ def walk_menu_items(username=None, severe=True):
409
412
  kwargs[
410
413
  constants.URL_PARAM_DISPLAY_MODE
411
414
  ] = constants.DISPLAY_MODE_LIST
412
- url = settings.SITE.buildurl(baseurl, **kwargs)
415
+ url = ADMIN_FRONT_END.build_plain_url(baseurl, **kwargs)
413
416
  response = test_client.get(url)
414
417
  result = check_json_result(
415
- response, None, "GET %s for user %s" % (url, username)
418
+ response, None, f"GET {url} for user {username}"
416
419
  )
417
420
  # if ar is not None:
418
421
  # sar = mt.request(parent=ar)
lino/core/__init__.py CHANGED
@@ -10,7 +10,6 @@ For some modules the documentation has already been migrated to prosa:
10
10
  .. autosummary::
11
11
  :toctree:
12
12
 
13
- actors
14
13
  boundaction
15
14
  callbacks
16
15
  choicelists
@@ -31,7 +30,6 @@ For some modules the documentation has already been migrated to prosa:
31
30
  layouts
32
31
  menus
33
32
  merge
34
- model
35
33
  permissions
36
34
  renderer
37
35
  requests
lino/core/actions.py CHANGED
@@ -11,7 +11,7 @@ from django.utils.encoding import force_str
11
11
  from django.utils.translation import gettext
12
12
  from lino.core import constants
13
13
  from lino.core import keyboard
14
- from lino.core.exceptions import ChangedAPI
14
+ # from lino.core.exceptions import ChangedAPI
15
15
  from .utils import traverse_ddh_fklist
16
16
  from .utils import navinfo
17
17
  from .utils import obj2unicode
@@ -37,20 +37,17 @@ class Action(Parametrizable, Permittable):
37
37
  save_action_name = None
38
38
  disable_primary_key = True
39
39
  keep_user_values = False
40
- icon_name = None
40
+ icon_name: str = None
41
41
  ui5_icon_name = None
42
42
  react_icon_name = None
43
43
  hidden_elements = frozenset()
44
44
  combo_group = None
45
45
  parameters: dict[str, Any] | None = None
46
-
47
46
  use_param_panel = False
48
47
  no_params_window = False
49
48
  sort_index = 90
50
49
  help_text = None
51
-
52
50
  auto_save = True
53
-
54
51
  extjs_main_panel = None
55
52
  js_handler = None
56
53
  action_name = None
@@ -67,6 +64,7 @@ class Action(Parametrizable, Permittable):
67
64
  show_in_plain = False
68
65
  show_in_toolbar = True
69
66
  show_in_workflow = False
67
+ buddy_name: str = None
70
68
  custom_handler = False
71
69
  select_rows = True
72
70
  http_method = "GET"
@@ -960,6 +958,27 @@ class DeleteSelected(MultipleRowAction):
960
958
  return 1
961
959
 
962
960
 
961
+ class ShowEditor(Action):
962
+ select_rows = True
963
+ button_text = "⏏"
964
+ show_in_toolbar = False
965
+
966
+ def __init__(self, fieldname, *args, **kwargs):
967
+ self.buddy_name = fieldname
968
+ super().__init__(*args, **kwargs)
969
+
970
+ def run_from_ui(self, ar, **kwargs):
971
+ kw = dict()
972
+ if ar.master_instance:
973
+ kw.update({
974
+ constants.URL_PARAM_MASTER_PK: ar.master_instance.pk,
975
+ constants.URL_PARAM_MASTER_TYPE: settings.SITE.models.gfks.ContentType.objects.get_for_model(
976
+ ar.master_instance.__class__).pk
977
+ })
978
+ ar.set_response(goto_url=ar.renderer.front_end.build_plain_url(
979
+ "api", *ar.actor.actor_id.split("."), str(ar.selected_rows[0].pk), self.buddy_name, **kw))
980
+
981
+
963
982
  # Some actions are described by a single action instance used by most actors:
964
983
 
965
984
  SHOW_INSERT = ShowInsert()
lino/core/actors.py CHANGED
@@ -3,8 +3,8 @@
3
3
  # License: GNU Affero General Public License v3 (see file COPYING for details)
4
4
  """This defines :class:`Actor` and related classes.
5
5
 
6
- See :doc:`/dev/actors`.
7
-
6
+ Introduction see :doc:`/dev/actors`.
7
+ Class reference see :doc:`/src/lino/core/actors`.
8
8
 
9
9
  """
10
10
 
@@ -215,138 +215,21 @@ class ActorMetaClass(type):
215
215
  # class Actor(metaclass=ActorMetaClass, type('NewBase', (actions.Parametrizable, Permittable), {}))):
216
216
  class Actor(Parametrizable, Permittable, metaclass=ActorMetaClass):
217
217
  """
218
- The base class for all actors (:term:`data table`). Subclassed by
219
- :class:`AbstractTable <lino.core.tables.AbstractTable>`, :class:`Table
220
- <lino.core.dbtables.Table>`, :class:`ChoiceList
221
- <lino.core.choicelists.ChoiceList>` and :class:`Frame
222
- <lino.core.frames.Frame>`.
223
-
224
- .. attribute:: known_values
225
-
226
- A `dict` of `fieldname` -> `value` pairs that specify "known values".
227
-
228
- Requests will automatically be filtered to show only existing
229
- records with those values. This is like :attr:`filter`, but new
230
- instances created in this Table will automatically have these
231
- values set.
232
-
233
- .. attribute:: welcome_message_when_count
234
-
235
- Set this to an integer (e.g. 0) to tell Lino to make a generic
236
- welcome message "You have X items in Y" when the number of rows
237
- in this table is *greater than* the given integer.
238
-
239
- The following class methods are `None` in the default
240
- implementation. Subclass can define them.
241
-
242
- .. classmethod:: get_handle_name(self, ar)
243
-
244
- Most actors use the same UI handle for each request. But
245
- e.g. :class:`lino_welfare.modlib.debts.models.PrintEntriesByBudget`
246
- and :class:`lino_xl.lib.events.EventsByType` override this to
247
- implement dynamic columns depending on it's master_instance.
248
-
249
-
250
- .. classmethod:: get_welcome_messages(self, ar)
251
-
252
- If a method of this name is defined on an actor, then it must
253
- be a class method that takes an :class:`ar
254
- <lino.core.requests.BaseRequest>` as single argument and
255
- returns or yields a list of :term:`welcome messages <welcome
256
- message>` (messages to be displayed in the welcome block of
257
- :xfile:`admin_main.html`).
258
-
259
- Note that this handler will be called independently of whether
260
- the user has permission to view the actor or not.
261
218
  """
262
219
 
263
220
  _detail_action_class = None
264
221
 
265
222
  obvious_fields = set()
266
- """A set of names of fields that are considered :term:`obvious field`. """
267
-
268
223
  required_roles = set([SiteUser])
269
- """See :doc:`/dev/perms`."""
270
-
271
224
  model = None
272
- """The model on which this table iterates.
273
-
274
- The :term:`application developer` can specify either the model class itself
275
- or a string of type ``'app.Model'``.
276
-
277
- If this is not `None`, it should be a subclass of
278
- :class:`lino.core.fields.TableRow`.
279
-
280
- """
281
-
282
- # actions = None
283
- # """An :class:`AttrDict <atelier.utils.AttrDict>` containing the
284
- # actions available on this actor.
285
- #
286
- # """
287
-
288
225
  only_fields = None
289
-
290
226
  default_display_modes = None
291
- """
292
- Which :term:`display mode` to use in a :term:`slave panel`,
293
- depending on available width.
294
-
295
- See :ref:`dg.table.default_display_modes`.
296
- """
297
-
298
227
  # extra_display_modes = set()
299
228
  # extra_display_modes = {constants.DISPLAY_MODE_SUMMARY}
300
229
  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
- """
306
-
307
230
  app_label = None
308
- """
309
- Specify this if you want to "override" an existing actor.
310
-
311
- The default value is deduced from the module where the subclass is
312
- defined.
313
-
314
- Note that this attribute is not inherited from base classes.
315
-
316
- :func:`lino.core.dbtables.table_factory` also uses this.
317
- """
318
-
319
231
  master = None
320
- """The class of master instances for requests on this table.
321
-
322
- Application code usually doesn't need to specify this because it
323
- is automatically set on actors whose :attr:`master_key` is
324
- specified.
325
-
326
- Setting this to something else than `None` will turn the table
327
- into a :term:`slave table`.
328
-
329
- If the :attr:`master` is something else than a database model
330
- (e.g. a ChoiceList), then the actor must also define a
331
- :meth:`get_master_instance` method.
332
-
333
- """
334
-
335
232
  master_key = None
336
- """The name of a field of this table's :attr:`model` that
337
- points to its :attr:`master`.
338
-
339
- The field named by :attr:`master_key` should usually be a
340
- :class:`ForeignKey` field.
341
-
342
- Special cases: :class:`lino_xl.lib.cal.EntriesByGuest` shows the entries
343
- having a presence pointing to this guest.
344
-
345
- The :attr:`master_key` is automatically added to :attr:`hidden_columns`.
346
-
347
-
348
- """
349
-
350
233
  details_of_master_template = _("%(details)s of %(master)s")
351
234
 
352
235
  parameters = None
@@ -458,39 +341,12 @@ class Actor(Parametrizable, Permittable, metaclass=ActorMetaClass):
458
341
  get_handle_name = None
459
342
 
460
343
  abstract = True
461
- """
462
- Set this to `True` to prevent Lino from generating useless
463
- JavaScript if this is just an abstract base class to be inherited
464
- by other actors.
465
-
466
- Note that this class attribute is not inherited to subclasses.
467
-
468
- """
469
344
  sum_text_column = 0
470
345
 
471
346
  preview_limit = None
472
347
 
473
348
  handle_uploaded_files = None
474
- """
475
- Handler for uploaded files.
476
- Same remarks as for :attr:`lino.core.actors.Actor.disabled_fields`.
477
- """
478
-
479
349
  default_record_id = None
480
- """
481
- Turn this table into a :doc:`single-row table </topics/singlerow>`.
482
-
483
- When this is not `None`, you must also implement a custom version of
484
- :meth:`get_row_by_pk` that returns the same :term:`database row` regardless
485
- the given :term:`primary key`.
486
-
487
- This must currently be either `None` or ``'row'`` (or ``'myself'``).
488
-
489
- TODO: Rename this to `single_row` and make it a simple boolean so that the
490
- application developer can say ``single_row = True`` instead of
491
- ``default_record_id = 'row'``.
492
-
493
- """
494
350
 
495
351
  def __init__(self, *args, **kw):
496
352
  raise Exception("Actors should never get instantiated")
@@ -1682,11 +1538,6 @@ class Actor(Parametrizable, Permittable, metaclass=ActorMetaClass):
1682
1538
 
1683
1539
  @classmethod
1684
1540
  def get_toolbar_actions(self, parent, user_type):
1685
- """
1686
- Return a list of actions for which a button should exist in the
1687
- toolbar of the specified "parent" action.
1688
- """
1689
-
1690
1541
  for ba in self.get_button_actions(parent):
1691
1542
  if ba.action.show_in_toolbar and ba.get_view_permission(user_type):
1692
1543
  if ba.action.readonly or not self.hide_editing(user_type):
@@ -1703,14 +1554,6 @@ class Actor(Parametrizable, Permittable, metaclass=ActorMetaClass):
1703
1554
 
1704
1555
  @classmethod
1705
1556
  def get_button_actions(self, parent):
1706
- """
1707
- Return a sorted list of actions that should be available as
1708
- buttons in the specified `parent` (a window action).
1709
-
1710
- This is used (1) by :meth:`get_toolbar_actions` and (2) to
1711
- reduce the list of disabled actions in `disabled_fields` to
1712
- those which make sense. `dbtables.make_disabled_fields`
1713
- """
1714
1557
  if not parent.opens_a_window:
1715
1558
  # return []
1716
1559
  raise Exception(
@@ -1720,9 +1563,6 @@ class Actor(Parametrizable, Permittable, metaclass=ActorMetaClass):
1720
1563
 
1721
1564
  @classmethod
1722
1565
  def get_actions(self):
1723
- """
1724
- Return a sorted list of all bound actions offered by this actor.
1725
- """
1726
1566
  return self._actions_list
1727
1567
 
1728
1568
  @classmethod
@@ -1960,8 +1800,9 @@ class Actor(Parametrizable, Permittable, metaclass=ActorMetaClass):
1960
1800
  is_on_main_actor=False)
1961
1801
  grp = Grouper(sar)
1962
1802
  html_text = grp.begin()
1803
+ limit = ar.limit or cls.preview_limit
1963
1804
  for i, obj in enumerate(sar.data_iterator):
1964
- if i == cls.preview_limit:
1805
+ if i == limit:
1965
1806
  break
1966
1807
  par = sar.row_as_paragraph(obj) # 20230207
1967
1808
  # assert_safe(par) # temporary 20240506
@@ -1980,10 +1821,10 @@ class Actor(Parametrizable, Permittable, metaclass=ActorMetaClass):
1980
1821
  html_text += grp.after_row(obj)
1981
1822
  html_text += grp.stop()
1982
1823
 
1983
- # 20250713
1984
- if len(toolbar := sar.plain_toolbar_buttons()):
1985
- p = mark_safe(btn_sep.join([tostring(b) for b in toolbar]))
1986
- html_text = p + html_text
1824
+ # 20250713
1825
+ # if len(toolbar := sar.plain_toolbar_buttons()):
1826
+ # p = mark_safe(btn_sep.join([tostring(b) for b in toolbar]))
1827
+ # html_text = p + html_text
1987
1828
 
1988
1829
  # if cls.editable and cls.insert_action is not None:
1989
1830
  # ir = cls.insert_action.request_from(sar)
@@ -2000,8 +1841,9 @@ class Actor(Parametrizable, Permittable, metaclass=ActorMetaClass):
2000
1841
  is_on_main_actor=False)
2001
1842
  tiles = SAFE_EMPTY
2002
1843
  prev = None
1844
+ limit = ar.limit or cls.preview_limit
2003
1845
  for i, obj in enumerate(sar.data_iterator):
2004
- if i == cls.preview_limit:
1846
+ if i == limit:
2005
1847
  break
2006
1848
  tiles += obj.as_tile(sar, prev)
2007
1849
  prev = obj
@@ -2013,8 +1855,9 @@ class Actor(Parametrizable, Permittable, metaclass=ActorMetaClass):
2013
1855
  sar = cls.create_request(parent=ar, master_instance=obj,
2014
1856
  is_on_main_actor=False)
2015
1857
  html = SAFE_EMPTY
1858
+ limit = ar.limit or cls.preview_limit
2016
1859
  for i, obj in enumerate(sar.data_iterator):
2017
- if i == cls.preview_limit:
1860
+ if i == limit:
2018
1861
  break
2019
1862
  s = obj.as_story_item(sar)
2020
1863
  # assert_safe(s) # temporary 20240506
@@ -2080,14 +1923,16 @@ class Actor(Parametrizable, Permittable, metaclass=ActorMetaClass):
2080
1923
  # assert isinstance(p, str)
2081
1924
  # assert_safe(p) # temporary 20240506
2082
1925
  # assert not "&lt;" in p
2083
- toolbar = ar.plain_toolbar_buttons()
2084
- if len(toolbar):
2085
- # p += "<br/>"
2086
- # p += " | "
2087
- if p:
2088
- p += cls.summary_sep
2089
- for b in toolbar:
2090
- p += tostring(b) + btn_sep
1926
+ # No toolbar needed after 20250714 #6202 ("Tickets to work" has its
1927
+ # insert button (+) duplicated in the dashboard):
1928
+ # toolbar = ar.plain_toolbar_buttons()
1929
+ # if len(toolbar):
1930
+ # # p += "<br/>"
1931
+ # # p += " | "
1932
+ # if p:
1933
+ # p += cls.summary_sep
1934
+ # for b in toolbar:
1935
+ # p += tostring(b) + btn_sep
2091
1936
  return p
2092
1937
 
2093
1938
  @classmethod
lino/core/dashboard.py CHANGED
@@ -67,7 +67,7 @@ class DashboardItem(Permittable):
67
67
  if self.header_level is not None:
68
68
  buttons = sar.plain_toolbar_buttons()
69
69
  # 20250713 Maybe add the ⏏ button already in plain_toolbar_buttons()
70
- buttons.append(sar.open_in_own_window_button())
70
+ # buttons.append(sar.open_in_own_window_button())
71
71
  elems = []
72
72
  for b in buttons:
73
73
  elems.append(b)
@@ -93,7 +93,7 @@ class DashboardItem(Permittable):
93
93
  # yield tostring(e)
94
94
  else:
95
95
  raise Exception("20240908 Cannot render {}".format(sar))
96
- yield "Cannot render {}".format(sar)
96
+ # yield "Cannot render {}".format(sar)
97
97
  yield mark_safe("</div>")
98
98
 
99
99
  def serialize(self):
@@ -156,6 +156,7 @@ class ActorItem(DashboardItem):
156
156
  # sar = ar.spawn_request(actor=T, limit=T.preview_limit)
157
157
  # raise Exception("20230331 {}".format(ar.subst_user))
158
158
 
159
+ # print("20250714 render()", sar.limit)
159
160
  # print("20210112 render()", ar, sar, ar.get_user(), sar.get_user())
160
161
 
161
162
  for i in self.render_request(ar, sar, **kwargs):
lino/core/dbtables.py CHANGED
@@ -72,7 +72,7 @@ def add_gridfilters(qs, gridfilters):
72
72
  flttype = flt["type"]
73
73
  kw = {}
74
74
  if flttype == "string":
75
- if isinstance(field, models.CharField):
75
+ if isinstance(field, (models.CharField, models.FileField)):
76
76
  kw[field.name + "__icontains"] = flt["value"]
77
77
  q = q & models.Q(**kw)
78
78
  elif isinstance(field, models.ForeignKey):
lino/core/elems.py CHANGED
@@ -1103,7 +1103,7 @@ class ForeignKeyElement(ComplexRemoteComboFieldElement):
1103
1103
  if actor is None:
1104
1104
  return kw
1105
1105
 
1106
- if settings.SITE.kernel.web_front_ends[0].app_label == "react":
1106
+ if settings.SITE.kernel.editing_front_end.app_label == "react":
1107
1107
  options = dict(
1108
1108
  related_actor_id=actor.actor_id, allowBlank=kw.get(
1109
1109
  "allowBlank", False)
lino/core/fields.py CHANGED
@@ -370,6 +370,9 @@ class FakeField(object):
370
370
  # if self.verbose_name is None and self.name:
371
371
  # self.verbose_name = self.name.replace('_', ' ')
372
372
 
373
+ def get(self, *args, **kwargs):
374
+ return None
375
+
373
376
 
374
377
  class RemoteField(FakeField):
375
378
  """
lino/core/kernel.py CHANGED
@@ -610,6 +610,7 @@ class Kernel(object):
610
610
  # print("\n".join(["- {0.url_prefix} --> {0.app_name} {1}".format(p, hash(p))
611
611
  # for p in self.web_front_ends]))
612
612
 
613
+ editing_wf = None
613
614
  for p in self.web_front_ends:
614
615
  if p.ui_handle_attr_name is not None:
615
616
  editing_wf = p
@@ -686,6 +687,7 @@ class Kernel(object):
686
687
  # trigger creation of params_layout.params_store and
687
688
  # virtual fields in LightWeightContainer
688
689
  for res in actors.actors_list:
690
+ field_actions = dict()
689
691
  for ba in res.get_actions():
690
692
  # ba._started = True
691
693
  if ba.help_text is None:
@@ -694,6 +696,10 @@ class Kernel(object):
694
696
  ba.action.params_layout.get_layout_handle()
695
697
  if ba.action.is_window_action():
696
698
  ba.get_layout_handel()
699
+ if ba.action.buddy_name:
700
+ lst = field_actions.setdefault(ba.action.buddy_name, [])
701
+ lst.append(ba)
702
+ res._field_actions = field_actions
697
703
 
698
704
  # logger.info("20161219 kernel_startup done")
699
705
 
lino/core/layouts.py CHANGED
@@ -4,7 +4,6 @@
4
4
 
5
5
  """
6
6
 
7
- from lino import logger
8
7
  import re
9
8
 
10
9
  from django.conf import settings
@@ -14,6 +13,8 @@ from django.db.models.fields import NOT_PROVIDED
14
13
  from django.db.models.fields.related import ForeignObject
15
14
  # from django.contrib.contenttypes.fields import GenericRelation
16
15
 
16
+ from lino import logger
17
+ from lino.utils import jsgen
17
18
  from lino.core import constants
18
19
  from lino.core import atomizer
19
20
  from lino.core import fields
@@ -222,6 +223,7 @@ class LayoutHandle:
222
223
  if len(elems) == 1 and elemname != "main":
223
224
  elems[0].setup(**kwargs)
224
225
  return elems[0]
226
+
225
227
  from lino.core.elems import create_layout_panel
226
228
 
227
229
  return create_layout_panel(self, elemname, vertical, elems, **kwargs)
@@ -813,16 +815,12 @@ class ActionParamsLayout(ParamsLayout):
813
815
  url_param_name = constants.URL_PARAM_FIELD_VALUES
814
816
 
815
817
  def setup_element(self, lh, e):
816
- from lino.utils import jsgen
817
-
818
818
  e.declare_type = jsgen.DECLARE_THIS
819
819
 
820
820
  def get_choices_url(self, ui, field, **kw):
821
- return settings.SITE.kernel.web_front_ends[0].build_plain_url(
821
+ return settings.SITE.kernel.editing_front_end.build_plain_url(
822
822
  "apchoices",
823
823
  self._datasource.defining_actor.app_label,
824
824
  self._datasource.defining_actor.__name__,
825
825
  self._datasource.action_name,
826
- field.name,
827
- **kw,
828
- )
826
+ field.name, **kw)
lino/core/model.py CHANGED
@@ -994,6 +994,7 @@ LINO_MODEL_ATTRIBS = (
994
994
  "_lino_tables",
995
995
  "show_in_site_search",
996
996
  "allow_merge_action",
997
+ "get_overview_elems",
997
998
  )
998
999
 
999
1000
 
lino/core/plugin.py CHANGED
@@ -65,7 +65,7 @@ class Plugin:
65
65
  raise Exception("%s has no attribute %s" % (self, k))
66
66
  setattr(self, k, v)
67
67
 
68
- def get_required_plugins(self):
68
+ def get_needed_plugins(self):
69
69
  return self.needs_plugins
70
70
 
71
71
  def get_used_libs(self, html=None):
lino/core/renderer.py CHANGED
@@ -19,6 +19,7 @@ from django.utils.translation import gettext as _
19
19
  from django.utils.translation import get_language
20
20
 
21
21
  from etgen.html2rst import RstTable
22
+ from etgen.utils import join_elems
22
23
  # from lino import logger
23
24
  from lino.utils import isiterable
24
25
  from lino.utils.jsgen import py2js, js_code
@@ -497,7 +498,7 @@ class HtmlRenderer(Renderer):
497
498
  return E.p(*buttons)
498
499
 
499
500
  def get_home_url(self, *args, **kw):
500
- return settings.SITE.kernel.web_front_ends[0].build_plain_url(*args, **kw)
501
+ return settings.SITE.kernel.editing_front_end.build_plain_url(*args, **kw)
501
502
 
502
503
  def obj2url(self, ar, obj):
503
504
  ba = obj.get_detail_action(ar)
@@ -836,10 +837,11 @@ class TextRenderer(HtmlRenderer):
836
837
  # print(f"20240929 {nosummary} {display_mode} {ar}")
837
838
  # yield "20240506 {}".format(ar)
838
839
  if display_mode == constants.DISPLAY_MODE_SUMMARY:
839
- s = to_rst(
840
- ar.actor.get_table_summary(ar),
841
- stripped=stripped,
842
- )
840
+ s = to_rst(tostring(E.span(*join_elems(ar.plain_toolbar_buttons()))),
841
+ stripped=stripped)
842
+ if s:
843
+ s += " | "
844
+ s += to_rst(ar.actor.get_table_summary(ar), stripped=stripped)
843
845
  if stripped:
844
846
  s = s.strip()
845
847
  yield s
lino/core/requests.py CHANGED
@@ -1562,7 +1562,7 @@ class BaseRequest:
1562
1562
  # assert iselement(btn)
1563
1563
  buttons.append(btn)
1564
1564
  # print("20181106", cls, self.bound_action, buttons)
1565
- # 20250713 buttons.append(self.open_in_own_window_button())
1565
+ buttons.append(self.open_in_own_window_button()) # 20250713
1566
1566
  return buttons
1567
1567
  # if len(buttons) == 0:
1568
1568
  # return None
@@ -1842,10 +1842,9 @@ class ActionRequest(BaseRequest):
1842
1842
  pv = self.actor.param_defaults(self)
1843
1843
  for k in pv.keys():
1844
1844
  if k not in self.actor.parameters:
1845
- raise Exception(
1846
- "%s.param_defaults() returned invalid keyword %r"
1847
- % (self.actor, k)
1848
- )
1845
+ msg = f"{self.actor} param_defaults() returned keyword {k}"
1846
+ msg += f" (must be one of {sorted(self.actor.parameters.keys())})"
1847
+ raise Exception(msg)
1849
1848
 
1850
1849
  # New since 20120913. E.g. newcomers.Newcomers is a
1851
1850
  # simple pcsw.Clients with
lino/core/site.py CHANGED
@@ -862,7 +862,7 @@ class Site(object):
862
862
  needed_by = ip
863
863
  # while needed_by.needed_by is not None:
864
864
  # needed_by = needed_by.needed_by
865
- for dep in ip.get_required_plugins():
865
+ for dep in ip.get_needed_plugins():
866
866
  k2 = dep.rsplit(".")[-1]
867
867
  if k2 not in self.plugins:
868
868
  install_plugin(dep, needed_by=needed_by)