lino 25.4.5__py3-none-any.whl → 25.5.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 +1 -1
- lino/api/doctest.py +14 -0
- lino/core/elems.py +10 -11
- lino/core/fields.py +37 -15
- lino/core/layouts.py +2 -2
- lino/core/model.py +15 -5
- lino/core/plugin.py +2 -2
- lino/core/site.py +11 -13
- lino/core/store.py +1 -1
- lino/core/tables.py +7 -1
- lino/core/utils.py +4 -23
- lino/help_texts.py +6 -0
- lino/modlib/help/models.py +2 -1
- lino/modlib/notify/fixtures/demo2.py +7 -2
- lino/modlib/publisher/fixtures/synodalworld.py +0 -2
- lino/utils/quantities.py +21 -3
- {lino-25.4.5.dist-info → lino-25.5.0.dist-info}/METADATA +1 -1
- {lino-25.4.5.dist-info → lino-25.5.0.dist-info}/RECORD +21 -21
- {lino-25.4.5.dist-info → lino-25.5.0.dist-info}/WHEEL +0 -0
- {lino-25.4.5.dist-info → lino-25.5.0.dist-info}/licenses/AUTHORS.rst +0 -0
- {lino-25.4.5.dist-info → lino-25.5.0.dist-info}/licenses/COPYING +0 -0
lino/__init__.py
CHANGED
@@ -31,7 +31,7 @@ from django import VERSION
|
|
31
31
|
from django.apps import AppConfig
|
32
32
|
from django.conf import settings
|
33
33
|
import warnings
|
34
|
-
__version__ = '25.
|
34
|
+
__version__ = '25.5.0'
|
35
35
|
|
36
36
|
# import setuptools # avoid UserWarning "Distutils was imported before Setuptools"?
|
37
37
|
|
lino/api/doctest.py
CHANGED
@@ -524,6 +524,20 @@ def show_choicelists():
|
|
524
524
|
print(rstgen.table(headers, rows))
|
525
525
|
|
526
526
|
|
527
|
+
def show_parent_layouts():
|
528
|
+
"""
|
529
|
+
Show all actors having a parent layout.
|
530
|
+
"""
|
531
|
+
headers = ["name", "parent layout"]
|
532
|
+
rows = []
|
533
|
+
for a in actors.actors_list:
|
534
|
+
if issubclass(a, AbstractTable) and a.parent_layout:
|
535
|
+
parent = a.parent_layout._datasource
|
536
|
+
row = [str(a), str(parent)] # , parent.__module__]
|
537
|
+
rows.append(row)
|
538
|
+
print(rstgen.table(headers, rows))
|
539
|
+
|
540
|
+
|
527
541
|
def show_permissions(*args):
|
528
542
|
print(visible_for(*args))
|
529
543
|
|
lino/core/elems.py
CHANGED
@@ -5,8 +5,6 @@
|
|
5
5
|
|
6
6
|
"""
|
7
7
|
|
8
|
-
from lino.utils.jsgen import VisibleComponent
|
9
|
-
import types
|
10
8
|
import decimal
|
11
9
|
from lxml.html import fromstring
|
12
10
|
from lxml import etree
|
@@ -15,7 +13,7 @@ from django.db import models
|
|
15
13
|
from django.utils.translation import gettext as _
|
16
14
|
from django.utils.functional import Promise
|
17
15
|
from django.utils.text import format_lazy
|
18
|
-
from django.utils.html import SafeString
|
16
|
+
from django.utils.html import SafeString
|
19
17
|
from django.conf import settings
|
20
18
|
from django.db.models.fields.related import (
|
21
19
|
ReverseOneToOneDescriptor as SingleRelatedObjectDescriptor,
|
@@ -40,16 +38,15 @@ from lino.core.gfks import GenericRelation, GenericRel
|
|
40
38
|
from lino.core import choicelists
|
41
39
|
from lino.modlib.bootstrap3.views import table2html
|
42
40
|
|
43
|
-
from lino.utils.
|
44
|
-
from lino.utils.html import html2text
|
41
|
+
from lino.utils.jsgen import VisibleComponent
|
42
|
+
from lino.utils.html import E, tostring, forcetext, html2text
|
45
43
|
from lino.utils.ranges import constrain
|
46
44
|
from lino.utils import jsgen
|
47
45
|
from lino.utils import mti
|
48
46
|
from lino.utils.jsgen import py2js, js_code
|
49
|
-
from lino.utils.html2xhtml import html2xhtml
|
47
|
+
# from lino.utils.html2xhtml import html2xhtml
|
50
48
|
from lino.utils import join_elems
|
51
49
|
from lino.core.actors import qs2summary
|
52
|
-
from lino.core.store import get_atomizer
|
53
50
|
|
54
51
|
from lino.core.layouts import (
|
55
52
|
FormLayout,
|
@@ -66,7 +63,6 @@ from lino.utils.format_date import fds
|
|
66
63
|
from lino.modlib.users.utils import get_user_profile
|
67
64
|
|
68
65
|
# from etgen import etree
|
69
|
-
from lino.utils.html import E, forcetext
|
70
66
|
from etgen.html2rst import html2rst
|
71
67
|
|
72
68
|
EXT_CHAR_WIDTH = 9
|
@@ -2756,6 +2752,7 @@ def get_summary_element(lh, de, name, **kw):
|
|
2756
2752
|
def create_layout_element(lh, name, **kw):
|
2757
2753
|
"""
|
2758
2754
|
Create a layout element from the named data element.
|
2755
|
+
`lh` is the layout handle of the actor who is the datasource of the data element.
|
2759
2756
|
"""
|
2760
2757
|
|
2761
2758
|
if True: # settings.SITE.catch_layout_exceptions:
|
@@ -2850,6 +2847,8 @@ def create_layout_element(lh, name, **kw):
|
|
2850
2847
|
# The data element refers to a slave table. Slave tables make
|
2851
2848
|
# no sense in an insert window because the master does not yet
|
2852
2849
|
# exist.
|
2850
|
+
de.attach_to_parent_layout(lh.layout)
|
2851
|
+
|
2853
2852
|
if settings.SITE.is_hidden_plugin(de.app_label):
|
2854
2853
|
return None
|
2855
2854
|
|
@@ -2858,8 +2857,8 @@ def create_layout_element(lh, name, **kw):
|
|
2858
2857
|
|
2859
2858
|
# print("20240317", de, lh)
|
2860
2859
|
if isinstance(lh.layout, FormLayout):
|
2861
|
-
# When a table is specified in the layout of a
|
2862
|
-
#
|
2860
|
+
# When a table is specified in the layout of a detail window, then it
|
2861
|
+
# is rendered as a :term:`slave panel`. The panel will have a
|
2863
2862
|
# tool button to "open that table in its own window". The display
|
2864
2863
|
# mode of that summary is defined by the `default_display_modes` of
|
2865
2864
|
# the table.
|
@@ -2884,7 +2883,7 @@ def create_layout_element(lh, name, **kw):
|
|
2884
2883
|
|
2885
2884
|
if (
|
2886
2885
|
len(de.default_display_modes) > 1
|
2887
|
-
and lh.ui.
|
2886
|
+
and lh.ui.media_name == "react"
|
2888
2887
|
):
|
2889
2888
|
# print(f"20240928 {de} in {lh} gets SlaveContainer")
|
2890
2889
|
return SlaveContainer(lh, name, de, **kw)
|
lino/core/fields.py
CHANGED
@@ -167,9 +167,17 @@ class PriceField(models.DecimalField):
|
|
167
167
|
`max_digits`.
|
168
168
|
"""
|
169
169
|
|
170
|
+
# def __init__(self, verbose_name=None, max_digits=10, **kwargs):
|
171
|
+
# defaults = dict(
|
172
|
+
# max_length=max_digits,
|
173
|
+
# max_digits=max_digits,
|
174
|
+
# decimal_places=2,
|
175
|
+
# )
|
176
|
+
# defaults.update(kwargs)
|
177
|
+
# super().__init__(verbose_name, **defaults)
|
178
|
+
|
170
179
|
def __init__(self, verbose_name=None, max_digits=10, **kwargs):
|
171
180
|
defaults = dict(
|
172
|
-
max_length=max_digits,
|
173
181
|
max_digits=max_digits,
|
174
182
|
decimal_places=2,
|
175
183
|
)
|
@@ -947,7 +955,7 @@ class QuantityField(models.CharField):
|
|
947
955
|
"""
|
948
956
|
|
949
957
|
description = _("Quantity (Decimal or Duration)")
|
950
|
-
overflow_value = None
|
958
|
+
# overflow_value = None
|
951
959
|
|
952
960
|
def __init__(self, *args, **kw):
|
953
961
|
kw.setdefault("max_length", settings.SITE.quantity_max_length)
|
@@ -974,16 +982,18 @@ class QuantityField(models.CharField):
|
|
974
982
|
I'd add "Any value allowed for this field when instantiating a model."
|
975
983
|
|
976
984
|
"""
|
977
|
-
if value:
|
985
|
+
if isinstance(value, quantities.Quantity):
|
986
|
+
return value
|
987
|
+
elif isinstance(value, Decimal):
|
988
|
+
return quantities.Quantity(value)
|
989
|
+
elif isinstance(value, str):
|
990
|
+
return quantities.parse(value)
|
991
|
+
elif value:
|
978
992
|
# try:
|
979
|
-
if isinstance(value, str):
|
980
|
-
return quantities.parse(value)
|
981
993
|
return quantities.Quantity(value)
|
982
994
|
# except Exception as e:
|
983
995
|
# raise ValidationError(
|
984
996
|
# "Invalid value {} for {} : {}".format(value, self, e))
|
985
|
-
elif isinstance(value, Decimal):
|
986
|
-
return quantities.Quantity(value)
|
987
997
|
return None
|
988
998
|
|
989
999
|
def from_db_value(self, value, expression, connection, context=None):
|
@@ -1001,14 +1011,18 @@ class QuantityField(models.CharField):
|
|
1001
1011
|
return str(value) # if value is None else ''
|
1002
1012
|
|
1003
1013
|
def clean(self, raw_value, obj):
|
1004
|
-
if
|
1005
|
-
|
1006
|
-
|
1007
|
-
|
1008
|
-
|
1009
|
-
|
1010
|
-
|
1011
|
-
|
1014
|
+
# if isinstance(raw_value, quantities.Quantity):
|
1015
|
+
raw_value = self.to_python(raw_value)
|
1016
|
+
if raw_value is not None:
|
1017
|
+
raw_value = raw_value.limit_length(self.max_length, ValidationError)
|
1018
|
+
# if len(str(raw_value)) > self.max_length:
|
1019
|
+
# if self.overflow_value:
|
1020
|
+
# return self.overflow_value
|
1021
|
+
# raise ValidationError(
|
1022
|
+
# f"Cannot accept quantity {raw_value} "
|
1023
|
+
# + f"because max_length is {self.max_length}")
|
1024
|
+
# # print("20230129 Can't store {}={} in {}".format(self.name, raw_value, obj))
|
1025
|
+
# # return -1
|
1012
1026
|
return super().clean(raw_value, obj)
|
1013
1027
|
|
1014
1028
|
|
@@ -1326,6 +1340,14 @@ class TableRow(object):
|
|
1326
1340
|
def disable_create(self, ar):
|
1327
1341
|
return None
|
1328
1342
|
|
1343
|
+
def before_ui_save(self, ar, cw):
|
1344
|
+
# Needed for polls.AnswerRemarkField
|
1345
|
+
pass
|
1346
|
+
|
1347
|
+
def get_master_data(self, ar, master_instance=None):
|
1348
|
+
# Needed for polls.AnswerRemarkField
|
1349
|
+
return
|
1350
|
+
|
1329
1351
|
def get_detail_action(self, ar):
|
1330
1352
|
"""
|
1331
1353
|
Return the (bound) detail action to use for showing this database row in
|
lino/core/layouts.py
CHANGED
@@ -99,7 +99,7 @@ class LayoutHandle(object):
|
|
99
99
|
assert isinstance(layout, BaseLayout)
|
100
100
|
assert isinstance(ui, Plugin)
|
101
101
|
self.layout = layout
|
102
|
-
self.ui = ui
|
102
|
+
self.ui = ui # TODO: rename ui to front_end
|
103
103
|
self.hidden_elements = layout.hidden_elements
|
104
104
|
self._data_elems = []
|
105
105
|
self._names = {}
|
@@ -299,7 +299,7 @@ class LayoutHandle(object):
|
|
299
299
|
else:
|
300
300
|
e.hidden = True
|
301
301
|
|
302
|
-
self.ui.setup_layout_element(e)
|
302
|
+
# self.ui.setup_layout_element(e)
|
303
303
|
self.layout.setup_element(self, e)
|
304
304
|
self._names[name] = e
|
305
305
|
return e
|
lino/core/model.py
CHANGED
@@ -7,7 +7,7 @@ See :doc:`/dev/models`, :doc:`/dev/delete`, :doc:`/dev/disable`,
|
|
7
7
|
:doc:`/dev/hide`, :doc:`/dev/format`
|
8
8
|
|
9
9
|
"""
|
10
|
-
|
10
|
+
# from bs4 import BeautifulSoup
|
11
11
|
from lino import logger
|
12
12
|
import copy
|
13
13
|
|
@@ -25,6 +25,7 @@ from lino.utils.soup import sanitize
|
|
25
25
|
from lino.core import fields
|
26
26
|
from lino.core import signals
|
27
27
|
from lino.core import actions
|
28
|
+
from lino.core import inject
|
28
29
|
|
29
30
|
from .fields import make_remote_field, RichTextField, displayfield
|
30
31
|
from .utils import error2str
|
@@ -331,9 +332,9 @@ class Model(models.Model, fields.TableRow):
|
|
331
332
|
@classmethod
|
332
333
|
def lookup_or_create(model, lookup_field, value, **known_values):
|
333
334
|
"""
|
334
|
-
|
335
|
-
|
336
|
-
(and optionally other `known_values` matching exactly).
|
335
|
+
|
336
|
+
Look up whether there is a row having value `value` for field
|
337
|
+
`lookup_field` (and optionally other `known_values` matching exactly).
|
337
338
|
|
338
339
|
If it doesn't exist, create it and emit an
|
339
340
|
:attr:`auto_create <lino.core.signals.auto_create>` signal.
|
@@ -425,7 +426,12 @@ class Model(models.Model, fields.TableRow):
|
|
425
426
|
old = getattr(self, f.name)
|
426
427
|
if old is None:
|
427
428
|
continue
|
428
|
-
|
429
|
+
|
430
|
+
if getattr(f, "format") == "plain":
|
431
|
+
# assert BeautifulSoup(old, "html.parser").find() is None
|
432
|
+
new = old
|
433
|
+
else:
|
434
|
+
new = sanitize(old, **kwargs)
|
429
435
|
# try:
|
430
436
|
# new = bleach.clean(
|
431
437
|
# new,
|
@@ -819,6 +825,10 @@ class Model(models.Model, fields.TableRow):
|
|
819
825
|
|
820
826
|
setattr(model, vfield_name, pick_choice)
|
821
827
|
|
828
|
+
@classmethod
|
829
|
+
def update_field(cls, *args, **kwargs):
|
830
|
+
inject.update_field(cls, *args, **kwargs)
|
831
|
+
|
822
832
|
@classmethod
|
823
833
|
def django2lino(cls, model):
|
824
834
|
"""
|
lino/core/plugin.py
CHANGED
@@ -223,8 +223,8 @@ class Plugin:
|
|
223
223
|
def get_dashboard_items(self, user):
|
224
224
|
return []
|
225
225
|
|
226
|
-
def setup_layout_element(self, el):
|
227
|
-
|
226
|
+
# def setup_layout_element(self, el):
|
227
|
+
# pass
|
228
228
|
|
229
229
|
def get_detail_url(self, ar, actor, pk, *args, **kw):
|
230
230
|
from lino.core.renderer import TextRenderer
|
lino/core/site.py
CHANGED
@@ -139,7 +139,6 @@ class Site(object):
|
|
139
139
|
quantity_max_length = 6
|
140
140
|
# upload_to_tpl = "uploads/%Y/%m"
|
141
141
|
auto_fit_column_widths = True
|
142
|
-
# locale = 'en_GB.utf-8'
|
143
142
|
site_locale = None
|
144
143
|
confdirs = None
|
145
144
|
kernel = None
|
@@ -978,7 +977,7 @@ class Site(object):
|
|
978
977
|
self.update_settings(ROOT_URLCONF=self.root_urlconf)
|
979
978
|
self.update_settings(MEDIA_URL="/media/")
|
980
979
|
|
981
|
-
if
|
980
|
+
if "STATIC_ROOT" not in self.django_settings:
|
982
981
|
# cache_root = os.environ.get("LINO_CACHE_ROOT", None)
|
983
982
|
# if cache_root:
|
984
983
|
# p = Path(cache_root)
|
@@ -986,10 +985,10 @@ class Site(object):
|
|
986
985
|
# p = self.site_dir
|
987
986
|
p = self.site_dir
|
988
987
|
self.update_settings(STATIC_ROOT=str(p / "static_root"))
|
989
|
-
if
|
988
|
+
if "STATIC_URL" not in self.django_settings:
|
990
989
|
self.update_settings(STATIC_URL="/static/")
|
991
990
|
|
992
|
-
if
|
991
|
+
if "USE_TZ" not in self.django_settings:
|
993
992
|
# django.utils.deprecation.RemovedInDjango50Warning: The default
|
994
993
|
# value of USE_TZ will change from False to True in Django 5.0. Set
|
995
994
|
# USE_TZ to False in your project settings if you want to keep the
|
@@ -1235,11 +1234,13 @@ class Site(object):
|
|
1235
1234
|
from lino.core.kernel import site_startup
|
1236
1235
|
|
1237
1236
|
site_startup(self)
|
1238
|
-
|
1239
|
-
|
1240
|
-
|
1241
|
-
|
1242
|
-
|
1237
|
+
|
1238
|
+
if self.site_locale is None:
|
1239
|
+
self.site_locale = '.'.join(locale.getlocale())
|
1240
|
+
try:
|
1241
|
+
locale.setlocale(locale.LC_ALL, self.site_locale)
|
1242
|
+
except locale.Error as e:
|
1243
|
+
self.logger.warning("%s : %s", self.site_locale, e)
|
1243
1244
|
self.clear_site_config()
|
1244
1245
|
|
1245
1246
|
def register_shutdown_task(self, task):
|
@@ -2257,10 +2258,7 @@ class Site(object):
|
|
2257
2258
|
return moneyfmt(v, places=places, **kw)
|
2258
2259
|
|
2259
2260
|
def format_currency(self, *args, **kwargs):
|
2260
|
-
|
2261
|
-
# if six.PY2:
|
2262
|
-
# res = res.decode(locale.nl_langinfo(locale.CODESET))
|
2263
|
-
return res
|
2261
|
+
return locale.currency(*args, **kwargs)
|
2264
2262
|
|
2265
2263
|
LOOKUP_OP = "__iexact"
|
2266
2264
|
|
lino/core/store.py
CHANGED
@@ -447,7 +447,7 @@ class PreviewTextStoreField(StoreField):
|
|
447
447
|
class VirtStoreField(StoreField):
|
448
448
|
def __init__(self, vf, delegate, name):
|
449
449
|
self.vf = vf
|
450
|
-
|
450
|
+
super().__init__(vf.return_type, name)
|
451
451
|
self.as_js = delegate.as_js
|
452
452
|
self.column_names = delegate.column_names
|
453
453
|
self.list_values_count = delegate.list_values_count
|
lino/core/tables.py
CHANGED
@@ -323,6 +323,12 @@ class AbstractTable(actors.Actor):
|
|
323
323
|
See :ref:`dg.table.default_display_modes`.
|
324
324
|
"""
|
325
325
|
|
326
|
+
parent_layout = None
|
327
|
+
|
328
|
+
@classmethod
|
329
|
+
def attach_to_parent_layout(cls, parent):
|
330
|
+
cls.parent_layout = parent
|
331
|
+
|
326
332
|
@classmethod
|
327
333
|
# def get_display_mode(cls, available_width=None):
|
328
334
|
def get_display_mode(cls):
|
@@ -532,7 +538,7 @@ method in order to sort the rows of the queryset.
|
|
532
538
|
@classmethod
|
533
539
|
def get_column_names(self, ar):
|
534
540
|
"""Dynamic tables can subclass this method and return a value for
|
535
|
-
:attr:`column_names`
|
541
|
+
:attr:`column_names` that depends on the request.
|
536
542
|
|
537
543
|
"""
|
538
544
|
# if settings.SITE.mobile_view:
|
lino/core/utils.py
CHANGED
@@ -6,19 +6,19 @@ A collection of utilities which require Django settings to be
|
|
6
6
|
importable.
|
7
7
|
"""
|
8
8
|
|
9
|
-
from .exceptions import ChangedAPI
|
10
9
|
from lino.utils import IncompleteDate
|
11
10
|
import copy
|
12
11
|
import sys
|
13
12
|
import datetime
|
14
13
|
# import yaml
|
15
|
-
|
14
|
+
from importlib import import_module
|
16
15
|
from django.utils.html import format_html, mark_safe, SafeString
|
17
16
|
from django.db import models
|
18
17
|
from django.db.models import Q
|
19
18
|
from django.core.exceptions import FieldDoesNotExist
|
20
19
|
# from django.utils.functional import lazy
|
21
|
-
from
|
20
|
+
from django.core.validators import validate_email, ValidationError, URLValidator
|
21
|
+
from django.apps import apps
|
22
22
|
from django.utils.translation import gettext as _
|
23
23
|
from django.conf import settings
|
24
24
|
from django.core import exceptions
|
@@ -28,9 +28,7 @@ from lino.utils.html import E, assert_safe, tostring
|
|
28
28
|
from lino.utils import capture_output
|
29
29
|
from lino.utils.ranges import isrange
|
30
30
|
|
31
|
-
from
|
32
|
-
|
33
|
-
from django.apps import apps
|
31
|
+
from .exceptions import ChangedAPI
|
34
32
|
|
35
33
|
get_models = apps.get_models
|
36
34
|
|
@@ -642,23 +640,6 @@ def navinfo(qs, elem, limit=None):
|
|
642
640
|
)
|
643
641
|
|
644
642
|
|
645
|
-
# class Handle(object):
|
646
|
-
# """Base class for :class:`lino.core.tables.TableHandle`,
|
647
|
-
# :class:`lino.core.frames.FrameHandle` etc.
|
648
|
-
|
649
|
-
# The "handle" of an actor is responsible for expanding layouts into
|
650
|
-
# sets of (renderer-specific) widgets (called "elements"). This
|
651
|
-
# operation is done once per actor per renderer.
|
652
|
-
|
653
|
-
# """
|
654
|
-
# # def __init__(self):
|
655
|
-
# # self.ui = settings.SITE.kernel.default_ui
|
656
|
-
|
657
|
-
# def setup(self, ar):
|
658
|
-
# settings.SITE.kernel.setup_handle(self, ar)
|
659
|
-
# # self.ui.setup_handle(self, ar)
|
660
|
-
|
661
|
-
|
662
643
|
class Parametrizable(object):
|
663
644
|
"""
|
664
645
|
Base class for both Actors and Actions. See :doc:`/dev/parameters`.
|
lino/help_texts.py
CHANGED
@@ -320,6 +320,11 @@ help_texts = {
|
|
320
320
|
'lino.core.model.Model.set_widget_options' : _("""Set default values for the widget options of a given element."""),
|
321
321
|
'lino.core.model.Model.get_overview_elems' : _("""Return a list of HTML elements to be shown in overview field."""),
|
322
322
|
'lino.core.model.Model.merge_row' : _("""Merge this object into another object of same class."""),
|
323
|
+
'lino.utils.quantities.Quantity' : _("""The base class for all quantities."""),
|
324
|
+
'lino.utils.quantities.Quantity.limit_length' : _("""Reduce the number of decimal places so that the value fits into a field of the specified max_length, if possible. This will round the value (reducing precision) if needed."""),
|
325
|
+
'lino.utils.quantities.Duration' : _("""The class to represent a duration."""),
|
326
|
+
'lino.utils.quantities.Percentage' : _("""The class to represent a percentage."""),
|
327
|
+
'lino.utils.quantities.Fraction' : _("""The class to represent a fraction. (Not yet implemented)"""),
|
323
328
|
'lino.core.model.Model.get_request_queryset' : _("""Return the Django queryset to be used by action request ar for any data table on this model."""),
|
324
329
|
'lino.core.model.Model.before_ui_save' : _("""A hook for adding custom code to be executed each time an instance of this model gets updated via the user interface and before the changes are written to the database."""),
|
325
330
|
'lino.core.model.Model.after_ui_save' : _("""Like before_ui_save(), but is called after the changes are written to the database."""),
|
@@ -709,4 +714,5 @@ help_texts = {
|
|
709
714
|
'lino.core.model.Model.create_FOO_choice' : _("""For every field named “FOO” for which a chooser exists, if the model also has a method called “create_FOO_choice”, then this chooser will be a learning chooser. That is, users can enter text into the combobox, and Lino will create a new database object from it."""),
|
710
715
|
'lino.core.model.Model.get_choices_text' : _("""Return the text to be displayed when an instance of this model is being used as a choice in a combobox of a ForeignKey field pointing to this model. request is the web request, actor is the requesting actor."""),
|
711
716
|
'lino.core.model.Model.disable_delete' : _("""Decide whether this database object may be deleted. Return None when there is no veto against deleting this database row, otherwise a translatable message that explains to the user why they can’t delete this row."""),
|
717
|
+
'lino.core.model.Model.update_field' : _("""Shortcut to call lino.core.inject.update_field() for usage during lino.core.site.Site.do_site_startup() in a settings.py or similar place."""),
|
712
718
|
}
|
lino/modlib/help/models.py
CHANGED
@@ -6,7 +6,6 @@ from django.utils.translation import get_language
|
|
6
6
|
from lino.core.actors import Actor
|
7
7
|
from lino.api import dd, _
|
8
8
|
from lino.modlib.memo.mixins import MemoReferrable
|
9
|
-
from lino_xl.lib.contacts.mixins import ContactRelated
|
10
9
|
|
11
10
|
use_contacts = dd.get_plugin_setting("help", "use_contacts")
|
12
11
|
make_help_pages = dd.get_plugin_setting("help", "make_help_pages")
|
@@ -61,6 +60,8 @@ if make_help_pages:
|
|
61
60
|
|
62
61
|
if use_contacts:
|
63
62
|
|
63
|
+
from lino_xl.lib.contacts.mixins import ContactRelated
|
64
|
+
|
64
65
|
class SiteContactTypes(dd.ChoiceList):
|
65
66
|
verbose_name = _("Site contact type")
|
66
67
|
verbose_name_plural = _("Site contact types")
|
@@ -1,4 +1,4 @@
|
|
1
|
-
# Copyright 2016-
|
1
|
+
# Copyright 2016-2025 Rumma & Ko Ltd
|
2
2
|
# License: GNU Affero General Public License v3 (see file COPYING for details)
|
3
3
|
"""Emit a broadcast notification "The database has been initialized."
|
4
4
|
|
@@ -14,7 +14,12 @@ from django.utils.timezone import make_aware
|
|
14
14
|
|
15
15
|
|
16
16
|
def objects():
|
17
|
-
|
17
|
+
|
18
|
+
# The messages are dated one day before today() because
|
19
|
+
# book/docs/specs/notify.rst has a code snippet that failed when pm prep had
|
20
|
+
# been run before 5:48am
|
21
|
+
|
22
|
+
now = datetime.datetime.combine(dd.today(-1), i2t(548))
|
18
23
|
if settings.USE_TZ:
|
19
24
|
now = make_aware(now)
|
20
25
|
mt = rt.models.notify.MessageTypes.system
|
lino/utils/quantities.py
CHANGED
@@ -1,10 +1,10 @@
|
|
1
1
|
# -*- coding: UTF-8 -*-
|
2
|
-
# Copyright 2012-
|
2
|
+
# Copyright 2012-2025 Rumma & Ko Ltd
|
3
3
|
# License: GNU Affero General Public License v3 (see file COPYING for details)
|
4
4
|
"""See :doc:`/dev/quantities`."""
|
5
5
|
|
6
6
|
import datetime
|
7
|
-
from decimal import Decimal
|
7
|
+
from decimal import Decimal, ROUND_HALF_UP, InvalidOperation
|
8
8
|
|
9
9
|
DEC2HOUR = Decimal(1) / Decimal(60)
|
10
10
|
|
@@ -19,13 +19,26 @@ class Quantity(Decimal):
|
|
19
19
|
# if isinstance(value, str):
|
20
20
|
# value = Decimal(value)
|
21
21
|
self = Decimal.__new__(cls, value, context)
|
22
|
+
# try:
|
23
|
+
# self = Decimal.__new__(cls, value, context)
|
24
|
+
# except InvalidOperation as e:
|
25
|
+
# raise Exception(f"Failed to parse {repr(value)}: {e}")
|
22
26
|
self._text = str(value)
|
23
27
|
return self
|
24
28
|
|
25
29
|
def __str__(self):
|
26
|
-
# return "{}%".format(self * 100)
|
27
30
|
return self._text
|
28
31
|
|
32
|
+
def limit_length(self, max_length, excl=Exception):
|
33
|
+
rv = self
|
34
|
+
while len(rv) > max_length:
|
35
|
+
if (pos := rv._text.find(".")) == -1:
|
36
|
+
raise excl(f"Cannot reduce length of {self} to {max_length}")
|
37
|
+
places = len(rv) - pos - 2 # one decimal place less than before
|
38
|
+
q = Decimal(10) ** -places
|
39
|
+
rv = self.__class__(self.quantize(q, rounding=ROUND_HALF_UP))
|
40
|
+
return rv
|
41
|
+
|
29
42
|
def __format__(self, format_spec):
|
30
43
|
if format_spec:
|
31
44
|
return format(self._text, format_spec)
|
@@ -191,6 +204,9 @@ class Duration(Quantity):
|
|
191
204
|
|
192
205
|
__rmul__ = __mul__
|
193
206
|
|
207
|
+
# def limit_length(self, max_length):
|
208
|
+
# raise Exception("Cannot limit the length of a duration")
|
209
|
+
|
194
210
|
|
195
211
|
def convert_from(value, context=None):
|
196
212
|
if isinstance(value, str):
|
@@ -220,6 +236,8 @@ def parse(s):
|
|
220
236
|
|
221
237
|
|
222
238
|
def parse_decimal(s):
|
239
|
+
if not s:
|
240
|
+
return None
|
223
241
|
if "." in s and "," in s:
|
224
242
|
raise ValueError("Invalid decimal value %r" % s)
|
225
243
|
s = s.replace(",", ".")
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: lino
|
3
|
-
Version: 25.
|
3
|
+
Version: 25.5.0
|
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
|
@@ -1,14 +1,14 @@
|
|
1
1
|
lino/.cvsignore,sha256=1vrrWoP-WD8hPfCszHHIiJEi8KUMRCt5WvoKB9TSB1k,28
|
2
2
|
lino/SciTEDirectory.properties,sha256=rCYi_e-6h8Yx5DwXhAa6MBPlVINcl6Vv9BQDYZV2_go,28
|
3
|
-
lino/__init__.py,sha256=
|
3
|
+
lino/__init__.py,sha256=AwfkNuetCwDhmYUEAk9zyjyrSX4Ia362yUdo_uuw1ps,6176
|
4
4
|
lino/ad.py,sha256=AQ-vJ4scac1mx3xegXezxnxyOQpV-a0q3VFMJSDbj2s,142
|
5
5
|
lino/apps.py,sha256=ECq-dPARDkuhngwNrcipse3b4Irj70HxJs44uWEZFc4,27
|
6
6
|
lino/hello.py,sha256=7-PJg7PnEiznyETqGjOwXcKh8rda0qLetpbS2gvRYy0,532
|
7
|
-
lino/help_texts.py,sha256=
|
7
|
+
lino/help_texts.py,sha256=Crr3VSPlkbhO-2pZssbMxcZnwKmcKhvr82H_Rl8ziVI,92012
|
8
8
|
lino/api/__init__.py,sha256=WmzHU-rHdZ68se_nI0SmepQTGE8-cd9tPpovHRH9aag,512
|
9
9
|
lino/api/ad.py,sha256=F6SrcKPRRalHKOZ7QLwsRWGq9hhykQIeo0b85cEk9NQ,314
|
10
10
|
lino/api/dd.py,sha256=-mxiSJS27LGCWyOgboA8qDudTkE9vg9vmUGOT08Hq6A,7410
|
11
|
-
lino/api/doctest.py,sha256
|
11
|
+
lino/api/doctest.py,sha256=--xCSBLFsqyE8OfKhfnbB4CJCDMg8j-Fi--jU4diwo8,24446
|
12
12
|
lino/api/rt.py,sha256=OCYWhrWnMcL988MdvBLBEP8qKQJEGXQhVoam_X0sotU,1376
|
13
13
|
lino/api/selenium.py,sha256=bOu8UaNz3Q7lGVvxjmvrtYtSWn1xfI1f5MN5sVcdYr8,9383
|
14
14
|
lino/api/shell.py,sha256=epyjwEZ396TiJ0AHqhVIvzX8TBIXU8xR4UHJlYOrRhc,536
|
@@ -40,32 +40,32 @@ lino/core/dbtables.py,sha256=bV1hcr9MXCT3doauxcOrnTZeBmHtb1tAQrLlnfvQj_o,29113
|
|
40
40
|
lino/core/dbutils.py,sha256=_QHcWd-ajLUwt5G8uOp8d47lZQKD3VseHnqKJke18rA,263
|
41
41
|
lino/core/ddh.py,sha256=dYScxWKTOCDEgow7wJNJe812ESasmmITPK2ovraBQno,3172
|
42
42
|
lino/core/diff.py,sha256=XQ-oQQDS_v3kXd4eRP9Hwr5UCgp-TPZIPVav9ZblUno,5882
|
43
|
-
lino/core/elems.py,sha256=
|
43
|
+
lino/core/elems.py,sha256=BjqG1yVT6yB2aPoRZGZ_M5fOjuHHeA1LL27QjJ3xF3U,108795
|
44
44
|
lino/core/exceptions.py,sha256=QDxDo5cllSyXQ8VWet9hGXzNadxCOmwMVrFXc6V-vpE,665
|
45
|
-
lino/core/fields.py,sha256=
|
45
|
+
lino/core/fields.py,sha256=NHnRDIM8AqyOyIcQzW3I1Skr2N4ZecbvhpR48Kp6oq8,59022
|
46
46
|
lino/core/frames.py,sha256=ISxgq9zyZfqW3tDZMWdKi9Ij455lT_81qBH0xex0bfE,1161
|
47
47
|
lino/core/gfks.py,sha256=6VXn2FSIXOrwVq0stfbPevT37EWg1tg4Fn-HMNVnbmk,1970
|
48
48
|
lino/core/help.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
49
49
|
lino/core/inject.py,sha256=Qd_PGEn0yMXNYVPI0wCv1vvo2CNdlPkyoBmKZELOtGM,13422
|
50
50
|
lino/core/kernel.py,sha256=kKgKmgUZqh7rFqpKjuM_T6pUvaw7K_vl3PEfRqvrg8Q,47794
|
51
51
|
lino/core/keyboard.py,sha256=W3jA6qtB5HMppoNnd_6PgIM7ZlyHilJEhBvdeZTY7Xk,1283
|
52
|
-
lino/core/layouts.py,sha256=
|
52
|
+
lino/core/layouts.py,sha256=VT0xcDB7JKSsNm2UOybAwJw3zOie57sRagRYlyA7nkg,28697
|
53
53
|
lino/core/menus.py,sha256=W0Co9K-ayvfX5Zt5rgSxJrFRejtiYrwIR5EecdYXPNc,10857
|
54
54
|
lino/core/merge.py,sha256=sKtTeZtHdoDKerdHj4NXuXXNzpKDsfdPaiq-CY0NqQc,9094
|
55
|
-
lino/core/model.py,sha256=
|
55
|
+
lino/core/model.py,sha256=h3pjAMa61JF-L2hHg74OGFohs3jK5XkLTHJezqdIkhY,37200
|
56
56
|
lino/core/permissions.py,sha256=Fnemz3NwWz21X0YATI9Q7ba2FcAdg-EMLHjIcbt_AVU,6840
|
57
|
-
lino/core/plugin.py,sha256=
|
57
|
+
lino/core/plugin.py,sha256=n_f5dSy4vqamnyzpNnaNcTlUp2EAgaAVxIJ5EHG7aHc,6837
|
58
58
|
lino/core/renderer.py,sha256=C00J0L41hLv9b2sAzqSSrT0Lz5Fc78JnwHiq9LqF81c,47310
|
59
59
|
lino/core/requests.py,sha256=aIT8DsWVWwYQWHf7xX7SbbUf0s_8dWfayR3104VDrnQ,95833
|
60
60
|
lino/core/roles.py,sha256=PXwk436xUupxdbJcygRSYFu7ixfKjAJPQRUQ8sy0lB0,4425
|
61
61
|
lino/core/signals.py,sha256=0JT89mkjSbRm57QZcSI9DoThoKUGkyi-egNhuLUKEds,948
|
62
|
-
lino/core/site.py,sha256=
|
63
|
-
lino/core/store.py,sha256=
|
64
|
-
lino/core/tables.py,sha256=
|
62
|
+
lino/core/site.py,sha256=8qV5nEPuUaA7ViEcFOeOGPpzILrmWKOFS9MGPjqFcAs,82713
|
63
|
+
lino/core/store.py,sha256=oIQMOTr5lSBYKZxkPMWjqfLFod8DJnBnF728brFldWg,51474
|
64
|
+
lino/core/tables.py,sha256=68j42gXfUl5vIpqkRdgNBATi6blWLdjjh6Iznz3Gb8M,24472
|
65
65
|
lino/core/urls.py,sha256=06QlmN1vpxjmb5snO3SPpP6lX1pMdE60bTiBiC77_vQ,2677
|
66
66
|
lino/core/user_types.py,sha256=0iSYmzr2M9v2Mn2y6hzAZeqareUT-gD7l3MfIPyG9ZI,867
|
67
67
|
lino/core/userprefs.py,sha256=cmufIS9xJErKDrw05uoWtQTUR6WRWIGkU1KdAqzNr5M,2850
|
68
|
-
lino/core/utils.py,sha256=
|
68
|
+
lino/core/utils.py,sha256=iYP4_AJVOVcLSj2GdPqQ9WEScze_-SFx0rPEpmSCcU8,36065
|
69
69
|
lino/core/views.py,sha256=qLjEN6GSXScbmAnKN7yDHySmsjL0h4sMKRIQCpOEdPU,7026
|
70
70
|
lino/core/widgets.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
71
71
|
lino/core/workflows.py,sha256=9O5INnx3nPW73UmeEPWgN146clQSWwna3Jq_w1MThcg,10567
|
@@ -3494,7 +3494,7 @@ lino/modlib/gfks/fields.py,sha256=43yIsudT07bXuKD1Gsqvn15_F3UaeZQqZzuzK9dDdLc,32
|
|
3494
3494
|
lino/modlib/gfks/mixins.py,sha256=BcITVETBR7zVj684diZGCii3rzm7lgrgzr2euy-D-iw,2626
|
3495
3495
|
lino/modlib/gfks/models.py,sha256=bDbuvtpSW-wxNWLC3pKWsDQPyN4g6MFMTS-66aQ2DX8,6723
|
3496
3496
|
lino/modlib/help/__init__.py,sha256=PlThatoYB6lThNahWkVL2ZatimW0d_9LAPTZIE7hiuE,4434
|
3497
|
-
lino/modlib/help/models.py,sha256=
|
3497
|
+
lino/modlib/help/models.py,sha256=StmQhBV9zqmT3eT1N0xTkgOG22ghYwjj4P56CuOxos0,3502
|
3498
3498
|
lino/modlib/help/utils.py,sha256=dWmz0huCw7N4ENIsCUoGiXyn3AbkE96jVR4YwyzPjjo,3944
|
3499
3499
|
lino/modlib/help/config/makehelp/actor.tpl.rst,sha256=Yl-ZAWvI93cVFLd7GG-qpn_vkGFvKe3iR0eWBJpHgMc,346
|
3500
3500
|
lino/modlib/help/config/makehelp/actors.tpl.rst,sha256=wXMKYQnlhLi432QggVgRUWGuHRDiSX9NyXSdnSar7Io,249
|
@@ -3560,7 +3560,7 @@ lino/modlib/notify/views.py,sha256=Ar2L_Tv1qqU5giHu2b0h_AKc3utvgfZwda4qncqoBsk,9
|
|
3560
3560
|
lino/modlib/notify/config/notify/individual.eml,sha256=Ct8fLx7dz_adE_ua8S87dYFSNESCHRbRGx9Ven1MlLI,307
|
3561
3561
|
lino/modlib/notify/config/notify/summary.eml,sha256=odd3Obaf76H5w7lrbwHqY3D4v3DHoMhhnc59EvzbQrk,551
|
3562
3562
|
lino/modlib/notify/fixtures/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
3563
|
-
lino/modlib/notify/fixtures/demo2.py,sha256=
|
3563
|
+
lino/modlib/notify/fixtures/demo2.py,sha256=X_KfsRM5FaOhkcFUpFoHiVx6lvG-UDjju3k14mnZyMk,1298
|
3564
3564
|
lino/modlib/notify/static/img/lino-logo.png,sha256=FvZYWjJ6rV1FJldgoTjulg2aZqwX2iqYdu9GWV-1hYQ,4031
|
3565
3565
|
lino/modlib/notify/static/js/push.js/CONTRIBUTING.md,sha256=4n-jT_-gVthlPrbVQZoIeIcoFydE2Rfg09ECaTmcARs,1755
|
3566
3566
|
lino/modlib/notify/static/js/push.js/LICENSE.md,sha256=zesGXKL-AuD7mNjpuNQ554PdBbjptae-jSZtPhomY80,1084
|
@@ -3602,7 +3602,7 @@ lino/modlib/publisher/config/publisher/page.pub.html,sha256=kjUTHInDLu25QiOyZDpx
|
|
3602
3602
|
lino/modlib/publisher/fixtures/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
3603
3603
|
lino/modlib/publisher/fixtures/demo2.py,sha256=KrF672tNLlhMX6EkxKDfcvvE9pGH4k9NgF7Rk5DkiLg,366
|
3604
3604
|
lino/modlib/publisher/fixtures/std.py,sha256=7xpMOr4kd-4SX8uhwxagktUlcXAr6fdZfD-H519CR6g,987
|
3605
|
-
lino/modlib/publisher/fixtures/synodalworld.py,sha256=
|
3605
|
+
lino/modlib/publisher/fixtures/synodalworld.py,sha256=FFUPYrl-hgF9rEWU56OCEyhmrwtNzIsdYsfQOXJXcmA,732
|
3606
3606
|
lino/modlib/restful/__init__.py,sha256=9CqWTQkJ2KpLW5MI1bKqKZU_7nXxodxl7HSgc8Agwmw,1506
|
3607
3607
|
lino/modlib/restful/fields.py,sha256=8dik0q2LaCtJhQX1lu-fe-y_38xdhAWy1ZX3EKmJWfs,567
|
3608
3608
|
lino/modlib/restful/serializers.py,sha256=AVyA19UkRUJ9AjlUN0fJyrj1LgfOyp-a2ANcVkecWtA,1143
|
@@ -4614,7 +4614,7 @@ lino/utils/odsreader_sample.ods,sha256=mb4Zd2RPL4pynnS6XidGXctdbXzTO1laS9ebYpgbc
|
|
4614
4614
|
lino/utils/pdf.py,sha256=caISmnrrADN1MTK_GIiasfkVUyqhTEuK9mtoMf_S7qU,688
|
4615
4615
|
lino/utils/pythontest.py,sha256=AA9i3CEWgpbJKNTkxc0Gz94sy1R1MBpyinqrlahKKno,4954
|
4616
4616
|
lino/utils/pyuca.py,sha256=YSLpCYEMqKUhx1y0q-ML8Ryu2ARDFO2HyIFxz1DzYEY,4344
|
4617
|
-
lino/utils/quantities.py,sha256=
|
4617
|
+
lino/utils/quantities.py,sha256=s9Q3AMLccI8h_sjR-NojLcT4VTcOCIT56MmJuFjrcoE,8131
|
4618
4618
|
lino/utils/ranges.py,sha256=wVsZMe3dlaRYWWELXCGSK2DvjBfW4eLbAwpQVYqzj3g,3303
|
4619
4619
|
lino/utils/report.py,sha256=i2-NyNud-coFlwIlHGgmIcvMgMhlNdmr3wXCO0prGf4,8013
|
4620
4620
|
lino/utils/restify.py,sha256=AbVCBjM9V9rYuFweV1gVo9ObO8lblJbA-mKjaPsebRE,14068
|
@@ -4635,8 +4635,8 @@ lino/utils/xml.py,sha256=EGDnO1UaREst9fS7KTESdbHnrrVCwKbRQdvut6B6GmQ,1612
|
|
4635
4635
|
lino/utils/mldbc/__init__.py,sha256=QqWRlzeXaOmFfbCk-vTY3SZMn1-FCf67XnpZdd_Nim0,1134
|
4636
4636
|
lino/utils/mldbc/fields.py,sha256=tAX8G5UKigr9c6g0F3ARIjZZtg406mdaZ--PWSbiH9E,2873
|
4637
4637
|
lino/utils/mldbc/mixins.py,sha256=CkYe5jDa7xp9fJq_V8zcZf8ocxgIjUgHc9KZccvA_Yw,1945
|
4638
|
-
lino-25.
|
4639
|
-
lino-25.
|
4640
|
-
lino-25.
|
4641
|
-
lino-25.
|
4642
|
-
lino-25.
|
4638
|
+
lino-25.5.0.dist-info/METADATA,sha256=5TiawKicYEoAhL81Eg0HOAp-GZ6oXqSXl_m6o-yhO3A,42534
|
4639
|
+
lino-25.5.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
4640
|
+
lino-25.5.0.dist-info/licenses/AUTHORS.rst,sha256=8VEm_G4HOmYEa4oi1nVoKKsdo4JanekEJCefWd2E8vk,981
|
4641
|
+
lino-25.5.0.dist-info/licenses/COPYING,sha256=DZak_2itbUtvHzD3E7GNUYSRK6jdOJ-GqncQ2weavLA,34523
|
4642
|
+
lino-25.5.0.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|