lino 25.2.1__py3-none-any.whl → 25.2.3__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 +8 -3
- lino/api/dd.py +13 -0
- lino/api/doctest.py +49 -17
- lino/api/selenium.py +1 -1
- lino/core/actors.py +16 -7
- lino/core/boundaction.py +1 -1
- lino/core/choicelists.py +1 -1
- lino/core/dbtables.py +15 -15
- lino/core/elems.py +1 -1
- lino/core/renderer.py +2 -2
- lino/core/requests.py +49 -13
- lino/core/site.py +5 -5
- lino/help_texts.py +8 -3
- lino/modlib/comments/models.py +1 -1
- lino/modlib/comments/ui.py +1 -1
- lino/modlib/extjs/__init__.py +2 -2
- lino/modlib/extjs/views.py +75 -29
- lino/modlib/help/config/makehelp/conf.tpl.py +1 -1
- lino/modlib/jinja/mixins.py +62 -0
- lino/modlib/jinja/models.py +6 -0
- lino/modlib/linod/fixtures/__init__.py +0 -0
- lino/modlib/linod/fixtures/linod.py +32 -0
- lino/modlib/linod/mixins.py +15 -1
- lino/modlib/memo/mixins.py +11 -3
- lino/modlib/memo/parser.py +1 -1
- lino/modlib/notify/models.py +1 -1
- lino/modlib/printing/actions.py +6 -12
- lino/modlib/printing/choicelists.py +7 -7
- lino/modlib/uploads/__init__.py +9 -6
- lino/modlib/uploads/models.py +3 -3
- lino/modlib/uploads/ui.py +5 -2
- lino/modlib/users/mixins.py +4 -0
- lino/utils/__init__.py +0 -1
- lino/utils/jscompressor.py +4 -4
- lino/utils/restify.py +2 -2
- lino/utils/soup.py +4 -4
- lino/utils/xml.py +19 -5
- {lino-25.2.1.dist-info → lino-25.2.3.dist-info}/METADATA +1 -1
- {lino-25.2.1.dist-info → lino-25.2.3.dist-info}/RECORD +42 -39
- lino/utils/requests.py +0 -55
- {lino-25.2.1.dist-info → lino-25.2.3.dist-info}/WHEEL +0 -0
- {lino-25.2.1.dist-info → lino-25.2.3.dist-info}/licenses/AUTHORS.rst +0 -0
- {lino-25.2.1.dist-info → lino-25.2.3.dist-info}/licenses/COPYING +0 -0
lino/__init__.py
CHANGED
@@ -26,7 +26,7 @@ defines no models, some template files, a series of :term:`django-admin commands
|
|
26
26
|
|
27
27
|
"""
|
28
28
|
|
29
|
-
__version__ = '25.2.
|
29
|
+
__version__ = '25.2.3'
|
30
30
|
|
31
31
|
# import setuptools # avoid UserWarning "Distutils was imported before Setuptools"?
|
32
32
|
|
@@ -64,14 +64,19 @@ import warnings
|
|
64
64
|
|
65
65
|
warnings.filterwarnings(
|
66
66
|
"error",
|
67
|
-
"DateTimeField .* received a naive datetime (.*) while time zone support is active.",
|
67
|
+
r"DateTimeField .* received a naive datetime (.*) while time zone support is active.",
|
68
68
|
RuntimeWarning,
|
69
69
|
"django.db.models.fields",
|
70
70
|
)
|
71
71
|
|
72
|
+
# TODO: Is it okay to ignore the followgin warning? It's because e.g.
|
73
|
+
# lino.modlib.excerpts has a pre_analyze receiver set_excerpts_actions(), which
|
74
|
+
# accesses the database to set actions before Lino analyzes the models. It
|
75
|
+
# catches OperationalError & Co because --of course-- it fails e.g. for
|
76
|
+
# admin-commands like "pm prep".
|
72
77
|
warnings.filterwarnings(
|
73
78
|
"ignore",
|
74
|
-
"Accessing the database during app initialization is discouraged\. To fix this warning, avoid executing queries in AppConfig\.ready\(\) or when your app modules are imported\.",
|
79
|
+
r"Accessing the database during app initialization is discouraged\. To fix this warning, avoid executing queries in AppConfig\.ready\(\) or when your app modules are imported\.",
|
75
80
|
RuntimeWarning,
|
76
81
|
"django.db.backends.utils",
|
77
82
|
)
|
lino/api/dd.py
CHANGED
@@ -209,6 +209,19 @@ from lino.modlib.linod.choicelists import Procedures
|
|
209
209
|
|
210
210
|
|
211
211
|
def background_task(**kwargs):
|
212
|
+
"""
|
213
|
+
Register the decorated function as a :term:`background task`.
|
214
|
+
|
215
|
+
Keyword arguments are used as default values when checkdata creates a
|
216
|
+
:class:`lino.modlib.linod.SystemTask` instance for this procedure.
|
217
|
+
|
218
|
+
Except for the special keyword ``class_name``, which defaults to
|
219
|
+
"linod.SystemTask". It is used by :mod:`lino_xl.lib.invoicing` to register a
|
220
|
+
procedure that will create an :term:`invoicing task` instead of a normal
|
221
|
+
:term:`background task`. :class:`lino_xl.lib.invoicing.InvoicingTask`
|
222
|
+
instead of :class:`lino.modlib.linod.SystemTask`.
|
223
|
+
|
224
|
+
"""
|
212
225
|
if "class_name" not in kwargs:
|
213
226
|
kwargs["class_name"] = "linod.SystemTask"
|
214
227
|
|
lino/api/doctest.py
CHANGED
@@ -1,28 +1,45 @@
|
|
1
1
|
# -*- coding: UTF-8 -*-
|
2
|
-
# Copyright 2015-
|
2
|
+
# Copyright 2015-2025 Rumma & Ko Ltd
|
3
3
|
# License: GNU Affero General Public License v3 (see file COPYING for details)
|
4
4
|
"""
|
5
|
-
A selection of names to be used in tested documents
|
5
|
+
A selection of names to be used in tested documents as follows:
|
6
|
+
|
7
|
+
>>> from lino.api.doctest import *
|
8
|
+
|
9
|
+
This is by convention everything we want to have in the global namespace of a
|
10
|
+
tested document. It includes
|
11
|
+
|
12
|
+
- well-known Python standard modules like os, sys, datetime and collections.
|
13
|
+
- A variable :data:`test_client`
|
14
|
+
|
6
15
|
"""
|
7
16
|
|
17
|
+
import os
|
18
|
+
import sys
|
19
|
+
import datetime
|
20
|
+
import collections
|
8
21
|
import six # TODO: remove here and then run all doctests
|
9
22
|
import logging
|
10
23
|
import sqlparse
|
24
|
+
import json
|
25
|
+
import textwrap
|
26
|
+
|
27
|
+
from bs4 import BeautifulSoup
|
28
|
+
from pprint import pprint, pformat
|
11
29
|
from urllib.parse import urlencode
|
30
|
+
|
12
31
|
import django
|
13
32
|
django.setup()
|
33
|
+
|
14
34
|
from lino.core.constants import *
|
15
35
|
from lino.api.shell import *
|
16
36
|
from django.utils import translation
|
17
37
|
from django.utils.encoding import force_str
|
18
38
|
from django.test import Client
|
19
39
|
from django.db import connection, reset_queries as reset_sql_queries
|
20
|
-
import json
|
21
|
-
from bs4 import BeautifulSoup
|
22
|
-
import textwrap
|
23
|
-
from pprint import pprint, pformat
|
24
40
|
|
25
|
-
|
41
|
+
import pytest
|
42
|
+
# from rstgen import table, ul
|
26
43
|
import rstgen
|
27
44
|
from rstgen import attrtable
|
28
45
|
from rstgen.utils import unindent, rmu, sixprint
|
@@ -49,10 +66,12 @@ from lino.core.actions import register_params
|
|
49
66
|
from lino.core.layouts import BaseLayout
|
50
67
|
|
51
68
|
test_client = Client()
|
52
|
-
|
53
|
-
# `lino_welfare.pcsw.models.Client`
|
69
|
+
"""An instance of :class:`django.test.Client`.
|
54
70
|
|
55
|
-
|
71
|
+
N.B. Naming it simply "client" caused conflict with a
|
72
|
+
:class:`lino_welfare.pcsw.models.Client`
|
73
|
+
|
74
|
+
"""
|
56
75
|
|
57
76
|
HttpQuery = collections.namedtuple(
|
58
77
|
"HttpQuery", ["username", "url_base", "json_fields", "expected_rows", "kwargs"]
|
@@ -293,7 +312,7 @@ def show_workflow(actions, all=False, language=None):
|
|
293
312
|
# required_roles
|
294
313
|
]
|
295
314
|
)
|
296
|
-
print(table(cols, cells).strip())
|
315
|
+
print(rstgen.table(cols, cells).strip())
|
297
316
|
|
298
317
|
if language:
|
299
318
|
with translation.override(language):
|
@@ -363,7 +382,7 @@ def fields_help(model, fieldnames=None, columns=False, all=None):
|
|
363
382
|
|
364
383
|
# return table(cols, cells).strip()
|
365
384
|
items = ["{} ({}) : {}".format(row[1], row[0], row[2]) for row in cells]
|
366
|
-
return ul(items).strip()
|
385
|
+
return rstgen.ul(items).strip()
|
367
386
|
|
368
387
|
|
369
388
|
def show_fields(*args, **kwargs):
|
@@ -552,7 +571,7 @@ def show_choicelist(cls):
|
|
552
571
|
for i in cls.get_list_items():
|
553
572
|
row = [i.value, i.name] + str2languages(i.text)
|
554
573
|
rows.append(row)
|
555
|
-
print(table(headers, rows))
|
574
|
+
print(rstgen.table(headers, rows))
|
556
575
|
|
557
576
|
|
558
577
|
def show_choicelists():
|
@@ -568,7 +587,7 @@ def show_choicelists():
|
|
568
587
|
i.verbose_name_plural
|
569
588
|
)
|
570
589
|
rows.append(row)
|
571
|
-
print(table(headers, rows))
|
590
|
+
print(rstgen.table(headers, rows))
|
572
591
|
|
573
592
|
|
574
593
|
def show_permissions(*args):
|
@@ -590,7 +609,7 @@ def show_translations(things, fmt, languages=None):
|
|
590
609
|
x, txt = fmt(thing)
|
591
610
|
cells.append(txt)
|
592
611
|
rows.append(cells)
|
593
|
-
print(table(headers, rows))
|
612
|
+
print(rstgen.table(headers, rows))
|
594
613
|
|
595
614
|
|
596
615
|
def show_model_translations(*models, **kwargs):
|
@@ -779,7 +798,7 @@ def show_change_watchers():
|
|
779
798
|
rows.append(
|
780
799
|
[full_model_name(m), ws.master_key, " ".join(sorted(ws.ignored_fields))]
|
781
800
|
)
|
782
|
-
print(table(headers, rows, max_width=40))
|
801
|
+
print(rstgen.table(headers, rows, max_width=40))
|
783
802
|
|
784
803
|
def show_display_modes():
|
785
804
|
"""
|
@@ -796,4 +815,17 @@ def show_display_modes():
|
|
796
815
|
("x" if dm in a.extra_display_modes else "")
|
797
816
|
for dm in dml]
|
798
817
|
)
|
799
|
-
print(table(headers, rows))
|
818
|
+
print(rstgen.table(headers, rows))
|
819
|
+
|
820
|
+
|
821
|
+
def checkdb(m, num):
|
822
|
+
"""
|
823
|
+
Raise an exception if the database doesn't contain the specified number of
|
824
|
+
rows of the specified model.
|
825
|
+
|
826
|
+
This is for usage in :xfile:`startup.py` scripts.
|
827
|
+
|
828
|
+
"""
|
829
|
+
if m.objects.count() != num:
|
830
|
+
raise Exception(
|
831
|
+
f"Model {m} should have {num} rows but has {m.objects.count()}")
|
lino/api/selenium.py
CHANGED
lino/core/actors.py
CHANGED
@@ -1948,17 +1948,26 @@ class Actor(actions.Parametrizable, Permittable, metaclass=ActorMetaClass):
|
|
1948
1948
|
return html
|
1949
1949
|
|
1950
1950
|
@classmethod
|
1951
|
-
def
|
1951
|
+
def get_slave_summary(cls, obj, ar=None):
|
1952
|
+
"""
|
1953
|
+
:param cls: Slave table
|
1954
|
+
:param obj: Master instance
|
1955
|
+
:param ar: Action request on master table
|
1956
|
+
"""
|
1957
|
+
if ar is None:
|
1958
|
+
return ''
|
1959
|
+
sar = cls.request(parent=ar, master_instance=obj, is_on_main_actor=False)
|
1960
|
+
return cls.get_table_summary(sar)
|
1961
|
+
|
1962
|
+
@classmethod
|
1963
|
+
def get_table_summary(cls, ar):
|
1952
1964
|
"""
|
1953
1965
|
Return the HTML `<div>` to be displayed by
|
1954
1966
|
:class:`lino.core.elems.TableSummaryPanel`.
|
1955
1967
|
It basically just calls :meth:`table_as_summary`.
|
1956
1968
|
|
1957
1969
|
"""
|
1958
|
-
|
1959
|
-
return ''
|
1960
|
-
sar = cls.request(parent=ar, master_instance=obj, is_on_main_actor=False)
|
1961
|
-
p = cls.table_as_summary(sar)
|
1970
|
+
p = cls.table_as_summary(ar)
|
1962
1971
|
# assert_safe(p) # temporary 20240506
|
1963
1972
|
# print("20240712", p)
|
1964
1973
|
# return format_html(DIVTPL, p)
|
@@ -1981,9 +1990,9 @@ class Actor(actions.Parametrizable, Permittable, metaclass=ActorMetaClass):
|
|
1981
1990
|
"""
|
1982
1991
|
p = qs2summary(
|
1983
1992
|
ar,
|
1984
|
-
ar.
|
1993
|
+
ar.sliced_data_iterator,
|
1985
1994
|
separator=cls.summary_sep,
|
1986
|
-
max_items=cls.preview_limit,
|
1995
|
+
max_items=ar.limit or cls.preview_limit,
|
1987
1996
|
wraptpl=None,
|
1988
1997
|
)
|
1989
1998
|
# assert isinstance(p, str)
|
lino/core/boundaction.py
CHANGED
lino/core/choicelists.py
CHANGED
@@ -823,7 +823,7 @@ class ChoiceList(with_metaclass(ChoiceListMeta, tables.AbstractTable)):
|
|
823
823
|
@classmethod
|
824
824
|
def filter(self, **fkw):
|
825
825
|
def f(item):
|
826
|
-
for k, v in
|
826
|
+
for k, v in fkw.items():
|
827
827
|
if getattr(item, k) != v:
|
828
828
|
return False
|
829
829
|
return True
|
lino/core/dbtables.py
CHANGED
@@ -315,22 +315,22 @@ class Table(AbstractTable):
|
|
315
315
|
# qs = self.model.get_request_queryset(ar)
|
316
316
|
# str(qs.query)
|
317
317
|
|
318
|
-
|
319
|
-
try:
|
318
|
+
if not settings.SITE.catch_layout_exceptions:
|
320
319
|
return qs.get(pk=pk)
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
|
320
|
+
else:
|
321
|
+
try:
|
322
|
+
return qs.get(pk=pk)
|
323
|
+
except self.model.DoesNotExist:
|
324
|
+
# sql = qs.query
|
325
|
+
# import sqlparse
|
326
|
+
# sql = str(sql).replace('"', '')
|
327
|
+
# sql = sqlparse.format(sql, reindent=True, keyword_case='upper')
|
328
|
+
# raise self.model.DoesNotExist(f"No row {pk} in {ar} ({sql})")
|
329
|
+
raise self.model.DoesNotExist(f"No row {pk} in {ar}") from None
|
330
|
+
# from django.core.exceptions import ObjectDoesNotExist
|
331
|
+
# assert isinstance(exc, ObjectDoesNotExist)
|
332
|
+
# raise exc
|
333
|
+
# return None
|
334
334
|
|
335
335
|
# @classmethod
|
336
336
|
# def disabled_actions(self, ar, obj): # no longer used since 20170909
|
lino/core/elems.py
CHANGED
@@ -1830,7 +1830,7 @@ class SlaveSummaryPanel(LightWeightContainer):
|
|
1830
1830
|
oui5_field_template = "openui5/elems/field/SlaveSummaryElement.xml"
|
1831
1831
|
|
1832
1832
|
def __init__(self, lh, slave, name, **kw):
|
1833
|
-
super().__init__(lh, slave, name, slave.
|
1833
|
+
super().__init__(lh, slave, name, slave.get_slave_summary, **kw)
|
1834
1834
|
|
1835
1835
|
|
1836
1836
|
class StoryElement(LightWeightContainer):
|
lino/core/renderer.py
CHANGED
@@ -284,7 +284,7 @@ class HtmlRenderer(Renderer):
|
|
284
284
|
raise Exception("Both nosummary and display_mode were specified")
|
285
285
|
|
286
286
|
if display_mode == constants.DISPLAY_MODE_SUMMARY:
|
287
|
-
yield ar.actor.get_table_summary(ar
|
287
|
+
yield ar.actor.get_table_summary(ar)
|
288
288
|
return
|
289
289
|
|
290
290
|
if header_level is not None:
|
@@ -834,7 +834,7 @@ class TextRenderer(HtmlRenderer):
|
|
834
834
|
# yield "20240506 {}".format(ar)
|
835
835
|
if display_mode == constants.DISPLAY_MODE_SUMMARY:
|
836
836
|
s = to_rst(
|
837
|
-
ar.actor.get_table_summary(ar
|
837
|
+
ar.actor.get_table_summary(ar),
|
838
838
|
stripped=stripped,
|
839
839
|
)
|
840
840
|
if stripped:
|
lino/core/requests.py
CHANGED
@@ -24,6 +24,7 @@ from django.conf import settings
|
|
24
24
|
from django.utils.translation import gettext_lazy as _
|
25
25
|
from django.utils.translation import gettext
|
26
26
|
from django.utils.translation import get_language, activate
|
27
|
+
from django.utils.html import format_html
|
27
28
|
from django.utils import translation
|
28
29
|
from django.utils import timezone
|
29
30
|
from django.core.mail import send_mail
|
@@ -103,6 +104,16 @@ def mi2bp(master_instance, bp):
|
|
103
104
|
# self.master_instance.__class__, self.master_instance))
|
104
105
|
|
105
106
|
|
107
|
+
class PrintLogger(logging.Logger):
|
108
|
+
format = "%(message)s"
|
109
|
+
|
110
|
+
def __init__(self, level):
|
111
|
+
super().__init__("~", level)
|
112
|
+
h = logging.StreamHandler(stream=sys.stdout)
|
113
|
+
h.setFormatter(logging.Formatter(self.format))
|
114
|
+
self.addHandler(h)
|
115
|
+
# print("20231012", logging.getLevelName(self.level), self.__class__)
|
116
|
+
|
106
117
|
class StringLogger(logging.Logger):
|
107
118
|
# Instantiated by BaseRequest.capture_logger()
|
108
119
|
|
@@ -535,12 +546,26 @@ class BaseRequest:
|
|
535
546
|
try:
|
536
547
|
# We instantiate a temporary Logger object, which is not known by the
|
537
548
|
# root logger.
|
549
|
+
# self.logger = PrintLogger(level)
|
538
550
|
self.logger = StringLogger(old_logger, level)
|
539
551
|
# self.logger.parent =
|
540
552
|
yield self.logger
|
541
553
|
|
542
554
|
finally:
|
543
555
|
self.logger.streamer.flush()
|
556
|
+
# print(self.logger.getvalue())
|
557
|
+
self.logger = old_logger
|
558
|
+
|
559
|
+
@contextmanager
|
560
|
+
def print_logger(self, level=logging.INFO):
|
561
|
+
old_logger = self.logger
|
562
|
+
try:
|
563
|
+
# We instantiate a temporary Logger object, which is not known by the
|
564
|
+
# root logger.
|
565
|
+
self.logger = PrintLogger(level)
|
566
|
+
yield None
|
567
|
+
|
568
|
+
finally:
|
544
569
|
self.logger = old_logger
|
545
570
|
|
546
571
|
@contextmanager
|
@@ -820,22 +845,29 @@ class BaseRequest:
|
|
820
845
|
Given a tuple of primary keys, set :attr:`selected_rows` to a list
|
821
846
|
of corresponding database objects.
|
822
847
|
|
823
|
-
|
848
|
+
TODO: Explain why the special primary keys -99998 and -99999 are
|
849
|
+
filtered out.
|
824
850
|
|
825
851
|
"""
|
826
852
|
# ~ print 20131003, selected_pks
|
827
853
|
self.selected_rows = []
|
828
|
-
|
829
|
-
#
|
830
|
-
#
|
831
|
-
|
854
|
+
|
855
|
+
# Until 20250212 we catched for ObjectDoesNotExist here in order to
|
856
|
+
# reformulate the message. This made #5924 (Menu "My invoicing plan"
|
857
|
+
# fails) difficult to diagnose.
|
858
|
+
if True:
|
832
859
|
for pk in selected_pks:
|
833
860
|
if pk and pk != "-99998" and pk != "-99999":
|
834
861
|
self.selected_rows.append(self.get_row_by_pk(pk))
|
835
|
-
|
836
|
-
|
837
|
-
|
838
|
-
|
862
|
+
else:
|
863
|
+
try:
|
864
|
+
for pk in selected_pks:
|
865
|
+
if pk and pk != "-99998" and pk != "-99999":
|
866
|
+
self.selected_rows.append(self.get_row_by_pk(pk))
|
867
|
+
except ObjectDoesNotExist:
|
868
|
+
# raise exceptions.BadRequest(
|
869
|
+
raise ObjectDoesNotExist(
|
870
|
+
f"No row with primary key {repr(pk)} in {self.actor}") from None
|
839
871
|
# self.selected_rows = filter(lambda x: x, self.selected_rows)
|
840
872
|
# note: ticket #523 was because the GET contained an empty pk ("&sr=")
|
841
873
|
|
@@ -1631,7 +1663,8 @@ class BaseRequest:
|
|
1631
1663
|
# print("20190703", self.actor, self.actor.default_action)
|
1632
1664
|
sar = self.spawn_request(actor=self.actor)
|
1633
1665
|
list_title = tostring(sar.href_to_request(sar, list_title, icon_name=None))
|
1634
|
-
return list_title + " » " + self.get_detail_title(elem)
|
1666
|
+
# return list_title + " » " + self.get_detail_title(elem)
|
1667
|
+
return format_html("{} » {}", list_title, self.get_detail_title(elem))
|
1635
1668
|
|
1636
1669
|
def form2obj_and_save(ar, data, elem, is_new):
|
1637
1670
|
"""
|
@@ -1885,10 +1918,13 @@ class ActionRequest(BaseRequest):
|
|
1885
1918
|
self.set_selected_pks(*selected_pks)
|
1886
1919
|
|
1887
1920
|
def __str__(self):
|
1888
|
-
return "{
|
1921
|
+
return f"{self.__class__.__name__} for {self.bound_action}"
|
1889
1922
|
|
1890
|
-
def
|
1891
|
-
|
1923
|
+
# def __str__(self):
|
1924
|
+
# return "{0} {1}".format(self.__class__.__name__, self.bound_action)
|
1925
|
+
#
|
1926
|
+
# def __repr__(self):
|
1927
|
+
# return "{0} {1}".format(self.__class__.__name__, self.bound_action)
|
1892
1928
|
|
1893
1929
|
def gen_insert_button(
|
1894
1930
|
self, target=None, button_attrs=dict(style="float: right;"), **values
|
lino/core/site.py
CHANGED
@@ -356,7 +356,7 @@ class Site(object):
|
|
356
356
|
verbose_client_info_message = False
|
357
357
|
|
358
358
|
stopsignal = "SIGTERM"
|
359
|
-
help_url = "
|
359
|
+
help_url = "https://www.lino-framework.org"
|
360
360
|
|
361
361
|
help_email = "users@lino-framework.org"
|
362
362
|
|
@@ -395,11 +395,11 @@ class Site(object):
|
|
395
395
|
|
396
396
|
date_format_strftime = "%d.%m.%Y"
|
397
397
|
|
398
|
-
date_format_regex = "/^[0123]?\d\.[01]?\d\.-?\d+$/"
|
398
|
+
date_format_regex = r"/^[0123]?\d\.[01]?\d\.-?\d+$/"
|
399
399
|
|
400
400
|
datetime_format_strftime = "%Y-%m-%dT%H:%M:%S"
|
401
401
|
|
402
|
-
datetime_format_extjs = "Y-m-d\TH:i:s"
|
402
|
+
datetime_format_extjs = r"Y-m-d\TH:i:s"
|
403
403
|
|
404
404
|
quick_startup = False
|
405
405
|
|
@@ -973,6 +973,7 @@ class Site(object):
|
|
973
973
|
|
974
974
|
def install_settings(self):
|
975
975
|
assert not self.help_url.endswith("/")
|
976
|
+
assert not self.server_url.endswith("/")
|
976
977
|
|
977
978
|
for p in self.installed_plugins:
|
978
979
|
p.install_django_settings(self)
|
@@ -2136,13 +2137,12 @@ class Site(object):
|
|
2136
2137
|
|
2137
2138
|
# yield "lino.modlib.lino_startup"
|
2138
2139
|
|
2139
|
-
# server_url = None
|
2140
2140
|
copyright_name = None
|
2141
2141
|
"""Name of copyright holder of the site's content."""
|
2142
2142
|
|
2143
2143
|
copyright_url = None
|
2144
2144
|
|
2145
|
-
server_url = "http://127.0.0.1:8000
|
2145
|
+
server_url = "http://127.0.0.1:8000"
|
2146
2146
|
"""The "official" URL used by "normal" users when accessing this Lino
|
2147
2147
|
site.
|
2148
2148
|
|
lino/help_texts.py
CHANGED
@@ -374,13 +374,14 @@ help_texts = {
|
|
374
374
|
'lino.modlib.linod.Procedures' : _("""The choicelist of background procedures available in this application."""),
|
375
375
|
'lino.modlib.linod.LogLevels' : _("""A choicelist of logging levels available in this application."""),
|
376
376
|
'lino.modlib.linod.SystemTask' : _("""Django model used to represent a background task."""),
|
377
|
-
'lino.modlib.linod.SystemTask.procedure' : _("""Pointer to an instance of Procedure."""),
|
378
377
|
'lino.modlib.linod.SystemTask.start_datetime' : _("""Tells at what time exactly this job started."""),
|
379
378
|
'lino.modlib.linod.SystemTask.message' : _("""Stores information about the job, mostly logs."""),
|
380
379
|
'lino.modlib.linod.SystemTask.disabled' : _("""Tells whether the task should be ignored."""),
|
381
380
|
'lino.modlib.linod.SystemTask.log_level' : _("""The logging level to apply when running this task."""),
|
382
381
|
'lino.modlib.linod.SystemTask.run' : _("""Performs a routine job."""),
|
383
382
|
'lino.modlib.linod.SystemTasks' : _("""The default actor for the SystemTask model."""),
|
383
|
+
'lino.modlib.linod.Runnable' : _("""Model mixin used by SystemTask and other models."""),
|
384
|
+
'lino.modlib.linod.Runnable.procedure' : _("""The background procedure to run in this task."""),
|
384
385
|
'lino.modlib.periods.StoredYear' : _("""The Django model used to store a fiscal year."""),
|
385
386
|
'lino.modlib.periods.StoredPeriod' : _("""The Django model used to store an accounting period."""),
|
386
387
|
'lino.modlib.periods.StoredYears' : _("""The fiscal years defined in this database."""),
|
@@ -511,14 +512,14 @@ help_texts = {
|
|
511
512
|
'lino.modlib.comments.AllComments' : _("""Show all comments."""),
|
512
513
|
'lino.modlib.comments.MyComments' : _("""Show the comments posted by the current user."""),
|
513
514
|
'lino.modlib.comments.RecentComments' : _("""Show the most recent comments that have been posted on this site."""),
|
514
|
-
'lino.modlib.comments.CommentsByRFC' : _("""Shows the comments
|
515
|
+
'lino.modlib.comments.CommentsByRFC' : _("""Shows the comments about a given database row."""),
|
515
516
|
'lino.modlib.comments.CommentEvents' : _("""The choicelist with selections for Comments.observed_event."""),
|
516
517
|
'lino.modlib.comments.Emotions' : _("""The list of available values for the Comment.emotion field."""),
|
517
518
|
'lino.modlib.comments.CommentType' : _("""The CommentType model is not being used in production, one day we will probably remove it."""),
|
518
519
|
'lino.modlib.comments.CommentTypes' : _("""The table with all existing comment types."""),
|
519
520
|
'lino.modlib.comments.Commentable' : _("""Mixin for models that are commentable, i.e. the rows of which can become discussion topic of comments."""),
|
520
521
|
'lino.modlib.comments.Commentable.add_comments_filter' : _("""Add filters to the given queryset of comments, requested by the given user."""),
|
521
|
-
'lino.modlib.comments.Commentable.get_rfc_description' : _("""Return a HTML formatted string with the description of this Commentable as it should be displayed by the slave summary of
|
522
|
+
'lino.modlib.comments.Commentable.get_rfc_description' : _("""Return a HTML formatted string with the description of this Commentable as it should be displayed by the slave summary of CommentsByRFC."""),
|
522
523
|
'lino.modlib.comments.Commentable.on_commented' : _("""This is automatically called when a comment has been created or modified."""),
|
523
524
|
'lino.modlib.files.Volume' : _("""The Django model representing a file volume."""),
|
524
525
|
'lino.modlib.files.Volume.id' : _("""The primary key used to point to this volume from a database object."""),
|
@@ -552,6 +553,10 @@ help_texts = {
|
|
552
553
|
'lino.modlib.gkfs.GenericForeignKey' : _("""Add verbose_name and help_text to Django’s GFK."""),
|
553
554
|
'lino.modlib.gkfs.GenericForeignKeyIdField' : _("""Use this instead of models.PositiveIntegerField for fields that are part of a GFK and you want Lino to render them using a Combobox."""),
|
554
555
|
'lino.modlib.jinja.JinjaBuildMethod' : _("""Inherits from lino.modlib.printing.DjangoBuildMethod."""),
|
556
|
+
'lino.modlib.jinja.XMLMaker' : _("""Usage example in /topics/xml"""),
|
557
|
+
'lino.modlib.jinja.XMLMaker.xml_file_name' : _("""The name of the XML file to generate. This file will be overwritten without asking. The name formatted with one name self in the context."""),
|
558
|
+
'lino.modlib.jinja.XMLMaker.xml_file_template' : _("""The name of a Jinja template to render for generating the XML content."""),
|
559
|
+
'lino.modlib.jinja.XMLMaker.xml_validator_file' : _("""The name of a “validator” to use for validating the XML content."""),
|
555
560
|
'lino.modlib.memo.Previewable' : _("""Adds three rich text fields (lino.core.fields.RichTextField):"""),
|
556
561
|
'lino.modlib.memo.Previewable.body' : _("""An editable text body."""),
|
557
562
|
'lino.modlib.memo.Previewable.body_short_preview' : _("""A read-only preview of the first paragraph of body."""),
|
lino/modlib/comments/models.py
CHANGED
@@ -290,7 +290,7 @@ class Comment(
|
|
290
290
|
def full_clean(self):
|
291
291
|
super().full_clean()
|
292
292
|
if self.reply_to_id and not self.owner_id:
|
293
|
-
# added only 2023-11-19
|
293
|
+
# added only 2023-11-19, that's why we have CommentChecker
|
294
294
|
self.owner = self.reply_to.owner
|
295
295
|
# self.owner.setup_comment(self)
|
296
296
|
|
lino/modlib/comments/ui.py
CHANGED
@@ -206,7 +206,7 @@ class CommentsByType(CommentsByX):
|
|
206
206
|
master_key = "comment_type"
|
207
207
|
column_names = "body created user *"
|
208
208
|
|
209
|
-
|
209
|
+
# TODO: rename CommentsByRFC to CommentsByOwner
|
210
210
|
class CommentsByRFC(CommentsByX):
|
211
211
|
master_key = "owner"
|
212
212
|
details_of_master_template = _("%(details)s about %(master)s")
|
lino/modlib/extjs/__init__.py
CHANGED
@@ -250,12 +250,12 @@ class Plugin(Plugin):
|
|
250
250
|
),
|
251
251
|
url(
|
252
252
|
rx + r"choices/(?P<app_label>\w+)/(?P<rptname>\w+)/"
|
253
|
-
"(?P<fldname>\w+)$",
|
253
|
+
r"(?P<fldname>\w+)$",
|
254
254
|
views.Choices.as_view(),
|
255
255
|
),
|
256
256
|
url(
|
257
257
|
rx + r"apchoices/(?P<app_label>\w+)/(?P<actor>\w+)/"
|
258
|
-
"(?P<an>\w+)/(?P<field>\w+)$",
|
258
|
+
r"(?P<an>\w+)/(?P<field>\w+)$",
|
259
259
|
views.ActionParamChoices.as_view(),
|
260
260
|
),
|
261
261
|
# the thread_id can be a negative number:
|