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/mixins/duplicable.py CHANGED
@@ -34,16 +34,18 @@ class Duplicate(actions.Action):
34
34
 
35
35
  # required_roles = set([Expert])
36
36
 
37
- def get_view_permission(self, user_type):
37
+ def get_action_view_permission(self, actor, user_type):
38
38
  # the action is readonly because it doesn't write to the
39
39
  # current object, but since it does modify the database we
40
40
  # want to hide it for readonly users.
41
+ if not actor.allow_create:
42
+ return False
41
43
  if user_type:
42
44
  if user_type.readonly:
43
45
  return False
44
46
  # if not user_type.has_required_roles([Expert]):
45
47
  # return False
46
- return super().get_view_permission(user_type)
48
+ return super().get_action_view_permission(actor, user_type)
47
49
 
48
50
  def run_from_code(self, ar, **known_values):
49
51
  obj = ar.selected_rows[0]
@@ -98,6 +100,10 @@ class Duplicate(actions.Action):
98
100
  def run_from_ui(self, ar, **kw):
99
101
  """This actually runs the action."""
100
102
 
103
+ if (msg := ar.actor.model.disable_create(ar)) is not None:
104
+ ar.error(msg)
105
+ return
106
+
101
107
  def ok(ar2):
102
108
  new = self.run_from_code(ar)
103
109
  kw = dict()
@@ -109,7 +109,7 @@ class Registrable(model.Model):
109
109
 
110
110
  @classmethod
111
111
  def get_registrable_fields(self, site):
112
- for f in super(MyModel, self).get_registrable_fields(site):
112
+ for f in super().get_registrable_fields(site):
113
113
  yield f
114
114
  yield 'user'
115
115
  yield 'date'
@@ -4,7 +4,8 @@
4
4
  Defines the :func:`watch_changes` function and :class:`WatcherSpec` class.
5
5
 
