lino 25.5.2__py3-none-any.whl → 25.6.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. lino/__init__.py +1 -1
  2. lino/api/dd.py +5 -3
  3. lino/api/doctest.py +2 -2
  4. lino/core/__init__.py +3 -3
  5. lino/core/actions.py +62 -582
  6. lino/core/actors.py +66 -32
  7. lino/core/atomizer.py +355 -0
  8. lino/core/boundaction.py +8 -4
  9. lino/core/constants.py +23 -1
  10. lino/core/dbtables.py +4 -3
  11. lino/core/elems.py +33 -21
  12. lino/core/fields.py +45 -210
  13. lino/core/kernel.py +18 -13
  14. lino/core/layouts.py +30 -57
  15. lino/core/model.py +6 -4
  16. lino/core/permissions.py +18 -0
  17. lino/core/renderer.py +15 -1
  18. lino/core/requests.py +19 -8
  19. lino/core/signals.py +1 -1
  20. lino/core/site.py +1 -1
  21. lino/core/store.py +13 -156
  22. lino/core/tables.py +5 -2
  23. lino/core/utils.py +124 -1
  24. lino/locale/bn/LC_MESSAGES/django.po +1034 -868
  25. lino/locale/de/LC_MESSAGES/django.mo +0 -0
  26. lino/locale/de/LC_MESSAGES/django.po +996 -892
  27. lino/locale/django.pot +968 -869
  28. lino/locale/es/LC_MESSAGES/django.po +1032 -869
  29. lino/locale/et/LC_MESSAGES/django.po +1032 -866
  30. lino/locale/fr/LC_MESSAGES/django.po +1034 -866
  31. lino/locale/nl/LC_MESSAGES/django.po +1040 -868
  32. lino/locale/pt_BR/LC_MESSAGES/django.po +1029 -868
  33. lino/locale/zh_Hant/LC_MESSAGES/django.po +1029 -868
  34. lino/mixins/duplicable.py +8 -2
  35. lino/mixins/registrable.py +1 -1
  36. lino/modlib/changes/utils.py +4 -3
  37. lino/modlib/extjs/ext_renderer.py +1 -1
  38. lino/modlib/extjs/views.py +6 -1
  39. lino/modlib/help/config/makehelp/plugin.tpl.rst +3 -1
  40. lino/modlib/help/management/commands/makehelp.py +1 -0
  41. lino/modlib/memo/mixins.py +1 -3
  42. lino/modlib/uploads/ui.py +6 -8
  43. lino/modlib/users/fixtures/demo_users.py +16 -13
  44. lino/utils/choosers.py +11 -1
  45. lino/utils/diag.py +17 -8
  46. lino/utils/fieldutils.py +14 -11
  47. lino/utils/instantiator.py +4 -2
  48. lino/utils/report.py +5 -3
  49. {lino-25.5.2.dist-info → lino-25.6.0.dist-info}/METADATA +1 -1
  50. {lino-25.5.2.dist-info → lino-25.6.0.dist-info}/RECORD +53 -52
  51. {lino-25.5.2.dist-info → lino-25.6.0.dist-info}/WHEEL +0 -0
  52. {lino-25.5.2.dist-info → lino-25.6.0.dist-info}/licenses/AUTHORS.rst +0 -0
  53. {lino-25.5.2.dist-info → lino-25.6.0.dist-info}/licenses/COPYING +0 -0
lino/core/actions.py CHANGED
@@ -1,464 +1,76 @@
1
1
  # -*- coding: UTF-8 -*-
2
- # Copyright 2009-2024 Rumma & Ko Ltd
2
+ # Copyright 2009-2025 Rumma & Ko Ltd
3
3
  # License: GNU Affero General Public License v3 (see file COPYING for details)
4
- """This defines the :class:`Action` class and the :func:`action`
5
- decorator, and some of the standard actions. See :ref:`dev.actions`.
4
+ # See src/core/actions.rst
6
5
 
7
- """
8
-
9
- from .utils import InstanceAction
6
+ from django.conf import settings
7
+ from django.utils.translation import gettext_lazy as _
8
+ from django.utils.text import format_lazy
9
+ from django.utils.encoding import force_str
10
+ from django.utils.translation import gettext
11
+ from lino.core import constants
12
+ from lino.core import keyboard
10
13
  from .utils import traverse_ddh_fklist
11
- from .utils import Parametrizable
12
14
  from .utils import navinfo
