lino 25.2.3__py3-none-any.whl → 25.3.1__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 +11 -48
- lino/api/doctest.py +34 -36
- lino/core/actions.py +25 -23
- lino/core/actors.py +37 -17
- lino/core/choicelists.py +10 -8
- lino/core/dbtables.py +1 -1
- lino/core/elems.py +46 -30
- lino/core/fields.py +19 -9
- lino/core/inject.py +7 -6
- lino/core/kernel.py +26 -66
- lino/core/model.py +44 -31
- lino/core/plugin.py +4 -4
- lino/core/requests.py +76 -55
- lino/core/site.py +84 -30
- lino/core/store.py +5 -2
- lino/core/utils.py +12 -7
- lino/help_texts.py +3 -8
- lino/management/commands/prep.py +1 -1
- lino/mixins/duplicable.py +6 -4
- lino/mixins/sequenced.py +17 -6
- lino/modlib/__init__.py +0 -2
- lino/modlib/changes/models.py +21 -10
- lino/modlib/checkdata/models.py +59 -24
- lino/modlib/comments/fixtures/demo2.py +12 -3
- lino/modlib/comments/models.py +7 -7
- lino/modlib/comments/ui.py +8 -5
- lino/modlib/export_excel/models.py +7 -5
- lino/modlib/extjs/views.py +39 -20
- lino/modlib/help/management/commands/makehelp.py +5 -2
- lino/modlib/jinja/mixins.py +25 -14
- lino/modlib/linod/__init__.py +1 -0
- lino/modlib/linod/choicelists.py +21 -0
- lino/modlib/linod/consumers.py +13 -4
- lino/modlib/linod/management/commands/linod.py +6 -2
- lino/modlib/linod/mixins.py +16 -11
- lino/modlib/linod/models.py +4 -2
- lino/modlib/notify/models.py +18 -10
- lino/modlib/printing/actions.py +41 -30
- lino/modlib/printing/choicelists.py +11 -9
- lino/modlib/printing/mixins.py +25 -20
- lino/modlib/publisher/models.py +5 -5
- lino/modlib/summaries/models.py +3 -2
- lino/modlib/system/models.py +28 -29
- lino/modlib/uploads/__init__.py +5 -5
- lino/modlib/uploads/actions.py +2 -8
- lino/modlib/uploads/choicelists.py +10 -10
- lino/modlib/uploads/fixtures/std.py +17 -0
- lino/modlib/uploads/mixins.py +20 -8
- lino/modlib/uploads/models.py +60 -35
- lino/modlib/uploads/ui.py +10 -7
- lino/utils/media.py +45 -23
- lino/utils/report.py +5 -4
- lino/utils/soup.py +22 -4
- {lino-25.2.3.dist-info → lino-25.3.1.dist-info}/METADATA +1 -1
- {lino-25.2.3.dist-info → lino-25.3.1.dist-info}/RECORD +59 -80
- lino/mixins/uploadable.py +0 -3
- lino/sandbox/bcss/PerformInvestigation.py +0 -2260
- lino/sandbox/bcss/SSDNReply.py +0 -3924
- lino/sandbox/bcss/SSDNRequest.py +0 -3723
- lino/sandbox/bcss/__init__.py +0 -0
- lino/sandbox/bcss/readme.txt +0 -1
- lino/sandbox/bcss/test.py +0 -92
- lino/sandbox/bcss/test2.py +0 -128
- lino/sandbox/bcss/test3.py +0 -161
- lino/sandbox/bcss/test4.py +0 -167
- lino/sandbox/contacts/__init__.py +0 -0
- lino/sandbox/contacts/fixtures/__init__.py +0 -0
- lino/sandbox/contacts/fixtures/demo.py +0 -365
- lino/sandbox/contacts/manage.py +0 -10
- lino/sandbox/contacts/models.py +0 -395
- lino/sandbox/contacts/settings.py +0 -67
- lino/sandbox/tx25/XSD/RetrieveTIGroupsV3.wsdl +0 -65
- lino/sandbox/tx25/XSD/RetrieveTIGroupsV3.xsd +0 -286
- lino/sandbox/tx25/XSD/rn25_Release201104.xsd +0 -2855
- lino/sandbox/tx25/xsd2py1.py +0 -68
- lino/sandbox/tx25/xsd2py2.py +0 -62
- lino/sandbox/tx25/xsd2py3.py +0 -56
- {lino-25.2.3.dist-info → lino-25.3.1.dist-info}/WHEEL +0 -0
- {lino-25.2.3.dist-info → lino-25.3.1.dist-info}/licenses/AUTHORS.rst +0 -0
- {lino-25.2.3.dist-info → lino-25.3.1.dist-info}/licenses/COPYING +0 -0
lino/core/site.py
CHANGED
@@ -3,17 +3,37 @@
|
|
3
3
|
# License: GNU Affero General Public License v3 (see file COPYING for details)
|
4
4
|
# doctest lino/core/site.py
|
5
5
|
|
6
|
+
import json
|
7
|
+
from lino.core.exceptions import ChangedAPI
|
8
|
+
from lino.core.utils import get_models, is_logserver
|
9
|
+
from lino.utils.html import E, tostring
|
10
|
+
from lino import assert_django_code, DJANGO_DEFAULT_LANGUAGE
|
11
|
+
# from lino.core import constants
|
12
|
+
from lino.core.plugin import Plugin
|
13
|
+
from lino.core.utils import full_model_name as fmn
|
14
|
+
from rstgen.confparser import ConfigParser
|
15
|
+
from django.utils import translation
|
16
|
+
from django.utils.html import mark_safe
|
17
|
+
from django.utils.translation import get_language
|
18
|
+
from django.utils.translation import gettext_lazy as _
|
19
|
+
from django.conf import settings
|
20
|
+
import rstgen
|
21
|
+
from lino.utils import AttrDict, date_offset, i2d, buildurl
|
22
|
+
from lino import logger, __version__
|
23
|
+
from importlib.util import find_spec
|
24
|
+
from importlib import import_module, reload
|
25
|
+
from pathlib import Path
|
6
26
|
import os
|
7
27
|
import re
|
8
28
|
import sys
|
9
|
-
from os.path import
|
29
|
+
from os.path import dirname, join, isdir, relpath, exists
|
10
30
|
import inspect
|
11
31
|
import datetime
|
12
32
|
import warnings
|
13
33
|
import collections
|
14
34
|
import locale
|
15
35
|
import logging
|
16
|
-
from pprint import pprint
|
36
|
+
# from pprint import pprint
|
17
37
|
from logging.handlers import SocketHandler
|
18
38
|
import time
|
19
39
|
|
@@ -30,34 +50,12 @@ ASYNC_LOGGING = False
|
|
30
50
|
# activated, accesses settings.DEFAULT_EXCEPTION_REPORTER, which fails at this
|
31
51
|
# moment because the settings aren't yet loaded.
|
32
52
|
|
33
|
-
from pathlib import Path
|
34
|
-
from importlib import import_module, reload
|
35
|
-
from importlib.util import find_spec
|
36
|
-
|
37
|
-
from lino import logger, __version__
|
38
|
-
from lino.utils import AttrDict, date_offset, i2d, buildurl
|
39
|
-
import rstgen
|
40
|
-
|
41
|
-
from django.conf import settings
|
42
|
-
from django.utils.translation import gettext_lazy as _
|
43
|
-
from django.utils.translation import get_language
|
44
|
-
from django.utils.html import mark_safe
|
45
|
-
from django.db.utils import DatabaseError
|
46
|
-
from django.utils import translation
|
47
53
|
|
48
54
|
has_socialauth = find_spec("social_django") is not None
|
49
55
|
has_elasticsearch = find_spec("elasticsearch_django") is not None
|
50
56
|
has_haystack = find_spec("haystack") is not None
|
51
57
|
|
52
|
-
from rstgen.confparser import ConfigParser
|
53
|
-
from lino.core.plugin import Plugin
|
54
|
-
from lino.core import constants
|
55
|
-
|
56
|
-
from lino import assert_django_code, DJANGO_DEFAULT_LANGUAGE
|
57
|
-
from lino.utils.html import E, join_elems, tostring
|
58
|
-
from lino.core.utils import get_models, is_logserver
|
59
58
|
|
60
|
-
from lino.core.exceptions import ChangedAPI
|
61
59
|
# from .roles import SiteUser
|
62
60
|
|
63
61
|
|
@@ -88,14 +86,14 @@ def to_locale(language):
|
|
88
86
|
p = language.find("-")
|
89
87
|
if p >= 0:
|
90
88
|
# Get correct locale for sr-latn
|
91
|
-
if len(language[p + 1
|
89
|
+
if len(language[p + 1:]) > 2:
|
92
90
|
return (
|
93
91
|
language[:p].lower()
|
94
92
|
+ "_"
|
95
93
|
+ language[p + 1].upper()
|
96
|
-
+ language[p + 2
|
94
|
+
+ language[p + 2:].lower()
|
97
95
|
)
|
98
|
-
return language[:p].lower() + "_" + language[p + 1
|
96
|
+
return language[:p].lower() + "_" + language[p + 1:].upper()
|
99
97
|
return language.lower()
|
100
98
|
|
101
99
|
|
@@ -103,7 +101,8 @@ def class2str(cl):
|
|
103
101
|
return cl.__module__ + "." + cl.__name__
|
104
102
|
|
105
103
|
|
106
|
-
gettext_noop
|
104
|
+
def gettext_noop(s): return s
|
105
|
+
|
107
106
|
|
108
107
|
PLUGIN_CONFIGS = {}
|
109
108
|
|
@@ -718,7 +717,6 @@ class Site(object):
|
|
718
717
|
else:
|
719
718
|
d["logger_ok"] = True
|
720
719
|
# self.update_settings(LOGGING=d)
|
721
|
-
# from pprint import pprint
|
722
720
|
# pprint(d)
|
723
721
|
# print("20161126 Site %s " % d['loggers'].keys())
|
724
722
|
# import yaml
|
@@ -1796,6 +1794,61 @@ class Site(object):
|
|
1796
1794
|
# ~ return v
|
1797
1795
|
# ~ return getattr(obj,attrname,*args)
|
1798
1796
|
|
1797
|
+
def mark_virgin(self):
|
1798
|
+
"""
|
1799
|
+
Mark the database as virgin. This is called by :manage:`prep`.
|
1800
|
+
"""
|
1801
|
+
dbhash = self.get_dbhash()
|
1802
|
+
fn = self.site_dir / "dbhash.json"
|
1803
|
+
with fn.open("w") as fp:
|
1804
|
+
json.dump(dbhash, fp)
|
1805
|
+
# self.site.logger.info("Wrote %s", fn)
|
1806
|
+
|
1807
|
+
def check_virgin(self):
|
1808
|
+
"""
|
1809
|
+
Verify whether the database is virgin. Print the differences if there
|
1810
|
+
are any.
|
1811
|
+
"""
|
1812
|
+
new = self.get_dbhash()
|
1813
|
+
db = self.site_dir
|
1814
|
+
fn = db / "dbhash.json"
|
1815
|
+
if not fn.exists():
|
1816
|
+
raise Exception(
|
1817
|
+
f"No `dbhash.json` in {db} (did you run `django-admin prep`?)")
|
1818
|
+
with fn.open("r") as fp:
|
1819
|
+
old = json.load(fp)
|
1820
|
+
|
1821
|
+
# noi1r has noi1e as master_site, but the react front end removes the
|
1822
|
+
# tinymce plugin, i.e. noi1r doesn't care about
|
1823
|
+
# tinymce.TextFieldTemplate model.
|
1824
|
+
|
1825
|
+
ok = True
|
1826
|
+
for k, v in new.items():
|
1827
|
+
v = set(v)
|
1828
|
+
oldv = set(old.get(k, None))
|
1829
|
+
if oldv != v:
|
1830
|
+
if ok:
|
1831
|
+
print(f"Database {db} isn't virgin:")
|
1832
|
+
ok = False
|
1833
|
+
diffs = []
|
1834
|
+
if (added := len(oldv-v)):
|
1835
|
+
diffs.append(f"{added} rows added")
|
1836
|
+
if (removed := len(v-oldv)):
|
1837
|
+
diffs.append(f"{removed} rows removed")
|
1838
|
+
print(f"- {k}: {', '.join(diffs)}")
|
1839
|
+
|
1840
|
+
def get_dbhash(self):
|
1841
|
+
"""
|
1842
|
+
Return a dictionary with a hash value of the current database content.
|
1843
|
+
"""
|
1844
|
+
rv = dict()
|
1845
|
+
for m in get_models(include_auto_created=True):
|
1846
|
+
k = fmn(m)
|
1847
|
+
if k != "sessions.Session":
|
1848
|
+
# rv[k] = m.objects.count()
|
1849
|
+
rv[k] = list(m.objects.values_list('pk', flat=True))
|
1850
|
+
return rv
|
1851
|
+
|
1799
1852
|
def diagnostic_report_rst(self, *args):
|
1800
1853
|
"""Returns a string with a diagnostic report about this
|
1801
1854
|
site. :manage:`diag` is a command-line shortcut to this.
|
@@ -1992,7 +2045,8 @@ class Site(object):
|
|
1992
2045
|
):
|
1993
2046
|
yield "social_django.middleware.SocialAuthExceptionMiddleware"
|
1994
2047
|
|
1995
|
-
|
2048
|
+
# removed 20240921, see #5755 (Should we remove AjaxExceptionResponse?)
|
2049
|
+
if False:
|
1996
2050
|
yield "lino.utils.ajax.AjaxExceptionResponse"
|
1997
2051
|
|
1998
2052
|
if self.use_security_features:
|
lino/core/store.py
CHANGED
@@ -343,7 +343,8 @@ class ForeignKeyStoreField(RelatedMixin, ComboStoreField):
|
|
343
343
|
|
344
344
|
relto_model = self.get_rel_to(obj)
|
345
345
|
if not relto_model:
|
346
|
-
raise Warning(
|
346
|
+
raise Warning(
|
347
|
+
"extract_form_data found no relto_model for %s" % self)
|
347
348
|
# logger.info("20111209 get_value_text: no relto_model")
|
348
349
|
# return
|
349
350
|
|
@@ -1134,7 +1135,7 @@ class ParameterStore(BaseStore):
|
|
1134
1135
|
data = getrqdata(request)
|
1135
1136
|
# print(20160329, data)
|
1136
1137
|
# assert 'pv' in data
|
1137
|
-
pv = data.getlist(self.url_param) #'fv', 'pv', post[fn] post[fv][fn]
|
1138
|
+
pv = data.getlist(self.url_param) # 'fv', 'pv', post[fn] post[fv][fn]
|
1138
1139
|
|
1139
1140
|
# logger.info("20120221 ParameterStore.parse_params(%s) --> %s",self.url_param,pv)
|
1140
1141
|
|
@@ -1220,6 +1221,8 @@ class Store(BaseStore):
|
|
1220
1221
|
|
1221
1222
|
form = rh.actor.insert_layout
|
1222
1223
|
if form:
|
1224
|
+
if isinstance(form, str):
|
1225
|
+
raise Exception(f"20250306 insert_layout {repr(rh.actor)}")
|
1223
1226
|
dh = form.get_layout_handle()
|
1224
1227
|
self.collect_fields(self.detail_fields, dh)
|
1225
1228
|
|
lino/core/utils.py
CHANGED
@@ -6,6 +6,8 @@ A collection of utilities which require Django settings to be
|
|
6
6
|
importable.
|
7
7
|
"""
|
8
8
|
|
9
|
+
from .exceptions import ChangedAPI
|
10
|
+
from lino.utils import IncompleteDate
|
9
11
|
import copy
|
10
12
|
import sys
|
11
13
|
import datetime
|
@@ -32,8 +34,6 @@ from django.apps import apps
|
|
32
34
|
|
33
35
|
get_models = apps.get_models
|
34
36
|
|
35
|
-
from lino.utils import IncompleteDate
|
36
|
-
from .exceptions import ChangedAPI
|
37
37
|
|
38
38
|
validate_url = URLValidator()
|
39
39
|
|
@@ -311,6 +311,7 @@ def range_filter(value, f1, f2):
|
|
311
311
|
q2 = Q(**{f2 + "__isnull": True}) | Q(**{f2 + "__gte": value})
|
312
312
|
return Q(q1, q2)
|
313
313
|
|
314
|
+
|
314
315
|
def inrange_filter(fld, rng, **kw):
|
315
316
|
"""Assuming a database model with a field named `fld`, return a Q
|
316
317
|
object to select the rows having value for `fld` within the given range `rng`.
|
@@ -343,7 +344,7 @@ def overlap_range_filter(sv, ev, f1, f2, **kw):
|
|
343
344
|
# raise ValueError(f"{rng} is not a valid range")
|
344
345
|
if not ev:
|
345
346
|
ev = sv
|
346
|
-
return Q(**{f1+"__lte"
|
347
|
+
return Q(**{f1+"__lte": ev, f2+"__gte": sv})
|
347
348
|
|
348
349
|
|
349
350
|
def babelkw(*args, **kw):
|
@@ -544,7 +545,8 @@ def resolve_field(name, app_label=None):
|
|
544
545
|
if len(l) == 2:
|
545
546
|
model = apps.get_model(app_label, l[0])
|
546
547
|
if model is None:
|
547
|
-
raise FieldDoesNotExist(
|
548
|
+
raise FieldDoesNotExist(
|
549
|
+
"No model named '%s.%s'" % (app_label, l[0]))
|
548
550
|
return model._meta.get_field(l[1])
|
549
551
|
# fld, remote_model, direct, m2m = model._meta.get_field_by_name(l[1])
|
550
552
|
# assert remote_model is None or issubclass(model, remote_model), \
|
@@ -812,7 +814,8 @@ def error2str(self, e):
|
|
812
814
|
return str(getattr(de, "verbose_name", name))
|
813
815
|
|
814
816
|
return "\n".join(
|
815
|
-
["%s : %s" % (fieldlabel(k), self.error2str(v))
|
817
|
+
["%s : %s" % (fieldlabel(k), self.error2str(v))
|
818
|
+
for k, v in md.items()]
|
816
819
|
)
|
817
820
|
return "\n".join(e.messages)
|
818
821
|
return str(e)
|
@@ -1067,7 +1070,8 @@ class InstanceAction:
|
|
1067
1070
|
"""
|
1068
1071
|
if len(args) and isinstance(args[0], BaseRequest):
|
1069
1072
|
raise ChangedAPI("20181004")
|
1070
|
-
ar = self.bound_action.request(
|
1073
|
+
ar = self.bound_action.request(
|
1074
|
+
renderer=settings.SITE.kernel.text_renderer)
|
1071
1075
|
self.run_from_code(ar, *args, **kwargs)
|
1072
1076
|
return ar.response
|
1073
1077
|
|
@@ -1114,7 +1118,6 @@ class PhantomRow(VirtualRow):
|
|
1114
1118
|
return str(self._ar.get_action_title())
|
1115
1119
|
|
1116
1120
|
|
1117
|
-
|
1118
1121
|
def login(username=None, **kwargs):
|
1119
1122
|
"""Return a basic :term:`action request` with the specified user signed in.
|
1120
1123
|
"""
|
@@ -1131,10 +1134,12 @@ def login(username=None, **kwargs):
|
|
1131
1134
|
# import lino.core.urls # hack: trigger ui instantiation
|
1132
1135
|
return BaseRequest(**kwargs)
|
1133
1136
|
|
1137
|
+
|
1134
1138
|
def show(*args, **kwargs):
|
1135
1139
|
"""Print the specified data table to stdout."""
|
1136
1140
|
return login().show(*args, **kwargs)
|
1137
1141
|
|
1142
|
+
|
1138
1143
|
def shows(*args, **kwargs):
|
1139
1144
|
"""Return the output of :func:`show`."""
|
1140
1145
|
return capture_output(show, *args, **kwargs)
|
lino/help_texts.py
CHANGED
@@ -171,10 +171,6 @@ help_texts = {
|
|
171
171
|
'lino.modlib.tinymce.Plugin.window_buttons2' : _("""The second row of toolbar buttons when editing in own window."""),
|
172
172
|
'lino.modlib.tinymce.Plugin.window_buttons3' : _("""The third row of toolbar buttons when editing in own window."""),
|
173
173
|
'lino.modlib.tinymce.Plugin.media_name' : _("""Lino currently includes three versions of TinyMCE, but for production sites we still use the eldest version 3.4.8."""),
|
174
|
-
'lino.modlib.uploads.Plugin' : _("""See /dev/plugins."""),
|
175
|
-
'lino.modlib.uploads.Plugin.remove_orphaned_files' : _("""Whether checkdata –fix should automatically delete orphaned files in the uploads folder."""),
|
176
|
-
'lino.modlib.uploads.Plugin.with_thumbnails' : _("""Whether to use PIL, the Python Imaging Library."""),
|
177
|
-
'lino.modlib.uploads.Plugin.with_volumes' : _("""Whether to use library files (volumes)."""),
|
178
174
|
'lino.modlib.weasyprint.Plugin' : _("""See /dev/plugins."""),
|
179
175
|
'lino.modlib.weasyprint.Plugin.header_height' : _("""Height of header in mm. Set to None if you want no header."""),
|
180
176
|
'lino.modlib.weasyprint.Plugin.footer_height' : _("""Height of footer in mm. Set to None if you want no header."""),
|
@@ -262,8 +258,7 @@ help_texts = {
|
|
262
258
|
'lino.utils.jsgen.Component.walk' : _("""Walk over this component and its children."""),
|
263
259
|
'lino.utils.jsgen.VisibleComponent' : _("""A visible component"""),
|
264
260
|
'lino.utils.jsgen.VisibleComponent.install_permission_handler' : _("""Define the allow_read handler used by get_view_permission(). This must be done only once, but after having configured debug_permissions and required_roles."""),
|
265
|
-
'lino.utils.media.MediaFile' : _("""Represents a file on the server below MEDIA_ROOT with two properties
|
266
|
-
'lino.utils.media.MediaFile.get_url' : _("""return the url that points to file on the server"""),
|
261
|
+
'lino.utils.media.MediaFile' : _("""Represents a file on the server below MEDIA_ROOT with two properties path and url."""),
|
267
262
|
'lino.utils.mldbc.fields.BabelCharField' : _("""Define a variable number of CharField database fields, one for each language of your lino.core.site.Site.languages. See mldbc."""),
|
268
263
|
'lino.utils.mldbc.fields.BabelTextField' : _("""Used for the clones of the master field, one for each non-default language. See mldbc."""),
|
269
264
|
'lino.utils.mldbc.fields.LanguageField' : _("""A field that lets the user select a language from the available lino.core.site.Site.languages."""),
|
@@ -379,7 +374,7 @@ help_texts = {
|
|
379
374
|
'lino.modlib.linod.SystemTask.disabled' : _("""Tells whether the task should be ignored."""),
|
380
375
|
'lino.modlib.linod.SystemTask.log_level' : _("""The logging level to apply when running this task."""),
|
381
376
|
'lino.modlib.linod.SystemTask.run' : _("""Performs a routine job."""),
|
382
|
-
'lino.modlib.linod.SystemTasks' : _("""The default
|
377
|
+
'lino.modlib.linod.SystemTasks' : _("""The default table for the SystemTask model."""),
|
383
378
|
'lino.modlib.linod.Runnable' : _("""Model mixin used by SystemTask and other models."""),
|
384
379
|
'lino.modlib.linod.Runnable.procedure' : _("""The background procedure to run in this task."""),
|
385
380
|
'lino.modlib.periods.StoredYear' : _("""The Django model used to store a fiscal year."""),
|
@@ -690,7 +685,7 @@ help_texts = {
|
|
690
685
|
'lino.modlib.uploads.AllUploads' : _("""Shows all upload files on this Lino site."""),
|
691
686
|
'lino.modlib.uploads.AreaUploads' : _("""Mixin for tables of upload files where the upload area is known."""),
|
692
687
|
'lino.modlib.uploads.MyUploads' : _("""Shows my uploads (i.e. those whose author is the requesting user)."""),
|
693
|
-
'lino.modlib.uploads.UploadBase' : _("""Abstract base class of Upload
|
688
|
+
'lino.modlib.uploads.UploadBase' : _("""Abstract base class of Upload encapsulating some really basic functionality."""),
|
694
689
|
'lino.modlib.uploads.UploadType' : _("""Django model representing an upload type."""),
|
695
690
|
'lino.modlib.uploads.UploadType.shortcut' : _("""Optional pointer to a virtual upload shortcut field. If this is not empty, then the given shortcut field will manage uploads of this type. See also Shortcuts."""),
|
696
691
|
'lino.modlib.uploads.UploadTypes' : _("""The table with all existing upload types."""),
|
lino/management/commands/prep.py
CHANGED
lino/mixins/duplicable.py
CHANGED
@@ -29,7 +29,7 @@ class Duplicate(actions.Action):
|
|
29
29
|
sort_index = 11
|
30
30
|
show_in_workflow = False
|
31
31
|
# readonly = False # like ShowInsert. See docs/blog/2012/0726
|
32
|
-
callable_from = "
|
32
|
+
callable_from = "t"
|
33
33
|
|
34
34
|
# required_roles = set([Expert])
|
35
35
|
|
@@ -42,7 +42,7 @@ class Duplicate(actions.Action):
|
|
42
42
|
return False
|
43
43
|
# if not user_type.has_required_roles([Expert]):
|
44
44
|
# return False
|
45
|
-
return super(
|
45
|
+
return super().get_view_permission(user_type)
|
46
46
|
|
47
47
|
def run_from_code(self, ar, **known_values):
|
48
48
|
obj = ar.selected_rows[0]
|
@@ -97,7 +97,8 @@ class Duplicate(actions.Action):
|
|
97
97
|
kw = dict()
|
98
98
|
# kw.update(refresh=True)
|
99
99
|
kw.update(
|
100
|
-
message=_("Duplicated %(old)s to %(new)s.") % dict(
|
100
|
+
message=_("Duplicated %(old)s to %(new)s.") % dict(
|
101
|
+
old=obj, new=new)
|
101
102
|
)
|
102
103
|
# ~ kw.update(new_status=dict(record_id=new.pk))
|
103
104
|
ar2.success(**kw)
|
@@ -108,7 +109,8 @@ class Duplicate(actions.Action):
|
|
108
109
|
|
109
110
|
obj = ar.selected_rows[0]
|
110
111
|
ar.confirm(
|
111
|
-
ok, _("This will create a copy of {}.").format(
|
112
|
+
ok, _("This will create a copy of {}.").format(
|
113
|
+
obj), _("Are you sure?")
|
112
114
|
)
|
113
115
|
|
114
116
|
|
lino/mixins/sequenced.py
CHANGED
@@ -33,7 +33,7 @@ class MoveByN(actions.Action):
|
|
33
33
|
This action is available on any :class:`Sequenced` object as
|
34
34
|
:attr:`Sequenced.move_by_n`.
|
35
35
|
|
36
|
-
It is currently only used by React to allow for drag and drop
|
36
|
+
It is currently only used by React to allow for drag and drop reordering.
|
37
37
|
|
38
38
|
"""
|
39
39
|
|
@@ -80,9 +80,11 @@ class MoveUp(actions.Action):
|
|
80
80
|
# label = _("Up")
|
81
81
|
# label = "\u2191" thin arrow up
|
82
82
|
# label = "\u25b2" # triangular arrow up
|
83
|
-
label = "
|
84
|
-
#
|
83
|
+
label = _("Move up")
|
84
|
+
# button_text = "\u25B2" # ▲ Black up-pointing triangle
|
85
|
+
button_text = "↑"
|
85
86
|
custom_handler = True
|
87
|
+
callable_from = "t"
|
86
88
|
# icon_name = 'arrow_up'
|
87
89
|
# ~ icon_file = 'arrow_up.png'
|
88
90
|
readonly = False
|
@@ -92,9 +94,12 @@ class MoveUp(actions.Action):
|
|
92
94
|
return False
|
93
95
|
if not super().get_action_permission(ar, obj, state):
|
94
96
|
return False
|
97
|
+
# if ar.order_by is None or "seqno" not in ar.order_by:
|
98
|
+
# return False
|
95
99
|
if ar.get_total_count() == 0:
|
96
100
|
return False
|
97
101
|
if ar.data_iterator[0] == obj:
|
102
|
+
# print(f"20250305 first of {ar.data_iterator}")
|
98
103
|
return False
|
99
104
|
# print("20161128", obj.seqno, ar.data_iterator.count())
|
100
105
|
return True
|
@@ -119,14 +124,16 @@ class MoveDown(actions.Action):
|
|
119
124
|
|
120
125
|
"""
|
121
126
|
|
127
|
+
label = _("Move down")
|
122
128
|
# label = _("Down")
|
123
|
-
|
129
|
+
button_text = "↓"
|
124
130
|
# label = "\u25bc" # triangular arrow down
|
125
131
|
# label = "\u2193"
|
126
|
-
|
132
|
+
# button_text = "\u25BC" # ▼ Black down-pointing triangle
|
127
133
|
# icon_name = 'arrow_down'
|
128
134
|
custom_handler = True
|
129
135
|
# ~ icon_file = 'arrow_down.png'
|
136
|
+
callable_from = "t"
|
130
137
|
readonly = False
|
131
138
|
|
132
139
|
def get_action_permission(self, ar, obj, state):
|
@@ -134,10 +141,13 @@ class MoveDown(actions.Action):
|
|
134
141
|
return False
|
135
142
|
if not super().get_action_permission(ar, obj, state):
|
136
143
|
return False
|
144
|
+
# if ar.order_by is None or "seqno" not in ar.order_by:
|
145
|
+
# return False
|
137
146
|
n = ar.get_total_count()
|
138
147
|
if n == 0:
|
139
148
|
return False
|
140
149
|
if ar.data_iterator[n - 1] == obj:
|
150
|
+
# print(f"20250305 last of {ar.data_iterator}")
|
141
151
|
return False
|
142
152
|
# ~ if obj.__class__.__name__=='Entry' and obj.seqno == 25:
|
143
153
|
# ~ print 20130706, ar.data_iterator.count(), ar.data_iterator
|
@@ -315,7 +325,8 @@ class Sequenced(Duplicable):
|
|
315
325
|
|
316
326
|
seq_no += 1
|
317
327
|
|
318
|
-
ar.success(
|
328
|
+
ar.success(
|
329
|
+
message=_("Renumbered {} of {} siblings.").format(n, qs.count()))
|
319
330
|
ar.set_response(refresh_all=True)
|
320
331
|
|
321
332
|
@fields.displayfield(_("Move"))
|
lino/modlib/__init__.py
CHANGED
lino/modlib/changes/models.py
CHANGED
@@ -64,7 +64,8 @@ class Change(UserAuthored):
|
|
64
64
|
related_name="changes_by_object",
|
65
65
|
)
|
66
66
|
object_id = GenericForeignKeyIdField(object_type, blank=True, null=True)
|
67
|
-
object = GenericForeignKey(
|
67
|
+
object = GenericForeignKey(
|
68
|
+
"object_type", "object_id", verbose_name=_("Object"))
|
68
69
|
|
69
70
|
master_type = dd.ForeignKey(
|
70
71
|
"contenttypes.ContentType",
|
@@ -74,9 +75,11 @@ class Change(UserAuthored):
|
|
74
75
|
related_name="changes_by_master",
|
75
76
|
)
|
76
77
|
master_id = GenericForeignKeyIdField(master_type, blank=True, null=True)
|
77
|
-
master = GenericForeignKey(
|
78
|
+
master = GenericForeignKey(
|
79
|
+
"master_type", "master_id", verbose_name=_("Master"))
|
78
80
|
|
79
|
-
diff = dd.RichTextField(_("Changes"), format="plain",
|
81
|
+
diff = dd.RichTextField(_("Changes"), format="plain",
|
82
|
+
blank=True, editable=False)
|
80
83
|
changed_fields = dd.CharField(_("Fields"), max_length=250, blank=True)
|
81
84
|
|
82
85
|
def __str__(self):
|
@@ -129,7 +132,8 @@ class Changes(dd.Table):
|
|
129
132
|
if pv.change_type:
|
130
133
|
qs = qs.filter(type=pv.change_type)
|
131
134
|
if pv.date:
|
132
|
-
qs = qs.filter(time__range=(
|
135
|
+
qs = qs.filter(time__range=(
|
136
|
+
pv.date, pv.date + datetime.timedelta(1)))
|
133
137
|
# if settings.SITE.user_model and ar.param_values.user:
|
134
138
|
# qs = qs.filter(user=ar.param_values.user)
|
135
139
|
if pv.object_type:
|
@@ -167,7 +171,9 @@ def log_change(type, request, master, obj, msg="", changed_fields=""):
|
|
167
171
|
remove_after = dd.plugins.changes.remove_after
|
168
172
|
if remove_after:
|
169
173
|
|
170
|
-
|
174
|
+
from lino.modlib.linod.choicelists import schedule_daily
|
175
|
+
|
176
|
+
@schedule_daily()
|
171
177
|
def delete_older_changes(ar):
|
172
178
|
days = datetime.timedelta(days=remove_after)
|
173
179
|
# django.core.exceptions.FieldError: Cannot resolve keyword 'time_lt' into field. Choices are: changed_fields, diff, id, list_item, master, master_id, master_type, master_type_id, name_column, navigation_panel, object, object_id, object_type, object_type_id, overview, time, type, user, user_id, workflow_buttons
|
@@ -206,7 +212,8 @@ def on_update(sender=None, watcher=None, request=None, **kw):
|
|
206
212
|
changes = []
|
207
213
|
for k, old, new in watcher.get_updates(cs.ignored_fields):
|
208
214
|
changed_fields += k + " "
|
209
|
-
changes.append("%s : %s --> %s" %
|
215
|
+
changes.append("%s : %s --> %s" %
|
216
|
+
(k, dd.obj2str(old), dd.obj2str(new)))
|
210
217
|
if len(changes) == 0:
|
211
218
|
msg = "(no changes)"
|
212
219
|
elif len(changes) == 1:
|
@@ -229,7 +236,8 @@ def on_delete(sender=None, request=None, **kw):
|
|
229
236
|
master = get_master(sender)
|
230
237
|
if master is None:
|
231
238
|
return
|
232
|
-
log_change(ChangeTypes.delete, request, master,
|
239
|
+
log_change(ChangeTypes.delete, request, master,
|
240
|
+
sender, dd.obj2str(sender, True))
|
233
241
|
|
234
242
|
|
235
243
|
@receiver(on_ui_created)
|
@@ -238,7 +246,8 @@ def on_ui_created(sender=None, request=None, **kw):
|
|
238
246
|
master = get_master(sender)
|
239
247
|
if master is None:
|
240
248
|
return
|
241
|
-
log_change(ChangeTypes.create, request, master,
|
249
|
+
log_change(ChangeTypes.create, request, master,
|
250
|
+
sender, dd.obj2str(sender, True))
|
242
251
|
|
243
252
|
|
244
253
|
@receiver(pre_add_child)
|
@@ -247,7 +256,8 @@ def on_add_child(sender=None, request=None, child=None, **kw):
|
|
247
256
|
if master is None:
|
248
257
|
return
|
249
258
|
log_change(
|
250
|
-
ChangeTypes.add_child, request, master, sender, dd.full_model_name(
|
259
|
+
ChangeTypes.add_child, request, master, sender, dd.full_model_name(
|
260
|
+
child)
|
251
261
|
)
|
252
262
|
|
253
263
|
|
@@ -257,7 +267,8 @@ def on_remove_child(sender=None, request=None, child=None, **kw):
|
|
257
267
|
if master is None:
|
258
268
|
return
|
259
269
|
log_change(
|
260
|
-
ChangeTypes.remove_child, request, master, sender, dd.full_model_name(
|
270
|
+
ChangeTypes.remove_child, request, master, sender, dd.full_model_name(
|
271
|
+
child)
|
261
272
|
)
|
262
273
|
|
263
274
|
|