lino 25.4.0__py3-none-any.whl → 25.4.2__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/dd.py +8 -10
- lino/core/dbtables.py +2 -3
- lino/core/fields.py +30 -15
- lino/core/kernel.py +19 -19
- lino/core/requests.py +24 -9
- lino/core/site.py +101 -68
- lino/core/tables.py +27 -29
- lino/help_texts.py +7 -3
- lino/mixins/__init__.py +27 -30
- lino/modlib/about/models.py +1 -1
- lino/modlib/changes/models.py +2 -2
- lino/modlib/export_excel/__init__.py +6 -8
- lino/modlib/export_excel/models.py +1 -3
- lino/modlib/extjs/ext_renderer.py +1 -1
- lino/modlib/extjs/views.py +1 -1
- lino/modlib/jinja/mixins.py +4 -1
- lino/modlib/linod/mixins.py +17 -12
- lino/modlib/memo/parser.py +4 -4
- lino/modlib/notify/mixins.py +14 -0
- lino/modlib/notify/models.py +10 -25
- lino/modlib/uploads/mixins.py +1 -1
- lino/modlib/users/actions.py +2 -4
- lino/modlib/users/models.py +13 -19
- lino/modlib/users/ui.py +1 -1
- lino/sphinxcontrib/logo/templates/part-of-synodalsoft.html +0 -1
- lino/utils/format_date.py +10 -5
- lino/utils/media.py +16 -31
- {lino-25.4.0.dist-info → lino-25.4.2.dist-info}/METADATA +1 -1
- {lino-25.4.0.dist-info → lino-25.4.2.dist-info}/RECORD +33 -33
- {lino-25.4.0.dist-info → lino-25.4.2.dist-info}/WHEEL +0 -0
- {lino-25.4.0.dist-info → lino-25.4.2.dist-info}/licenses/AUTHORS.rst +0 -0
- {lino-25.4.0.dist-info → lino-25.4.2.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.4.
|
34
|
+
__version__ = '25.4.2'
|
35
35
|
|
36
36
|
# import setuptools # avoid UserWarning "Distutils was imported before Setuptools"?
|
37
37
|
|
lino/api/dd.py
CHANGED
@@ -155,7 +155,7 @@ from lino.utils import IncompleteDate, read_exception
|
|
155
155
|
|
156
156
|
from lino.utils.format_date import fdm, fdl, fdf, fdmy
|
157
157
|
from lino.utils.format_date import fds as fds_
|
158
|
-
from lino.utils.format_date import
|
158
|
+
from lino.utils.format_date import fdtl, fdtf
|
159
159
|
|
160
160
|
|
161
161
|
def fds(d):
|
@@ -175,14 +175,8 @@ field2kw = settings.SITE.field2kw
|
|
175
175
|
decfmt = settings.SITE.decfmt
|
176
176
|
str2kw = settings.SITE.str2kw
|
177
177
|
str2dict = settings.SITE.str2dict
|
178
|
-
|
179
|
-
|
180
|
-
def today(*args, **kwargs):
|
181
|
-
# make it serializable for Django migrations
|
182
|
-
return settings.SITE.today(*args, **kwargs)
|
183
|
-
|
184
|
-
|
185
|
-
# today = settings.SITE.today
|
178
|
+
now = settings.SITE.now
|
179
|
+
# today = settings.SITE.today # see below
|
186
180
|
strftime = settings.SITE.strftime
|
187
181
|
demo_date = settings.SITE.demo_date
|
188
182
|
is_abstract_model = settings.SITE.is_abstract_model
|
@@ -190,7 +184,6 @@ is_installed = settings.SITE.is_installed
|
|
190
184
|
is_hidden_plugin = settings.SITE.is_hidden_plugin
|
191
185
|
resolve_plugin = settings.SITE.resolve_plugin
|
192
186
|
get_plugin_setting = settings.SITE.get_plugin_setting
|
193
|
-
# get_db_overview_rst = settings.SITE.get_db_overview_rst
|
194
187
|
add_welcome_handler = settings.SITE.add_welcome_handler
|
195
188
|
build_media_url = settings.SITE.build_media_url
|
196
189
|
build_site_cache_url = settings.SITE.build_site_cache_url
|
@@ -203,6 +196,11 @@ plugins = settings.SITE.plugins
|
|
203
196
|
format_currency = settings.SITE.format_currency
|
204
197
|
|
205
198
|
|
199
|
+
def today(*args, **kwargs):
|
200
|
+
# make it serializable for Django migrations
|
201
|
+
return settings.SITE.today(*args, **kwargs)
|
202
|
+
|
203
|
+
|
206
204
|
get_language = translation.get_language
|
207
205
|
|
208
206
|
|
lino/core/dbtables.py
CHANGED
@@ -613,7 +613,7 @@ class Table(AbstractTable):
|
|
613
613
|
qs = qs.filter(**kw)
|
614
614
|
|
615
615
|
if ar.exclude:
|
616
|
-
qs = qs.exclude(
|
616
|
+
qs = qs.exclude(ar.exclude)
|
617
617
|
# qs = qs.exclude(ar.exclude)
|
618
618
|
|
619
619
|
if ar.param_values is not None:
|
@@ -654,8 +654,7 @@ class Table(AbstractTable):
|
|
654
654
|
qs = qs.filter(**d)
|
655
655
|
|
656
656
|
if self.exclude:
|
657
|
-
qs = qs.exclude(
|
658
|
-
# TODO: use Q object instead of dict
|
657
|
+
qs = qs.exclude(self.exclude)
|
659
658
|
|
660
659
|
if ar.quick_search:
|
661
660
|
qs = self.add_quick_search_filter(qs, ar.quick_search)
|
lino/core/fields.py
CHANGED
@@ -6,6 +6,8 @@ Defines extended database field classes and utility functions
|
|
6
6
|
related to fields.
|
7
7
|
"""
|
8
8
|
|
9
|
+
#fmt: off
|
10
|
+
|
9
11
|
from lino import logger
|
10
12
|
import datetime
|
11
13
|
from decimal import Decimal
|
@@ -112,10 +114,10 @@ class PercentageField(models.DecimalField):
|
|
112
114
|
defaults = dict(
|
113
115
|
max_length=5,
|
114
116
|
max_digits=5,
|
115
|
-
decimal_places=
|
117
|
+
decimal_places=0,
|
116
118
|
)
|
117
119
|
defaults.update(kwargs)
|
118
|
-
super(
|
120
|
+
super().__init__(*args, **defaults)
|
119
121
|
|
120
122
|
|
121
123
|
class TimeField(models.TimeField):
|
@@ -818,7 +820,7 @@ def virtualfield(return_type, **kwargs):
|
|
818
820
|
|
819
821
|
def decorator(fn):
|
820
822
|
if isinstance(return_type, DummyField):
|
821
|
-
rv = DummyField(
|
823
|
+
rv = DummyField(return_type.get_default())
|
822
824
|
else:
|
823
825
|
rv = VirtualField(return_type, fn, **kwargs)
|
824
826
|
rv.__doc__ = fn.__doc__
|
@@ -1001,10 +1003,8 @@ class QuantityField(models.CharField):
|
|
1001
1003
|
if self.overflow_value:
|
1002
1004
|
return self.overflow_value
|
1003
1005
|
raise ValidationError(
|
1004
|
-
"Cannot accept quantity {} "
|
1005
|
-
|
1006
|
-
)
|
1007
|
-
)
|
1006
|
+
f"Cannot accept quantity {raw_value} "
|
1007
|
+
+ f"because max_length is {self.max_length}")
|
1008
1008
|
# print("20230129 Can't store {}={} in {}".format(self.name, raw_value, obj))
|
1009
1009
|
# return -1
|
1010
1010
|
return super().clean(raw_value, obj)
|
@@ -1111,8 +1111,20 @@ class Dummy(object):
|
|
1111
1111
|
class DummyField(FakeField):
|
1112
1112
|
"""
|
1113
1113
|
Represents a field that doesn't exist in the current configuration
|
1114
|
-
but might exist in other configurations.
|
1115
|
-
|
1114
|
+
but might exist in other configurations.
|
1115
|
+
|
1116
|
+
The "value" of a DummyField is always `None` or any other value to be
|
1117
|
+
optionally specified at instantiation.
|
1118
|
+
|
1119
|
+
.. attribute:: dummy_value
|
1120
|
+
|
1121
|
+
The value to be returned by this field. Default value is `None`.
|
1122
|
+
|
1123
|
+
Usage examples:
|
1124
|
+
|
1125
|
+
- The value of :attr:`lino.modlib.users.User.nickname` is always ``""`` when
|
1126
|
+
:data:`lino.modlib.users.with_nickname` is `False`.
|
1127
|
+
|
1116
1128
|
|
1117
1129
|
See e.g. :func:`ForeignKey` and :func:`fields_list`.
|
1118
1130
|
"""
|
@@ -1120,8 +1132,9 @@ class DummyField(FakeField):
|
|
1120
1132
|
# choices = []
|
1121
1133
|
# primary_key = False
|
1122
1134
|
|
1123
|
-
def __init__(self,
|
1124
|
-
|
1135
|
+
def __init__(self, dummy_value=None):
|
1136
|
+
super().__init__()
|
1137
|
+
self.dummy_value = dummy_value
|
1125
1138
|
|
1126
1139
|
# def __init__(self, name, *args, **kw):
|
1127
1140
|
# self.name = name
|
@@ -1132,10 +1145,10 @@ class DummyField(FakeField):
|
|
1132
1145
|
def __get__(self, instance, owner):
|
1133
1146
|
if instance is None:
|
1134
1147
|
return self
|
1135
|
-
return
|
1148
|
+
return self.dummy_value
|
1136
1149
|
|
1137
1150
|
def get_default(self):
|
1138
|
-
return
|
1151
|
+
return self.dummy_value
|
1139
1152
|
|
1140
1153
|
def contribute_to_class(self, cls, name):
|
1141
1154
|
self.name = name
|
@@ -1548,10 +1561,12 @@ def pointer_factory(cls, othermodel, *args, **kw):
|
|
1548
1561
|
|
1549
1562
|
"""
|
1550
1563
|
if othermodel is None:
|
1551
|
-
return DummyField(othermodel, *args, **kw)
|
1564
|
+
# return DummyField(othermodel, *args, **kw)
|
1565
|
+
return DummyField(None)
|
1552
1566
|
if isinstance(othermodel, str):
|
1553
1567
|
if not settings.SITE.is_installed_model_spec(othermodel):
|
1554
|
-
return DummyField(othermodel, *args, **kw)
|
1568
|
+
# return DummyField(othermodel, *args, **kw)
|
1569
|
+
return DummyField(None)
|
1555
1570
|
|
1556
1571
|
kw.setdefault("on_delete", models.CASCADE)
|
1557
1572
|
return cls(othermodel, *args, **kw)
|
lino/core/kernel.py
CHANGED
@@ -41,7 +41,7 @@ from django.db import models
|
|
41
41
|
|
42
42
|
# import lino # for is_testing
|
43
43
|
from lino import logger
|
44
|
-
from lino.utils import codetime
|
44
|
+
# from lino.utils import codetime
|
45
45
|
from lino.utils.html import E
|
46
46
|
# from lino.core.utils import format_request
|
47
47
|
# from lino.utils import isiterable
|
@@ -111,22 +111,21 @@ class Kernel(object):
|
|
111
111
|
self.GFK_LIST = []
|
112
112
|
# logger.info("20140227 Kernel.__init__() done")
|
113
113
|
|
114
|
-
_code_mtime = None
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
return self._code_mtime
|
114
|
+
# _code_mtime = None
|
115
|
+
#
|
116
|
+
# @property
|
117
|
+
# def code_mtime(self):
|
118
|
+
# if self._code_mtime is None:
|
119
|
+
# # packages = [os.environ['DJANGO_SETTINGS_MODULE'], 'lino']
|
120
|
+
# # self._code_mtime = codetime(*packages)
|
121
|
+
# if self.site.developer_site_cache:
|
122
|
+
# self._code_mtime = codetime()
|
123
|
+
# else:
|
124
|
+
# # On a production server we test only the timestamp of
|
125
|
+
# # settings.py file because otherwise the result can differ
|
126
|
+
# # depending on which modules have already been imported.
|
127
|
+
# self._code_mtime = codetime(settings.SETTINGS_MODULE)
|
128
|
+
# return self._code_mtime
|
130
129
|
|
131
130
|
def kernel_startup(self, site):
|
132
131
|
"""This is a part of a Lino site startup. The Django Model
|
@@ -908,7 +907,7 @@ class Kernel(object):
|
|
908
907
|
self._must_build = True
|
909
908
|
|
910
909
|
def make_cache_file(self, fn, write, force=False, verbosity=1):
|
911
|
-
"""Make the specified cache file. This is used internally at
|
910
|
+
"""Make the specified cache file. This is used internally at site
|
912
911
|
startup.
|
913
912
|
|
914
913
|
"""
|
@@ -919,7 +918,8 @@ class Kernel(object):
|
|
919
918
|
fn = self.site.media_root / fn
|
920
919
|
if not force and not self._must_build and fn.exists():
|
921
920
|
mtime = os.stat(fn).st_mtime
|
922
|
-
if mtime > self.code_mtime:
|
921
|
+
# if mtime > self.code_mtime:
|
922
|
+
if mtime > self.site.lino_version:
|
923
923
|
# logger.debug("%s (%s) is up to date.", fn, time.ctime(mtime))
|
924
924
|
return 0
|
925
925
|
|
lino/core/requests.py
CHANGED
@@ -829,7 +829,7 @@ class BaseRequest:
|
|
829
829
|
unicode=str, # backwards-compatibility. In new templates
|
830
830
|
# you should prefer `str`.
|
831
831
|
pgettext=pgettext,
|
832
|
-
now=
|
832
|
+
now=dd.now(),
|
833
833
|
getattr=getattr,
|
834
834
|
restify=restify,
|
835
835
|
activate_language=activate,
|
@@ -1805,6 +1805,8 @@ class ActionRequest(BaseRequest):
|
|
1805
1805
|
selected_rows=None,
|
1806
1806
|
**kw
|
1807
1807
|
):
|
1808
|
+
if exclude is not None:
|
1809
|
+
assert isinstance(exclude, models.Q)
|
1808
1810
|
if title is not None:
|
1809
1811
|
self.title = title
|
1810
1812
|
if offset is not None:
|
@@ -1908,7 +1910,11 @@ class ActionRequest(BaseRequest):
|
|
1908
1910
|
# raise Exception("20230426 b {}".format(self.master))
|
1909
1911
|
|
1910
1912
|
if issubclass(self.actor, AbstractTable):
|
1911
|
-
|
1913
|
+
if self.actor.exclude is not None:
|
1914
|
+
if not isinstance(self.actor.exclude, models.Q):
|
1915
|
+
raise Exception(
|
1916
|
+
f"{self.actor}.exclude must be a Q object",
|
1917
|
+
f" not {self.actor.exclude}")
|
1912
1918
|
self.exclude = exclude or self.actor.exclude
|
1913
1919
|
self.page_length = self.actor.page_length
|
1914
1920
|
|
@@ -2019,8 +2025,7 @@ class ActionRequest(BaseRequest):
|
|
2019
2025
|
# often and since exception loggers usually send an email to the
|
2020
2026
|
# local system admin, make sure to log each exception only once.
|
2021
2027
|
e = str(e)
|
2022
|
-
self.no_data_text =
|
2023
|
-
details)"
|
2028
|
+
self.no_data_text = e + " (set catch_layout_exceptions to see details)"
|
2024
2029
|
self._data_iterator = []
|
2025
2030
|
w = WARNINGS_LOGGED.get(e)
|
2026
2031
|
if w is None:
|
@@ -2042,8 +2047,18 @@ class ActionRequest(BaseRequest):
|
|
2042
2047
|
self._data_iterator, self)
|
2043
2048
|
)
|
2044
2049
|
else:
|
2050
|
+
offset = self.offset
|
2051
|
+
|
2052
|
+
if self.actor.start_at_bottom and offset is None and self.limit is not None:
|
2053
|
+
count = self.get_total_count()
|
2054
|
+
if not self.actor.no_phantom_row:
|
2055
|
+
count += 1
|
2056
|
+
offset = (count // self.limit) * self.limit
|
2057
|
+
if (count % self.limit) == 0 and count != 0:
|
2058
|
+
offset -= self.limit
|
2059
|
+
|
2045
2060
|
self._sliced_data_iterator = sliced_data_iterator(
|
2046
|
-
self._data_iterator,
|
2061
|
+
self._data_iterator, offset, self.limit
|
2047
2062
|
)
|
2048
2063
|
# logger.info("20171116 executed : %s", self._sliced_data_iterator)
|
2049
2064
|
|
@@ -2069,11 +2084,11 @@ class ActionRequest(BaseRequest):
|
|
2069
2084
|
assert issubclass(self.actor, AbstractTable)
|
2070
2085
|
self.actor.check_params(self.param_values)
|
2071
2086
|
if self.actor.get_data_rows is not None:
|
2072
|
-
|
2087
|
+
lst = []
|
2073
2088
|
for row in self.actor.get_data_rows(self):
|
2074
2089
|
group = self.actor.group_from_row(row)
|
2075
|
-
group.process_row(
|
2076
|
-
return
|
2090
|
+
group.process_row(lst, row)
|
2091
|
+
return lst
|
2077
2092
|
# ~ logger.info("20120914 tables.get_data_iterator %s",self)
|
2078
2093
|
# ~ logger.info("20120914 tables.get_data_iterator %s",self.actor)
|
2079
2094
|
# print("20181121 get_data_iterator", self.actor)
|
@@ -2145,7 +2160,7 @@ class ActionRequest(BaseRequest):
|
|
2145
2160
|
if self.known_values:
|
2146
2161
|
# kw.update(self.known_values)
|
2147
2162
|
for k, v in self.known_values.items():
|
2148
|
-
if
|
2163
|
+
if "__" not in k:
|
2149
2164
|
kw[k] = v
|
2150
2165
|
obj = self.actor.create_instance(self, **kw)
|
2151
2166
|
return obj
|
lino/core/site.py
CHANGED
@@ -3,23 +3,8 @@
|
|
3
3
|
# License: GNU Affero General Public License v3 (see file COPYING for details)
|
4
4
|
# doctest lino/core/site.py
|
5
5
|
|
6
|
-
from lino.core.exceptions import ChangedAPI
|
7
|
-
from django.apps import apps
|
8
|
-
from lino.utils.html import E, tostring
|
9
|
-
from lino import assert_django_code, DJANGO_DEFAULT_LANGUAGE
|
10
|
-
# from lino.core import constants
|
11
|
-
from lino.core.plugin import Plugin
|
12
|
-
from rstgen.confparser import ConfigParser
|
13
|
-
from django.utils import translation
|
14
|
-
from django.utils.html import mark_safe
|
15
|
-
from django.utils.translation import get_language
|
16
|
-
from django.utils.translation import gettext_lazy as _
|
17
|
-
from django.conf import settings
|
18
|
-
import rstgen
|
19
|
-
from lino.utils import AttrDict, date_offset, i2d, buildurl
|
20
|
-
from lino import logger, __version__
|
21
6
|
from importlib.util import find_spec
|
22
|
-
from importlib import import_module
|
7
|
+
from importlib import import_module
|
23
8
|
from pathlib import Path
|
24
9
|
import os
|
25
10
|
import re
|
@@ -34,6 +19,22 @@ import logging
|
|
34
19
|
# from pprint import pprint
|
35
20
|
from logging.handlers import SocketHandler
|
36
21
|
import time
|
22
|
+
import rstgen
|
23
|
+
from rstgen.confparser import ConfigParser
|
24
|
+
from django.apps import apps
|
25
|
+
from django.utils import timezone
|
26
|
+
from django.utils import translation
|
27
|
+
from django.utils.html import mark_safe
|
28
|
+
from django.utils.translation import get_language
|
29
|
+
from django.utils.translation import gettext_lazy as _
|
30
|
+
from django.conf import settings
|
31
|
+
from lino import assert_django_code, DJANGO_DEFAULT_LANGUAGE
|
32
|
+
from lino import logger, __version__
|
33
|
+
from lino.core.exceptions import ChangedAPI
|
34
|
+
from lino.utils.html import E, tostring
|
35
|
+
# from lino.core import constants
|
36
|
+
from lino.core.plugin import Plugin
|
37
|
+
from lino.utils import AttrDict, date_offset, i2d, buildurl
|
37
38
|
|
38
39
|
NO_REMOTE_AUTH = True
|
39
40
|
# 20240518 We have only one production site still using remote http
|
@@ -558,17 +559,18 @@ class Site(object):
|
|
558
559
|
break
|
559
560
|
|
560
561
|
if self.master_site is None:
|
561
|
-
cache_root = os.environ.get("LINO_CACHE_ROOT", None)
|
562
|
-
if cache_root:
|
563
|
-
|
564
|
-
|
565
|
-
|
566
|
-
|
567
|
-
|
568
|
-
|
569
|
-
|
570
|
-
else:
|
571
|
-
|
562
|
+
# cache_root = os.environ.get("LINO_CACHE_ROOT", None)
|
563
|
+
# if cache_root:
|
564
|
+
# # TODO: deprecate
|
565
|
+
# cr = Path(cache_root).absolute()
|
566
|
+
# if not cr.exists():
|
567
|
+
# msg = "LINO_CACHE_ROOT ({0}) does not exist!".format(cr)
|
568
|
+
# raise Exception(msg)
|
569
|
+
# self.site_dir = (cr / self.project_name).resolve()
|
570
|
+
# self.setup_cache_directory()
|
571
|
+
# else:
|
572
|
+
# self.site_dir = self.project_dir
|
573
|
+
self.site_dir = self.project_dir
|
572
574
|
db = self.get_database_settings()
|
573
575
|
if db is not None:
|
574
576
|
self.django_settings.update(DATABASES=db)
|
@@ -987,11 +989,12 @@ class Site(object):
|
|
987
989
|
self.update_settings(MEDIA_URL="/media/")
|
988
990
|
|
989
991
|
if not "STATIC_ROOT" in self.django_settings:
|
990
|
-
cache_root = os.environ.get("LINO_CACHE_ROOT", None)
|
991
|
-
if cache_root:
|
992
|
-
|
993
|
-
else:
|
994
|
-
|
992
|
+
# cache_root = os.environ.get("LINO_CACHE_ROOT", None)
|
993
|
+
# if cache_root:
|
994
|
+
# p = Path(cache_root)
|
995
|
+
# else:
|
996
|
+
# p = self.site_dir
|
997
|
+
p = self.site_dir
|
995
998
|
self.update_settings(STATIC_ROOT=str(p / "static_root"))
|
996
999
|
if not "STATIC_URL" in self.django_settings:
|
997
1000
|
self.update_settings(STATIC_URL="/static/")
|
@@ -1142,40 +1145,40 @@ class Site(object):
|
|
1142
1145
|
|
1143
1146
|
# print(20150331, self.django_settings['FIXTURE_DIRS'])
|
1144
1147
|
|
1145
|
-
def setup_cache_directory(self):
|
1146
|
-
|
1147
|
-
|
1148
|
-
|
1149
|
-
|
1150
|
-
|
1151
|
-
|
1152
|
-
|
1153
|
-
|
1154
|
-
|
1155
|
-
|
1156
|
-
|
1157
|
-
|
1158
|
-
|
1159
|
-
|
1160
|
-
|
1161
|
-
|
1162
|
-
|
1163
|
-
|
1164
|
-
|
1165
|
-
|
1166
|
-
|
1167
|
-
|
1168
|
-
|
1169
|
-
|
1170
|
-
|
1171
|
-
|
1172
|
-
|
1173
|
-
|
1174
|
-
|
1175
|
-
|
1176
|
-
|
1177
|
-
|
1178
|
-
|
1148
|
+
# def setup_cache_directory(self):
|
1149
|
+
# stamp = self.site_dir / "lino_cache.txt"
|
1150
|
+
# this = class2str(self.__class__)
|
1151
|
+
# if stamp.exists():
|
1152
|
+
# other = stamp.read_file()
|
1153
|
+
# if other == this:
|
1154
|
+
# ok = True
|
1155
|
+
# else:
|
1156
|
+
# ok = False
|
1157
|
+
# for parent in self.__class__.__mro__:
|
1158
|
+
# if other == class2str(parent):
|
1159
|
+
# ok = True
|
1160
|
+
# break
|
1161
|
+
# if not ok:
|
1162
|
+
# # Can happen e.g. when `python -m lino.hello` is
|
1163
|
+
# # called. in certain conditions.
|
1164
|
+
# msg = (
|
1165
|
+
# "Cannot use {site_dir} for {this} "
|
1166
|
+
# "because it is used for {other}. (Settings {settings})"
|
1167
|
+
# )
|
1168
|
+
# msg = msg.format(
|
1169
|
+
# site_dir=self.site_dir,
|
1170
|
+
# this=this,
|
1171
|
+
# settings=self.django_settings.get("SETTINGS_MODULE"),
|
1172
|
+
# other=other,
|
1173
|
+
# )
|
1174
|
+
# if True:
|
1175
|
+
# raise Exception(msg)
|
1176
|
+
# else:
|
1177
|
+
# # print(msg)
|
1178
|
+
# self.site_dir = None
|
1179
|
+
# else:
|
1180
|
+
# self.makedirs_if_missing(self.site_dir)
|
1181
|
+
# stamp.write_file(this)
|
1179
1182
|
|
1180
1183
|
def set_user_model(self, spec):
|
1181
1184
|
# if self.user_model is not None:
|
@@ -1384,6 +1387,11 @@ class Site(object):
|
|
1384
1387
|
)
|
1385
1388
|
return date_offset(base, *args, **kwargs)
|
1386
1389
|
|
1390
|
+
def now(self, *args, **kwargs):
|
1391
|
+
t = self.today()
|
1392
|
+
now = timezone.now()
|
1393
|
+
return now.replace(year=t.year, month=t.month, day=t.day)
|
1394
|
+
|
1387
1395
|
def welcome_text(self):
|
1388
1396
|
return "This is %s using %s." % (self.site_version(), self.using_text())
|
1389
1397
|
|
@@ -2004,6 +2012,17 @@ class Site(object):
|
|
2004
2012
|
s = self.plugins.jinja.render_from_ar(ar, "admin_main.html", **context)
|
2005
2013
|
return mark_safe(s)
|
2006
2014
|
|
2015
|
+
_lino_version = None
|
2016
|
+
|
2017
|
+
@property
|
2018
|
+
def lino_version(self):
|
2019
|
+
p = self.site_dir / "lino_version.txt"
|
2020
|
+
if self._lino_version is None:
|
2021
|
+
if not p.exists():
|
2022
|
+
p.touch()
|
2023
|
+
self._lino_version = p.stat().st_mtime
|
2024
|
+
return self._lino_version
|
2025
|
+
|
2007
2026
|
def build_site_cache(self, force=False, later=False, verbosity=1):
|
2008
2027
|
from lino.modlib.users.utils import with_user_profile
|
2009
2028
|
from lino.modlib.users.choicelists import UserTypes
|
@@ -2011,10 +2030,12 @@ class Site(object):
|
|
2011
2030
|
# if not self.is_prepared:
|
2012
2031
|
# self.prepare_layouts()
|
2013
2032
|
# self.is_prepared = True
|
2033
|
+
# settings_file = self.django_settings.get("__file__")
|
2034
|
+
# Path(settings_file).touch()
|
2035
|
+
p = self.site_dir / "lino_version.txt"
|
2036
|
+
p.touch()
|
2014
2037
|
|
2015
2038
|
if later:
|
2016
|
-
settings_file = self.django_settings.get("__file__")
|
2017
|
-
Path(settings_file).touch()
|
2018
2039
|
# print("20230823 later")
|
2019
2040
|
return
|
2020
2041
|
|
@@ -2067,6 +2088,18 @@ class Site(object):
|
|
2067
2088
|
time.time() - started,
|
2068
2089
|
)
|
2069
2090
|
|
2091
|
+
_top_link_generator = []
|
2092
|
+
|
2093
|
+
def get_top_links(self, ar):
|
2094
|
+
messages = []
|
2095
|
+
for l in self._top_link_generator:
|
2096
|
+
for msg in l(ar):
|
2097
|
+
messages.append(msg)
|
2098
|
+
return tostring(E.div(*messages))
|
2099
|
+
|
2100
|
+
def add_top_link_generator(self, func):
|
2101
|
+
self._top_link_generator.append(func)
|
2102
|
+
|
2070
2103
|
def get_welcome_messages(self, ar):
|
2071
2104
|
for h in self._welcome_handlers:
|
2072
2105
|
for msg in h(ar):
|
lino/core/tables.py
CHANGED
@@ -1,12 +1,10 @@
|
|
1
1
|
# -*- coding: UTF-8 -*-
|
2
|
-
# Copyright 2009-
|
2
|
+
# Copyright 2009-2025 Rumma & Ko Ltd
|
3
3
|
# License: GNU Affero General Public License v3 (see file COPYING for details)
|
4
4
|
"""Defines the classes :class:`AbstractTable` and
|
5
5
|
:class:`VirtualTable`.
|
6
6
|
|
7
7
|
"""
|
8
|
-
from lino import logger
|
9
|
-
from future.utils import with_metaclass
|
10
8
|
|
11
9
|
import os
|
12
10
|
import yaml
|
@@ -15,8 +13,9 @@ from django.db import models
|
|
15
13
|
from django.db.utils import OperationalError, ProgrammingError
|
16
14
|
from django.conf import settings
|
17
15
|
from django.utils.translation import gettext_lazy as _
|
18
|
-
from django.core.exceptions import BadRequest
|
16
|
+
# from django.core.exceptions import BadRequest
|
19
17
|
|
18
|
+
from lino import logger
|
20
19
|
from lino.core import actors
|
21
20
|
from lino.core import actions
|
22
21
|
from lino.core import fields
|
@@ -191,17 +190,15 @@ class AbstractTable(actors.Actor):
|
|
191
190
|
will not pop-in.
|
192
191
|
"""
|
193
192
|
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
#
|
204
|
-
# """
|
193
|
+
start_at_bottom = False
|
194
|
+
"""Set this to `True` if you want your table to *start at the
|
195
|
+
bottom*. Unlike reverse ordering, the rows remain in their
|
196
|
+
natural order, but when we open a grid on this table, we want it
|
197
|
+
to start on the last page.
|
198
|
+
|
199
|
+
Use cases are in :class:`lino_xl.lib.trading.InvoicesByJournal` and
|
200
|
+
:class:`lino_xl.lib.accounting.InvoicesByJournal`.
|
201
|
+
"""
|
205
202
|
|
206
203
|
group_by = None
|
207
204
|
"""
|
@@ -397,21 +394,20 @@ method in order to sort the rows of the queryset.
|
|
397
394
|
"""
|
398
395
|
|
399
396
|
filter = None
|
400
|
-
"""
|
401
|
-
|
402
|
-
`filter
|
397
|
+
"""
|
398
|
+
If specified, this must be a :class:`django.db.models.Q` object that will be
|
399
|
+
passed to Django's `filter
|
403
400
|
<https://docs.djangoproject.com/en/5.0/ref/models/querysets/#filter>`__
|
404
401
|
method.
|
405
402
|
|
406
|
-
|
407
|
-
|
408
|
-
|
409
|
-
creates a new row.
|
403
|
+
If you allow a user to insert rows into a filtered table, you should make
|
404
|
+
sure that new records satisfy your filter condition, otherwise you can get
|
405
|
+
surprising behaviour if the user creates a new row.
|
410
406
|
|
411
|
-
If your filter consists of simple static values on some known
|
412
|
-
|
413
|
-
|
414
|
-
|
407
|
+
If your filter consists of simple static values on some known field, then
|
408
|
+
you might prefer to use :attr:`known_values
|
409
|
+
<lino.core.actors.Actor.known_values>` instead because this will add
|
410
|
+
automatic behaviour.
|
415
411
|
|
416
412
|
One advantage of :attr:`filter` over
|
417
413
|
:attr:`known_values <lino.core.actors.Actor.known_values>`
|
@@ -421,11 +417,13 @@ method in order to sort the rows of the queryset.
|
|
421
417
|
"""
|
422
418
|
|
423
419
|
exclude = None
|
424
|
-
"""
|
425
|
-
`
|
420
|
+
"""
|
421
|
+
If specified, this must be a :class:`django.db.models.Q` object that will be
|
422
|
+
passed to Django's `exclude
|
426
423
|
<https://docs.djangoproject.com/en/5.0/ref/models/querysets/#exclude>`__
|
427
|
-
method
|
424
|
+
method.
|
428
425
|
|
426
|
+
This is the logical opposite of :attr:`filter`.
|
429
427
|
"""
|
430
428
|
|
431
429
|
extra = None
|