lino 25.4.2__py3-none-any.whl → 25.4.4__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/kernel.py +33 -7
- lino/core/renderer.py +3 -3
- lino/core/site.py +10 -36
- lino/help_texts.py +3 -3
- lino/management/commands/demotest.py +16 -22
- lino/mixins/__init__.py +5 -5
- lino/mixins/dupable.py +2 -4
- lino/mixins/registrable.py +5 -2
- lino/modlib/about/models.py +2 -2
- lino/modlib/checkdata/choicelists.py +4 -4
- lino/modlib/checkdata/models.py +11 -3
- lino/modlib/comments/fixtures/demo2.py +4 -0
- lino/modlib/comments/models.py +1 -1
- lino/modlib/dupable/mixins.py +3 -5
- lino/modlib/extjs/ext_renderer.py +1 -1
- lino/modlib/extjs/views.py +1 -1
- lino/modlib/help/fixtures/demo2.py +3 -2
- lino/modlib/jinja/mixins.py +18 -5
- lino/modlib/linod/models.py +1 -1
- lino/modlib/linod/routing.py +49 -46
- lino/modlib/memo/mixins.py +3 -2
- lino/modlib/notify/api.py +33 -14
- lino/modlib/notify/mixins.py +4 -3
- lino/modlib/printing/mixins.py +1 -1
- lino/modlib/publisher/choicelists.py +41 -20
- lino/modlib/publisher/config/publisher/page.pub.html +24 -0
- lino/modlib/publisher/fixtures/demo2.py +12 -0
- lino/modlib/publisher/fixtures/std.py +2 -1
- lino/modlib/publisher/fixtures/synodalworld.py +17 -0
- lino/modlib/publisher/mixins.py +7 -0
- lino/modlib/publisher/models.py +77 -7
- lino/modlib/publisher/ui.py +5 -5
- lino/modlib/publisher/views.py +9 -2
- lino/modlib/system/models.py +1 -1
- lino/modlib/uploads/models.py +2 -2
- lino/static/bootstrap.css +1 -1
- lino/utils/dbfreader.py +64 -32
- lino/utils/dbhash.py +3 -2
- {lino-25.4.2.dist-info → lino-25.4.4.dist-info}/METADATA +1 -1
- {lino-25.4.2.dist-info → lino-25.4.4.dist-info}/RECORD +44 -43
- lino/management/commands/monitor.py +0 -160
- {lino-25.4.2.dist-info → lino-25.4.4.dist-info}/WHEEL +0 -0
- {lino-25.4.2.dist-info → lino-25.4.4.dist-info}/licenses/AUTHORS.rst +0 -0
- {lino-25.4.2.dist-info → lino-25.4.4.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.4'
|
35
35
|
|
36
36
|
# import setuptools # avoid UserWarning "Distutils was imported before Setuptools"?
|
37
37
|
|
lino/core/kernel.py
CHANGED
@@ -21,6 +21,7 @@ application.
|
|
21
21
|
|
22
22
|
import os
|
23
23
|
import sys
|
24
|
+
import time
|
24
25
|
import codecs
|
25
26
|
import atexit
|
26
27
|
import signal
|
@@ -41,7 +42,7 @@ from django.db import models
|
|
41
42
|
|
42
43
|
# import lino # for is_testing
|
43
44
|
from lino import logger
|
44
|
-
|
45
|
+
from lino.utils import codetime
|
45
46
|
from lino.utils.html import E
|
46
47
|
# from lino.core.utils import format_request
|
47
48
|
# from lino.utils import isiterable
|
@@ -111,6 +112,32 @@ class Kernel(object):
|
|
111
112
|
self.GFK_LIST = []
|
112
113
|
# logger.info("20140227 Kernel.__init__() done")
|
113
114
|
|
115
|
+
# On a production server we test only the timestamp of settings.py file
|
116
|
+
# because otherwise the result can differ depending on which modules have
|
117
|
+
# already been imported.
|
118
|
+
|
119
|
+
_lino_version = None
|
120
|
+
|
121
|
+
def touch_lino_version(self):
|
122
|
+
if is_devserver():
|
123
|
+
self._lino_version = time.time()
|
124
|
+
else:
|
125
|
+
p = self.site.site_dir / "lino_version.txt"
|
126
|
+
p.touch()
|
127
|
+
self._lino_version = p.stat().st_mtime
|
128
|
+
|
129
|
+
@property
|
130
|
+
def lino_version(self):
|
131
|
+
if self._lino_version is None:
|
132
|
+
if is_devserver():
|
133
|
+
self._lino_version = codetime()
|
134
|
+
else:
|
135
|
+
p = self.site.site_dir / "lino_version.txt"
|
136
|
+
if not p.exists():
|
137
|
+
p.touch()
|
138
|
+
self._lino_version = p.stat().st_mtime
|
139
|
+
return self._lino_version
|
140
|
+
|
114
141
|
# _code_mtime = None
|
115
142
|
#
|
116
143
|
# @property
|
@@ -118,13 +145,12 @@ class Kernel(object):
|
|
118
145
|
# if self._code_mtime is None:
|
119
146
|
# # packages = [os.environ['DJANGO_SETTINGS_MODULE'], 'lino']
|
120
147
|
# # self._code_mtime = codetime(*packages)
|
121
|
-
# if self.site.developer_site_cache:
|
148
|
+
# # if self.site.developer_site_cache:
|
149
|
+
# if is_devserver():
|
122
150
|
# self._code_mtime = codetime()
|
123
151
|
# else:
|
124
|
-
#
|
125
|
-
# #
|
126
|
-
# # depending on which modules have already been imported.
|
127
|
-
# self._code_mtime = codetime(settings.SETTINGS_MODULE)
|
152
|
+
# self._code_mtime = self.lino_version
|
153
|
+
# # self._code_mtime = codetime(settings.SETTINGS_MODULE)
|
128
154
|
# return self._code_mtime
|
129
155
|
|
130
156
|
def kernel_startup(self, site):
|
@@ -919,7 +945,7 @@ class Kernel(object):
|
|
919
945
|
if not force and not self._must_build and fn.exists():
|
920
946
|
mtime = os.stat(fn).st_mtime
|
921
947
|
# if mtime > self.code_mtime:
|
922
|
-
if mtime > self.
|
948
|
+
if mtime > self.lino_version:
|
923
949
|
# logger.debug("%s (%s) is up to date.", fn, time.ctime(mtime))
|
924
950
|
return 0
|
925
951
|
|
lino/core/renderer.py
CHANGED
@@ -124,6 +124,9 @@ class Renderer(object):
|
|
124
124
|
# if ar is None or a.get_bound_action_permission(ar, obj, None):
|
125
125
|
# return a
|
126
126
|
|
127
|
+
def add_help_text(self, kw, help_text, title, datasource, fieldname):
|
128
|
+
pass
|
129
|
+
|
127
130
|
def get_detail_url(self, *args, **kwargs):
|
128
131
|
return self.front_end.get_detail_url(*args, **kwargs)
|
129
132
|
|
@@ -750,9 +753,6 @@ class HtmlRenderer(Renderer):
|
|
750
753
|
def goto_instance(self, ar, obj, **kw):
|
751
754
|
pass
|
752
755
|
|
753
|
-
def add_help_text(self, kw, help_text, title, datasource, fieldname):
|
754
|
-
pass
|
755
|
-
|
756
756
|
|
757
757
|
class TextRenderer(HtmlRenderer):
|
758
758
|
"""
|
lino/core/site.py
CHANGED
@@ -559,21 +559,13 @@ class Site(object):
|
|
559
559
|
break
|
560
560
|
|
561
561
|
if self.master_site is None:
|
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
562
|
self.site_dir = self.project_dir
|
574
|
-
|
575
|
-
|
576
|
-
|
563
|
+
self.django_settings.update(DATABASES={
|
564
|
+
"default": {
|
565
|
+
"ENGINE": "django.db.backends.sqlite3",
|
566
|
+
"NAME": str(self.site_dir / "default.db")
|
567
|
+
}
|
568
|
+
})
|
577
569
|
else:
|
578
570
|
self.site_dir = self.master_site.site_dir
|
579
571
|
self._history_aware_logging = self.master_site._history_aware_logging
|
@@ -721,15 +713,6 @@ class Site(object):
|
|
721
713
|
# import yaml
|
722
714
|
# print("20231019", yaml.dump(d))
|
723
715
|
|
724
|
-
def get_database_settings(self):
|
725
|
-
if self.site_dir is None:
|
726
|
-
pass # raise Exception("20160516 No site_dir")
|
727
|
-
else:
|
728
|
-
dbname = self.site_dir / "default.db"
|
729
|
-
return {
|
730
|
-
"default": {"ENGINE": "django.db.backends.sqlite3", "NAME": str(dbname)}
|
731
|
-
}
|
732
|
-
|
733
716
|
def get_anonymous_user(self):
|
734
717
|
# The code below works even when users is not installed
|
735
718
|
from lino.modlib.users.choicelists import UserTypes
|
@@ -1829,6 +1812,7 @@ class Site(object):
|
|
1829
1812
|
self.install_settings()
|
1830
1813
|
|
1831
1814
|
def is_imported_partner(self, obj):
|
1815
|
+
# Deprecated.
|
1832
1816
|
# ~ return obj.id is not None and (obj.id < 200000 or obj.id > 299999)
|
1833
1817
|
return False
|
1834
1818
|
# ~ return obj.id is not None and (obj.id > 10 and obj.id < 21)
|
@@ -2012,17 +1996,6 @@ class Site(object):
|
|
2012
1996
|
s = self.plugins.jinja.render_from_ar(ar, "admin_main.html", **context)
|
2013
1997
|
return mark_safe(s)
|
2014
1998
|
|
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
|
-
|
2026
1999
|
def build_site_cache(self, force=False, later=False, verbosity=1):
|
2027
2000
|
from lino.modlib.users.utils import with_user_profile
|
2028
2001
|
from lino.modlib.users.choicelists import UserTypes
|
@@ -2032,8 +2005,9 @@ class Site(object):
|
|
2032
2005
|
# self.is_prepared = True
|
2033
2006
|
# settings_file = self.django_settings.get("__file__")
|
2034
2007
|
# Path(settings_file).touch()
|
2035
|
-
p = self.site_dir / "lino_version.txt"
|
2036
|
-
p.touch()
|
2008
|
+
# p = self.site_dir / "lino_version.txt"
|
2009
|
+
# p.touch()
|
2010
|
+
self.kernel.touch_lino_version()
|
2037
2011
|
|
2038
2012
|
if later:
|
2039
2013
|
# print("20230823 later")
|
lino/help_texts.py
CHANGED
@@ -11,8 +11,8 @@ help_texts = {
|
|
11
11
|
'lino.mixins.Modified' : _("""Adds a a timestamp field that holds the last modification time of every individual database object."""),
|
12
12
|
'lino.mixins.Modified.modified' : _("""The time when this database object was last modified."""),
|
13
13
|
'lino.mixins.Modified.auto_touch' : _("""Whether to touch objects automatically when saving them."""),
|
14
|
-
'lino.mixins.Created' : _("""Adds a timestamp field
|
15
|
-
'lino.mixins.Created.created' : _("""The time when this
|
14
|
+
'lino.mixins.Created' : _("""Adds a timestamp field that holds the creation time of every individual database row."""),
|
15
|
+
'lino.mixins.Created.created' : _("""The time when this database row was created."""),
|
16
16
|
'lino.mixins.CreatedModified' : _("""Adds two timestamp fields created and modified."""),
|
17
17
|
'lino.mixins.ProjectRelated' : _("""Mixin for models that are related to a “project”, i.e. to an object of the type given by your lino.core.site.Site.project_model."""),
|
18
18
|
'lino.mixins.ProjectRelated.project' : _("""Pointer to the project to which this object is related."""),
|
@@ -646,7 +646,7 @@ help_texts = {
|
|
646
646
|
'lino.modlib.system.SiteConfig.simulate_today' : _("""A constant user-defined date to be substituted as current system date."""),
|
647
647
|
'lino.modlib.system.SiteConfig.site_company' : _("""The site operator, i.e. the legal person that operates this Lino site."""),
|
648
648
|
'lino.modlib.system.SiteConfig.hide_events_before' : _("""If this is not empty, any calendar events before that date are being hidden in certain places."""),
|
649
|
-
'lino.modlib.system.SiteConfigManager' : _("""
|
649
|
+
'lino.modlib.system.SiteConfigManager' : _("""Returns the cached instance, which holds the one and only database instance."""),
|
650
650
|
'lino.modlib.system.Lockable' : _("""Mixin to add row-level edit locking to any model."""),
|
651
651
|
'lino.modlib.system.BuildSiteCache' : _("""Rebuild the site cache. This action is available on About."""),
|
652
652
|
'lino.modlib.system.SiteConfigs' : _("""The table used to present the SiteConfig row in a Detail form."""),
|
@@ -68,34 +68,32 @@ class TestCase(DemoTestCase):
|
|
68
68
|
|
69
69
|
# For this test we reduce max_blacklist_time because we are going to
|
70
70
|
# simulate a hacker who patiently waits:
|
71
|
-
ipdict.max_blacklist_time = timedelta(seconds=
|
71
|
+
ipdict.max_blacklist_time = timedelta(seconds=4)
|
72
72
|
|
73
73
|
self.assertEqual(ipdict.ip_records, {})
|
74
74
|
|
75
|
-
def login(pwd):
|
75
|
+
def login(pwd, expected):
|
76
76
|
d = self.login("robin", pwd)
|
77
|
-
|
77
|
+
if d.message != expected:
|
78
|
+
self.fail(f"Expected {expected} but got {d.message} ({d})")
|
78
79
|
|
79
|
-
|
80
|
+
login("bad", "Failed to sign in as robin.")
|
80
81
|
rec = ipdict.ip_records["127.0.0.1"]
|
81
82
|
self.assertEqual(rec.login_failures, 1)
|
82
|
-
|
83
|
+
login("bad", "Failed to sign in as robin.")
|
83
84
|
self.assertEqual(rec.login_failures, 2)
|
84
|
-
|
85
|
+
login("bad", "Failed to sign in as robin.")
|
85
86
|
self.assertEqual(rec.login_failures, 3)
|
86
|
-
|
87
|
+
login("bad", "Failed to sign in as robin.")
|
87
88
|
self.assertEqual(rec.login_failures, 4)
|
88
|
-
|
89
|
-
login("bad"), "Too many authentication failures from 127.0.0.1"
|
90
|
-
)
|
89
|
+
login("bad", "Too many authentication failures from 127.0.0.1")
|
91
90
|
|
92
91
|
# login_failures doesn't continue to increase when the ip is blacklisted:
|
93
92
|
self.assertEqual(rec.login_failures, 4)
|
94
93
|
|
95
94
|
# Even with the right password you cannot unlock a blacklisted ip
|
96
|
-
|
97
|
-
|
98
|
-
)
|
95
|
+
login(dd.plugins.users.demo_password,
|
96
|
+
"Too many authentication failures from 127.0.0.1")
|
99
97
|
|
100
98
|
# After max_blacklist_time, the IP gets removed from the blacklist, but
|
101
99
|
# every new failure will now blacklist it again, the
|
@@ -103,24 +101,20 @@ class TestCase(DemoTestCase):
|
|
103
101
|
|
104
102
|
# time.sleep(1.5)
|
105
103
|
time.sleep(5)
|
106
|
-
|
104
|
+
login("bad", "Failed to sign in as robin.")
|
107
105
|
self.assertEqual(rec.login_failures, 5)
|
108
|
-
|
109
|
-
login("bad"), "Too many authentication failures from 127.0.0.1"
|
110
|
-
)
|
106
|
+
login("bad", "Too many authentication failures from 127.0.0.1")
|
111
107
|
self.assertEqual(rec.login_failures, 5)
|
112
108
|
|
113
|
-
time.sleep(
|
114
|
-
|
115
|
-
login(dd.plugins.users.demo_password),
|
116
|
-
"Now signed in as Robin Rood")
|
109
|
+
time.sleep(5)
|
110
|
+
login(dd.plugins.users.demo_password, "Now signed in as Robin Rood")
|
117
111
|
|
118
112
|
# Once you manage to authenticate, your ip address gets removed from the
|
119
113
|
# blacklist, i.e. when you log out and in for some reason, you get again
|
120
114
|
# max_failed_auth_per_ip attempts
|
121
115
|
|
122
116
|
self.assertEqual(ipdict.ip_records, {})
|
123
|
-
|
117
|
+
login("bad", "Failed to sign in as robin.")
|
124
118
|
rec = ipdict.ip_records["127.0.0.1"]
|
125
119
|
self.assertEqual(rec.login_failures, 1)
|
126
120
|
|
lino/mixins/__init__.py
CHANGED
@@ -121,14 +121,14 @@ class Modified(model.Model):
|
|
121
121
|
|
122
122
|
class Created(model.Model):
|
123
123
|
"""
|
124
|
-
Adds a timestamp field
|
125
|
-
individual database
|
124
|
+
Adds a timestamp field that holds the creation time of every
|
125
|
+
individual :term:`database row`.
|
126
126
|
|
127
127
|
.. attribute:: created
|
128
128
|
|
129
|
-
The time when this
|
129
|
+
The time when this :term:`database row` was created.
|
130
130
|
|
131
|
-
Does
|
131
|
+
Does not use Django's `auto_now` and `auto_now_add` features
|
132
132
|
because their deserialization would be problematic.
|
133
133
|
"""
|
134
134
|
|
@@ -142,7 +142,7 @@ class Created(model.Model):
|
|
142
142
|
return naturaltime(self.created)
|
143
143
|
|
144
144
|
def save(self, *args, **kwargs):
|
145
|
-
if self.created is None and not settings.SITE.loading_from_dump:
|
145
|
+
if self.created is None: # and not settings.SITE.loading_from_dump:
|
146
146
|
self.created = settings.SITE.now()
|
147
147
|
super().save(*args, **kwargs)
|
148
148
|
|
lino/mixins/dupable.py
CHANGED
@@ -39,6 +39,7 @@ from lino.core.actions import SubmitInsert
|
|
39
39
|
from lino.utils import join_elems
|
40
40
|
from lino.utils.html import E, tostring, mark_safe
|
41
41
|
from lino.core import constants
|
42
|
+
from lino.modlib.checkdata.choicelists import Checker
|
42
43
|
|
43
44
|
|
44
45
|
class CheckedSubmitInsert(SubmitInsert):
|
@@ -242,9 +243,6 @@ class Dupable(dd.Model):
|
|
242
243
|
return qs[:limit]
|
243
244
|
|
244
245
|
|
245
|
-
from lino.modlib.checkdata.choicelists import Checker
|
246
|
-
|
247
|
-
|
248
246
|
class DupableChecker(Checker):
|
249
247
|
"""Checks for the following repairable problem:
|
250
248
|
|
@@ -255,7 +253,7 @@ class DupableChecker(Checker):
|
|
255
253
|
verbose_name = _("Check for missing phonetic words")
|
256
254
|
model = Dupable
|
257
255
|
|
258
|
-
def get_checkdata_problems(self, obj, fix=False):
|
256
|
+
def get_checkdata_problems(self, ar, obj, fix=False):
|
259
257
|
msg = obj.update_dupable_words(fix)
|
260
258
|
if msg:
|
261
259
|
yield (True, msg)
|
lino/mixins/registrable.py
CHANGED
@@ -119,8 +119,11 @@ class Registrable(model.Model):
|
|
119
119
|
|
120
120
|
def disabled_fields(self, ar):
|
121
121
|
if not self.state.is_editable:
|
122
|
-
return self._registrable_fields
|
123
|
-
|
122
|
+
# return self._registrable_fields
|
123
|
+
# Copy _registrable_fields otherwise _registrable_fields get
|
124
|
+
# modified as more disabled fields are added to the set.
|
125
|
+
return self._registrable_fields.copy()
|
126
|
+
return super().disabled_fields(ar)
|
124
127
|
|
125
128
|
def get_row_permission(self, ar, state, ba):
|
126
129
|
"""Only rows in an editable state may be edited.
|
lino/modlib/about/models.py
CHANGED
@@ -119,8 +119,8 @@ class About(EmptyTable):
|
|
119
119
|
packages = set(["django"])
|
120
120
|
|
121
121
|
items.append(
|
122
|
-
E.li(gettext("Server timestamp"), " : ",
|
123
|
-
|
122
|
+
E.li(gettext("Server timestamp"), " : ",
|
123
|
+
E.b(dtfmt(site.kernel.lino_version))))
|
124
124
|
|
125
125
|
for p in site.installed_plugins:
|
126
126
|
packages.add(p.app_name.split(".")[0])
|
@@ -64,8 +64,8 @@ class Checker(dd.Choice):
|
|
64
64
|
ar.logger.info(msg.format(len(todo), len(done), cls.self))
|
65
65
|
|
66
66
|
@classmethod
|
67
|
-
def check_instance(cls, *args, **kwargs):
|
68
|
-
return cls.self.get_checkdata_problems(*args, **kwargs)
|
67
|
+
def check_instance(cls, ar, *args, **kwargs):
|
68
|
+
return cls.self.get_checkdata_problems(ar, *args, **kwargs)
|
69
69
|
|
70
70
|
def get_checkable_models(self):
|
71
71
|
if self.model is None:
|
@@ -92,7 +92,7 @@ class Checker(dd.Choice):
|
|
92
92
|
|
93
93
|
done = []
|
94
94
|
todo = []
|
95
|
-
for fixable, msg in self.get_checkdata_problems(obj, fix):
|
95
|
+
for fixable, msg in self.get_checkdata_problems(ar, obj, fix):
|
96
96
|
if fixable:
|
97
97
|
# attn: do not yet translate
|
98
98
|
# msg = string_concat(u"(\u2605) ", msg)
|
@@ -126,7 +126,7 @@ class Checker(dd.Choice):
|
|
126
126
|
prb.save()
|
127
127
|
return (todo, done)
|
128
128
|
|
129
|
-
def get_checkdata_problems(self, obj, fix=False):
|
129
|
+
def get_checkdata_problems(self, ar, obj, fix=False):
|
130
130
|
return []
|
131
131
|
|
132
132
|
def get_responsible_user(self, obj):
|
lino/modlib/checkdata/models.py
CHANGED
@@ -21,6 +21,9 @@ from lino.api import dd, rt, _
|
|
21
21
|
from .choicelists import Checker, Checkers
|
22
22
|
from .roles import CheckdataUser
|
23
23
|
|
24
|
+
MAX_LENGTH = 250
|
25
|
+
MORE = " (...)"
|
26
|
+
|
24
27
|
|
25
28
|
class CheckerAction(dd.Action):
|
26
29
|
fix_them = False
|
@@ -150,7 +153,7 @@ class Message(Controllable, UserAuthored):
|
|
150
153
|
checker = Checkers.field(verbose_name=_("Checker"))
|
151
154
|
# severity = Severities.field()
|
152
155
|
# feedback = Feedbacks.field(blank=True)
|
153
|
-
message = models.CharField(_("Message text"), max_length=
|
156
|
+
message = models.CharField(_("Message text"), max_length=MAX_LENGTH)
|
154
157
|
# fixable = models.BooleanField(_("Fixable"), default=False)
|
155
158
|
|
156
159
|
update_problem = UpdateMessage()
|
@@ -166,6 +169,11 @@ class Message(Controllable, UserAuthored):
|
|
166
169
|
def __str__(self):
|
167
170
|
return self.message
|
168
171
|
|
172
|
+
def full_clean(self):
|
173
|
+
if len(self.message) > MAX_LENGTH:
|
174
|
+
self.message = self.message[:MAX_LENGTH - len(MORE)] + MORE
|
175
|
+
super().full_clean()
|
176
|
+
|
169
177
|
@classmethod
|
170
178
|
def get_simple_parameters(cls):
|
171
179
|
for p in super(Message, cls).get_simple_parameters():
|
@@ -301,9 +309,9 @@ def get_checkers_for(model):
|
|
301
309
|
return checkers
|
302
310
|
|
303
311
|
|
304
|
-
def check_instance(obj, **kwargs):
|
312
|
+
def check_instance(ar, obj, **kwargs):
|
305
313
|
for chk in get_checkers_for(obj.__class__):
|
306
|
-
for fixable, msg in chk.check_instance(obj, **kwargs):
|
314
|
+
for fixable, msg in chk.check_instance(ar, obj, **kwargs):
|
307
315
|
if fixable:
|
308
316
|
msg = f"(\u2605) {msg}"
|
309
317
|
print(msg)
|
@@ -42,12 +42,16 @@ BODIES.items.insert(0, "")
|
|
42
42
|
|
43
43
|
|
44
44
|
def objects():
|
45
|
+
|
45
46
|
Comment = rt.models.comments.Comment
|
46
47
|
User = rt.models.users.User
|
47
48
|
Comment.auto_touch = False
|
48
49
|
# use_linod = settings.SITE.use_linod
|
49
50
|
# settings.SITE.use_linod = False
|
50
51
|
|
52
|
+
# avoid channels.exceptions.ChannelFull:
|
53
|
+
settings.SITE.loading_from_dump = True
|
54
|
+
|
51
55
|
MENTIONED = Cycler()
|
52
56
|
for model in rt.models_by_base(Commentable):
|
53
57
|
if model.memo_command is not None:
|
lino/modlib/comments/models.py
CHANGED
@@ -429,7 +429,7 @@ class CommentChecker(Checker):
|
|
429
429
|
model = Comment
|
430
430
|
msg_missing = _("Missing owner in reply to comment.")
|
431
431
|
|
432
|
-
def get_checkdata_problems(self, obj, fix=False):
|
432
|
+
def get_checkdata_problems(self, ar, obj, fix=False):
|
433
433
|
if obj.reply_to_id and not obj.owner_id and obj.reply_to.owner_id:
|
434
434
|
yield (True, self.msg_missing)
|
435
435
|
if fix:
|
lino/modlib/dupable/mixins.py
CHANGED
@@ -13,6 +13,7 @@ from lino.api import dd, rt, _
|
|
13
13
|
from lino.core.actions import SubmitInsert
|
14
14
|
from lino.core.gfks import gfk2lookup
|
15
15
|
from lino.core.gfks import ContentType
|
16
|
+
from lino.modlib.checkdata.choicelists import Checker
|
16
17
|
|
17
18
|
|
18
19
|
class CheckedSubmitInsert(SubmitInsert):
|
@@ -154,9 +155,6 @@ class Dupable(dd.Model):
|
|
154
155
|
return qs[:limit]
|
155
156
|
|
156
157
|
|
157
|
-
from lino.modlib.checkdata.choicelists import Checker
|
158
|
-
|
159
|
-
|
160
158
|
class DupableChecker(Checker):
|
161
159
|
"""Checks for the following repairable problem:
|
162
160
|
|
@@ -167,7 +165,7 @@ class DupableChecker(Checker):
|
|
167
165
|
verbose_name = _("Check for missing phonetic words")
|
168
166
|
model = Dupable
|
169
167
|
|
170
|
-
def get_checkdata_problems(self, obj, fix=False):
|
168
|
+
def get_checkdata_problems(self, ar, obj, fix=False):
|
171
169
|
msg = obj.update_dupable_words(fix)
|
172
170
|
if msg:
|
173
171
|
yield (True, msg)
|
@@ -180,7 +178,7 @@ class SimilarObjectsChecker(Checker):
|
|
180
178
|
model = Dupable
|
181
179
|
verbose_name = _("Check for similar objects")
|
182
180
|
|
183
|
-
def get_checkdata_problems(self, obj, fix=False):
|
181
|
+
def get_checkdata_problems(self, ar, obj, fix=False):
|
184
182
|
lst = list(obj.find_similar_instances(1))
|
185
183
|
if len(lst):
|
186
184
|
msg = _("Similar clients: {clients}").format(
|
@@ -1481,7 +1481,7 @@ class ExtRenderer(JsCacheRenderer):
|
|
1481
1481
|
# if settings.SITE.never_build_site_cache:
|
1482
1482
|
# yield "GEN_TIMESTAMP = '%s';" % settings.SITE.kernel.lino_version
|
1483
1483
|
# else:
|
1484
|
-
yield "GEN_TIMESTAMP = %s;" % py2js(settings.SITE.lino_version)
|
1484
|
+
yield "GEN_TIMESTAMP = %s;" % py2js(settings.SITE.kernel.lino_version)
|
1485
1485
|
|
1486
1486
|
return "\n".join(fn())
|
1487
1487
|
|
lino/modlib/extjs/views.py
CHANGED
@@ -138,7 +138,7 @@ def test_version_mismatch(request):
|
|
138
138
|
if os.environ.get("PYCHARM_HOSTED", False):
|
139
139
|
return {}
|
140
140
|
lv = request.GET.get(constants.URL_PARAM_LINO_VERSION)
|
141
|
-
if lv is None or float(lv) == settings.SITE.lino_version:
|
141
|
+
if lv is None or float(lv) == settings.SITE.kernel.lino_version:
|
142
142
|
return {}
|
143
143
|
# print("20201217", lv, settings.SITE.kernel.code_mtime)
|
144
144
|
cache.clear()
|
@@ -8,15 +8,16 @@ from lino.api import dd, rt, _
|
|
8
8
|
if dd.get_plugin_setting("help", "use_contacts"):
|
9
9
|
|
10
10
|
from lino.api.shell import help, contacts
|
11
|
+
from lino_xl.lib.contacts.models import PARTNER_NUMBERS_START_AT as PS
|
11
12
|
|
12
13
|
def site_contact(type, company=None, **kwargs):
|
13
14
|
return help.SiteContact(site_contact_type=type, company=company, **kwargs)
|
14
15
|
|
15
16
|
def objects():
|
16
17
|
yield site_contact("owner", settings.SITE.site_config.site_company)
|
17
|
-
yield site_contact("serveradmin", contacts.Company.objects.get(pk=
|
18
|
+
yield site_contact("serveradmin", contacts.Company.objects.get(pk=PS+6))
|
18
19
|
yield site_contact(
|
19
20
|
"hotline",
|
20
|
-
contact_person=contacts.Person.objects.get(pk=
|
21
|
+
contact_person=contacts.Person.objects.get(pk=PS+13),
|
21
22
|
**dd.babelkw("remark", _("Mon and Fri from 11:30 to 12:00")),
|
22
23
|
)
|
lino/modlib/jinja/mixins.py
CHANGED
@@ -8,10 +8,9 @@ from pathlib import Path
|
|
8
8
|
from lxml import etree
|
9
9
|
|
10
10
|
from django.conf import settings
|
11
|
-
from django.utils import translation
|
12
11
|
from django.utils.html import mark_safe, escape
|
13
12
|
|
14
|
-
from lino.api import dd
|
13
|
+
from lino.api import dd, _
|
15
14
|
from lino.utils.xml import validate_xml
|
16
15
|
from lino.utils.media import MediaFile
|
17
16
|
|
@@ -31,12 +30,20 @@ class XMLMaker(dd.Model):
|
|
31
30
|
xml_file_template = None
|
32
31
|
# xml_file_name = None
|
33
32
|
|
33
|
+
_xmlfile = None
|
34
|
+
|
35
|
+
@property
|
36
|
+
def xmlfile(self):
|
37
|
+
if self._xmlfile is None:
|
38
|
+
self._xmlfile = MediaFile(False, *self.get_xml_file_parts())
|
39
|
+
return self._xmlfile
|
40
|
+
|
34
41
|
def get_xml_file_parts(self):
|
35
42
|
yield 'xml'
|
36
43
|
yield self.get_printable_target_stem() + ".xml"
|
37
44
|
|
38
45
|
def get_xml_file(self):
|
39
|
-
return
|
46
|
+
return self.xmlfile
|
40
47
|
|
41
48
|
def make_xml_file(self, ar):
|
42
49
|
renderer = settings.SITE.plugins.jinja.renderer
|
@@ -48,9 +55,9 @@ class XMLMaker(dd.Model):
|
|
48
55
|
# parts = [
|
49
56
|
# dd.plugins.accounting.xml_media_dir,
|
50
57
|
# self.xml_file_name.format(self=self)]
|
51
|
-
xmlfile = self.
|
58
|
+
xmlfile = self.xmlfile
|
52
59
|
# xmlfile = Path(settings.MEDIA_ROOT, *parts)
|
53
|
-
ar.logger.
|
60
|
+
ar.logger.debug("Make %s from %s ...", xmlfile.path, self)
|
54
61
|
xmlfile.path.parent.mkdir(exist_ok=True, parents=True)
|
55
62
|
xmlfile.path.write_text(xml)
|
56
63
|
# xmlfile.write_text(etree.tostring(xml))
|
@@ -74,3 +81,9 @@ class XMLMaker(dd.Model):
|
|
74
81
|
# return mark_safe(f"""<a href="{url}">{url}</a>""")
|
75
82
|
# return (xmlfile, url)
|
76
83
|
return xmlfile
|
84
|
+
|
85
|
+
@dd.displayfield(_("XML file"))
|
86
|
+
def xml_file(self, ar):
|
87
|
+
mf = self.xmlfile
|
88
|
+
href = settings.SITE.media_root / mf.url
|
89
|
+
return mark_safe(f"<a href=\"{href}\" target=\"blank\">{mf.path.name}</a>")
|
lino/modlib/linod/models.py
CHANGED
@@ -55,7 +55,7 @@ class SystemTaskChecker(Checker):
|
|
55
55
|
verbose_name = _("Check for missing system tasks")
|
56
56
|
model = None
|
57
57
|
|
58
|
-
def get_checkdata_problems(self, obj, fix=False):
|
58
|
+
def get_checkdata_problems(self, ar, obj, fix=False):
|
59
59
|
for proc in Procedures.get_list_items():
|
60
60
|
if proc.class_name == "linod.SystemTask":
|
61
61
|
if SystemTask.objects.filter(procedure=proc).count() == 0:
|