13
- from .utils import resolve_model
14
15
  from .utils import obj2unicode
15
- from .permissions import Permittable
16
- from lino.utils.choosers import check_for_chooser
17
- from lino.modlib.users.utils import get_user_profile
18
- from lino.core import keyboard
19
- from lino.core import fields
20
- from lino.core import layouts
21
- from lino.core import constants
22
- from lino import logger
16
+ # from .action import Action
23
17
 
24
- from django.utils.translation import gettext_lazy as _
25
- from django.utils.translation import gettext
26
- from django.utils.text import format_lazy
27
- from django.utils.encoding import force_str
28
- from django.conf import settings
29
- from django.db import models
30
18
  from django.core.exceptions import BadRequest
31
19
 
32
- from django.apps import apps
33
-
34
-
35
- def discover_choosers():
36
- logger.debug("Discovering choosers for database fields...")
37
- for model in apps.get_models():
38
- for field in model._meta.fields:
39
- check_for_chooser(model, field)
40
-
41
-
42
- def resolve_layout(cls, k, spec, layout_class, **options):
43
- # k: just for naming the culprit in error messages
44
- if isinstance(spec, str):
45
- if "\n" in spec or not "." in spec:
46
- return layout_class(spec, cls, **options)
47
- else:
48
- layout_class = settings.SITE.models.resolve(spec)
49
- if layout_class is None:
50
- raise Exception(
51
- "Unresolved {} {!r} for {}".format(k, spec, cls))
52
- return layout_class(None, cls, **options)
53
- elif isinstance(spec, layouts.Panel):
54
- options.update(spec.options)
55
- return layout_class(spec.desc, cls, **options)
56
- else:
57
- if not isinstance(spec, layout_class):
58
- if not isinstance(cls, type):
59
- # cls is an action instance
60
- cls = cls.__class__
61
- msg = (
62
- "{}.{}.{} must be a string, " "a Panel or an instance of {} (not {!r})"
63
- )
64
- raise Exception(
65
- msg.format(cls.__module__, cls.__name__,
66
- k, layout_class.__name__, spec)
67
- )
68
- if spec._datasource is None:
69
- spec.set_datasource(cls)
70
- return spec
71
- elif not issubclass(cls, spec._datasource):
72
- raise Exception(
73
- "Cannot reuse %s instance (%s of %r) for %r"
74
- % (spec.__class__, k, spec._datasource, cls)
75
- )
76
- return spec
77
-
78
-
79
- def install_layout(cls, k, layout_class, **options):
80
- """
81
- - `cls` is the actor (a class object)
82
-
83
- - `k` is one of 'grid_layout', 'detail_layout', 'insert_layout',
84
- 'params_layout', 'card_layout'
85
-
86
- - `layout_class`
87
-
88
- """
89
- # if str(cls) == 'courses.Pupils':
90
- # print("20160329 install_layout", k, layout_class)
91
- dl = cls.__dict__.get(k, None)
92
- if dl is None: # and not cls._class_init_done:
93
- dl = getattr(cls, k, None)
94
- if dl is None:
95
- return
96
- setattr(cls, k, resolve_layout(cls, k, dl, layout_class, **options))
97
-
98
-
99
- def register_params(cls):
100
- """`cls` is either an actor (a class object) or an action (an
101
- instance).
102
-
103
- """
104
- if cls.parameters is not None:
105
- for k, v in cls.parameters.items():
106
- v.set_attributes_from_name(k)
107
- v.table = cls
108
- # v.model = cls # 20181023 experimentally
109
-
110
- if cls.params_layout is None:
111
- cls.params_layout = cls._params_layout_class.join_str.join(
112
- cls.parameters.keys()
113
- )
114
- install_layout(cls, "params_layout", cls._params_layout_class)
115
-
116
- # e.g. accounting.ByJournal is just a mixin but provides a default value for its children
117
- elif cls.params_layout is not None:
118
- raise Exception("{} has a params_layout but no parameters".format(cls))
119
-
120
- # if isinstance(cls, type) and cls.__name__.endswith("Users"):
121
- # # if isinstance(cls, type) and cls.model is not None and cls.model.__name__ == "User":
122
- # # if str(cls.model) != "users.User":
123
- # # raise Exception("{} {}".format(cls, cls.model))
124
- # print("20200825 {}.register_params {} {}".format(
125
- # cls, cls.parameters, cls.params_layout))
126
-
127
-
128
- def setup_params_choosers(self):
129
- if self.parameters:
130
- for k, fld in self.parameters.items():
131
- if isinstance(fld, models.ForeignKey):
132
- msg = "Invalid target %s in parameter {} of {}".format(k, self)
133
- fld.remote_field.model = resolve_model(
134
- fld.remote_field.model, strict=msg
135
- )
136
- fields.set_default_verbose_name(fld)
137
-
138
- check_for_chooser(self, fld)
139
-
140
-
141
- def make_params_layout_handle(self):
142
- # `self` is either an Action instance or an Actor class object
143
- return self.params_layout.get_layout_handle()
20
+ from .utils import InstanceAction
21
+ from .permissions import Permittable
22
+ from lino.core import layouts
23
+ from lino.core import fields
24
+ from lino.core.utils import Parametrizable
25
+ # from lino.core.fields import setup_params_choosers
26
+ from lino.core.utils import register_params, make_params_layout_handle
144
27
 