6
6
  """
7
- from lino.core import fields
7
+ from lino.core.fields import RemoteField
8
+ from lino.core.atomizer import fields_list
8
9
 
9
10
 
10
11
  class WatcherSpec:
@@ -40,13 +41,13 @@ def return_self(obj):
40
41
 
41
42
  def watch_changes(model, ignore=[], master_key=None, **options):
42
43
  if isinstance(ignore, str):
43
- ignore = fields.fields_list(model, ignore)
44
+ ignore = fields_list(model, ignore)
44
45
  if isinstance(master_key, str):
45
46
  fld = model.get_data_elem(master_key)
46
47
  if fld is None:
47
48
  raise Exception("No field %r in %s" % (master_key, model))
48
49
  master_key = fld
49
- if isinstance(master_key, fields.RemoteField):
50
+ if isinstance(master_key, RemoteField):
50
51
  get_master = master_key.func
51
52
  elif master_key is None:
52
53
  get_master = return_self
@@ -2,12 +2,11 @@
2
2
  # Copyright 2014-2025 Rumma & Ko Ltd
3
3
  # License: GNU Affero General Public License v3 (see file COPYING for details)
4
4
 
5
- import os
6
-
7
5
  from django.conf import settings
8
6
  from django.db.models import Model
9
7
  from django.utils.functional import Promise
10
8
  from django.utils.html import SafeString
9
+ from django.utils.timezone import make_naive, is_aware
11
10
  from lino.utils.html import iselement, to_rst
12
11
 
13
12
  from lino.core import actions
@@ -100,6 +99,11 @@ def ar2workbook(ar, column_names=None):
100
99
  value = str(value)
101
100
  elif isinstance(value, Model):
102
101
  value = str(value)
102
+ elif isinstance(value, datetime.datetime):
103
+ # Excel does not support timezones in datetimes. The tzinfo in
104
+ # the datetime/time object must be set to None.
105
+ if is_aware(value):
106
+ value = make_naive()
103
107
  elif isinstance(value, str):
104
108
  # if it is a future.newstr, change it to a real string to avoid
105
109
  # ValueError: Cannot convert 'Hans Altenberg' to Excel
@@ -109,7 +113,7 @@ def ar2workbook(ar, column_names=None):
109
113
  if style is not None:
110
114
  cell.style = style
111
115
  cell.value = value
112
- except ValueError as e:
116
+ except ValueError:
113
117
  raise Exception("20190222 {} {}".format(
114
118
  value.__class__, value))
115
119
 
@@ -23,6 +23,7 @@ from lino.core import constants
23
23
  # from lino.core.renderer import JsRenderer
24
24
  from lino.core.renderer import JsCacheRenderer
25
25
  from lino.core.gfks import ContentType
26
+ from lino.core.permissions import get_view_permission
26
27
 
27
28
  from lino.core.actions import (
28
29
  ShowEmptyTable,
@@ -576,7 +577,6 @@ class ExtRenderer(JsCacheRenderer):
576
577
  )
577
578
 
578
579
  def before_row_edit(self, panel):
579
- from lino.core.actions import get_view_permission
580
580
 
581
581
  # ~ l.append("console.log('before_row_edit',record);")
582
582
  for e in panel.active_children:
@@ -49,6 +49,7 @@ from lino.utils import ucsv
49
49
  from lino import logger
50
50
  # from lino.utils import dblogger
51
51
  from lino.core import constants
52
+ from lino.core import actions
52
53
  from lino.core.fields import choices_for_field
53
54
  from lino.core.views import requested_actor, action_request
54
55
  from lino.core.views import json_response
@@ -440,6 +441,7 @@ class ApiElement(View):
440
441
 
441
442
  if ba.action.opens_a_window:
442
443
  if fmt == constants.URL_FORMAT_JSON:
444
+ # datarec = ba.action.get_datarec(pk)
443
445
  if pk == "-99999":
444
446
  elem = ar.create_instance()
445
447
  datarec = ar.elem2rec_insert(ar.ah, elem)
@@ -449,6 +451,9 @@ class ApiElement(View):
449
451
  elif elem is None:
450
452
  datarec = dict(
451
453
  success=False, message=NOT_FOUND % (rpt, pk))
454
+ elif isinstance(ba.action, actions.ShowInsert):
455
+ elem = ar.create_instance()
456
+ datarec = ar.elem2rec_insert(ar.ah, elem)
452
457
  else:
453
458
  datarec = ar.elem2rec_detailed(elem)
454
459
  datarec.update(**vm)
@@ -3,18 +3,14 @@
3
3
  # License: GNU Affero General Public License v3 (see file COPYING for details)
4
4
  # See https://dev.lino-framework.org/plugins/linod.html
5
5
 
6
- import logging
7
6
  import traceback
8
7
  import asyncio
9
- import pickle
10
8
  from datetime import timedelta
11
- from django.conf import settings
12
9
  from django.db import models
13
10
  from django.utils import timezone
14
11
  # from django.core.exceptions import ValidationError
15
12
  from asgiref.sync import sync_to_async, async_to_sync
16
13
 
17
- from lino import logger
18
14
  from lino.api import dd, _
19
15
  from lino.mixins import Sequenced
20
16
  from lino.modlib.system.mixins import RecurrenceSet
@@ -45,7 +41,8 @@ class RunNow(dd.Action):
45
41
 
46
42
  def run_from_ui(self, ar, **kwargs):
47
43
  # print("20231102 RunNow", ar.selected_rows)
48
- now = dd.now()
44
+ # now = dd.now()
45
+ now = timezone.now()
49
46
  for obj in ar.selected_rows:
50
47
  assert issubclass(obj.__class__, Runnable)
51
48
  if True: # dd.plugins.linod.use_channels:
@@ -155,7 +152,8 @@ class Runnable(Sequenced, RecurrenceSet):
155
152
  await ar.adebug("Start %s with logging level %s", self, self.log_level)
156
153
  # ar.info("Start %s with logging level %s", astr(self), self.log_level)
157
154
  # forget about any previous run:
158
- now = await sync_to_async(dd.now)()
155
+ # now = await sync_to_async(dd.now)()
156
+ now = timezone.now()
159
157
  self.last_start_time = now
160
158
  self.requested_at = None
161
159
  self.last_end_time = None
@@ -186,8 +184,8 @@ class Runnable(Sequenced, RecurrenceSet):
186
184
  self.disabled = True
187
185
  await ar.awarning("Disabled %s after exception %s", self, e)
188
186
  # ar.warning("Disabled %s after exception %s", astr(self), e)
189
- now = await sync_to_async(dd.now)()
190
- self.last_end_time = now
187
+ # now = await sync_to_async(dd.now)()
188
+ self.last_end_time = timezone.now()
191
189
  self.message = "<pre>" + self.message + "</pre>"
192
190
  await sync_to_async(self.full_clean)()
193
191
  # self.full_clean()
@@ -222,8 +220,8 @@ async def start_task_runner(ar=None, max_count=None):
222
220
  count = 0
223
221
  while True:
224
222
  await ar.adebug("Start next task runner loop.")
225
-
226
- now = await sync_to_async(dd.now)()
223
+ # now = await sync_to_async(dd.now)()
224
+ now = timezone.now()
227
225
  next_time = now + \
228
226
  timedelta(seconds=dd.plugins.linod.background_sleep_time)
229
227
 
@@ -286,7 +284,8 @@ async def start_task_runner(ar=None, max_count=None):
286
284
  if max_count is not None and count >= max_count:
287
285
  await ar.ainfo("Stop after %s loops.", max_count)
288
286
  return next_time
289
- now = await sync_to_async(dd.now)()
287
+ # now = await sync_to_async(dd.now)()
288
+ now = timezone.now()
290
289
  if (to_sleep := (next_time - now).total_seconds()) <= 0:
291
290
  continue
292
291
  await ar.adebug("Let task runner sleep for %s seconds.", to_sleep)
@@ -16,13 +16,11 @@ from django.utils.text import Truncator
16
16
  from django.utils.html import format_html
17
17
 
18
18
  from lino.core.gfks import gfk2lookup
19
- from lino.core.model import Model
20
- from lino.core.fields import fields_list, RichTextField, PreviewTextField
19
+ from lino.core.fields import RichTextField, PreviewTextField
21
20
  from lino.utils.html import E, tostring, mark_safe
22
21
  from lino.utils.restify import restify
23
22
  from lino.utils.soup import truncate_comment
24
23
  from lino.utils.mldbc.fields import BabelTextField
25
- from lino.core.exceptions import ChangedAPI
26
24
  from lino.modlib.checkdata.choicelists import Checker
27
25
  from lino.api import rt, dd, _
28
26
 
@@ -11,8 +11,8 @@ class Plugin(ad.Plugin):
11
11
  end_year = None
12
12
  duration_max_length = 6
13
13
 
14
- def on_init(self):
14
+ def pre_site_startup(self, site):
15
15
  if self.end_year is None:
16
- self.end_year = self.site.today().year
16
+ self.end_year = site.today().year
17
17
  if self.start_year is None:
18
18
  self.start_year = self.end_year - 2
lino/modlib/uploads/ui.py CHANGED
@@ -148,6 +148,11 @@ class MyUploads(My, Uploads):
148
148
  # return kw
149
149
 
150
150
 
151
+ def format_row_in_slave_summary(obj):
152
+ """almost as str(), but without the type"""
153
+ return obj.description or filename_leaf(obj.file.name) or str(obj.id)
154
+
155
+
151
156
  class AreaUploads(Uploads):
152
157
  # required_roles = dd.login_required((OfficeUser, OfficeOperator))
153
158
  required_roles = dd.login_required(UploadsReader)
@@ -165,11 +170,6 @@ class AreaUploads(Uploads):
165
170
  # return self._upload_area.text
166
171
  # return self._label or self.__name__
167
172
 
168
- @classmethod
169
- def format_row_in_slave_summary(self, ar, obj):
170
- """almost as str(), but without the type"""
171
- return obj.description or filename_leaf(obj.file.name) or str(obj.id)
172
-
173
173
  @classmethod
174
174
  def get_table_summary(self, ar):
175
175
  obj = ar.master_instance
@@ -198,9 +198,7 @@ class AreaUploads(Uploads):
198
198
  # logger.info("20140430 %s", sar.data_iterator.query)
199
199
  files = []
200
200
  for m in sar:
201
- text = self.format_row_in_slave_summary(ar, m)
202
- if text is None:
203
- continue
201
+ text = format_row_in_slave_summary(m)
204
202
  edit = ar.obj2html(
205
203
  m,
206
204
  text, # _("Edit"),
@@ -16,14 +16,14 @@ def root_user(lang, **kw):
16
16
  lang = lang.django_code
17
17
  kw.update(language=lang)
18
18
  lang = lang[:2]
19
- if lang == "de":
19
+ if lang == "en":
20
+ kw.update(first_name="Robin", last_name="Rood")
21
+ elif lang == "de":
20
22
  kw.update(first_name="Rolf", last_name="Rompen")
21
23
  elif lang == "fr":
22
24
  kw.update(first_name="Romain", last_name="Raffault")
23
25
  elif lang == "et":
24
26
  kw.update(first_name="Rando", last_name="Roosi")
25
- elif lang == "en":
26
- kw.update(first_name="Robin", last_name="Rood")
27
27
  elif lang == "pt":
28
28
  kw.update(first_name="Ronaldo", last_name="Rosa")
29
29
  elif lang == "es":
@@ -41,13 +41,16 @@ def root_user(lang, **kw):
41
41
 
42
42
  def objects():
43
43
  # logger.info("20150323 %s", settings.SITE.languages)
44
- User = settings.SITE.user_model
45
- if User is not None:
46
- for lang in settings.SITE.languages:
47
- if (
48
- settings.SITE.hidden_languages is None
49
- or not lang.django_code in settings.SITE.hidden_languages
50
- ):
51
- kw = root_user(lang)
52
- if kw:
53
- yield User(**kw)
44
+ SITE = settings.SITE
45
+ User = SITE.user_model
46
+ if User is None:
47
+ return
48
+ for lang in SITE.languages:
49
+ if (SITE.hidden_languages is None
50
+ or lang.django_code not in SITE.hidden_languages):
51
+ kw = root_user(lang)
52
+ if kw:
53
+ u = User(**kw)
54
+ if SITE.is_demo_site:
55
+ u.set_password(SITE.plugins.users.demo_password)
56
+ yield u
lino/utils/choosers.py CHANGED
@@ -21,7 +21,8 @@ from dateutil import parser as dateparser
21
21
  from django.core.exceptions import BadRequest
22
22
  from django.conf import settings
23
23
  from django.db import models
24
-
24
+ from django.apps import apps
25
+ from lino import logger
25
26
  from lino.core.utils import getrqdata
26
27
  from lino.core import fields
27
28
  from lino.core import constants
@@ -520,6 +521,8 @@ def check_for_chooser(holder, field):
520
521
  # raise Exception("20200425 {} is not {}".format(holder, ch.model))
521
522
  return ch
522
523
 
524
+ # print("20250518", holder, ch, d)
525
+
523
526
  methname = field.name + "_choices"
524
527
  m = getattr(holder, methname, None)
525
528
  if m is not None:
@@ -536,3 +539,10 @@ def check_for_chooser(holder, field):
536
539
  return ch
537
540
  # if field.name == 'city':
538
541
  # logger.info("20140822 chooser for %s.%s", holder, field.name)
542
+
543
+
544
+ def discover_choosers():
545
+ logger.debug("Discovering choosers for database fields...")
546
+ for model in apps.get_models():
547
+ for field in model._meta.fields:
548
+ check_for_chooser(model, field)
lino/utils/diag.py CHANGED
@@ -13,7 +13,6 @@ from django.utils.encoding import force_str
13
13
 
14
14
  from lino.modlib.system.choicelists import PeriodEvents
15
15
  from lino.core.layouts import BaseLayout
16
- from lino.core.layouts import ParamsLayout
17
16
  from lino.core.fields import DummyField
18
17
  from lino.core.elems import Container, Wrapper, FieldElement
19
18
  from lino.modlib.users.choicelists import UserTypes
@@ -21,6 +20,7 @@ from lino.core import actors
21
20
  from lino.core import actions
22
21
  from lino.core.utils import get_models, sorted_models_list
23
22
  from lino.core.utils import full_model_name as fmn
23
+ from lino.core.atomizer import get_atomizer
24
24
  from lino.api import dd
25
25
 
26
26
 
@@ -40,12 +40,15 @@ class Analyzer(object):
40
40
  if a.abstract:
41
41
  continue
42
42
  for ba in a.get_actions():
43
+ # if ba.actor.abstract:
44
+ # raise Exception(f"20250523 {repr(a)} {repr(ba.actor)}")
43
45
  if ba.action.is_window_action():
44
46
  # We call ba.get_layout_handel() just to increase test
45
47
  # coverage. For example #5608 (presto says Invalid data
46
48
  # element 'topics.InterestsByPartner') by the following
47
49
  # line:
48
- ba.get_layout_handel()
50
+ # ba.get_layout_handel()
51
+ # 20250523 this is now done by the kernel
49
52
 
50
53
  wl = ba.get_window_layout() or ba.action.params_layout
51
54
  if wl is not None:
@@ -395,7 +398,7 @@ def visible_for(ba):
395
398
  return "nobody"
396
399
  # if len(hidden) < len(visible):
397
400
  # if len(hidden) <= 3:
398
- # return "all except %s" % ', '.join(hidden)
401
+ # return "all except %s" % ' '.join(hidden)
399
402
  return " ".join(visible)
400
403
 
401
404
 
@@ -440,7 +443,6 @@ def py2rst(self, doctestfmt=False, fmt=None):
440
443
  something that reflects better what it does.
441
444
 
442
445
  """
