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 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.4.5'
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, escape
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.html import E, py2html, tostring
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 DetailWindow, then it
2862
- # will be rendered as a :term:`slave panel`. The panel will have a
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.renderer.front_end.media_name == "react"
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 len(str(raw_value)) > self.max_length:
1005
- if self.overflow_value:
1006
- return self.overflow_value
1007
- raise ValidationError(
1008
- f"Cannot accept quantity {raw_value} "
1009
- + f"because max_length is {self.max_length}")
1010
- # print("20230129 Can't store {}={} in {}".format(self.name, raw_value, obj))
1011
- # return -1
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
- Look up whether there is a model instance having
335
- `lookup_field` with value `value`
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
- new = sanitize(old, **kwargs)
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
- pass
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 not "STATIC_ROOT" in self.django_settings:
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 not "STATIC_URL" in self.django_settings:
988
+ if "STATIC_URL" not in self.django_settings:
990
989
  self.update_settings(STATIC_URL="/static/")
991
990
 
992
- if not "USE_TZ" in self.django_settings:
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
- if self.site_locale:
1239
- try:
1240
- locale.setlocale(locale.LC_ALL, self.site_locale)
1241
- except locale.Error as e:
1242
- self.logger.warning("%s : %s", self.site_locale, e)
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
- res = locale.currency(*args, **kwargs)
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
- StoreField.__init__(self, vf.return_type, name)
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` which depends on the request.
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 importlib import import_module
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 django.core.validators import validate_email, ValidationError, URLValidator
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
  }
@@ -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-2018 Rumma & Ko Ltd
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
- now = datetime.datetime.combine(dd.today(), i2t(548))
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
@@ -15,8 +15,6 @@ home_children = [
15
15
 
16
16
  def objects():
17
17
  image = rt.models.uploads.Upload.objects.first()
18
- print('='*80)
19
- print(image)
20
18
  def iterate(iterable):
21
19
  try:
22
20
  for obj in iterable:
lino/utils/quantities.py CHANGED
@@ -1,10 +1,10 @@
1
1
  # -*- coding: UTF-8 -*-
2
- # Copyright 2012-2021 Rumma & Ko Ltd
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.4.5
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=BwMhah1-EWVulbl6pSZkJqaPAx15Bc3Lg58OFimYFqI,6176
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=z4EGZdwOchg1_ksrjA3oWaQUlFRCxF14JpK4e8WHihY,91224
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=87j1_xGpomQlEmUUh8CprBFbbqKuQe1OWr5iIXABWag,24028
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=3PZ-0RDr6c8QoZa9sWKV36w-wn1Oj2xFpjW8eHrEpzs,108811
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=zPP90oYxodCyF4DzE62008fUzH75C5RimOtoaWbfRz4,58231
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=Wojx5UUyhy7PsZE8FWmiXcmZDj4wz2y_1_wlz1G560Q,28663
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=YENZ-v1sggtdTitgmC8jBmuihN1EsKXQMlbPAuTSat8,36870
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=8FdxFF5tqHK-N8aXANzpWXY97hj6Lty4ulM6PWcq5A8,6833
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=WqL_QrimT85POO7wKp3R4s8O4P_tvOOxg6WDO968vOQ,82796
63
- lino/core/store.py,sha256=6pd4J5Y-U7Muz4mKFSL6K9KEZpJUbpM-I7RQTWyCo-8,51483
64
- lino/core/tables.py,sha256=Yoj-H2ASW9xIger8kuW8xst5A_tEmtWpV17L11Ebca0,24348
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=6yFnP9BFOW5O4oABdv0ASAeJf6oYQ7BZioEqtBBTVzk,36618
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=Ik-fkUH8KHiPajar-SYCbuaQGfIbbgfI6-9rMkr1Np8,3497
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=KegKuqfkV5KJPRmIshVWo1KHoGBQ7rZoxe23opM_hIA,1124
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=7OPQXVq2Ou8fcRlNli0UFXoGhGPtC6AVXnGoCqn5oKc,767
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=m_oi647uCTf2NhhQo6ytphZa2jQq1QXIUcts-cLkEJM,7367
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.4.5.dist-info/METADATA,sha256=iRK3Rzw51gGXTd8xkApmMmpq6BLWOZZC3WV-3NFQLTw,42534
4639
- lino-25.4.5.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
4640
- lino-25.4.5.dist-info/licenses/AUTHORS.rst,sha256=8VEm_G4HOmYEa4oi1nVoKKsdo4JanekEJCefWd2E8vk,981
4641
- lino-25.4.5.dist-info/licenses/COPYING,sha256=DZak_2itbUtvHzD3E7GNUYSRK6jdOJ-GqncQ2weavLA,34523
4642
- lino-25.4.5.dist-info/RECORD,,
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