145
28
 
146
29
  class Action(Parametrizable, Permittable):
147
- """
148
- Abstract base class for all actions.
149
-
150
- The first argument is the optional `label`, other arguments should
151
- be specified as keywords and can be any of the existing class
152
- attributes.
153
-
154
- """
155
-
156
- # ~ __metaclass__ = ActionMetaClass
157
30
  _params_layout_class = layouts.ActionParamsLayout
158
-
159
31
  label = None
160
32
  button_text: str = None
161
-
162
33
  button_color = None
163
- """
164
- The color to be used on icon-less buttons for this action
165
- (i.e. which have no :attr:`icon_name`). See also
166
- :attr:`lino.core.site.Site.use_silk_icons`.
167
-
168
- Not yet implemented. This is currently being ignored.
169
- """
170
-
171
34
  debug_permissions = False
172
35
  save_action_name = None
173
-
174
36
  disable_primary_key = True
175
- """
176
- Whether primary key fields should be disabled when using this
177
- action. This is `True` for all actions except :class:`ShowInsert`.
178
- """
179
-
180
37
  keep_user_values = False
181
- """
182
- Whether the parameter window should keep its values between
183
- different calls. If this is True, Lino does not fill any default
184
- values and leaves those from a previous call.
185
-
186
- Deprecated because it (1) is not used on any production site, (2) has a
187
- least two side effect: the fields *never* get a default value, even not on
188
- first execution, and you cannot explicitly specify programmatic field
189
- values. And (3) we actually wouldn't want to specify this per action but per
190
- field.
191
-
192
- """
193
38
  icon_name = None
194
- """
195
- The class name of an icon to be used for this action when
196
- rendered as toolbar button. Allowed icon names are defined in
197
- :data:`lino.core.constants.ICON_NAMES`.
198
-
199
- """
200
39
  ui5_icon_name = None
201
40
  react_icon_name = None
202
41
  hidden_elements = frozenset()
203
-
204
42
  combo_group = None
205
- """
206
- The name of another action to which to "attach" this action.
207
- Both actions will then be rendered as a single combobutton.
208
-
209
- """
210
43
  parameters = None
211
- """
212
- See :attr:`lino.core.utils.Parametrizable.parameters`.
213
- """
214
44
 
215
45
  use_param_panel = False
216
- """
217
- Used internally. This is True for window actions whose window use
218
- the parameter panel: grid and emptytable (but not showdetail)
219
-
220
- """
221
46
  no_params_window = False
222
- """
223
- Set this to `True` if your action has :attr:`parameters` but you
224
- do *not* want it to open a window where the user can edit these
225
- parameters before calling the action.
226
-
227
- Setting this attribute to `True` means that the calling code must
228
- explicitly set all parameter values. Usage example are the
229
- :attr:`lino_xl.lib.polls.models.AnswersByResponse.answer_buttons`
230
- and :attr:`lino_xl.lib-tickets.Ticket.quick_assign_to`
231
- virtual fields.
232
-
233
- """
234
47
  sort_index = 90
