lino 25.5.1__py3-none-any.whl → 25.5.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 (54) 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 +60 -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 +3 -1
  10. lino/core/dbtables.py +4 -3
  11. lino/core/elems.py +33 -21
  12. lino/core/fields.py +40 -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 +5 -1
  18. lino/core/requests.py +13 -7
  19. lino/core/signals.py +1 -1
  20. lino/core/site.py +7 -8
  21. lino/core/store.py +13 -156
  22. lino/core/tables.py +10 -1
  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/export_excel/models.py +7 -3
  38. lino/modlib/extjs/ext_renderer.py +1 -1
  39. lino/modlib/extjs/views.py +5 -0
  40. lino/modlib/linod/mixins.py +10 -11
  41. lino/modlib/memo/mixins.py +1 -3
  42. lino/modlib/summaries/__init__.py +2 -2
  43. lino/modlib/uploads/ui.py +6 -8
  44. lino/modlib/users/fixtures/demo_users.py +16 -13
  45. lino/utils/choosers.py +11 -1
  46. lino/utils/diag.py +6 -4
  47. lino/utils/fieldutils.py +14 -11
  48. lino/utils/instantiator.py +4 -2
  49. lino/utils/report.py +5 -3
  50. {lino-25.5.1.dist-info → lino-25.5.3.dist-info}/METADATA +1 -1
  51. {lino-25.5.1.dist-info → lino-25.5.3.dist-info}/RECORD +54 -53
  52. {lino-25.5.1.dist-info → lino-25.5.3.dist-info}/WHEEL +0 -0
  53. {lino-25.5.1.dist-info → lino-25.5.3.dist-info}/licenses/AUTHORS.rst +0 -0
  54. {lino-25.5.1.dist-info → lino-25.5.3.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,9 @@ 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
- """
677
249
  # if not actor.editable and not self.readonly:
678
250
  # return False
679
251
  if self.defining_actor is not None:
@@ -690,50 +262,25 @@ class Action(Parametrizable, Permittable):
690
262
  # "tried to attach named action %s.%s as %s" %
691
263
  # (actor, self.action_name, name))
692
264
  self.action_name = name
693
- setup_params_choosers(self)
694
- # setup_params_choosers(self.__class__)
265
+ fields.setup_params_choosers(self)
266
+ # fields.setup_params_choosers(self.__class__)
695
267
  return True
696
268
 
697
269
  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
270
  return True
715
271
 
716
272
  def get_view_permission(self, user_type):
717
- """
718
- Return True if this action is visible for users of given user_type.
273
+ return self.get_action_view_permission(self.defining_actor, user_type)
274
+ # raise Exception("20250323 replaced by get_action_view_permission()")
719
275
 
720
- """
276
+ def get_action_view_permission(self, actor, user_type):
721
277
  return True
722
278
 
723
279
  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
280
  raise BadRequest("{} has no run_from_ui() method".format(
729
281
  self.__class__.__name__))
730
282
 
731
283
  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
284
  self.run_from_ui(ar, *args, **kwargs)
738
285
 
739
286
  def run_from_session(self, ses, *args, **kw): # 20130820
@@ -745,17 +292,6 @@ class Action(Parametrizable, Permittable):
745
292
  return ia.run_from_session(ses, **kw)
746
293
 
747
294
  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
295
  for k, pf in self.parameters.items():
760
296
  # print 20151203, pf.name, repr(pf.rel.to)
761
297
  kw[k] = pf.get_default()
@@ -765,27 +301,15 @@ class Action(Parametrizable, Permittable):
765
301
  pass
766
302
 
767
303
  def get_layout_aliases(self):
768
- """
304
+ return []
769
305
 
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
306
 
773
- """
774
- return []
307
+ action = Action.decorate
775
308
 
776
309
 
777
310
  class TableAction(Action):
778
311
  pass
779
312
 
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
313
 
790
314
  class ShowTable(TableAction):
791
315
  use_param_panel = True
@@ -846,9 +370,6 @@ class ShowDetail(Action):
846
370
 
847
371
 
848
372
  class ShowEmptyTable(ShowDetail):
849
- """
850
- The default action for :class:`lino.utils.report.EmptyTable`.
851
- """
852
373
 
853
374
  use_param_panel = True
854
375
  action_name = "show"
@@ -930,13 +451,15 @@ class ShowInsert(TableAction):
930
451
  if wl is not None:
931
452
  return wl.window_size
932
453
 
933
- def get_view_permission(self, user_type):
454
+ def get_action_view_permission(self, actor, user_type):
934
455
  # the action is readonly because it doesn't write to the
935
456
  # current object, but since it does modify the database we
936
457
  # want to hide it for readonly users.
458
+ if not actor.allow_create:
459
+ return False
937
460
  if user_type and user_type.readonly:
938
461
  return False
939
- return super().get_view_permission(user_type)
462
+ return super().get_action_view_permission(actor, user_type)
940
463
 
941
464
  def create_instance(self, ar):
942
465
  """
@@ -993,11 +516,6 @@ class ValidateForm(Action):
993
516
 
994
517
 
995
518
  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
519
 
1002
520
  sort_index = 10
1003
521
  show_in_workflow = False
@@ -1019,13 +537,6 @@ class SaveGridCell(Action):
1019
537
 
1020
538
 
1021
539
  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
540
 
1030
541
  sort_index = 100
1031
542
  icon_name = "disk"
@@ -1051,9 +562,6 @@ class SubmitDetail(SaveGridCell):
1051
562
 
1052
563
 
1053
564
  class CreateRow(Action):
1054
- """
1055
- Called when user edited a cell of a phantom record in a grid.
1056
- """
1057
565
 
1058
566
  sort_index = 10
1059
567
  auto_save = False
@@ -1188,10 +696,6 @@ class ExplicitRefresh(Action): # experimental 20170929
1188
696
 
1189
697
 
1190
698
  class ShowSlaveTable(Action):
1191
- """
1192
- An action that opens a window showing another table (to be
1193
- specified when instantiating the action).
1194
- """
1195
699
 
1196
700
  TABLE2ACTION_ATTRS = (
1197
701
  "icon_name",
@@ -1211,9 +715,10 @@ class ShowSlaveTable(Action):
1211
715
  self._help_text = help_text
1212
716
  super().__init__(**kw)
1213
717
 
1214
- @classmethod
1215
- def get_actor_label(self):
1216
- return self.get_label() or self.slave_table.label
718
+ # Removed 20250521 because I don't see why it is needed
719
+ # @classmethod
720
+ # def get_actor_label(self):
721
+ # return self.get_label() or self.slave_table.label
1217
722
 
1218
723
  def attach_to_actor(self, actor, name):
1219
724
  if isinstance(self.slave_table, str):
@@ -1246,15 +751,6 @@ class ShowSlaveTable(Action):
1246
751
 
1247
752
 
1248
753
  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
754
 
1259
755
  instance = None # for Renderer.menu_item_button()
1260
756
  show_in_toolbar = True
@@ -1308,7 +804,6 @@ class WrappedAction(Action):
1308
804
 
1309
805
 
1310
806
  class MultipleRowAction(Action):
1311
- """Base class for actions that update something on every selected row."""
1312
807
 
1313
808
  custom_handler = True
1314
809
 
@@ -1333,11 +828,6 @@ class MultipleRowAction(Action):
1333
828
 
1334
829
 
1335
830
  class DeleteSelected(MultipleRowAction):
1336
- """Delete the selected row(s).
1337
-
1338
- This action is automatically installed on every editable actor.
1339
-
1340
- """
1341
831
 
1342
832
  action_name = "delete_selected" # because...
1343
833
  if True: # settings.SITE.use_silk_icons:
@@ -1436,21 +926,9 @@ class DeleteSelected(MultipleRowAction):
1436
926
  return 1
1437
927
 
1438
928
 
1439
- action = Action.decorate
929
+ # Some actions are described by a single action instance used by most actors:
1440
930
 
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
931
+ SUBMIT_DETAIL = SubmitDetail()
932
+ DELETE_ACTION = DeleteSelected()
933
+ UPDATE_ACTION = SaveGridCell()
934
+ VALIDATE_FORM = ValidateForm()