lino 25.7.1__py3-none-any.whl → 25.7.3__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 (55) hide show
  1. lino/__init__.py +1 -1
  2. lino/api/doctest.py +41 -12
  3. lino/core/__init__.py +0 -2
  4. lino/core/actions.py +15 -7
  5. lino/core/actors.py +2 -162
  6. lino/core/atomizer.py +9 -8
  7. lino/core/auth/utils.py +9 -1
  8. lino/core/callbacks.py +2 -2
  9. lino/core/elems.py +1 -1
  10. lino/core/fields.py +3 -1
  11. lino/core/kernel.py +14 -18
  12. lino/core/layouts.py +5 -7
  13. lino/core/model.py +12 -3
  14. lino/core/plugin.py +1 -1
  15. lino/core/renderer.py +1 -1
  16. lino/core/requests.py +3 -4
  17. lino/core/site.py +1 -1
  18. lino/core/store.py +3 -3
  19. lino/core/utils.py +20 -17
  20. lino/help_texts.py +10 -7
  21. lino/mixins/__init__.py +3 -2
  22. lino/mixins/{duplicable.py → clonable.py} +45 -50
  23. lino/mixins/dupable.py +2 -2
  24. lino/mixins/registrable.py +7 -5
  25. lino/mixins/sequenced.py +12 -14
  26. lino/modlib/dupable/models.py +2 -2
  27. lino/modlib/extjs/views.py +7 -0
  28. lino/modlib/linod/__init__.py +1 -1
  29. lino/modlib/memo/__init__.py +1 -2
  30. lino/modlib/notify/api.py +5 -0
  31. lino/modlib/office/roles.py +0 -1
  32. lino/modlib/printing/actions.py +2 -6
  33. lino/modlib/printing/choicelists.py +6 -6
  34. lino/modlib/printing/mixins.py +4 -4
  35. lino/modlib/publisher/__init__.py +21 -30
  36. lino/modlib/publisher/models.py +3 -1
  37. lino/modlib/publisher/views.py +4 -11
  38. lino/modlib/summaries/mixins.py +6 -4
  39. lino/modlib/users/actions.py +5 -0
  40. lino/modlib/weasyprint/__init__.py +9 -0
  41. lino/modlib/weasyprint/choicelists.py +14 -9
  42. lino/modlib/weasyprint/config/weasyprint/base.weasy.html +15 -13
  43. lino/sphinxcontrib/__init__.py +1 -1
  44. lino/sphinxcontrib/actordoc.py +1 -1
  45. lino/utils/diag.py +2 -2
  46. lino/utils/instantiator.py +21 -1
  47. {lino-25.7.1.dist-info → lino-25.7.3.dist-info}/METADATA +1 -1
  48. {lino-25.7.1.dist-info → lino-25.7.3.dist-info}/RECORD +51 -55
  49. lino/modlib/forms/__init__.py +0 -51
  50. lino/modlib/forms/models.py +0 -0
  51. lino/modlib/forms/renderer.py +0 -74
  52. lino/modlib/forms/views.py +0 -311
  53. {lino-25.7.1.dist-info → lino-25.7.3.dist-info}/WHEEL +0 -0
  54. {lino-25.7.1.dist-info → lino-25.7.3.dist-info}/licenses/AUTHORS.rst +0 -0
  55. {lino-25.7.1.dist-info → lino-25.7.3.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.1'
34
+ __version__ = '25.7.3'
35
35
 
36
36
  # import setuptools # avoid UserWarning "Distutils was imported before Setuptools"?
37
37
 
lino/api/doctest.py CHANGED
@@ -14,6 +14,7 @@ tested document. It includes
14
14
 
15
15
  """
16
16
 
17
+ from lino.mixins.clonable import Clonable
17
18
  from lino.utils.fieldutils import get_fields, fields_help
18
19
  from lino.core.boundaction import BoundAction
19
20
  from lino.core.tables import AbstractTable
@@ -23,7 +24,9 @@ from lino.core.actions import ShowTable
23
24
  from lino.core.menus import Menu
24
25
  from lino.utils.html import html2text
25
26
  from lino.utils import dbhash
26
- from lino.core.utils import full_model_name, get_models
27
+ from lino.core.utils import get_models
28
+ from lino.core.utils import full_model_name
29
+ from lino.core.utils import full_model_name as fmn
27
30
  from lino.utils.diag import visible_for
28
31
  from lino.sphinxcontrib.actordoc import menuselection_text
29
32
  from lino import logger
@@ -83,6 +86,8 @@ HttpQuery = collections.namedtuple(
83
86
 
84
87
  # settings.SITE.is_testing = True
85
88
 
89
+ ADMIN_FRONT_END = settings.SITE.kernel.editing_front_end
90
+
86
91
 
87
92
  def get_json_dict(username, uri, an="detail", **kwargs):
88
93
  url = "/api/{0}?fmt=json&an={1}".format(uri, an)
@@ -381,27 +386,28 @@ def walk_menu_items(username=None, severe=True):
381
386
  mnu = settings.SITE.get_site_menu(user_type)
382
387
  items = []
383
388
  for mi in mnu.walk_items():
384
- if mi.bound_action:
389
+ if ba := mi.bound_action:
385
390
  item = menuselection_text(mi)
386
391
  # item += " ({})".format(mi)
387
392
  item += " : "
388
- if isinstance(mi.bound_action.action, ShowTable) and not mi.params:
393
+ if isinstance(ba.action, ShowTable) and not mi.params:
389
394
  # url = settings.SITE.kernel.default_ui.renderer.request_handler()
390
- # sar = mi.bound_action.request(parent=ar)
395
+ # sar = ba.request(parent=ar)
391
396
  if False:
392
- url = ar.get_permalink(mi.bound_action, mi.instance)
397
+ url = ar.get_permalink(ba, mi.instance)
393
398
  else:
394
- mt = mi.bound_action.actor
399
+ mt = ba.actor
395
400
  baseurl = "api/{}/{}".format(mt.app_label, mt.__name__)
396
401
  kwargs = dict(fmt="json")
397
- url = settings.SITE.buildurl(baseurl, **kwargs)
402
+ # url = settings.SITE.buildurl(baseurl, **kwargs)
403
+ url = ADMIN_FRONT_END.build_plain_url(baseurl, **kwargs)
404
+
398
405
  try:
399
406
  response = test_client.get(url)
400
407
  # response = test_client.get(url,
401
408
  # REMOTE_USER=str(username))
402
409
  result = check_json_result(
403
- response, None, "GET %s for user %s" % (url, username)
404
- )
410
+ response, None, f"GET {url} for user {username}")
405
411
  item += str(result["count"])
406
412
  # Also ask in display_mode "list" to cover assert_safe() bugs.
407
413
  # But not e.g. UserRoles because it's not on a model and doesn't have a list mode
@@ -409,10 +415,10 @@ def walk_menu_items(username=None, severe=True):
409
415
  kwargs[
410
416
  constants.URL_PARAM_DISPLAY_MODE
411
417
  ] = constants.DISPLAY_MODE_LIST
412
- url = settings.SITE.buildurl(baseurl, **kwargs)
418
+ url = ADMIN_FRONT_END.build_plain_url(baseurl, **kwargs)
413
419
  response = test_client.get(url)
414
420
  result = check_json_result(
415
- response, None, "GET %s for user %s" % (url, username)
421
+ response, None, f"GET {url} for user {username}"
416
422
  )
417
423
  # if ar is not None:
418
424
  # sar = mt.request(parent=ar)
@@ -743,7 +749,7 @@ def show_change_watchers():
743
749
  ws = m.change_watcher_spec
744
750
  if ws:
745
751
  rows.append(
746
- [full_model_name(m), ws.master_key, " ".join(sorted(ws.ignored_fields))]
752
+ [fmn(m), ws.master_key, " ".join(sorted(ws.ignored_fields))]
747
753
  )
748
754
  print(rstgen.table(headers, rows, max_width=40))
749
755
 
@@ -798,3 +804,26 @@ def checkdb(m, num):
798
804
  if m.objects.count() != num:
799
805
  raise Exception(
800
806
  f"Model {m} should have {num} rows but has {m.objects.count()}")
807
+
808
+
809
+ def show_clonables():
810
+ """
811
+ Print a list of all :class:`Clonable <lino.mixins.clonable.Clonable>`
812
+ models, together with their related slaves, i.e. the data that will be
813
+ cloned in cascade with their master.
814
+ """
815
+ items = []
816
+ for m in get_models():
817
+ if issubclass(m, Clonable):
818
+ rels = []
819
+ if (obj := m.objects.first()) is not None:
820
+ new, related = obj.duplication_plan()
821
+ for fk, qs in related:
822
+ rels.append(f"{fmn(qs.model)}.{fk.name}")
823
+ if len(rels):
824
+ x = ", ".join(rels)
825
+ items.append(f"{fmn(m)} : {x}")
826
+ else:
827
+ items.append(fmn(m))
828
+ items = sorted(items)
829
+ print(rstgen.ul(items).strip())
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"
@@ -100,6 +98,13 @@ class Action(Parametrizable, Permittable):
100
98
  raise Exception(
101
99
  "Unkonwn icon_name '{0}'".format(self.icon_name))
102
100
 
101
+ params = {}
102
+ if self.parameters is not None:
103
+ params.update(self.parameters)
104
+ self.setup_parameters(params)
105
+ if len(params):
106
+ self.parameters = params
107
+
103
108
  register_params(self)
104
109
 
105
110
  if self.callable_from is not None:
@@ -153,6 +158,9 @@ class Action(Parametrizable, Permittable):
153
158
 
154
159
  return decorator
155
160
 
161
+ def setup_parameters(self, params):
162
+ pass
163
+
156
164
  def get_help_text(self, ba):
157
165
  if ba is ba.actor.default_action:
158
166
  if self.default_record_id is not None:
@@ -966,7 +974,7 @@ class ShowEditor(Action):
966
974
  show_in_toolbar = False
967
975
 
968
976
  def __init__(self, fieldname, *args, **kwargs):
969
- self.fieldname = fieldname
977
+ self.buddy_name = fieldname
970
978
  super().__init__(*args, **kwargs)
971
979
 
972
980
  def run_from_ui(self, ar, **kwargs):
@@ -978,7 +986,7 @@ class ShowEditor(Action):
978
986
  ar.master_instance.__class__).pk
979
987
  })
980
988
  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))
989
+ "api", *ar.actor.actor_id.split("."), str(ar.selected_rows[0].pk), self.buddy_name, **kw))
982
990
 
983
991
 
984
992
  # 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/atomizer.py CHANGED
@@ -309,10 +309,10 @@ def fields_list(model, field_names):
309
309
  Return a set with the names of the specified fields, checking
310
310
  whether each of them exists.
311
311
 
312
- Arguments: `model` is any subclass of `django.db.models.Model`. It
313
- may be a string with the full name of a model
314
- (e.g. ``"myapp.MyModel"``). `field_names` is a single string with
315
- a space-separated list of field names.
312
+ Arguments: `model` is any subclass of `django.db.models.Model`. It may be a
313
+ string with the full name of a model (e.g. ``"myapp.MyModel"``).
314
+ `field_names` is an iterable of field names or a single string with a
315
+ space-separated list of field names.
316
316
 
317
317
  If one of the names refers to a dummy field, this name will be ignored
318
318
  silently.
@@ -326,16 +326,17 @@ def fields_list(model, field_names):
326
326
  iterable on the fields.
327
327
  """
328
328
  lst = set()
329
- names_list = field_names.split()
329
+ if isinstance(field_names, str):
330
+ field_names = field_names.split()
330
331
 
331
- for name in names_list:
332
+ for name in field_names:
332
333
  if name == "*":
333
334
  explicit_names = set()
334
335
  for name in names_list:
335
336
  if name != "*":
336
337
  explicit_names.add(name)
337
338
  for de in fields.wildcard_data_elems(model):
338
- if not isinstance(de, DummyField):
339
+ if not isinstance(de, fields.DummyField):
339
340
  if de.name not in explicit_names:
340
341
  if fields.use_as_wildcard(de):
341
342
  lst.add(de.name)
@@ -343,7 +344,7 @@ def fields_list(model, field_names):
343
344
  e = model.get_data_elem(name)
344
345
  if e is None:
345
346
  raise fields.FieldDoesNotExist(
346
- "No data element %r in %s" % (name, model))
347
+ f"No data element '{name}' in {model}")
347
348
  if not hasattr(e, "name"):