235
- """
236
- Determines the sort order in which the actions will be presented
237
- to the user.
238
-
239
- List actions are negative and come first.
240
-
241
- Predefined `sort_index` values are:
242
-
243
- ===== =================================
244
- value action
245
- ===== =================================
246
- -1 :class:`as_pdf <lino_xl.lib.appypod.PrintTableAction>`
247
- 10 :class:`ShowInsert`
248
- 11 :attr:`duplicate <lino.mixins.duplicable.Duplicable.duplicate>`
249
- 20 :class:`detail <ShowDetail>`
250
- 30 :class:`delete <DeleteSelected>`
251
- 31 :class:`merge <lino.core.merge.MergeAction>`
252
- 50 :class:`Print <lino.mixins.printable.BasePrintAction>`
253
- 51 :class:`Clear Cache <lino.mixins.printable.ClearCacheAction>`
254
- 52 :attr:`lino.modlib.users.UserPlan.start_plan`
255
- 53 :attr:`lino.modlib.users.UserPlan.update_plan`
256
- 60 :class:`ShowSlaveTable`
257
- 90 default for all custom row actions
258
- 100 :class:`SubmitDetail`
259
- 200 default for all workflow actions (:class:`ChangeStateAction <lino.core.workflows.ChangeStateAction>`)
260
- ===== =================================
261
-
262
-
263
- """
264
48
  help_text = None
265
49
 
266
50
  auto_save = True
267
- """
268
- What to do when this action is being called while the user is on a
269
- dirty record.
270
-
271
- - `False` means: forget any changes in current record and run the
272
- action.
273
-
274
- - `True` means: save any changes in current record before running
275
- the action. `None` means: ask the user.
276
-
277
- """
278
51
 
279
52
  extjs_main_panel = None
280
- """
281
- Used by :mod:`lino_xl.lib.extensible` and
282
- :mod:`lino.modlib.awesome_uploader`.
283
-
284
- Example::
285
-
286
- class CalendarAction(dd.Action):
287
- extjs_main_panel = "Lino.CalendarApp().get_main_panel()"
288
- ...
289
-
290
-
291
- """
292
-
293
53
  js_handler = None
294
- """
295
- This is usually `None`. Otherwise it is the name of a Javascript
296
- callable to be called without arguments. That callable must have
297
- been defined in a :attr:`lino.core.plugin.Plugin.site_js_snippets`
298
- of the plugin.
299
-
300
- Also can be defined as a class method, that takes the actor as the only
301
- argument and should return a JavaScript executable.
302
- An example use case is defined in
303
- :class:`lino.modlib.help.OpenHelpWindow` where the return string
304
- follows the format::
305
-
306
- return "let _ = window.open('URL')"
307
-
308
- Callable Example::
309
-
310
- def js_handler(self, actor):
311
- ...
312
- return JS_EXECUTABLE
313
-
314
- """
315
-
316
54
  action_name = None
317
- """
318
- Internally used to store the name of this action within the
319
- defining Actor's namespace.
320
-
321
- """
322
-
323
55
  defining_actor = None
324
- """
325
- The :class:`lino.core.actors.Actor` who uses this action for the
326
- first time. This is set during :meth:`attach_to_actor`. This is
327
- used internally e.g. by :mod:`lino.modlib.extjs` when generating
328
- JavaScript code for certain actions.
329
- """
330
-
331
56
  hotkey = None
332
- """
333
- An instance of :class:`lino.core.keyboard.Hotkey`. Used as a keyboard
334
- shortcut to trigger actions.
335
- """
336
-
337
57
  default_format = "html"
338
- """
339
- Used internally.
340
- """
341
-
342
58
  editable = True
343
- """
344
-
345
- Whether the parameter fields should be editable.
346
- Setting this to False seems nonsense.
347
- """
348
-
349
59
  readonly = True
350
-
351
60
  opens_a_window = False
352
-
353
61
  hide_top_toolbar = False # 20210509
354
- """
355
- This is set to `True` for :class:`ShowInsert`.
356
-
357
- As an applicationdeveloper you don't need this action attribute, but
358
- see :attr:`lino.core.actors.Actor.hide_top_toolbar`.
359
-
360
- """
361
62
  hide_navigator = False # 20210509
362
- """
363
- Hide navigator actions on window opened by this action.
364
-
365
- """
366
-
367
63
  never_collapse = False
368
- """
369
- When `True` the action will always be visible, regardless of whether
370
- the toolbar collapsed or not.
371
- """
372
-
373
64
  show_in_side_toolbar = False
374
-
375
65
  show_in_plain = False
376
- """
377
- Whether this action should be displayed as a button in the toolbar
378
- of a plain html view.
379
- """
380
-
381
66
  show_in_toolbar = True
