lino 25.7.1__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.
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.1'
34
+ __version__ = '25.7.2'
35
35
 
36
36
  # import setuptools # avoid UserWarning "Distutils was imported before Setuptools"?
37
37
 
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"
@@ -966,7 +964,7 @@ class ShowEditor(Action):
966
964
  show_in_toolbar = False
967
965
 
968
966
  def __init__(self, fieldname, *args, **kwargs):
969
- self.fieldname = fieldname
967
+ self.buddy_name = fieldname
970
968
  super().__init__(*args, **kwargs)
971
969
 
972
970
  def run_from_ui(self, ar, **kwargs):
@@ -978,7 +976,7 @@ class ShowEditor(Action):
978
976
  ar.master_instance.__class__).pk
979
977
  })
980
978
  ar.set_response(goto_url=ar.renderer.front_end.build_plain_url(
981
- "api", *ar.actor.actor_id.split("."), str(ar.selected_rows[0].pk), self.fieldname, **kw))
979
+ "api", *ar.actor.actor_id.split("."), str(ar.selected_rows[0].pk), self.buddy_name, **kw))
982
980
 
983
981
 
984
982
  # Some actions are described by a single action instance used by most actors:
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
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
@@ -498,7 +498,7 @@ class HtmlRenderer(Renderer):
498
498
  return E.p(*buttons)
499
499
 
500
500
  def get_home_url(self, *args, **kw):
501
- 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)
502
502
 
503
503
  def obj2url(self, ar, obj):
504
504
  ba = obj.get_detail_action(ar)
lino/core/requests.py CHANGED
@@ -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)
lino/core/utils.py CHANGED
@@ -664,10 +664,11 @@ class Parametrizable:
664
664
  parameters = None
665
665
  params_layout = None
666
666
  params_panel_hidden = True
667
- params_panel_pos = "top" # allowed values "top", "bottom", "left" and "right"
667
+ params_panel_pos = "bottom" # allowed values "top", "bottom", "left" and "right"
668
668
  use_detail_param_panel = False
669
669
 
670
670
  _params_layout_class = NotImplementedError
671
+ _field_actions = dict()
671
672
 
672
673
  def get_window_layout(self, actor):
673
674
  return self.params_layout
@@ -1238,11 +1239,12 @@ def register_params(cls):
1238
1239
  cls.params_layout = cls._params_layout_class.join_str.join(
1239
1240
  cls.parameters.keys()
1240
1241
  )
1242
+ if cls.params_layout is not None:
1241
1243
  install_layout(cls, "params_layout", cls._params_layout_class)
1242
1244
 
1243
- # e.g. accounting.ByJournal is just a mixin but provides a default value for its children
1244
- elif cls.params_layout is not None:
1245
- raise Exception("{} has a params_layout but no parameters".format(cls))
1245
+ # # e.g. accounting.ByJournal is just a mixin but provides a default value for its children
1246
+ # elif cls.params_layout is not None:
1247
+ # raise Exception("{} has a params_layout but no parameters".format(cls))
1246
1248
 
1247
1249
  # if isinstance(cls, type) and cls.__name__.endswith("Users"):
1248
1250
  # # if isinstance(cls, type) and cls.model is not None and cls.model.__name__ == "User":