348
349
  raise fields.FieldDoesNotExist(
349
350
  "%s %r in %s has no name" % (e.__class__, name, model)
lino/core/auth/utils.py CHANGED
@@ -73,7 +73,8 @@ class AnonymousUser:
73
73
 
74
74
 
75
75
  def activate_social_auth_testing(
76
- globals_dict, google=True, github=True, wikimedia=True, facebook=True
76
+ globals_dict, google=True, github=True, wikimedia=True, facebook=True,
77
+ smart_id=False
77
78
  ):
78
79
  """
79
80
  Used for testing a development server.
@@ -97,6 +98,13 @@ def activate_social_auth_testing(
97
98
  # 'social_core.backends.google.GoogleOAuth2',
98
99
  # 'social_core.backends.google.GoogleOAuth',
99
100
  # 'social_core.backends.facebook.FacebookOAuth2',
101
+ if smart_id:
102
+ Site.social_auth_backends.append("lino.utils.smart_id.SmartID")
103
+ # https://oauth.ee/docs
104
+ globals_dict.update(
105
+ SOCIAL_AUTH_SMART_ID_KEY="xxx",
106
+ SOCIAL_AUTH_SMART_ID_SECRET="yyy",
107
+ )
100
108
  if wikimedia:
101
109
  Site.social_auth_backends.append("social_core.backends.mediawiki.MediaWiki")
102
110
  globals_dict.update(
lino/core/callbacks.py CHANGED
@@ -72,9 +72,9 @@ class Callback(object):
72
72
  - func: a callable to be executed when user selects this choice
73
73
  - the label of the button
74
74
  """
75
- assert not name in self.choices_dict
75
+ assert name not in self.choices_dict
76
76
  allowed_names = ("yes", "no", "ok", "cancel")
77
- if not name in allowed_names:
77
+ if name not in allowed_names:
78
78
  raise Exception("Sorry, name must be one of %s" % allowed_names)
79
79
  cbc = CallbackChoice(name, func, label)
80
80
  self.choices.append(cbc)
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
  """
@@ -1489,7 +1492,6 @@ class TableRow(object):
1489
1492
 
1490
1493
  def save_existing_instance(self, ar):
1491
1494
  watcher = ChangeWatcher(self)
1492
- # print("20210213 save_existing_instance", ar.ah, ar.rqdata, self.disabled_fields)
1493
1495
  ar.ah.store.form2obj(ar, ar.rqdata, self, False)
1494
1496
  self.full_clean()
1495
1497
  pre_ui_save.send(sender=self.__class__, instance=self, ar=ar)
lino/core/kernel.py CHANGED
@@ -230,25 +230,15 @@ class Kernel(object):
230
230
 
231
231
  Model.django2lino(model)
232
232
 
233
- if isinstance(model.hidden_columns, str):
234
- model.hidden_columns = frozenset(
235
- atomizer.fields_list(model, model.hidden_columns)
236
- )
237
-
238
- if isinstance(model.active_fields, str):
239
- model.active_fields = frozenset(
240
- atomizer.fields_list(model, model.active_fields)
241
- )
233
+ if model.allow_cascaded_copy is None:
234
+ model.allow_cascaded_copy = model.allow_cascaded_delete
242
235
 
243
- if isinstance(model.allow_cascaded_delete, str):
244
- model.allow_cascaded_delete = frozenset(
245
- atomizer.fields_list(model, model.allow_cascaded_delete)
246
- )
247
-
248
- if isinstance(model.allow_cascaded_copy, str):
249
- model.allow_cascaded_copy = frozenset(
250
- atomizer.fields_list(model, model.allow_cascaded_copy)
251
- )
236
+ # 'suppress_cascaded_copy'
237
+ for k in ('hidden_columns', 'active_fields',
238
+ 'allow_cascaded_delete', 'allow_cascaded_copy'):
239
+ # resolve_fields_list(model, k, frozenset, frozenset())
240
+ if (v := getattr(model, k, None)) is not None:
241
+ setattr(model, k, atomizer.fields_list(model, v))
252
242
 
253
243
  # Note how to inherit this from from parent model.
254
244
  if model.quick_search_fields is None:
@@ -610,6 +600,7 @@ class Kernel(object):
610
600
  # print("\n".join(["- {0.url_prefix} --> {0.app_name} {1}".format(p, hash(p))
611
601
  # for p in self.web_front_ends]))
612
602
 
603
+ editing_wf = None
613
604
  for p in self.web_front_ends:
614
605
  if p.ui_handle_attr_name is not None:
615
606
  editing_wf = p
@@ -686,6 +677,7 @@ class Kernel(object):
686
677
  # trigger creation of params_layout.params_store and
687
678
  # virtual fields in LightWeightContainer
688
679
  for res in actors.actors_list:
680
+ field_actions = dict()
689
681
  for ba in res.get_actions():
690
682
  # ba._started = True
691
683
  if ba.help_text is None:
@@ -694,6 +686,10 @@ class Kernel(object):
694
686
  ba.action.params_layout.get_layout_handle()
695
687
  if ba.action.is_window_action():
696
688
  ba.get_layout_handel()
689
+ if ba.action.buddy_name:
690
+ lst = field_actions.setdefault(ba.action.buddy_name, [])
691
+ lst.append(ba)
692
+ res._field_actions = field_actions
697
693
 
698
694
  # logger.info("20161219 kernel_startup done")
699
695
 
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)