382
- """
383
-
384
- Whether this action should be displayed in the toolbar.
385
-
386
- In ExtJS this will also cause it to be in the context menu of a grid.
387
-
388
- For example the :class:`CheckinVisitor
389
- <lino_xl.lib.reception.CheckinVisitor>`,
390
- :class:`ReceiveVisitor
391
- <lino_xl.lib.reception.ReceiveVisitor>` and
392
- :class:`CheckoutVisitor
393
- <lino_xl.lib.reception.CheckoutVisitor>` actions have this
394
- attribute explicitly set to `False` because otherwise they would be
395
- visible in the toolbar.
396
- """
397
-
398
67
  show_in_workflow = False
399
- """
400
- Whether this action should be displayed in the
401
- :attr:`workflow_buttons <lino.core.model.Model.workflow_buttons>`
402
- column. If this is True, then Lino will automatically set
403
- :attr:`custom_handler` to True.
404
- """
405
-
406
68
  custom_handler = False
407
- """
408
- Whether this action is implemented as Javascript function call.
409
- This is necessary if you want your action to be callable using an
410
- "action link" (html button).
411
- """
412
-
413
69
  select_rows = True
414
- """
415
- True if this action needs an object to act on.
416
-
417
- Set this to `False` if this action is a list action, not a row
418
- action.
419
- """
420
70
  http_method = "GET"
421
- """
422
- HTTP method to use when this action is called using an AJAX call.
423
-
424
- """
425
-
426
71
  preprocessor = "null" # None
427
- """
428
- Name of a Javascript function to be invoked on the web client when
429
- this action is called.
430
- """
431
-
432
72
  window_type = None
433
- """
434
- On actions that opens_a_window this must be a unique one-letter
435
- string expressing the window type.
436
-
437
- See `constants.WINDOW_TYPES`.
438
-
439
- Allowed values are:
440
-
441
- - None : opens_a_window is False
442
- - 't' : ShowTable
443
- - 'd' : ShowDetail
444
- - 'i' : ShowInsert
445
-
446
- This can be used e.g. by a summary view to decide how to present the
447
- summary data (usage example
448
- :meth:`lino.modlib.uploads.AreaUploads.get_table_summary`).
449
-
450
- """
451
-
452
73
  callable_from = "td"
453
- """
454
- A string that specifies from which :attr:`window_type` this action
455
- is callable. None means that it is only callable from code.
456
-
457
- Default value is 'td' which means from both table and detail
458
- (including ShowEmptyTable which is subclass of ShowDetail). But
459
- not callable from ShowInsert.
460
- """
461
-
462
74
  hide_virtual_fields = False
463
75
  required_states = None
464
76
  default_record_id = None
@@ -488,57 +100,40 @@ class Action(Parametrizable, Permittable):
488
100
 
489
101
  if self.callable_from is not None:
490
102
  for c in self.callable_from:
491
- if not c in constants.WINDOW_TYPES:
103
+ if c not in constants.WINDOW_TYPES:
492
104
  raise Exception(f"Invalid window_type spec {c} in {self}")
493
105
 
494
106
  def __get__(self, instance, owner):
495
- """
496
- When a model has an action "foo", then getting an attribute
497
- "foo" of a model instance will return an :class:`InstanceAction`.
498
- """
499
107
  if instance is None:
500
108
  return self
501
109
  return InstanceAction(self, None, instance, owner)
502
110
 
503
- # def get_a_href_target(self):
504
- # return None
505
-
506
- def get_django_form(self):
507
- """returns a django form object based on the params of this action"""
508
- from django import forms
509
-
510
- mapping = {"PasswordField": "CharField"}
511
-
512
- class LinoForm(forms.Form):
513
- pass
514
-
515
- for name, field in self.parameters.items():
516
- setattr(
517
- LinoForm,
518
- name,
519
- getattr(
520
- forms,
521
- mapping.get(field.__class__.__name__,
522
- field.__class__.__name__),
523
- )(),
524
- )
525
- return LinoForm
111
+ # def get_django_form(self):
112
+ # """returns a django form object based on the params of this action"""
113
+ # from django import forms
114
+ #
115
+ # mapping = {"PasswordField": "CharField"}
116
+ #
117
+ # class LinoForm(forms.Form):
118
+ # pass
119
+ #
120
+ # for name, field in self.parameters.items():
121
+ # setattr(
122
+ # LinoForm,
123
+ # name,
124
+ # getattr(
125
+ # forms,
126
+ # mapping.get(field.__class__.__name__,
127
+ # field.__class__.__name__),
128
+ # )(),
129
+ # )
130
+ # return LinoForm
526
131
 