443
- from lino.core.store import get_atomizer
444
446
 
445
447
  if isinstance(self, models.Model) and fmt is None:
446
448
  ar = self.get_default_table().create_request()
lino/utils/fieldutils.py CHANGED
@@ -2,24 +2,27 @@
2
2
  # Copyright 2009-2025 Rumma & Ko Ltd
3
3
  # License: GNU Affero General Public License v3 (see file COPYING for details)
4
4
 
5
- from lino.core.boundaction import BoundAction
6
- from lino.core.actions import Action
7
- from django.db.models import Model
8
- from lino.core.tables import AbstractTable
9
- from lino.core.layouts import BaseLayout
10
- from lino.core.actions import register_params
11
- from rstgen.utils import unindent
12
5
  import rstgen
6
+ from rstgen.utils import unindent
7
+ from django.db.models import Model
8
+ from django.db import models
9
+ from lino import logger
10
+ from lino.core.boundaction import BoundAction
11
+ from lino.core.utils import register_params
12
+ # from lino.core.utils import full_model_name
13
+ from lino.core import actions
14
+ from lino.core import tables
15
+ from lino.core import layouts
13
16
 
14
17
 
15
18
  def get_fields(model, fieldnames=None, columns=None):
16
- """Return a list of field in the given database model, action or table.
19
+ """Return a list of field in the given database model, action or table.
17
20
  """
18
21
  if isinstance(model, BoundAction):
19
22
  get_field = model.action.parameters.get
20
23
  if fieldnames is None:
21
24
  fieldnames = model.action.params_layout
22
- elif isinstance(model, Action):
25
+ elif isinstance(model, actions.Action):
23
26
  get_field = model.parameters.get
24
27
  if fieldnames is None:
25
28
  fieldnames = model.params_layout.main
@@ -28,7 +31,7 @@ def get_fields(model, fieldnames=None, columns=None):
28
31
  # get_field = model.get_data_elem
29
32
  if fieldnames is None:
30
33
  fieldnames = [f.name for f in model._meta.get_fields()]
31
- elif issubclass(model, AbstractTable):
34
+ elif issubclass(model, tables.AbstractTable):
32
35
  if columns:
33
36
  get_field = model.get_data_elem
34
37
  if fieldnames is None:
@@ -37,7 +40,7 @@ def get_fields(model, fieldnames=None, columns=None):
37
40
  else:
38
41
  get_field = model.parameters.get
39
42
  if fieldnames is None:
40
- if not isinstance(model.params_layout, BaseLayout):
43
+ if not isinstance(model.params_layout, layouts.BaseLayout):
41
44
  register_params(model)
42
45
  fieldnames = model.params_layout.main
43
46
  if fieldnames is None:
@@ -8,7 +8,8 @@ used for generating database objects in :ref:`python fixtures <dpy>`.
8
8
  from lino.core.utils import resolve_model, UnresolvedModel
9
9
  from lino.core.utils import obj2str
10
10
  from lino.utils import i2d # for backward compatibility of .py fixtures
11
- from lino.core.fields import make_remote_field, RemoteField
11
+ from lino.core.fields import RemoteField
12
+ from lino.core.atomizer import make_remote_field
12
13
  from lino.utils.choosers import make_converter
13
14
 
14
15
 
@@ -210,10 +211,11 @@ def create_and_get(model, **kw):
210
211
  o = create(model, **kw)
211
212
  return model.objects.get(pk=o.pk)
212
213
 
214
+
213
215
  def make_if_needed(model, **values):
214
216
  qs = model.objects.filter(**values)
215
217
  if qs.count() == 1:
216
- pass # ok, nothing to do
218
+ pass # ok, nothing to do
217
219
  elif qs.count() == 0:
218
220
  return model(**values)
219
221
  else:
lino/utils/report.py CHANGED
@@ -10,8 +10,9 @@ from django.conf import settings
10
10
 
11
11
  from lino.core.frames import Frame
12
12
  from lino.core.utils import InstanceAction, VirtualRow
13
- from lino.core.actions import ShowEmptyTable, Action
14
13
  from lino.core import fields
14
+ from lino.core import layouts
15
+ from lino.core import actions
15
16
  from lino.modlib.printing.mixins import Printable
16
17
  from lino.modlib.printing.mixins import DirectPrintAction
17
18
  from lino.utils import curry
@@ -83,7 +84,7 @@ class EmptyTableRow(VirtualRow, Printable):
83
84
  # if name not in ('get_story'):
84
85
  # raise Exception("20170910 %s" % name)
85
86
  v = getattr(self._table, name)
86
- if isinstance(v, Action):
87
+ if isinstance(v, actions.Action):
87
88
  return InstanceAction(v, self._table, self, None)
88
89
  # 20130525 dd.Report calls `get_story` on `self`, not on the `cls`
89
90
  if callable(v):
@@ -105,7 +106,7 @@ class EmptyTable(Frame):
105
106
  :class:`Report`.
106
107
  """
107
108
 
108
- _detail_action_class = ShowEmptyTable
109
+ _detail_action_class = actions.ShowEmptyTable
109
110
 
110
111
  # ~ debug_permissions = True
111
112
  # ~ has_navigator = False
@@ -187,6 +188,7 @@ class Report(EmptyTable):
187
188
 
188
189
  detail_layout = "body"
189
190
  abstract = True
191
+ _params_layout_class = layouts.ParamsLayout
190
192
 
191
193
  do_print = DirectPrintAction()
192
194
  # go_button = ExplicitRefresh()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: lino
3
- Version: 25.5.1
3
+ Version: 25.5.3
4
4
  Summary: A framework for writing desktop-like web applications using Django and ExtJS or React
5
5
  Project-URL: Homepage, https://www.lino-framework.org
6
6
  Project-URL: Repository, https://gitlab.com/lino-framework/lino