lino 25.2.2__py3-none-any.whl → 25.3.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.
- lino/__init__.py +8 -3
- lino/api/dd.py +11 -35
- lino/api/doctest.py +49 -17
- lino/api/selenium.py +1 -1
- lino/core/actions.py +25 -23
- lino/core/actors.py +52 -23
- lino/core/choicelists.py +10 -8
- lino/core/dbtables.py +1 -1
- lino/core/elems.py +47 -31
- lino/core/fields.py +19 -9
- lino/core/kernel.py +26 -20
- lino/core/model.py +27 -16
- lino/core/renderer.py +2 -2
- lino/core/requests.py +103 -56
- lino/core/site.py +5 -5
- lino/core/store.py +5 -2
- lino/core/utils.py +12 -7
- lino/help_texts.py +7 -8
- lino/mixins/duplicable.py +6 -4
- lino/mixins/sequenced.py +17 -6
- lino/modlib/__init__.py +0 -2
- lino/modlib/changes/models.py +21 -10
- lino/modlib/checkdata/models.py +59 -24
- lino/modlib/comments/fixtures/demo2.py +12 -3
- lino/modlib/comments/models.py +7 -7
- lino/modlib/comments/ui.py +8 -5
- lino/modlib/export_excel/models.py +7 -5
- lino/modlib/extjs/__init__.py +2 -2
- lino/modlib/extjs/views.py +66 -22
- lino/modlib/help/config/makehelp/conf.tpl.py +1 -1
- lino/modlib/jinja/mixins.py +73 -0
- lino/modlib/jinja/models.py +6 -0
- lino/modlib/linod/__init__.py +1 -0
- lino/modlib/linod/choicelists.py +21 -0
- lino/modlib/linod/consumers.py +13 -4
- lino/modlib/linod/fixtures/__init__.py +0 -0
- lino/modlib/linod/fixtures/linod.py +32 -0
- lino/modlib/linod/management/commands/linod.py +6 -2
- lino/modlib/linod/mixins.py +18 -14
- lino/modlib/linod/models.py +4 -2
- lino/modlib/memo/mixins.py +2 -1
- lino/modlib/memo/parser.py +1 -1
- lino/modlib/notify/models.py +19 -11
- lino/modlib/printing/actions.py +47 -42
- lino/modlib/printing/choicelists.py +17 -15
- lino/modlib/printing/mixins.py +22 -20
- lino/modlib/publisher/models.py +5 -5
- lino/modlib/summaries/models.py +3 -2
- lino/modlib/system/models.py +28 -29
- lino/modlib/uploads/__init__.py +14 -11
- lino/modlib/uploads/actions.py +2 -8
- lino/modlib/uploads/choicelists.py +10 -10
- lino/modlib/uploads/fixtures/std.py +17 -0
- lino/modlib/uploads/mixins.py +20 -8
- lino/modlib/uploads/models.py +62 -38
- lino/modlib/uploads/ui.py +15 -9
- lino/utils/__init__.py +0 -1
- lino/utils/jscompressor.py +4 -4
- lino/utils/media.py +45 -23
- lino/utils/report.py +5 -4
- lino/utils/restify.py +2 -2
- lino/utils/soup.py +26 -8
- lino/utils/xml.py +19 -5
- {lino-25.2.2.dist-info → lino-25.3.0.dist-info}/METADATA +1 -1
- {lino-25.2.2.dist-info → lino-25.3.0.dist-info}/RECORD +68 -65
- lino/mixins/uploadable.py +0 -3
- lino/utils/requests.py +0 -55
- {lino-25.2.2.dist-info → lino-25.3.0.dist-info}/WHEEL +0 -0
- {lino-25.2.2.dist-info → lino-25.3.0.dist-info}/licenses/AUTHORS.rst +0 -0
- {lino-25.2.2.dist-info → lino-25.3.0.dist-info}/licenses/COPYING +0 -0
lino/__init__.py
CHANGED
@@ -26,7 +26,7 @@ defines no models, some template files, a series of :term:`django-admin commands
|
|
26
26
|
|
27
27
|
"""
|
28
28
|
|
29
|
-
__version__ = '25.
|
29
|
+
__version__ = '25.3.0'
|
30
30
|
|
31
31
|
# import setuptools # avoid UserWarning "Distutils was imported before Setuptools"?
|
32
32
|
|
@@ -64,14 +64,19 @@ import warnings
|
|
64
64
|
|
65
65
|
warnings.filterwarnings(
|
66
66
|
"error",
|
67
|
-
"DateTimeField .* received a naive datetime (.*) while time zone support is active.",
|
67
|
+
r"DateTimeField .* received a naive datetime (.*) while time zone support is active.",
|
68
68
|
RuntimeWarning,
|
69
69
|
"django.db.models.fields",
|
70
70
|
)
|
71
71
|
|
72
|
+
# TODO: Is it okay to ignore the followgin warning? It's because e.g.
|
73
|
+
# lino.modlib.excerpts has a pre_analyze receiver set_excerpts_actions(), which
|
74
|
+
# accesses the database to set actions before Lino analyzes the models. It
|
75
|
+
# catches OperationalError & Co because --of course-- it fails e.g. for
|
76
|
+
# admin-commands like "pm prep".
|
72
77
|
warnings.filterwarnings(
|
73
78
|
"ignore",
|
74
|
-
"Accessing the database during app initialization is discouraged\. To fix this warning, avoid executing queries in AppConfig\.ready\(\) or when your app modules are imported\.",
|
79
|
+
r"Accessing the database during app initialization is discouraged\. To fix this warning, avoid executing queries in AppConfig\.ready\(\) or when your app modules are imported\.",
|
75
80
|
RuntimeWarning,
|
76
81
|
"django.db.backends.utils",
|
77
82
|
)
|
lino/api/dd.py
CHANGED
@@ -1,7 +1,15 @@
|
|
1
1
|
# -*- coding: UTF-8 -*-
|
2
|
-
# Copyright 2011-
|
2
|
+
# Copyright 2011-2025 Rumma & Ko Ltd
|
3
3
|
# License: GNU Affero General Public License v3 (see file COPYING for details)
|
4
4
|
|
5
|
+
# from lino.modlib.linod.choicelists import background_task, schedule_often, schedule_daily
|
6
|
+
from lino.core.roles import SiteStaff, SiteUser, SiteAdmin, login_required
|
7
|
+
from django.utils import translation
|
8
|
+
from importlib import import_module
|
9
|
+
from lino.modlib.system.choicelists import Genders, PeriodEvents, YesNo
|
10
|
+
from lino.utils.mldbc.fields import BabelCharField, LanguageField
|
11
|
+
from lino.utils.mldbc.fields import BabelTextField
|
12
|
+
from lino.utils.format_date import fdl as dtosl
|
5
13
|
from lino import logger
|
6
14
|
|
7
15
|
# import logging ; logger = logging.getLogger(__name__)
|
@@ -96,7 +104,8 @@ from lino.core.fields import CharField
|
|
96
104
|
from lino.core.utils import babelkw
|
97
105
|
|
98
106
|
# from lino.core.utils import babelattr
|
99
|
-
|
107
|
+
# alias for babelkw for backward compat
|
108
|
+
from lino.core.utils import babel_values
|
100
109
|
|
101
110
|
from lino.utils.choosers import chooser, action_chooser
|
102
111
|
|
@@ -156,18 +165,11 @@ def fds(d):
|
|
156
165
|
|
157
166
|
# backward compatibility
|
158
167
|
dtos = fds
|
159
|
-
from lino.utils.format_date import fdl as dtosl
|
160
168
|
|
161
169
|
babelitem = settings.SITE.babelitem
|
162
170
|
field2kw = settings.SITE.field2kw
|
163
171
|
# urlkwargs = settings.SITE.urlkwargs
|
164
172
|
|
165
|
-
from lino.utils.mldbc.fields import BabelTextField
|
166
|
-
from lino.utils.mldbc.fields import BabelCharField, LanguageField
|
167
|
-
|
168
|
-
from lino.modlib.system.choicelists import Genders, PeriodEvents, YesNo
|
169
|
-
|
170
|
-
from importlib import import_module
|
171
173
|
|
172
174
|
decfmt = settings.SITE.decfmt
|
173
175
|
str2kw = settings.SITE.str2kw
|
@@ -199,35 +201,9 @@ babelattr = settings.SITE.babelattr
|
|
199
201
|
plugins = settings.SITE.plugins
|
200
202
|
format_currency = settings.SITE.format_currency
|
201
203
|
|
202
|
-
from django.utils import translation
|
203
204
|
|
204
205
|
get_language = translation.get_language
|
205
206
|
|
206
|
-
from lino.core.roles import SiteStaff, SiteUser, SiteAdmin, login_required
|
207
|
-
|
208
|
-
from lino.modlib.linod.choicelists import Procedures
|
209
|
-
|
210
|
-
|
211
|
-
def background_task(**kwargs):
|
212
|
-
if "class_name" not in kwargs:
|
213
|
-
kwargs["class_name"] = "linod.SystemTask"
|
214
|
-
|
215
|
-
def decorator(func):
|
216
|
-
Procedures.add_item(func, **kwargs)
|
217
|
-
return func
|
218
|
-
|
219
|
-
return decorator
|
220
|
-
|
221
|
-
|
222
|
-
def schedule_often(every=10, **kwargs):
|
223
|
-
kwargs.update(every_unit="secondly", every=every)
|
224
|
-
return background_task(**kwargs)
|
225
|
-
|
226
|
-
|
227
|
-
def schedule_daily(**kwargs):
|
228
|
-
kwargs.update(every_unit="daily", every=1)
|
229
|
-
return background_task(**kwargs)
|
230
|
-
|
231
207
|
|
232
208
|
def auto_height(n):
|
233
209
|
"""
|
lino/api/doctest.py
CHANGED
@@ -1,28 +1,45 @@
|
|
1
1
|
# -*- coding: UTF-8 -*-
|
2
|
-
# Copyright 2015-
|
2
|
+
# Copyright 2015-2025 Rumma & Ko Ltd
|
3
3
|
# License: GNU Affero General Public License v3 (see file COPYING for details)
|
4
4
|
"""
|
5
|
-
A selection of names to be used in tested documents
|
5
|
+
A selection of names to be used in tested documents as follows:
|
6
|
+
|
7
|
+
>>> from lino.api.doctest import *
|
8
|
+
|
9
|
+
This is by convention everything we want to have in the global namespace of a
|
10
|
+
tested document. It includes
|
11
|
+
|
12
|
+
- well-known Python standard modules like os, sys, datetime and collections.
|
13
|
+
- A variable :data:`test_client`
|
14
|
+
|
6
15
|
"""
|
7
16
|
|
17
|
+
import os
|
18
|
+
import sys
|
19
|
+
import datetime
|
20
|
+
import collections
|
8
21
|
import six # TODO: remove here and then run all doctests
|
9
22
|
import logging
|
10
23
|
import sqlparse
|
24
|
+
import json
|
25
|
+
import textwrap
|
26
|
+
|
27
|
+
from bs4 import BeautifulSoup
|
28
|
+
from pprint import pprint, pformat
|
11
29
|
from urllib.parse import urlencode
|
30
|
+
|
12
31
|
import django
|
13
32
|
django.setup()
|
33
|
+
|
14
34
|
from lino.core.constants import *
|
15
35
|
from lino.api.shell import *
|
16
36
|
from django.utils import translation
|
17
37
|
from django.utils.encoding import force_str
|
18
38
|
from django.test import Client
|
19
39
|
from django.db import connection, reset_queries as reset_sql_queries
|
20
|
-
import json
|
21
|
-
from bs4 import BeautifulSoup
|
22
|
-
import textwrap
|
23
|
-
from pprint import pprint, pformat
|
24
40
|
|
25
|
-
|
41
|
+
import pytest
|
42
|
+
# from rstgen import table, ul
|
26
43
|
import rstgen
|
27
44
|
from rstgen import attrtable
|
28
45
|
from rstgen.utils import unindent, rmu, sixprint
|
@@ -49,10 +66,12 @@ from lino.core.actions import register_params
|
|
49
66
|
from lino.core.layouts import BaseLayout
|
50
67
|
|
51
68
|
test_client = Client()
|
52
|
-
|
53
|
-
# `lino_welfare.pcsw.models.Client`
|
69
|
+
"""An instance of :class:`django.test.Client`.
|
54
70
|
|
55
|
-
|
71
|
+
N.B. Naming it simply "client" caused conflict with a
|
72
|
+
:class:`lino_welfare.pcsw.models.Client`
|
73
|
+
|
74
|
+
"""
|
56
75
|
|
57
76
|
HttpQuery = collections.namedtuple(
|
58
77
|
"HttpQuery", ["username", "url_base", "json_fields", "expected_rows", "kwargs"]
|
@@ -293,7 +312,7 @@ def show_workflow(actions, all=False, language=None):
|
|
293
312
|
# required_roles
|
294
313
|
]
|
295
314
|
)
|
296
|
-
print(table(cols, cells).strip())
|
315
|
+
print(rstgen.table(cols, cells).strip())
|
297
316
|
|
298
317
|
if language:
|
299
318
|
with translation.override(language):
|
@@ -363,7 +382,7 @@ def fields_help(model, fieldnames=None, columns=False, all=None):
|
|
363
382
|
|
364
383
|
# return table(cols, cells).strip()
|
365
384
|
items = ["{} ({}) : {}".format(row[1], row[0], row[2]) for row in cells]
|
366
|
-
return ul(items).strip()
|
385
|
+
return rstgen.ul(items).strip()
|
367
386
|
|
368
387
|
|
369
388
|
def show_fields(*args, **kwargs):
|
@@ -552,7 +571,7 @@ def show_choicelist(cls):
|
|
552
571
|
for i in cls.get_list_items():
|
553
572
|
row = [i.value, i.name] + str2languages(i.text)
|
554
573
|
rows.append(row)
|
555
|
-
print(table(headers, rows))
|
574
|
+
print(rstgen.table(headers, rows))
|
556
575
|
|
557
576
|
|
558
577
|
def show_choicelists():
|
@@ -568,7 +587,7 @@ def show_choicelists():
|
|
568
587
|
i.verbose_name_plural
|
569
588
|
)
|
570
589
|
rows.append(row)
|
571
|
-
print(table(headers, rows))
|
590
|
+
print(rstgen.table(headers, rows))
|
572
591
|
|
573
592
|
|
574
593
|
def show_permissions(*args):
|
@@ -590,7 +609,7 @@ def show_translations(things, fmt, languages=None):
|
|
590
609
|
x, txt = fmt(thing)
|
591
610
|
cells.append(txt)
|
592
611
|
rows.append(cells)
|
593
|
-
print(table(headers, rows))
|
612
|
+
print(rstgen.table(headers, rows))
|
594
613
|
|
595
614
|
|
596
615
|
def show_model_translations(*models, **kwargs):
|
@@ -779,7 +798,7 @@ def show_change_watchers():
|
|
779
798
|
rows.append(
|
780
799
|
[full_model_name(m), ws.master_key, " ".join(sorted(ws.ignored_fields))]
|
781
800
|
)
|
782
|
-
print(table(headers, rows, max_width=40))
|
801
|
+
print(rstgen.table(headers, rows, max_width=40))
|
783
802
|
|
784
803
|
def show_display_modes():
|
785
804
|
"""
|
@@ -796,4 +815,17 @@ def show_display_modes():
|
|
796
815
|
("x" if dm in a.extra_display_modes else "")
|
797
816
|
for dm in dml]
|
798
817
|
)
|
799
|
-
print(table(headers, rows))
|
818
|
+
print(rstgen.table(headers, rows))
|
819
|
+
|
820
|
+
|
821
|
+
def checkdb(m, num):
|
822
|
+
"""
|
823
|
+
Raise an exception if the database doesn't contain the specified number of
|
824
|
+
rows of the specified model.
|
825
|
+
|
826
|
+
This is for usage in :xfile:`startup.py` scripts.
|
827
|
+
|
828
|
+
"""
|
829
|
+
if m.objects.count() != num:
|
830
|
+
raise Exception(
|
831
|
+
f"Model {m} should have {num} rows but has {m.objects.count()}")
|
lino/api/selenium.py
CHANGED
lino/core/actions.py
CHANGED
@@ -6,6 +6,19 @@ decorator, and some of the standard actions. See :ref:`dev.actions`.
|
|
6
6
|
|
7
7
|
"""
|
8
8
|
|
9
|
+
from .utils import InstanceAction
|
10
|
+
from .utils import traverse_ddh_fklist
|
11
|
+
from .utils import Parametrizable
|
12
|
+
from .utils import navinfo
|
13
|
+
from .utils import resolve_model
|
14
|
+
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
|
9
22
|
from lino import logger
|
10
23
|
|
11
24
|
from django.utils.translation import gettext_lazy as _
|
@@ -20,21 +33,6 @@ from django.apps import apps
|
|
20
33
|
|
21
34
|
get_models = apps.get_models
|
22
35
|
|
23
|
-
from lino.core import constants
|
24
|
-
from lino.core import layouts
|
25
|
-
from lino.core import fields
|
26
|
-
from lino.core import keyboard
|
27
|
-
from lino.modlib.users.utils import get_user_profile
|
28
|
-
from lino.utils.choosers import check_for_chooser
|
29
|
-
|
30
|
-
from .permissions import Permittable
|
31
|
-
from .utils import obj2unicode
|
32
|
-
from .utils import resolve_model
|
33
|
-
from .utils import navinfo
|
34
|
-
from .utils import Parametrizable
|
35
|
-
from .utils import traverse_ddh_fklist
|
36
|
-
from .utils import InstanceAction
|
37
|
-
|
38
36
|
|
39
37
|
def discover_choosers():
|
40
38
|
logger.debug("Discovering choosers for database fields...")
|
@@ -55,7 +53,8 @@ def resolve_layout(cls, k, spec, layout_class, **options):
|
|
55
53
|
else:
|
56
54
|
layout_class = settings.SITE.models.resolve(spec)
|
57
55
|
if layout_class is None:
|
58
|
-
raise Exception(
|
56
|
+
raise Exception(
|
57
|
+
"Unresolved {} {!r} for {}".format(k, spec, cls))
|
59
58
|
return layout_class(None, cls, **options)
|
60
59
|
elif isinstance(spec, layouts.Panel):
|
61
60
|
options.update(spec.options)
|
@@ -69,7 +68,8 @@ def resolve_layout(cls, k, spec, layout_class, **options):
|
|
69
68
|
"{}.{}.{} must be a string, " "a Panel or an instance of {} (not {!r})"
|
70
69
|
)
|
71
70
|
raise Exception(
|
72
|
-
msg.format(cls.__module__, cls.__name__,
|
71
|
+
msg.format(cls.__module__, cls.__name__,
|
72
|
+
k, layout_class.__name__, spec)
|
73
73
|
)
|
74
74
|
if spec._datasource is None:
|
75
75
|
spec.set_datasource(cls)
|
@@ -163,7 +163,7 @@ class Action(Parametrizable, Permittable):
|
|
163
163
|
_params_layout_class = layouts.ActionParamsLayout
|
164
164
|
|
165
165
|
label = None
|
166
|
-
button_text = None
|
166
|
+
button_text: str = None
|
167
167
|
|
168
168
|
button_color = None
|
169
169
|
"""
|
@@ -487,7 +487,8 @@ class Action(Parametrizable, Permittable):
|
|
487
487
|
|
488
488
|
if self.icon_name:
|
489
489
|
if self.icon_name not in constants.ICON_NAMES:
|
490
|
-
raise Exception(
|
490
|
+
raise Exception(
|
491
|
+
"Unkonwn icon_name '{0}'".format(self.icon_name))
|
491
492
|
|
492
493
|
register_params(self)
|
493
494
|
|
@@ -496,7 +497,6 @@ class Action(Parametrizable, Permittable):
|
|
496
497
|
if not c in constants.WINDOW_TYPES:
|
497
498
|
raise Exception(f"Invalid window_type spec {c} in {self}")
|
498
499
|
|
499
|
-
|
500
500
|
def __get__(self, instance, owner):
|
501
501
|
"""
|
502
502
|
When a model has an action "foo", then getting an attribute
|
@@ -524,7 +524,8 @@ class Action(Parametrizable, Permittable):
|
|
524
524
|
name,
|
525
525
|
getattr(
|
526
526
|
forms,
|
527
|
-
mapping.get(field.__class__.__name__,
|
527
|
+
mapping.get(field.__class__.__name__,
|
528
|
+
field.__class__.__name__),
|
528
529
|
)(),
|
529
530
|
)
|
530
531
|
return LinoForm
|
@@ -815,7 +816,7 @@ class ShowDetail(Action):
|
|
815
816
|
help_text = _("Open a detail window on this record.")
|
816
817
|
action_name = "detail"
|
817
818
|
label = _("Detail")
|
818
|
-
icon_name = "application_form"
|
819
|
+
# icon_name = "application_form"
|
819
820
|
ui5_icon_name = "sap-icon://detail-view"
|
820
821
|
opens_a_window = True
|
821
822
|
window_type = constants.WINDOW_TYPE_DETAIL
|
@@ -1415,7 +1416,8 @@ class DeleteSelected(MultipleRowAction):
|
|
1415
1416
|
d.update(type=ar.actor.model._meta.verbose_name_plural)
|
1416
1417
|
if len(objects) > 10:
|
1417
1418
|
objects = objects[:9] + ["..."]
|
1418
|
-
msg = gettext(
|
1419
|
+
msg = gettext(
|
1420
|
+
"You are about to delete %(num)d %(type)s\n(%(targets)s)") % d
|
1419
1421
|
|
1420
1422
|
if len(cascaded_objects):
|
1421
1423
|
lst = [
|
lino/core/actors.py
CHANGED
@@ -75,7 +75,8 @@ def discover():
|
|
75
75
|
actors_list = []
|
76
76
|
actors_dict = AttrDict()
|
77
77
|
|
78
|
-
logger.debug("actors.discover() : registering %d actors",
|
78
|
+
logger.debug("actors.discover() : registering %d actors",
|
79
|
+
len(actor_classes))
|
79
80
|
for cls in actor_classes:
|
80
81
|
register_actor(cls)
|
81
82
|
actor_classes = None
|
@@ -751,7 +752,8 @@ class Actor(actions.Parametrizable, Permittable, metaclass=ActorMetaClass):
|
|
751
752
|
if hasattr(cls, "display_mode"):
|
752
753
|
# cls.default_display_modes = {k:v for k, v in cls.display_mode}
|
753
754
|
# logger.info(f"{cls} uses deprecated `display_mode`, please convert to `default_display_modes`.")
|
754
|
-
raise ChangedAPI(
|
755
|
+
raise ChangedAPI(
|
756
|
+
f"{cls} must convert `display_mode` to `default_display_modes`")
|
755
757
|
|
756
758
|
master = getattr(cls, "master", None)
|
757
759
|
if isinstance(master, str):
|
@@ -795,6 +797,9 @@ class Actor(actions.Parametrizable, Permittable, metaclass=ActorMetaClass):
|
|
795
797
|
# cls.extra_display_modes = cls.extra_display_modes | {v}
|
796
798
|
edm.add(v)
|
797
799
|
|
800
|
+
if cls.hide_navigator:
|
801
|
+
return
|
802
|
+
|
798
803
|
if cls.card_layout is not None:
|
799
804
|
edm.add(constants.DISPLAY_MODE_CARDS)
|
800
805
|
if 'row_as_paragraph' in cls.__dict__:
|
@@ -805,7 +810,8 @@ class Actor(actions.Parametrizable, Permittable, metaclass=ActorMetaClass):
|
|
805
810
|
if model.extra_display_modes is not None:
|
806
811
|
for v in model.extra_display_modes:
|
807
812
|
if v not in constants.DISPLAY_MODES:
|
808
|
-
raise Exception(
|
813
|
+
raise Exception(
|
814
|
+
f"Invalid extra_display_modes mode {v} in {model}")
|
809
815
|
edm |= model.extra_display_modes
|
810
816
|
if 'as_paragraph' in model.__dict__:
|
811
817
|
edm.add(constants.DISPLAY_MODE_LIST)
|
@@ -1097,14 +1103,16 @@ class Actor(actions.Parametrizable, Permittable, metaclass=ActorMetaClass):
|
|
1097
1103
|
cls.delete_action = cls._bind_action(
|
1098
1104
|
"delete_action", DELETE_ACTION, True
|
1099
1105
|
)
|
1100
|
-
cls.update_action = cls._bind_action(
|
1106
|
+
cls.update_action = cls._bind_action(
|
1107
|
+
"update_action", UPDATE_ACTION, True)
|
1101
1108
|
if cls.detail_layout:
|
1102
1109
|
cls.validate_form = cls._bind_action(
|
1103
1110
|
"validate_form", VALIDATE_FORM, True
|
1104
1111
|
)
|
1105
1112
|
|
1106
1113
|
if is_string(cls.workflow_owner_field):
|
1107
|
-
cls.workflow_owner_field = cls.get_data_elem(
|
1114
|
+
cls.workflow_owner_field = cls.get_data_elem(
|
1115
|
+
cls.workflow_owner_field)
|
1108
1116
|
if is_string(cls.workflow_state_field):
|
1109
1117
|
# if isinstance(cls.workflow_state_field, string_types):
|
1110
1118
|
fld = cls.get_data_elem(cls.workflow_state_field)
|
@@ -1136,7 +1144,8 @@ class Actor(actions.Parametrizable, Permittable, metaclass=ActorMetaClass):
|
|
1136
1144
|
da = cls.get_default_action()
|
1137
1145
|
if da is not None:
|
1138
1146
|
if isinstance(da, actions.Action):
|
1139
|
-
cls.default_action = cls._bind_action(
|
1147
|
+
cls.default_action = cls._bind_action(
|
1148
|
+
"default_action", da, True)
|
1140
1149
|
else:
|
1141
1150
|
cls.default_action = da
|
1142
1151
|
|
@@ -1300,7 +1309,8 @@ class Actor(actions.Parametrizable, Permittable, metaclass=ActorMetaClass):
|
|
1300
1309
|
if cls.model is None:
|
1301
1310
|
return False
|
1302
1311
|
if not isinstance(cls.model, type):
|
1303
|
-
raise Exception(
|
1312
|
+
raise Exception(
|
1313
|
+
"{}.model is {!r} (must be a class)".format(cls, cls.model))
|
1304
1314
|
return issubclass(cls.model, fields.TableRow)
|
1305
1315
|
|
1306
1316
|
@classmethod
|
@@ -1417,13 +1427,15 @@ class Actor(actions.Parametrizable, Permittable, metaclass=ActorMetaClass):
|
|
1417
1427
|
return format_html("<div>{}</div>", text)
|
1418
1428
|
|
1419
1429
|
@classmethod
|
1420
|
-
def override_column_headers(self, ar, **
|
1430
|
+
def override_column_headers(self, ar, **headers):
|
1421
1431
|
"""A hook to dynamically override the column headers. This has no
|
1422
1432
|
effect on a GridPanel, only in printed documents or plain
|
1423
1433
|
html.
|
1424
1434
|
|
1425
1435
|
"""
|
1426
|
-
|
1436
|
+
if self.model is None:
|
1437
|
+
return headers
|
1438
|
+
return self.model.override_column_headers(ar, **headers)
|
1427
1439
|
|
1428
1440
|
@classmethod
|
1429
1441
|
def get_sum_text(self, ar, sums):
|
@@ -1494,11 +1506,13 @@ class Actor(actions.Parametrizable, Permittable, metaclass=ActorMetaClass):
|
|
1494
1506
|
else:
|
1495
1507
|
name = "main"
|
1496
1508
|
if name in kw:
|
1497
|
-
raise Exception(
|
1509
|
+
raise Exception(
|
1510
|
+
"set_detail() got two definitions for %r." % name)
|
1498
1511
|
kw[name] = dtl
|
1499
1512
|
else:
|
1500
1513
|
if not isinstance(dtl, lcl):
|
1501
|
-
msg = "{} is neither a string nor a layout".format(
|
1514
|
+
msg = "{} is neither a string nor a layout".format(
|
1515
|
+
type(dtl))
|
1502
1516
|
raise Exception(msg)
|
1503
1517
|
assert dtl._datasource is None
|
1504
1518
|
# added for 20120914c but it wasn't the problem
|
@@ -1557,7 +1571,8 @@ class Actor(actions.Parametrizable, Permittable, metaclass=ActorMetaClass):
|
|
1557
1571
|
# disabled because UsersWithClients defines virtual fields
|
1558
1572
|
# on connection_created
|
1559
1573
|
if name in cls.virtual_fields:
|
1560
|
-
raise Exception(
|
1574
|
+
raise Exception(
|
1575
|
+
"Duplicate add_virtual_field() %s.%s" % (cls, name))
|
1561
1576
|
# assert vf.model is None
|
1562
1577
|
# if vf.model is not None:
|
1563
1578
|
# # inherit from parent actor
|
@@ -1896,7 +1911,8 @@ class Actor(actions.Parametrizable, Permittable, metaclass=ActorMetaClass):
|
|
1896
1911
|
@classmethod
|
1897
1912
|
def get_table_as_list(cls, obj, ar):
|
1898
1913
|
# raise Exception("20240316")
|
1899
|
-
sar = cls.request(parent=ar, master_instance=obj,
|
1914
|
+
sar = cls.request(parent=ar, master_instance=obj,
|
1915
|
+
is_on_main_actor=False)
|
1900
1916
|
grp = Grouper(sar)
|
1901
1917
|
html_text = grp.begin()
|
1902
1918
|
for i, obj in enumerate(sar.data_iterator):
|
@@ -1930,7 +1946,8 @@ class Actor(actions.Parametrizable, Permittable, metaclass=ActorMetaClass):
|
|
1930
1946
|
|
1931
1947
|
@classmethod
|
1932
1948
|
def get_table_story(cls, obj, ar):
|
1933
|
-
sar = cls.request(parent=ar, master_instance=obj,
|
1949
|
+
sar = cls.request(parent=ar, master_instance=obj,
|
1950
|
+
is_on_main_actor=False)
|
1934
1951
|
html = SAFE_EMPTY
|
1935
1952
|
for i, obj in enumerate(sar.data_iterator):
|
1936
1953
|
if i == cls.preview_limit:
|
@@ -1948,17 +1965,27 @@ class Actor(actions.Parametrizable, Permittable, metaclass=ActorMetaClass):
|
|
1948
1965
|
return html
|
1949
1966
|
|
1950
1967
|
@classmethod
|
1951
|
-
def
|
1968
|
+
def get_slave_summary(cls, obj, ar=None):
|
1969
|
+
"""
|
1970
|
+
:param cls: Slave table
|
1971
|
+
:param obj: Master instance
|
1972
|
+
:param ar: Action request on master table
|
1973
|
+
"""
|
1974
|
+
if ar is None:
|
1975
|
+
return ''
|
1976
|
+
sar = cls.request(parent=ar, master_instance=obj,
|
1977
|
+
is_on_main_actor=False)
|
1978
|
+
return cls.get_table_summary(sar)
|
1979
|
+
|
1980
|
+
@classmethod
|
1981
|
+
def get_table_summary(cls, ar):
|
1952
1982
|
"""
|
1953
1983
|
Return the HTML `<div>` to be displayed by
|
1954
1984
|
:class:`lino.core.elems.TableSummaryPanel`.
|
1955
1985
|
It basically just calls :meth:`table_as_summary`.
|
1956
1986
|
|
1957
1987
|
"""
|
1958
|
-
|
1959
|
-
return ''
|
1960
|
-
sar = cls.request(parent=ar, master_instance=obj, is_on_main_actor=False)
|
1961
|
-
p = cls.table_as_summary(sar)
|
1988
|
+
p = cls.table_as_summary(ar)
|
1962
1989
|
# assert_safe(p) # temporary 20240506
|
1963
1990
|
# print("20240712", p)
|
1964
1991
|
# return format_html(DIVTPL, p)
|
@@ -1981,9 +2008,9 @@ class Actor(actions.Parametrizable, Permittable, metaclass=ActorMetaClass):
|
|
1981
2008
|
"""
|
1982
2009
|
p = qs2summary(
|
1983
2010
|
ar,
|
1984
|
-
ar.
|
2011
|
+
ar.sliced_data_iterator,
|
1985
2012
|
separator=cls.summary_sep,
|
1986
|
-
max_items=cls.preview_limit,
|
2013
|
+
max_items=ar.limit or cls.preview_limit,
|
1987
2014
|
wraptpl=None,
|
1988
2015
|
)
|
1989
2016
|
# assert isinstance(p, str)
|
@@ -2101,7 +2128,8 @@ def resolve_action(spec, action=None):
|
|
2101
2128
|
a = spec.get_action_by_name(action)
|
2102
2129
|
# ~ print 20121210, a
|
2103
2130
|
if a is None:
|
2104
|
-
raise Exception(
|
2131
|
+
raise Exception(
|
2132
|
+
"{} has no action named '{}'".format(spec, action))
|
2105
2133
|
else:
|
2106
2134
|
a = spec.default_action
|
2107
2135
|
# assert a is not None
|
@@ -2109,4 +2137,5 @@ def resolve_action(spec, action=None):
|
|
2109
2137
|
raise Exception("%r default_action is None?!" % spec)
|
2110
2138
|
return a
|
2111
2139
|
|
2112
|
-
raise Exception("Action spec %r returned invalid object %r" %
|
2140
|
+
raise Exception("Action spec %r returned invalid object %r" %
|
2141
|
+
(givenspec, spec))
|
lino/core/choicelists.py
CHANGED
@@ -36,9 +36,10 @@ VALUE_FIELD.attname = "value"
|
|
36
36
|
|
37
37
|
# @deconstructible
|
38
38
|
class Choice(fields.TableRow):
|
39
|
-
"""
|
40
|
-
|
41
|
-
|
39
|
+
"""
|
40
|
+
A constant value whose string representation depends on the current language
|
41
|
+
at runtime. Every item of a :class:`ChoiceList` must be an instance of
|
42
|
+
:class:`Choice` or a subclass thereof.
|
42
43
|
|
43
44
|
.. attribute:: choicelist
|
44
45
|
|
@@ -478,11 +479,11 @@ class ChoiceList(with_metaclass(ChoiceListMeta, tables.AbstractTable)):
|
|
478
479
|
|
479
480
|
old2new = {}
|
480
481
|
"""
|
481
|
-
A dict
|
482
|
+
A dict that maps old values to their new values.
|
482
483
|
|
483
|
-
This
|
484
|
-
|
485
|
-
|
484
|
+
This is consulted when an unknown value is read from database (e.g. during a
|
485
|
+
migration). If it contains a replacement for the old value, Lino will
|
486
|
+
return the choice with the new value.
|
486
487
|
"""
|
487
488
|
|
488
489
|
@classmethod
|
@@ -1050,7 +1051,8 @@ class MultiChoiceListField(ChoiceListField):
|
|
1050
1051
|
return []
|
1051
1052
|
if isinstance(value, list):
|
1052
1053
|
return value
|
1053
|
-
value = [self.choicelist.to_python(
|
1054
|
+
value = [self.choicelist.to_python(
|
1055
|
+
v) for v in value.split(self.delimiter_char)]
|
1054
1056
|
return value
|
1055
1057
|
|
1056
1058
|
def get_prep_value(self, value):
|
lino/core/dbtables.py
CHANGED
@@ -266,7 +266,7 @@ class Table(AbstractTable):
|
|
266
266
|
yield self.detail_action.request(user=user)
|
267
267
|
|
268
268
|
# @classmethod
|
269
|
-
# def
|
269
|
+
# def elem_get_printable_target_stem(cls,elem):
|
270
270
|
# return elem._meta.app_label + '.' + elem.__class__.__name__ + '-' + str(elem.pk)
|
271
271
|
@classmethod
|
272
272
|
def get_detail_sets(self):
|