527
132
  @classmethod
528
133
  def decorate(cls, *args, **kw):
529
- """
530
- Return a decorator that turns an instance method on a model or a
531
- class method on an actor into an action of this class.
532
-
533
- The decorated method will be installed as the actions's
534
- :meth:`run_from_ui <Action.run_from_ui>` method.
535
-
536
- All arguments are forwarded to :meth:`Action.__init__`.
537
-
538
- """
539
134
 
540
135
  def decorator(fn):
541
- assert not "required" in kw
136
+ assert "required" not in kw
542
137
  # print 20140422, fn.__name__
543
138
  kw.setdefault("custom_handler", True)
544
139
  a = cls(*args, **kw)
@@ -556,12 +151,6 @@ class Action(Parametrizable, Permittable):
556
151
  return actor.required_roles
557
152
 
558
153
  def is_callable_from(self, caller):
559
- """
560
- Return `True` if this action makes sense as a button from within
561
- the specified `caller` (an action instance which must have a
562
- :attr:`window_type`). Do not override this method on your
563
- subclass ; rather specify :attr:`callable_from`.
564
- """
565
154
  assert caller.window_type is not None
566
155
  if self.callable_from is None:
567
156
  return False
@@ -569,10 +158,6 @@ class Action(Parametrizable, Permittable):
569
158
  # return isinstance(caller, self.callable_from)
570
159
 
571
160
  def is_window_action(self):
572
- """Return `True` if this is a "window action" (i.e. which opens a GUI
573
- window on the client before executing).
574
-
575
- """
576
161
  return self.opens_a_window or (self.parameters and not self.no_params_window)
577
162
 
578
163
  def get_status(self, ar, **kw):
@@ -612,10 +197,6 @@ class Action(Parametrizable, Permittable):
612
197
  return options
613
198
 
614
199
  def get_label(self):
615
- """
616
- Return the `label` of this action, or the `action_name` if the
617
- action has no explicit label.
618
- """
619
200
  return self.label or self.action_name
620
201
 
621
202
  def get_button_label(self, actor):
@@ -662,18 +243,11 @@ class Action(Parametrizable, Permittable):
662
243
  assert self.action_name == name
663
244
  self.action_name = name
664
245
  self.defining_actor = wf
665
- setup_params_choosers(self)
246
+ fields.setup_params_choosers(self)
666
247
 
667
248
  def attach_to_actor(self, owner, name):
668
- """
669
- Called once per actor and per action on startup before a
670
- :class:`BoundAction` instance is created. If this returns
671
- False, then the action won't be attached to the given actor.
672
-
673
- The owner is the actor which "defines" the action, i.e. uses
674
- that instance for the first time. Subclasses of the owner may
675
- re-use the same instance without becoming the owner.
676
- """
249
+ if not owner.editable and not self.readonly:
250
+ return False
677
251
  # if not actor.editable and not self.readonly:
678
252
  # return False
679
253
  if self.defining_actor is not None:
@@ -690,50 +264,25 @@ class Action(Parametrizable, Permittable):
690
264
  # "tried to attach named action %s.%s as %s" %
691
265
  # (actor, self.action_name, name))
692
266
  self.action_name = name
693
- setup_params_choosers(self)
694
- # setup_params_choosers(self.__class__)
267
+ fields.setup_params_choosers(self)
268
+ # fields.setup_params_choosers(self.__class__)
695
269
  return True
696
270
 
697
271
  def get_action_permission(self, ar, obj, state):
698
- """Return (True or False) whether the given :class:`ActionRequest
699
- <lino.core.requests.BaseRequest>` `ar` should get permission
700
- to run on the given Model instance `obj` (which is in the
701
- given `state`).
702
-
703
- Derived Action classes may override this to add vetos.
704
- E.g. the MoveUp action of a Sequenced is not available on the
705
- first row of given `ar`.
706
-
707
- This should be used only for light-weight tests. If this
708
- requires a database lookup, consider disabling the action in
709
- :meth:`disabled_fields
710
- <lino.core.model.Model.disabled_fields>` where you can disable
711
- multiple actions and fields at once.
712
-
713
- """
714
272
  return True