lino/help_texts.py CHANGED
@@ -312,7 +312,6 @@ help_texts = {
312
312
  'lino.core.model.Model.create_from_choice' : _("""Called when a learning combo has been submitted. Create a persistent database object if the given text contains enough information."""),
313
313
  'lino.core.model.Model.choice_text_to_dict' : _("""Return a dict of the fields to fill when the given text contains enough information for creating a new database object."""),
314
314
  'lino.core.model.Model.allow_cascaded_delete' : _("""A set of names of ForeignKey or GenericForeignKey fields of this model that allow for cascaded delete."""),
315
- 'lino.core.model.Model.disabled_fields' : _("""Return a set of field names that should be disabled (i.e. not editable) for this database object."""),
316
315
  'lino.core.model.Model.__str__' : _("""Return a translatable text that describes this database row."""),
317
316
  'lino.core.model.Model.as_str' : _("""Return a translatable text that describes this database row. Unlike __str__() this method gets an action request when it is called, so it knows the context."""),
318
317
  'lino.core.model.Model.get_str_words' : _("""Yield a series of words that describe this database row in plain text."""),
@@ -705,11 +704,13 @@ help_texts = {
705
704
  'lino.modlib.weasyprint.WeasyHtmlBuildMethod' : _("""Renders the input template and returns the unmodified output as plain HTML."""),
706
705
  'lino.modlib.weasyprint.WeasyPdfBuildMethod' : _("""Like WeasyBuildMethod, but the rendered HTML is then passed through weasyprint which converts from HTML to PDF."""),
707
706
  'lino.core.model.Model' : _("""Lino extension of Django’s database model. This is a subclass of Django’s Model class (django.db.models.Model)."""),
707
+ 'lino.core.model.Model.__init__' : _("""The first positional argument is the optional label, other arguments should be specified as keywords and can be any of the existing class attributes."""),
708
708
  'lino.core.model.Model.overview' : _("""A multi-paragraph representation of this database row."""),
709
709
  'lino.core.model.Model.navigation_panel' : _("""A virtual field that displays the navigation panel for this row. This may be included in a detail layout, usually either on the left or the right side with full height."""),
710
710
  'lino.core.model.Model.workflow_buttons' : _("""Shows the current workflow state of this database row and a list of available workflow actions."""),
711
711
  'lino.core.model.Model.workflow_state_field' : _("""Optional default value for the workflow_state_field of all data tables based on this model."""),
712
712
  'lino.core.model.Model.workflow_owner_field' : _("""Optional default value for workflow_owner_field on all data tables based on this model."""),
713
+ 'lino.core.model.Model.disabled_fields' : _("""Return a set of field names that should be disabled (i.e. not editable) for this database object."""),
713
714
  'lino.core.model.Model.FOO_changed' : _("""Called when field FOO of an instance of this model has been modified through the user interface."""),
714
715
  'lino.core.model.Model.FOO_choices' : _("""Return a queryset or list of allowed choices for field FOO."""),
715
716
  'lino.core.model.Model.create_FOO_choice' : _("""For every field named “FOO” for which a chooser exists, if the model also has a method called “create_FOO_choice”, then this chooser will be a learning chooser. That is, users can enter text into the combobox, and Lino will create a new database object from it."""),
@@ -118,12 +118,14 @@ class Registrable(model.Model):
118
118
  # yield 'date'
119
119
 
120
120
  def disabled_fields(self, ar):
121
+ rv = super().disabled_fields(ar)
121
122
  if not self.state.is_editable:
122
123
  # return self._registrable_fields
123
124
  # Copy _registrable_fields otherwise _registrable_fields get
124
125
  # modified as more disabled fields are added to the set.
125
- return self._registrable_fields.copy()
126
- return super().disabled_fields(ar)
126
+ # return self._registrable_fields.copy()
127
+ rv |= self._registrable_fields
128
+ return rv
127
129
 
128
130
  def get_row_permission(self, ar, state, ba):
129
131
  """Only rows in an editable state may be edited.
@@ -463,6 +463,13 @@ class ApiElement(View):
463
463
  else:
464
464
  datarec = ar.elem2rec_detailed(elem)
465
465
  datarec.update(**vm)
466
+
467
+ fa = dict()
468
+ for k, lst in rpt._field_actions.items():
469
+ fa[k] = [ar.row_action_button(elem, ba) for ba in lst]
470
+ if fa:
471
+ datarec.update(field_actions=fa)
472
+
466
473
  return json_response(datarec)
467
474
 
468
475
  after_show = ar.get_status(record_id=pk)
@@ -50,7 +50,7 @@ class Plugin(ad.Plugin):
50
50
  m = m.add_menu(mg.app_label, mg.verbose_name)
51
51
  m.add_action("linod.Procedures")
52
52
 
53
- def get_required_plugins(self):
53
+ def get_needed_plugins(self):
54
54
  # We don't use needs_plugins because it depends on use_channels. We must
55
55
  # not install the plugin when the Python package isn't installed because
56
56
  # otherwise `pm install` fails with ModuleNotFoundError: No module named
@@ -50,7 +50,7 @@ class Plugin(ad.Plugin):
50
50
  """The front end to use when writing previews.
51
51
 
52
52
  If this is `None`, Lino will use the default :term:`front end`
53
- (:attr:`lino.core.site.Site.web_front_ends`).
53
+ (:attr:`lino.core.site.Site.editing_front_end`).
54
54
 
55
55
  Used on sites that are available through more than one web front ends. The
56
56
  :term:`server administrator` must then decide which front end is the primary
@@ -115,7 +115,6 @@ class Plugin(ad.Plugin):
115
115
  def post_site_startup(self, site):
116
116
  if self.front_end is None:
117
117
  self.front_end = site.kernel.editing_front_end
118
- # web_front_ends[0]
119
118
  else:
120
119
  self.front_end = site.plugins.resolve(self.front_end)
121
120
 
@@ -1,7 +1,6 @@
1
1
  # Copyright 2015-2023 Rumma & Ko Ltd
2
2
  # License: GNU Affero General Public License v3 (see file COPYING for details)
3
3
 
4
- from lino.core.roles import UserRole
5
4
  from lino.modlib.uploads.roles import UploadsReader
6
5
 
7
6
 
@@ -294,19 +294,15 @@ class EditTemplate(BasePrintAction):
294
294
  ar.confirm(ok, msg, _("Are you sure?"))
295
295
 
296
296
 
297
- class ClearCacheAction(Action):
297
+ class ClearCache(Action):
298
298
  sort_index = 51
299
299
  url_action_name = "clear"
300
300
  label = _("Clear cache")
301
301
  icon_name = "printer_delete"
302
302
 
303
- # def disabled_for(self,obj,request):
304
- # if not obj.build_time:
305
- # return True
306
-
307
303
  def get_action_permission(self, ar, obj, state):
308
304
  # obj may be None when Lino asks whether this action
309
- # should be visible in the UI
305
+ # should be visible in the table toolbar
310
306
  if obj is not None and not obj.build_time:
311
307
  return False
312
308
  return super().get_action_permission(ar, obj, state)