lino 25.1.6__py3-none-any.whl → 25.2.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/core/actions.py +6 -0
- lino/core/actors.py +9 -3
- lino/core/callbacks.py +3 -2
- lino/core/fields.py +4 -0
- lino/core/keyboard.py +7 -2
- lino/core/model.py +1 -0
- lino/core/requests.py +7 -7
- lino/core/site.py +1 -2
- lino/core/store.py +2 -2
- lino/help_texts.py +3 -1
- lino/locale/bn/LC_MESSAGES/django.po +782 -710
- lino/locale/de/LC_MESSAGES/django.mo +0 -0
- lino/locale/de/LC_MESSAGES/django.po +1259 -1280
- lino/locale/django.pot +751 -702
- lino/locale/es/LC_MESSAGES/django.po +777 -708
- lino/locale/et/LC_MESSAGES/django.po +784 -709
- lino/locale/fr/LC_MESSAGES/django.po +1339 -1191
- lino/locale/nl/LC_MESSAGES/django.po +787 -712
- lino/locale/pt_BR/LC_MESSAGES/django.po +769 -700
- lino/locale/zh_Hant/LC_MESSAGES/django.po +769 -700
- lino/management/commands/demotest.py +2 -1
- lino/mixins/__init__.py +1 -1
- lino/modlib/checkdata/choicelists.py +5 -4
- lino/modlib/checkdata/models.py +9 -8
- lino/modlib/help/models.py +5 -0
- lino/modlib/jinja/__init__.py +0 -4
- lino/modlib/memo/__init__.py +1 -1
- lino/modlib/periods/mixins.py +1 -25
- lino/modlib/periods/models.py +42 -9
- lino/modlib/system/choicelists.py +12 -11
- lino/utils/config.py +2 -0
- lino/utils/dpy.py +15 -3
- lino/utils/soup.py +136 -103
- {lino-25.1.6.dist-info → lino-25.2.0.dist-info}/METADATA +1 -1
- {lino-25.1.6.dist-info → lino-25.2.0.dist-info}/RECORD +39 -39
- {lino-25.1.6.dist-info → lino-25.2.0.dist-info}/WHEEL +0 -0
- {lino-25.1.6.dist-info → lino-25.2.0.dist-info}/licenses/AUTHORS.rst +0 -0
- {lino-25.1.6.dist-info → lino-25.2.0.dist-info}/licenses/COPYING +0 -0
@@ -101,7 +101,8 @@ class TestCase(DemoTestCase):
|
|
101
101
|
# every new failure will now blacklist it again, the
|
102
102
|
# max_failed_auth_per_ip no longer counts.
|
103
103
|
|
104
|
-
time.sleep(1.5)
|
104
|
+
# time.sleep(1.5)
|
105
|
+
time.sleep(5)
|
105
106
|
self.assertEqual(login("bad"), "Failed to sign in as robin.")
|
106
107
|
self.assertEqual(rec.login_failures, 5)
|
107
108
|
self.assertEqual(
|
lino/mixins/__init__.py
CHANGED
@@ -70,7 +70,7 @@ class Phonable(model.Model):
|
|
70
70
|
|
71
71
|
class Modified(model.Model):
|
72
72
|
"""
|
73
|
-
Adds a a timestamp field
|
73
|
+
Adds a a timestamp field that holds the last modification time of
|
74
74
|
every individual database object.
|
75
75
|
|
76
76
|
.. attribute:: modified
|
@@ -34,8 +34,9 @@ class Checker(dd.Choice):
|
|
34
34
|
self = None
|
35
35
|
model = None
|
36
36
|
help_text = None
|
37
|
+
no_auto = False # SupplierChecker.activate() sets no_auto to True
|
37
38
|
|
38
|
-
def __init__(self):
|
39
|
+
def __init__(self, **kwargs):
|
39
40
|
# value = self.__module__ + '.' + self.__class__.__name__
|
40
41
|
value = self.__module__.split(".")[-2] + "." + self.__class__.__name__
|
41
42
|
# if isinstance(self.model, six.string_types):
|
@@ -46,13 +47,13 @@ class Checker(dd.Choice):
|
|
46
47
|
text = value
|
47
48
|
else:
|
48
49
|
text = self.verbose_name
|
49
|
-
super().__init__(value, text, None)
|
50
|
+
super().__init__(value, text, None, **kwargs)
|
50
51
|
|
51
52
|
@classmethod
|
52
|
-
def activate(cls):
|
53
|
+
def activate(cls, **kwargs):
|
53
54
|
if cls.self is not None:
|
54
55
|
raise Exception("Duplicate call to {0}.activate()".format(cls))
|
55
|
-
cls.self = cls()
|
56
|
+
cls.self = cls(**kwargs)
|
56
57
|
Checkers.add_item_instance(cls.self)
|
57
58
|
|
58
59
|
@classmethod
|
lino/modlib/checkdata/models.py
CHANGED
@@ -250,18 +250,19 @@ def get_checkers_for(model):
|
|
250
250
|
return get_checkable_models()[model]
|
251
251
|
|
252
252
|
|
253
|
-
def check_instance(obj):
|
254
|
-
"""
|
255
|
-
Run all checkers on the given instance. Return list of problem messages.
|
256
|
-
"""
|
253
|
+
def check_instance(obj, **kwargs):
|
257
254
|
for chk in get_checkers_for(obj.__class__):
|
258
|
-
for
|
259
|
-
|
255
|
+
for fixable, msg in chk.check_instance(obj, **kwargs):
|
256
|
+
if fixable:
|
257
|
+
msg = f"(\u2605) {msg}"
|
258
|
+
print(msg)
|
260
259
|
|
261
260
|
|
262
|
-
def get_checkable_models(*args):
|
261
|
+
def get_checkable_models(*args, only_auto=False):
|
263
262
|
checkable_models = OrderedDict()
|
264
263
|
for chk in Checkers.get_list_items():
|
264
|
+
if only_auto and chk.no_auto:
|
265
|
+
continue
|
265
266
|
if len(args):
|
266
267
|
skip = True
|
267
268
|
for arg in args:
|
@@ -280,7 +281,7 @@ def check_data(ar, args=[], fix=True, prune=False):
|
|
280
281
|
# verbosity 0=minimal output, 1=normal output, 2=verbose output, 3=very verbose output
|
281
282
|
Message = rt.models.checkdata.Message
|
282
283
|
# raise Exception("20231230")
|
283
|
-
mc = get_checkable_models(*args)
|
284
|
+
mc = get_checkable_models(*args, only_auto=True)
|
284
285
|
if len(mc) == 0 and len(args) > 0:
|
285
286
|
raise Exception("No checker matches {0}".format(args))
|
286
287
|
if prune:
|
lino/modlib/help/models.py
CHANGED
@@ -16,6 +16,11 @@ class OpenHelpWindow(dd.Action):
|
|
16
16
|
action_name = "open_help"
|
17
17
|
# icon_name = 'help'
|
18
18
|
default_format = "ajax"
|
19
|
+
|
20
|
+
# add spaces because React enlarges button_text when len() is 1 and for "?"
|
21
|
+
# this doesn't look nice. But adding spaces breaks a series of doctests, so
|
22
|
+
# I undid that change:
|
23
|
+
# button_text = " ? "
|
19
24
|
button_text = "?"
|
20
25
|
select_rows = False
|
21
26
|
help_text = _("Open Help Window")
|
lino/modlib/jinja/__init__.py
CHANGED
@@ -39,10 +39,6 @@ class Plugin(ad.Plugin):
|
|
39
39
|
|
40
40
|
self.renderer = JinjaRenderer(self)
|
41
41
|
|
42
|
-
# internal backwards compat:
|
43
|
-
# kernel.site.jinja_env = self.renderer.jinja_env
|
44
|
-
# TODO: remove above lines and convert old code
|
45
|
-
|
46
42
|
def list_templates(self, ext, *groups):
|
47
43
|
"""Return a list of possible choices for a field that contains a
|
48
44
|
template name.
|
lino/modlib/memo/__init__.py
CHANGED
lino/modlib/periods/mixins.py
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
# -*- coding: UTF-8 -*-
|
2
|
-
# Copyright 2008-
|
2
|
+
# Copyright 2008-2025 Rumma & Ko Ltd
|
3
3
|
# License: GNU Affero General Public License v3 (see file COPYING for details)
|
4
4
|
|
5
5
|
import datetime
|
@@ -12,30 +12,6 @@ from lino.mixins import Referrable
|
|
12
12
|
from lino.utils import ONE_DAY
|
13
13
|
|
14
14
|
from lino.modlib.office.roles import OfficeStaff
|
15
|
-
from lino.modlib.system.choicelists import DurationUnits
|
16
|
-
|
17
|
-
|
18
|
-
def get_range_for_date(date):
|
19
|
-
"""
|
20
|
-
Return the default start and end date of the period to create for the given
|
21
|
-
date.
|
22
|
-
"""
|
23
|
-
pt = dd.plugins.periods.period_type
|
24
|
-
month = date.month
|
25
|
-
year = date.year
|
26
|
-
month -= dd.plugins.periods.start_month
|
27
|
-
if month < 0:
|
28
|
-
month += 12
|
29
|
-
year -= 1
|
30
|
-
period = int(month / pt.duration)
|
31
|
-
month = dd.plugins.periods.start_month + period * pt.duration
|
32
|
-
if month > 12:
|
33
|
-
month -= 12
|
34
|
-
year += 1
|
35
|
-
sd = datetime.date(year, month, 1)
|
36
|
-
# ed = sd.replace(month=sd.month + pt.duration + 1, 1) - ONE_DAY
|
37
|
-
ed = DurationUnits.months.add_duration(sd, pt.duration) - ONE_DAY
|
38
|
-
return (sd, ed)
|
39
15
|
|
40
16
|
|
41
17
|
class PeriodRange(dd.Model):
|
lino/modlib/periods/models.py
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
# -*- coding: UTF-8 -*-
|
2
|
-
# Copyright 2008-
|
2
|
+
# Copyright 2008-2025 Rumma & Ko Ltd
|
3
3
|
# License: GNU Affero General Public License v3 (see file COPYING for details)
|
4
4
|
|
5
5
|
import datetime
|
@@ -11,9 +11,8 @@ from lino import mixins
|
|
11
11
|
from lino.utils import ONE_DAY
|
12
12
|
from lino.mixins.periods import DateRange
|
13
13
|
from lino.mixins import Referrable
|
14
|
-
|
14
|
+
from lino.modlib.system.choicelists import DurationUnits
|
15
15
|
from lino.modlib.office.roles import OfficeStaff
|
16
|
-
from .mixins import get_range_for_date
|
17
16
|
from .choicelists import PeriodTypes, PeriodStates
|
18
17
|
|
19
18
|
NEXT_YEAR_SEP = "/"
|
@@ -59,15 +58,26 @@ class StoredYear(DateRange, Referrable):
|
|
59
58
|
return str(year) + NEXT_YEAR_SEP + str(year+1)[-2:]
|
60
59
|
|
61
60
|
@classmethod
|
62
|
-
def
|
61
|
+
def get_range_for_date(cls, date):
|
62
|
+
month = date.month
|
63
|
+
year = date.year
|
64
|
+
month -= dd.plugins.periods.start_month - 1
|
65
|
+
if month < 1:
|
66
|
+
year -= 1
|
67
|
+
sd = datetime.date(year, dd.plugins.periods.start_month, 1)
|
68
|
+
ed = sd.replace(year=year+1) - ONE_DAY
|
69
|
+
return (sd, ed)
|
70
|
+
|
71
|
+
@classmethod
|
72
|
+
def get_or_create_from_date(cls, date, save=True):
|
63
73
|
ref = cls.get_ref_for_date(date)
|
64
74
|
obj = cls.get_by_ref(ref, None)
|
65
75
|
if obj is None:
|
66
|
-
sd =
|
67
|
-
ed = sd.replace(year=date.year+1) - ONE_DAY
|
76
|
+
sd, ed = cls.get_range_for_date(date)
|
68
77
|
obj = cls(ref=ref, start_date=sd, end_date=ed)
|
69
|
-
|
70
|
-
|
78
|
+
if save:
|
79
|
+
obj.full_clean()
|
80
|
+
obj.save()
|
71
81
|
return obj
|
72
82
|
|
73
83
|
def __str__(self):
|
@@ -157,12 +167,35 @@ class StoredPeriod(DateRange, Referrable):
|
|
157
167
|
# period = (month_offset % (periods_per_year-1)) + 1
|
158
168
|
return pt.ref_template.format(**locals())
|
159
169
|
|
170
|
+
@classmethod
|
171
|
+
def get_range_for_date(cls, date):
|
172
|
+
"""
|
173
|
+
Return the default start and end date of the period to create for the given
|
174
|
+
date.
|
175
|
+
"""
|
176
|
+
pt = dd.plugins.periods.period_type
|
177
|
+
month = date.month
|
178
|
+
year = date.year
|
179
|
+
month -= dd.plugins.periods.start_month
|
180
|
+
if month < 1:
|
181
|
+
month += 12
|
182
|
+
year -= 1
|
183
|
+
period = int(month / pt.duration)
|
184
|
+
month = dd.plugins.periods.start_month + period * pt.duration
|
185
|
+
if month > 12:
|
186
|
+
month -= 12
|
187
|
+
year += 1
|
188
|
+
sd = datetime.date(year, month, 1)
|
189
|
+
# ed = sd.replace(month=sd.month + pt.duration + 1, 1) - ONE_DAY
|
190
|
+
ed = DurationUnits.months.add_duration(sd, pt.duration) - ONE_DAY
|
191
|
+
return (sd, ed)
|
192
|
+
|
160
193
|
@classmethod
|
161
194
|
def get_or_create_from_date(cls, date): # get_default_for_date until 20241020
|
162
195
|
ref = date2ref(date)
|
163
196
|
obj = cls.get_by_ref(ref, None)
|
164
197
|
if obj is None:
|
165
|
-
sd, ed = get_range_for_date(date)
|
198
|
+
sd, ed = cls.get_range_for_date(date)
|
166
199
|
obj = cls(ref=ref, start_date=sd, end_date=ed)
|
167
200
|
obj.full_clean()
|
168
201
|
obj.save()
|
@@ -17,7 +17,7 @@ from lino.core.roles import login_required, Explorer
|
|
17
17
|
from lino.core.fields import virtualfield
|
18
18
|
from lino.utils.dates import DateRangeValue
|
19
19
|
from lino.utils.format_date import day_and_month, fds
|
20
|
-
from lino.utils.html import mark_safe, format_html
|
20
|
+
from lino.utils.html import mark_safe, format_html, escape
|
21
21
|
|
22
22
|
|
23
23
|
class YesNo(ChoiceList):
|
@@ -236,21 +236,22 @@ class DisplayColors(ChoiceList):
|
|
236
236
|
item_class = DisplayColor
|
237
237
|
required_roles = login_required(Explorer)
|
238
238
|
column_names = "value name text font_color"
|
239
|
+
preferred_width = 10
|
239
240
|
|
240
241
|
@virtualfield(models.CharField(_("Font color")))
|
241
242
|
def font_color(cls, choice, ar):
|
242
243
|
return choice.font_color
|
243
244
|
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
245
|
+
@classmethod
|
246
|
+
def display_text(cls, bc):
|
247
|
+
# text = escape(bc.text)
|
248
|
+
# txt = f"""<span style="background-color:{bc.name};color:{bc.font_color}">{text}</span>"""
|
249
|
+
# txt = mark_safe(txt)
|
250
|
+
sample = f"""<span style="padding:3pt;background-color:{bc.name};color:{bc.font_color}">(sample)</span>"""
|
251
|
+
sample = mark_safe(sample)
|
252
|
+
txt = format_html("{} {}", bc.text, sample)
|
253
|
+
# raise Exception(f"20250118 {txt.__class__}")
|
254
|
+
return txt
|
254
255
|
|
255
256
|
add = DisplayColors.add_item
|
256
257
|
# cssColors = 'White Silver Gray Black Red Maroon Yellow Olive Lime Green Aqua Teal Blue Navy Fuchsia Purple'
|
lino/utils/config.py
CHANGED
@@ -65,6 +65,8 @@ class ConfigDirCache(object):
|
|
65
65
|
config_dirs = []
|
66
66
|
|
67
67
|
def add_config_dir(name, mod):
|
68
|
+
if mod.__file__ is None:
|
69
|
+
raise Exception(f"20250206 Module {mod} has no __file__!")
|
68
70
|
pth = join(dirname(mod.__file__), SUBDIR_NAME)
|
69
71
|
if isdir(pth):
|
70
72
|
# logger.info("add_config_dir %s %s", name, pth)
|
lino/utils/dpy.py
CHANGED
@@ -10,7 +10,8 @@ from lino import logger
|
|
10
10
|
from packaging.version import Version
|
11
11
|
|
12
12
|
import os
|
13
|
-
import
|
13
|
+
import importlib.util
|
14
|
+
import sys
|
14
15
|
from unipath import Path
|
15
16
|
# from lino import AFTER17
|
16
17
|
|
@@ -33,6 +34,16 @@ from lino.core.utils import obj2str, full_model_name
|
|
33
34
|
SUFFIX = ".py"
|
34
35
|
|
35
36
|
|
37
|
+
# Importing a source file directly from path
|
38
|
+
# https://docs.python.org/3/library/importlib.html#importing-a-source-file-directly
|
39
|
+
def import_from_path(module_name, file_path):
|
40
|
+
spec = importlib.util.spec_from_file_location(module_name, file_path)
|
41
|
+
module = importlib.util.module_from_spec(spec)
|
42
|
+
sys.modules[module_name] = module
|
43
|
+
spec.loader.exec_module(module)
|
44
|
+
return module
|
45
|
+
|
46
|
+
|
36
47
|
def create_mti_child(parent_model, pk, child_model, **kw):
|
37
48
|
"""Similar to :func:`lino.utils.mti.insert_child`, but for usage in
|
38
49
|
Python dumps (generated by :cmd:`pm dump2py`).
|
@@ -396,11 +407,12 @@ class DpyDeserializer(LoaderBase):
|
|
396
407
|
fqname = fqname[: -len(SUFFIX)]
|
397
408
|
print(fqname)
|
398
409
|
|
399
|
-
desc = (SUFFIX, "r", imp.PY_SOURCE)
|
410
|
+
# desc = (SUFFIX, "r", imp.PY_SOURCE)
|
400
411
|
# logger.info("20160817 %s...", options)
|
401
412
|
logger.info("Loading data from %s", fp.name)
|
402
413
|
|
403
|
-
module =
|
414
|
+
module = import_from_path(fqname, fp.name)
|
415
|
+
# module = imp.load_module(fqname, fp, fp.name, desc)
|
404
416
|
# module = __import__(filename)
|
405
417
|
|
406
418
|
for o in self.deserialize_module(module, **options):
|