715
273
 
716
274
  def get_view_permission(self, user_type):
717
- """
718
- Return True if this action is visible for users of given user_type.
275
+ return self.get_action_view_permission(self.defining_actor, user_type)
276
+ # raise Exception("20250323 replaced by get_action_view_permission()")
719
277
 
720
- """
278
+ def get_action_view_permission(self, actor, user_type):
721
279
  return True
722
280
 
723
281
  def run_from_ui(self, ar, **kwargs):
724
- """
725
- Execute the action. `ar` is a :class:`BaseRequest
726
- <lino.core.requests.BaseRequest>` object.
727
- """
728
282
  raise BadRequest("{} has no run_from_ui() method".format(
729
283
  self.__class__.__name__))
730
284
 
731
285
  def run_from_code(self, ar=None, *args, **kwargs):
732
- """
733
- Probably to be deprecated.
734
- Execute the action. The default calls :meth:`run_from_ui`. You
735
- may override this to define special behaviour
736
- """
737
286
  self.run_from_ui(ar, *args, **kwargs)
738
287
 
739
288
  def run_from_session(self, ses, *args, **kw): # 20130820
@@ -745,17 +294,6 @@ class Action(Parametrizable, Permittable):
745
294
  return ia.run_from_session(ses, **kw)
746
295
 
747
296
  def action_param_defaults(self, ar, obj, **kw):
748
- """Same as :meth:`lino.core.actors.Actor.param_defaults`, except that
749
- on an action it is a instance method.
750
-
751
- Note that this method is not called for actions which are rendered
752
- in a toolbar (:ticket:`1336`).
753
-
754
- Usage examples:
755
- :class:`lino.modlib.users.actions.SendWelcomeMail`
756
-
757
- """
758
-
759
297
  for k, pf in self.parameters.items():
760
298
  # print 20151203, pf.name, repr(pf.rel.to)
761
299
  kw[k] = pf.get_default()
@@ -765,27 +303,15 @@ class Action(Parametrizable, Permittable):
765
303
  pass
766
304
 
767
305
  def get_layout_aliases(self):
768
- """
306
+ return []
769
307
 
770
- Yield a series of (ALIAS, repl) tuples that cause a name ALIAS in a
771
- layout based on this action to be replaced by its replacement `repl`.
772
308
 
773
- """
774
- return []
309
+ action = Action.decorate
775
310
 
776
311
 
777
312
  class TableAction(Action):
778
313
  pass
779
314
 
780
- # def get_action_title(self, ar):
781
- # return ar.get_title()
782
-
783
-
784
- # class RedirectAction(Action):
785
-
786
- # def get_target_url(self, elem):
787
- # raise NotImplementedError
788
-
789
315
 
790
316
  class ShowTable(TableAction):
791
317
  use_param_panel = True
@@ -846,9 +372,6 @@ class ShowDetail(Action):
846
372
 
847
373
 
848
374
  class ShowEmptyTable(ShowDetail):
849
- """
850
- The default action for :class:`lino.utils.report.EmptyTable`.
851
- """
852
375
 
853
376
  use_param_panel = True
854
377
  action_name = "show"
@@ -930,13 +453,15 @@ class ShowInsert(TableAction):
930
453
  if wl is not None:
931
454
  return wl.window_size
932
455
 
933
- def get_view_permission(self, user_type):
456
+ def get_action_view_permission(self, actor, user_type):
934
457
  # the action is readonly because it doesn't write to the
935
458
  # current object, but since it does modify the database we
936
459
  # want to hide it for readonly users.
460
+ if not actor.allow_create:
461
+ return False
937
462
  if user_type and user_type.readonly:
938
463
  return False
939
- return super().get_view_permission(user_type)
464
+ return super().get_action_view_permission(actor, user_type)
940
465
 
941
466
  def create_instance(self, ar):
942
467
  """
@@ -993,11 +518,6 @@ class ValidateForm(Action):
993
518
 
994
519
 
995
520
  class SaveGridCell(Action):
996
- """
997
- Called when user edited a cell of a non-phantom record in a grid.
998
- Installed as `update_action` on every :class:`Actor`.
999
-
1000
- """
1001
521
 
1002
522
  sort_index = 10
1003
523
  show_in_workflow = False
@@ -1019,13 +539,6 @@ class SaveGridCell(Action):
1019
539
 
1020
540
 
1021
541
  class SubmitDetail(SaveGridCell):
1022
- """Save changes in the detail form.
1023
-
1024
- This is rendered as the "Save" button of a :term:`detail window`.
1025
-
1026
- Installed as `submit_detail` on every actor.
1027
-
1028
- """
1029
542
 
1030
543
  sort_index = 100
1031
544
  icon_name = "disk"
@@ -1051,9 +564,6 @@ class SubmitDetail(SaveGridCell):
1051
564
 
1052
565
 
1053
566
  class CreateRow(Action):
1054
- """
1055
- Called when user edited a cell of a phantom record in a grid.
1056
- """
1057
567
 
1058
568
  sort_index = 10
1059
569
  auto_save = False
@@ -1188,10 +698,6 @@ class ExplicitRefresh(Action): # experimental 20170929
1188
698
 
1189
699
 
1190
700
  class ShowSlaveTable(Action):
1191
- """
1192
- An action that opens a window showing another table (to be
1193
- specified when instantiating the action).
1194
- """
1195
701
 
1196
702
  TABLE2ACTION_ATTRS = (
1197
703
  "icon_name",
@@ -1211,9 +717,10 @@ class ShowSlaveTable(Action):
1211
717
  self._help_text = help_text
1212
718
  super().__init__(**kw)
1213
719
 
1214
- @classmethod
1215
- def get_actor_label(self):
1216
- return self.get_label() or self.slave_table.label
720
+ # Removed 20250521 because I don't see why it is needed
721
+ # @classmethod
722
+ # def get_actor_label(self):
723
+ # return self.get_label() or self.slave_table.label
1217
724
 
1218
725
  def attach_to_actor(self, actor, name):
1219
726
  if isinstance(self.slave_table, str):
@@ -1246,15 +753,6 @@ class ShowSlaveTable(Action):
1246
753
 
1247
754
 
1248
755
  class WrappedAction(Action):
1249
- """
1250
-
1251
- On instantiation it takes a :class:`BoundAction
1252
- <lino.core.boundaction.BoundAction>` as a positional argument and returns an
1253
- action instance that behaves as a wrapper around the given
1254
- *BoundAction.action* useful when binding to another :class:`Actor
1255
- <lino.core.actors.Actor>`.
1256
-
1257
- """
1258
756
 
1259
757
  instance = None # for Renderer.menu_item_button()
1260
758
  show_in_toolbar = True
@@ -1308,7 +806,6 @@ class WrappedAction(Action):
1308
806
 
1309
807
 
1310
808
  class MultipleRowAction(Action):
1311
- """Base class for actions that update something on every selected row."""
1312
809
 
1313
810
  custom_handler = True
1314
811
 
@@ -1333,11 +830,6 @@ class MultipleRowAction(Action):
1333
830
 
1334
831
 
1335
832
  class DeleteSelected(MultipleRowAction):
1336
- """Delete the selected row(s).
1337
-
1338
- This action is automatically installed on every editable actor.
1339
-
1340
- """
1341
833
 
1342
834
  action_name = "delete_selected" # because...
1343
835
  if True: # settings.SITE.use_silk_icons:
@@ -1436,21 +928,9 @@ class DeleteSelected(MultipleRowAction):
1436
928
  return 1
1437
929
 
1438
930
 
1439
- action = Action.decorate
931
+ # Some actions are described by a single action instance used by most actors:
1440
932
 
1441
-
1442
- def get_view_permission(e):
1443
- if isinstance(e, Permittable) and not e.get_view_permission(get_user_profile()):
1444
- return False
1445
- # e.g. pcsw.ClientDetail has a tab "Other", visible only to system
1446
- # admins but the "Other" contains a GridElement RolesByPerson
1447
- # which is not per se reserved for system admins. js of normal
1448
- # users should not try to call on_master_changed() on it
1449
- parent = e.parent
1450
- while parent is not None:
1451
- if isinstance(parent, Permittable) and not parent.get_view_permission(
1452
- get_user_profile()
1453
- ):
1454
- return False # bug 3 (bcss_summary) blog/2012/0927
1455
- parent = parent.parent
1456
- return True
933
+ SUBMIT_DETAIL = SubmitDetail()
934
+ DELETE_ACTION = DeleteSelected()
935
+ UPDATE_ACTION = SaveGridCell()
936
+ VALIDATE_FORM = ValidateForm()