lino 25.6.1__py3-none-any.whl → 25.7.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- lino/__init__.py +1 -1
- lino/api/doctest.py +21 -0
- lino/core/actions.py +59 -25
- lino/core/actors.py +38 -16
- lino/core/boundaction.py +16 -0
- lino/core/choicelists.py +7 -7
- lino/core/constants.py +3 -0
- lino/core/dashboard.py +1 -0
- lino/core/dbtables.py +1 -1
- lino/core/elems.py +38 -13
- lino/core/fields.py +20 -11
- lino/core/kernel.py +8 -0
- lino/core/layouts.py +6 -2
- lino/core/menus.py +3 -6
- lino/core/model.py +5 -4
- lino/core/renderer.py +14 -5
- lino/core/requests.py +8 -7
- lino/core/signals.py +1 -0
- lino/core/site.py +48 -28
- lino/core/store.py +4 -2
- lino/core/tables.py +23 -10
- lino/core/utils.py +4 -1
- lino/core/workflows.py +2 -1
- lino/help_texts.py +1 -2
- lino/management/commands/prep.py +2 -2
- lino/management/commands/show.py +8 -10
- lino/mixins/__init__.py +14 -13
- lino/mixins/periods.py +2 -0
- lino/mixins/sequenced.py +1 -1
- lino/modlib/about/models.py +4 -3
- lino/modlib/checkdata/__init__.py +42 -36
- lino/modlib/checkdata/choicelists.py +9 -1
- lino/modlib/checkdata/fixtures/checkdata.py +4 -2
- lino/modlib/checkdata/models.py +9 -2
- lino/modlib/comments/models.py +4 -3
- lino/modlib/extjs/ext_renderer.py +4 -4
- lino/modlib/extjs/views.py +8 -2
- lino/modlib/gfks/fields.py +1 -1
- lino/modlib/help/__init__.py +3 -3
- lino/modlib/help/config/makehelp/conf.tpl.py +2 -2
- lino/modlib/help/fixtures/demo2.py +6 -1
- lino/modlib/help/management/commands/makehelp.py +4 -1
- lino/modlib/help/models.py +2 -1
- lino/modlib/help/utils.py +12 -6
- lino/modlib/linod/choicelists.py +57 -4
- lino/modlib/linod/fixtures/{linod.py → checkdata.py} +3 -13
- lino/modlib/linod/management/commands/linod.py +0 -13
- lino/modlib/linod/mixins.py +8 -0
- lino/modlib/linod/models.py +29 -30
- lino/modlib/memo/__init__.py +7 -7
- lino/modlib/memo/management/__init__,py +0 -0
- lino/modlib/memo/management/commands/__init__.py +0 -0
- lino/modlib/memo/management/commands/removeurls.py +67 -0
- lino/modlib/memo/mixins.py +1 -9
- lino/modlib/memo/parser.py +1 -1
- lino/modlib/notify/config/notify/summary.eml +5 -2
- lino/modlib/notify/fixtures/demo2.py +5 -6
- lino/modlib/notify/models.py +9 -10
- lino/modlib/periods/__init__.py +11 -8
- lino/modlib/periods/choicelists.py +16 -10
- lino/modlib/periods/models.py +45 -45
- lino/modlib/summaries/fixtures/checksummaries.py +4 -2
- lino/modlib/system/models.py +17 -18
- lino/modlib/uploads/fixtures/demo.py +9 -3
- lino/modlib/uploads/mixins.py +5 -2
- lino/modlib/uploads/models.py +15 -9
- lino/modlib/uploads/utils.py +4 -1
- lino/modlib/users/__init__.py +59 -18
- lino/modlib/users/actions.py +24 -20
- lino/modlib/users/fixtures/demo_users.py +2 -35
- lino/modlib/users/mixins.py +3 -4
- lino/modlib/users/models.py +53 -13
- lino/modlib/users/ui.py +30 -16
- lino/modlib/users/utils.py +5 -6
- lino/projects/std/settings.py +1 -1
- lino/sphinxcontrib/logo/templates/footer.html +1 -0
- lino/utils/ajax.py +1 -1
- lino/utils/cycler.py +5 -0
- lino/utils/dbhash.py +4 -9
- lino/utils/dpy.py +2 -2
- lino/utils/format_date.py +4 -3
- lino/utils/html.py +13 -5
- lino/utils/jsgen.py +1 -1
- lino/utils/quantities.py +8 -0
- lino/utils/soup.py +75 -106
- {lino-25.6.1.dist-info → lino-25.7.0.dist-info}/METADATA +1 -1
- {lino-25.6.1.dist-info → lino-25.7.0.dist-info}/RECORD +90 -87
- {lino-25.6.1.dist-info → lino-25.7.0.dist-info}/WHEEL +0 -0
- {lino-25.6.1.dist-info → lino-25.7.0.dist-info}/licenses/AUTHORS.rst +0 -0
- {lino-25.6.1.dist-info → lino-25.7.0.dist-info}/licenses/COPYING +0 -0
lino/core/fields.py
CHANGED
@@ -22,6 +22,8 @@ from django.db.models.fields import NOT_PROVIDED
|
|
22
22
|
from django.utils.functional import cached_property
|
23
23
|
|
24
24
|
from lino import logger
|
25
|
+
if settings.SITE.is_installed("contenttypes"):
|
26
|
+
from lino.modlib.gfks.fields import GenericForeignKey
|
25
27
|
from lino.utils.html import E, forcetext, tostring, SafeString, escape, mark_safe
|
26
28
|
|
27
29
|
from lino.core.utils import (
|
@@ -147,8 +149,9 @@ class MonthField(models.DateField):
|
|
147
149
|
A DateField that uses a MonthPicker instead of a normal DateWidget
|
148
150
|
"""
|
149
151
|
|
150
|
-
|
151
|
-
|
152
|
+
pass
|
153
|
+
# def __init__(self, *args, **kw):
|
154
|
+
# models.DateField.__init__(self, *args, **kw)
|
152
155
|
|
153
156
|
|
154
157
|
# def PriceField(*args, **kwargs):
|
@@ -164,7 +167,7 @@ class MonthField(models.DateField):
|
|
164
167
|
class PriceField(models.DecimalField):
|
165
168
|
"""
|
166
169
|
A thin wrapper around Django's `DecimalField
|
167
|
-
<https://docs.djangoproject.com/en/5.
|
170
|
+
<https://docs.djangoproject.com/en/5.2/ref/models/fields/#decimalfield>`_
|
168
171
|
with price-like default values for `decimal_places`, `max_length` and
|
169
172
|
`max_digits`.
|
170
173
|
"""
|
@@ -300,6 +303,7 @@ class FakeField(object):
|
|
300
303
|
max_length = None
|
301
304
|
generated = False
|
302
305
|
choicelist = None # avoid 'DummyField' object has no attribute 'choicelist'
|
306
|
+
hide_unless_explicit = True
|
303
307
|
|
304
308
|
wildcard_data_elem = False
|
305
309
|
"""Whether to consider this field as wildcard data element.
|
@@ -471,7 +475,7 @@ class DisplayField(FakeField):
|
|
471
475
|
|
472
476
|
def __init__(self, verbose_name=None, **kwargs):
|
473
477
|
self.verbose_name = verbose_name
|
474
|
-
super(
|
478
|
+
super().__init__(**kwargs)
|
475
479
|
|
476
480
|
# the following dummy methods are never called but needed when
|
477
481
|
# using a DisplayField as return_type of a VirtualField
|
@@ -982,7 +986,7 @@ class QuantityField(models.CharField):
|
|
982
986
|
def to_python(self, value):
|
983
987
|
"""
|
984
988
|
Excerpt from `Django docs
|
985
|
-
<https://docs.djangoproject.com/en/5.
|
989
|
+
<https://docs.djangoproject.com/en/5.2/howto/custom-model-fields/#converting-values-to-python-objects>`__:
|
986
990
|
|
987
991
|
As a general rule, :meth:`to_python` should deal gracefully with
|
988
992
|
any of the following arguments:
|
@@ -1433,7 +1437,7 @@ class TableRow(object):
|
|
1433
1437
|
return escape(str(self))
|
1434
1438
|
return self.as_paragraph(ar)
|
1435
1439
|
|
1436
|
-
@displayfield(_("Select multiple rows"))
|
1440
|
+
@displayfield(_("Select multiple rows"), wildcard_data_elem=True)
|
1437
1441
|
def rowselect(self, ar):
|
1438
1442
|
"""A place holder for primereact Datatable column "Selection Column\""""
|
1439
1443
|
return None
|
@@ -1468,8 +1472,9 @@ class TableRow(object):
|
|
1468
1472
|
return escape(str(self))
|
1469
1473
|
return tostring(self.as_summary_item(ar, **kwargs))
|
1470
1474
|
|
1471
|
-
def as_tile(self, ar, **kwargs):
|
1472
|
-
|
1475
|
+
def as_tile(self, ar, prev, **kwargs):
|
1476
|
+
s = self.as_paragraph(ar, **kwargs)
|
1477
|
+
return constants.TILE_TEMPLATE.format(chunk=s)
|
1473
1478
|
|
1474
1479
|
def as_story_item(self, ar, **kwargs):
|
1475
1480
|
kwargs.update(display_mode=constants.DISPLAY_MODE_STORY)
|
@@ -1535,9 +1540,13 @@ def wildcard_data_elems(model):
|
|
1535
1540
|
yield f
|
1536
1541
|
for f in meta.many_to_many:
|
1537
1542
|
yield f
|
1543
|
+
|
1544
|
+
# private_fields are available at meta.fields on which we iterate
|
1545
|
+
# over just above, but, some field.is_relation are filtered out.
|
1538
1546
|
for f in meta.private_fields:
|
1539
|
-
if
|
1540
|
-
|
1547
|
+
if settings.SITE.is_installed("contenttypes"):
|
1548
|
+
if isinstance(f, GenericForeignKey):
|
1549
|
+
yield f
|
1541
1550
|
# todo: for slave in self.report.slaves
|
1542
1551
|
|
1543
1552
|
|
@@ -1557,7 +1566,7 @@ def pointer_factory(cls, othermodel, *args, **kw):
|
|
1557
1566
|
case. This is useful when designing reusable models.
|
1558
1567
|
|
1559
1568
|
- Explicitly sets the default value for `on_delete
|
1560
|
-
<https://docs.djangoproject.com/en/5.
|
1569
|
+
<https://docs.djangoproject.com/en/5.2/ref/models/fields/#django.db.models.ForeignKey.on_delete>`__
|
1561
1570
|
to ``CASCADE`` (as required by Django 2).
|
1562
1571
|
|
1563
1572
|
"""
|
lino/core/kernel.py
CHANGED
@@ -88,6 +88,10 @@ from .utils import resolve_fields_list
|
|
88
88
|
from lino.core.fields import set_default_verbose_name
|
89
89
|
# from lino.core.requests import ActorRequest
|
90
90
|
|
91
|
+
from lino.utils import dbhash
|
92
|
+
from lino.core.signals import database_prepared
|
93
|
+
database_prepared.connect(dbhash.mark_virgin)
|
94
|
+
|
91
95
|
startup_rlock = threading.RLock() # Lock() or RLock()?
|
92
96
|
|
93
97
|
GFK_TARGETS = (models.AutoField, models.IntegerField)
|
@@ -683,6 +687,9 @@ class Kernel(object):
|
|
683
687
|
# virtual fields in LightWeightContainer
|
684
688
|
for res in actors.actors_list:
|
685
689
|
for ba in res.get_actions():
|
690
|
+
# ba._started = True
|
691
|
+
if ba.help_text is None:
|
692
|
+
ba.help_text = ba.action.get_help_text(ba)
|
686
693
|
if ba.action.params_layout is not None:
|
687
694
|
ba.action.params_layout.get_layout_handle()
|
688
695
|
if ba.action.is_window_action():
|
@@ -895,6 +902,7 @@ class Kernel(object):
|
|
895
902
|
except Warning as e:
|
896
903
|
ar.error(e, alert=True)
|
897
904
|
except Exception as e:
|
905
|
+
# raise # A plain traceback can be easier to debug.
|
898
906
|
# print(f"20240911 oops {repr(e)}")
|
899
907
|
msg = ar.ah.actor.error2str(e)
|
900
908
|
ar.error(msg, alert=True)
|
lino/core/layouts.py
CHANGED
@@ -9,6 +9,7 @@ import re
|
|
9
9
|
|
10
10
|
from django.conf import settings
|
11
11
|
from django.utils.translation import gettext_lazy as _
|
12
|
+
from django.utils.functional import Promise
|
12
13
|
from django.db.models.fields import NOT_PROVIDED
|
13
14
|
from django.db.models.fields.related import ForeignObject
|
14
15
|
# from django.contrib.contenttypes.fields import GenericRelation
|
@@ -118,7 +119,9 @@ class LayoutHandle:
|
|
118
119
|
# if 'label_align' in kwargs:
|
119
120
|
# print("20170921 desc2elem", elemname, desc, kwargs)
|
120
121
|
|
121
|
-
if
|
122
|
+
if isinstance(desc, Promise):
|
123
|
+
desc = str(desc)
|
124
|
+
elif not isinstance(desc, str):
|
122
125
|
raise Exception("{} is {} (must be a string)".format(elemname, desc))
|
123
126
|
|
124
127
|
# flatten continued lines:
|
@@ -171,7 +174,8 @@ class LayoutHandle:
|
|
171
174
|
if de.name not in explicit_specs:
|
172
175
|
if self.use_as_wildcard(de):
|
173
176
|
wildcard_names.append(de.name)
|
174
|
-
if len(explicit_specs) or
|
177
|
+
if len(explicit_specs) or (
|
178
|
+
isinstance(de, fields.FakeField) and de.hide_unless_explicit):
|
175
179
|
self.hidden_elements.add(de.name)
|
176
180
|
wildcard_str = self.layout.join_str.join(wildcard_names)
|
177
181
|
desc = desc.replace("*", wildcard_str)
|
lino/core/menus.py
CHANGED
@@ -179,15 +179,12 @@ def create_item(unused, spec, action=None, help_text=None, **kw):
|
|
179
179
|
if action_spec is not None:
|
180
180
|
kw.update(action=resolve_action(action_spec))
|
181
181
|
else:
|
182
|
-
|
182
|
+
ba = resolve_action(spec, action)
|
183
183
|
# if str(spec).startswith("webshop"):
|
184
184
|
# print("20210319", spec, action, a)
|
185
|
-
kw.update(action=
|
185
|
+
kw.update(action=ba)
|
186
186
|
if help_text is None:
|
187
|
-
|
188
|
-
help_text = a.actor.help_text or a.action.help_text
|
189
|
-
else:
|
190
|
-
help_text = a.action.help_text
|
187
|
+
ba.get_help_text()
|
191
188
|
if help_text is not None:
|
192
189
|
kw.update(help_text=help_text)
|
193
190
|
return MenuItem(**kw)
|
lino/core/model.py
CHANGED
@@ -7,9 +7,8 @@ See :doc:`/dev/models`, :doc:`/dev/delete`, :doc:`/dev/disable`,
|
|
7
7
|
:doc:`/dev/hide`, :doc:`/dev/format`
|
8
8
|
|
9
9
|
"""
|
10
|
-
# from bs4 import BeautifulSoup
|
11
|
-
from lino import logger
|
12
10
|
import copy
|
11
|
+
# from bs4 import BeautifulSoup
|
13
12
|
|
14
13
|
from django.db import models
|
15
14
|
from django.conf import settings
|
@@ -19,7 +18,8 @@ from django.utils.translation import gettext_lazy as _
|
|
19
18
|
from django.db.models.signals import pre_delete
|
20
19
|
from django.utils.text import format_lazy
|
21
20
|
|
22
|
-
from lino
|
21
|
+
from lino import logger
|
22
|
+
from lino.utils.html import E, tostring, join_elems
|
23
23
|
from lino.utils.soup import sanitize
|
24
24
|
|
25
25
|
from lino.core import fields
|
@@ -29,6 +29,7 @@ from lino.core import actions
|
|
29
29
|
from lino.core import inject
|
30
30
|
|
31
31
|
from lino.core.atomizer import make_remote_field
|
32
|
+
from lino.core.exceptions import ChangedAPI
|
32
33
|
from .fields import RichTextField, displayfield
|
33
34
|
from .utils import error2str
|
34
35
|
from .utils import obj2str
|
@@ -1008,7 +1009,7 @@ def pre_delete_handler(sender, instance=None, **kw):
|
|
1008
1009
|
|
1009
1010
|
It seems that Django deletes *generic related objects* only if
|
1010
1011
|
the object being deleted has a `GenericRelation
|
1011
|
-
<https://docs.djangoproject.com/en/5.
|
1012
|
+
<https://docs.djangoproject.com/en/5.2/ref/contrib/contenttypes/#django.contrib.contenttypes.fields.GenericRelation>`_
|
1012
1013
|
field (according to `Why won't my GenericForeignKey cascade
|
1013
1014
|
when deleting?
|
1014
1015
|
<https://stackoverflow.com/questions/6803018/why-wont-my-genericforeignkey-cascade-when-deleting>`_).
|
lino/core/renderer.py
CHANGED
@@ -441,7 +441,7 @@ class HtmlRenderer(Renderer):
|
|
441
441
|
url = self.js2url(self.action_call(ar, ba, status))
|
442
442
|
# ~ logger.info('20121002 window_action_button %s %r',a,unicode(label))
|
443
443
|
return self.href_button_action(
|
444
|
-
ba, url, str(label), title or ba.
|
444
|
+
ba, url, str(label), title or ba.get_help_text(), **kw
|
445
445
|
)
|
446
446
|
|
447
447
|
def quick_add_buttons(self, ar):
|
@@ -543,7 +543,7 @@ class HtmlRenderer(Renderer):
|
|
543
543
|
js = self.ar2js(ar, obj, **ar._status)
|
544
544
|
uri = self.js2url(js)
|
545
545
|
return self.href_button_action(
|
546
|
-
ba, uri, label, title or ba.
|
546
|
+
ba, uri, label, title or ba.get_help_text(), **kwargs
|
547
547
|
)
|
548
548
|
|
549
549
|
def menu_item_button(self, ar, mi, label=None, icon_name=None, **kwargs):
|
@@ -607,7 +607,7 @@ class HtmlRenderer(Renderer):
|
|
607
607
|
js = self.action_call_on_instance(obj, ar, ba, request_kwargs)
|
608
608
|
uri = self.js2url(js)
|
609
609
|
return self.href_button_action(
|
610
|
-
ba, uri, label, title or ba.
|
610
|
+
ba, uri, label, title or ba.get_help_text(), **button_attrs
|
611
611
|
)
|
612
612
|
|
613
613
|
def row_action_button_ar(
|
@@ -622,7 +622,7 @@ class HtmlRenderer(Renderer):
|
|
622
622
|
js = self.action_call_on_instance(obj, ar, ba)
|
623
623
|
uri = self.js2url(js)
|
624
624
|
return self.href_button_action(
|
625
|
-
ba, uri, label, title or ba.
|
625
|
+
ba, uri, label, title or ba.get_help_text(), **kw
|
626
626
|
)
|
627
627
|
|
628
628
|
def show_story(self, ar, story, stripped=True, **kwargs):
|
@@ -859,7 +859,7 @@ class TextRenderer(HtmlRenderer):
|
|
859
859
|
if cls.insert_action is not None and cls.editable:
|
860
860
|
ir = cls.insert_action.request_from(ar)
|
861
861
|
if ir.get_permission():
|
862
|
-
items.append("(+) {}".format(cls.insert_action.
|
862
|
+
items.append("(+) {}".format(cls.insert_action.get_help_text()))
|
863
863
|
# for i, obj in enumerate(ar.data_iterator):
|
864
864
|
# if i == cls.preview_limit:
|
865
865
|
# break
|
@@ -874,6 +874,15 @@ class TextRenderer(HtmlRenderer):
|
|
874
874
|
yield rstgen.ul(items).strip()
|
875
875
|
return
|
876
876
|
|
877
|
+
if display_mode == constants.DISPLAY_MODE_TILES:
|
878
|
+
prev = None
|
879
|
+
for obj in ar.sliced_data_iterator:
|
880
|
+
txt = obj.as_tile(ar, prev)
|
881
|
+
txt = html2text(txt)
|
882
|
+
yield txt.strip()
|
883
|
+
prev = obj
|
884
|
+
return
|
885
|
+
|
877
886
|
# At this point, display_mode is one of story, grid or html.
|
878
887
|
|
879
888
|
if header_level is not None:
|
lino/core/requests.py
CHANGED
@@ -1562,6 +1562,7 @@ class BaseRequest:
|
|
1562
1562
|
# assert iselement(btn)
|
1563
1563
|
buttons.append(btn)
|
1564
1564
|
# print("20181106", cls, self.bound_action, buttons)
|
1565
|
+
# 20250713 buttons.append(self.open_in_own_window_button())
|
1565
1566
|
return buttons
|
1566
1567
|
# if len(buttons) == 0:
|
1567
1568
|
# return None
|
@@ -1576,6 +1577,13 @@ class BaseRequest:
|
|
1576
1577
|
# raise Exception("20230331 {}".format(self.subst_user))
|
1577
1578
|
return self.renderer.ar2button(self, *args, **kwargs)
|
1578
1579
|
|
1580
|
+
def as_button(self, *args, **kw):
|
1581
|
+
"""Return a button which when activated executes (a copy of)
|
1582
|
+
this request.
|
1583
|
+
|
1584
|
+
"""
|
1585
|
+
return self.renderer.action_button(None, self, self.bound_action, *args, **kw)
|
1586
|
+
|
1579
1587
|
def instance_action_button(self, ia, *args, **kwargs):
|
1580
1588
|
"""Return an HTML element with a button that would run the given
|
1581
1589
|
:class:`InstanceAction <lino.core.requests.InstanceAction>`
|
@@ -1607,13 +1615,6 @@ class BaseRequest:
|
|
1607
1615
|
def get_main_card(self):
|
1608
1616
|
return self.actor.get_main_card(self)
|
1609
1617
|
|
1610
|
-
def as_button(self, *args, **kw):
|
1611
|
-
"""Return a button which when activated executes (a copy of)
|
1612
|
-
this request.
|
1613
|
-
|
1614
|
-
"""
|
1615
|
-
return self.renderer.action_button(None, self, self.bound_action, *args, **kw)
|
1616
|
-
|
1617
1618
|
def elem2rec1(ar, rh, elem, fields=None, **rec):
|
1618
1619
|
rec.update(data=rh.store.row2dict(ar, elem, fields))
|
1619
1620
|
return rec
|
lino/core/signals.py
CHANGED
lino/core/site.py
CHANGED
@@ -3,9 +3,6 @@
|
|
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 importlib.util import find_spec
|
7
|
-
from importlib import import_module
|
8
|
-
from pathlib import Path
|
9
6
|
import os
|
10
7
|
import re
|
11
8
|
import sys
|
@@ -20,6 +17,10 @@ import logging
|
|
20
17
|
from logging.handlers import SocketHandler
|
21
18
|
import time
|
22
19
|
import rstgen
|
20
|
+
from importlib.util import find_spec
|
21
|
+
from importlib import import_module
|
22
|
+
from pathlib import Path
|
23
|
+
from asgiref.sync import sync_to_async
|
23
24
|
from rstgen.confparser import ConfigParser
|
24
25
|
from django.apps import apps
|
25
26
|
from django.utils import timezone
|
@@ -264,6 +265,7 @@ class Site(object):
|
|
264
265
|
never_build_site_cache = False
|
265
266
|
keep_erroneous_cache_files = False
|
266
267
|
use_java = True
|
268
|
+
use_systemd = False
|
267
269
|
use_silk_icons = False
|
268
270
|
use_new_unicode_symbols = False
|
269
271
|
use_experimental_features = False
|
@@ -367,8 +369,8 @@ class Site(object):
|
|
367
369
|
|
368
370
|
csv_params = dict()
|
369
371
|
|
370
|
-
# attributes documented in book/docs/
|
371
|
-
_history_aware_logging =
|
372
|
+
# attributes documented in book/docs/topics/logging.rst:
|
373
|
+
_history_aware_logging = False
|
372
374
|
log_each_action_request = False
|
373
375
|
default_loglevel = "INFO"
|
374
376
|
logger_filename = "lino.log"
|
@@ -667,15 +669,19 @@ class Site(object):
|
|
667
669
|
"encoding": "UTF-8",
|
668
670
|
"formatter": "verbose",
|
669
671
|
}
|
670
|
-
|
671
|
-
|
672
|
-
|
673
|
-
|
674
|
-
|
675
|
-
|
676
|
-
|
677
|
-
|
678
|
-
|
672
|
+
elif self.use_systemd:
|
673
|
+
handlers["file"] = {
|
674
|
+
"class": "systemd.journal.JournalHandler",
|
675
|
+
"SYSLOG_IDENTIFIER": str(self.project_name),
|
676
|
+
}
|
677
|
+
# try:
|
678
|
+
# from systemd.journal import JournalHandler
|
679
|
+
# handlers["file"] = {
|
680
|
+
# "class": "systemd.journal.JournalHandler",
|
681
|
+
# "SYSLOG_IDENTIFIER": str(self.project_name),
|
682
|
+
# }
|
683
|
+
# except ImportError:
|
684
|
+
# pass
|
679
685
|
|
680
686
|
# when a file handler exists, we have the loggers use it even if this
|
681
687
|
# instance didn't create it:
|
@@ -952,6 +958,8 @@ class Site(object):
|
|
952
958
|
reqs.add(r)
|
953
959
|
if self.textfield_bleached:
|
954
960
|
reqs.add("beautifulsoup4")
|
961
|
+
if self.use_systemd:
|
962
|
+
reqs.add("systemd-python")
|
955
963
|
return sorted(reqs)
|
956
964
|
|
957
965
|
def setup_plugins(self):
|
@@ -1377,6 +1385,9 @@ class Site(object):
|
|
1377
1385
|
base = timezone.now().date()
|
1378
1386
|
return date_offset(base, *args, **kwargs)
|
1379
1387
|
|
1388
|
+
async def atoday(self, *args, **kwargs):
|
1389
|
+
return await sync_to_async(self.today)(*args, **kwargs)
|
1390
|
+
|
1380
1391
|
def now(self, *args, **kwargs):
|
1381
1392
|
t = self.today(*args, **kwargs)
|
1382
1393
|
now = timezone.now()
|
@@ -1587,7 +1598,7 @@ class Site(object):
|
|
1587
1598
|
for info in tuple(new_languages):
|
1588
1599
|
if "-" in info.django_code:
|
1589
1600
|
base, loc = info.django_code.split("-")
|
1590
|
-
if not
|
1601
|
+
if base not in self.language_dict:
|
1591
1602
|
self.language_dict[base] = info
|
1592
1603
|
|
1593
1604
|
# replace the complicated info by a simplified one
|
@@ -1825,11 +1836,9 @@ class Site(object):
|
|
1825
1836
|
# ~ return obj.id is not None and (obj.id > 10 and obj.id < 21)
|
1826
1837
|
|
1827
1838
|
def site_header(self):
|
1828
|
-
if self.is_installed("contacts")
|
1829
|
-
if self.
|
1830
|
-
return
|
1831
|
-
# ~ s = unicode(self.site_config.site_company) + " / " + s
|
1832
|
-
# ~ return ''
|
1839
|
+
if self.is_installed("contacts"):
|
1840
|
+
if (owner := self.plugins.contacts.site_owner) is not None:
|
1841
|
+
return owner.get_address("<br/>")
|
1833
1842
|
|
1834
1843
|
# def setup_main_menu(self):
|
1835
1844
|
# """
|
@@ -1843,11 +1852,22 @@ class Site(object):
|
|
1843
1852
|
for i in p.get_dashboard_items(user):
|
1844
1853
|
yield i
|
1845
1854
|
|
1855
|
+
@property
|
1856
|
+
def copyright_name(self):
|
1857
|
+
"""Name of copyright holder of the site's content."""
|
1858
|
+
if (owner := self.get_plugin_setting('contacts', 'site_owner')) is not None:
|
1859
|
+
# print("20230423", self.site_company)
|
1860
|
+
return str(owner)
|
1861
|
+
|
1862
|
+
@property
|
1863
|
+
def copyright_url(self):
|
1864
|
+
if (owner := self.get_plugin_setting('contacts', 'site_owner')) is not None:
|
1865
|
+
return owner.url
|
1866
|
+
|
1846
1867
|
@property
|
1847
1868
|
def site_config(self):
|
1848
1869
|
if "system" not in self.models:
|
1849
1870
|
return None
|
1850
|
-
|
1851
1871
|
return self.models.system.SiteConfig.get_site_config()
|
1852
1872
|
|
1853
1873
|
def get_config_value(self, name, default=None):
|
@@ -2081,6 +2101,9 @@ class Site(object):
|
|
2081
2101
|
def add_top_link_generator(self, func):
|
2082
2102
|
self._top_link_generator.append(func)
|
2083
2103
|
|
2104
|
+
def get_footer_html(self, ar):
|
2105
|
+
return mark_safe("<p>This is a new feature.</p>")
|
2106
|
+
|
2084
2107
|
def get_welcome_messages(self, ar):
|
2085
2108
|
for h in self._welcome_handlers:
|
2086
2109
|
for msg in h(ar):
|
@@ -2138,11 +2161,6 @@ class Site(object):
|
|
2138
2161
|
|
2139
2162
|
# yield "lino.modlib.lino_startup"
|
2140
2163
|
|
2141
|
-
copyright_name = None
|
2142
|
-
"""Name of copyright holder of the site's content."""
|
2143
|
-
|
2144
|
-
copyright_url = None
|
2145
|
-
|
2146
2164
|
server_url = "http://127.0.0.1:8000"
|
2147
2165
|
"""The "official" URL used by "normal" users when accessing this Lino
|
2148
2166
|
site.
|
@@ -2151,7 +2169,7 @@ class Site(object):
|
|
2151
2169
|
:mod:`lino.modlib.notify` to send notification emails)
|
2152
2170
|
|
2153
2171
|
Django has a `HttpRequest.build_absolute_uri()
|
2154
|
-
<https://docs.djangoproject.com/en/5.
|
2172
|
+
<https://docs.djangoproject.com/en/5.2/ref/request-response/#django.http.HttpRequest.build_absolute_uri>`__
|
2155
2173
|
method, but e.g. notification emails are sent via :manage:`linod` where no
|
2156
2174
|
HttpRequest exists. That's why we need to manually set :attr:`server_url`.
|
2157
2175
|
|
@@ -2236,7 +2254,9 @@ class Site(object):
|
|
2236
2254
|
yield E.span(*p)
|
2237
2255
|
|
2238
2256
|
def get_letter_date_text(self, today=None):
|
2239
|
-
|
2257
|
+
if self.is_installed("contacts"):
|
2258
|
+
if (sc := self.plugins.contacts.site_owner) is None:
|
2259
|
+
return
|
2240
2260
|
if today is None:
|
2241
2261
|
today = self.today()
|
2242
2262
|
from lino.utils.format_date import fdl
|
lino/core/store.py
CHANGED
@@ -689,7 +689,7 @@ class DisableEditingStoreField(SpecialStoreField):
|
|
689
689
|
|
690
690
|
class BooleanStoreField(StoreField):
|
691
691
|
"""A :class:`StoreField` for
|
692
|
-
`BooleanField <https://docs.djangoproject.com/en/5.
|
692
|
+
`BooleanField <https://docs.djangoproject.com/en/5.2/ref/models/fields/#booleanfield>`__.
|
693
693
|
|
694
694
|
"""
|
695
695
|
|
@@ -797,7 +797,7 @@ class IntegerStoreField(StoreField):
|
|
797
797
|
|
798
798
|
class AutoStoreField(StoreField):
|
799
799
|
"""A :class:`StoreField` for
|
800
|
-
`AutoField <https://docs.djangoproject.com/en/5.
|
800
|
+
`AutoField <https://docs.djangoproject.com/en/5.2/ref/models/fields/#autofield>`__
|
801
801
|
|
802
802
|
"""
|
803
803
|
|
@@ -1252,6 +1252,8 @@ class Store(BaseStore):
|
|
1252
1252
|
if isinstance(row, PhantomRow):
|
1253
1253
|
for fld in self.grid_fields:
|
1254
1254
|
fld.value2list(ar, None, l, row)
|
1255
|
+
# instead of calling ar.scrap_row_meta, add independently the meta item
|
1256
|
+
l.append({'meta': True, 'phantom': True})
|
1255
1257
|
else:
|
1256
1258
|
for fld in self.grid_fields:
|
1257
1259
|
if fld.delayed_value:
|
lino/core/tables.py
CHANGED
@@ -403,7 +403,7 @@ class AbstractTable(actors.Actor):
|
|
403
403
|
order_by = None
|
404
404
|
"""If specified, this must be a tuple or list of field names that
|
405
405
|
will be passed to Django's `order_by
|
406
|
-
<https://docs.djangoproject.com/en/5.
|
406
|
+
<https://docs.djangoproject.com/en/5.2/ref/models/querysets/#order-by>`__
|
407
407
|
method in order to sort the rows of the queryset.
|
408
408
|
|
409
409
|
"""
|
@@ -412,7 +412,7 @@ method in order to sort the rows of the queryset.
|
|
412
412
|
"""
|
413
413
|
If specified, this must be a :class:`django.db.models.Q` object that will be
|
414
414
|
passed to Django's `filter
|
415
|
-
<https://docs.djangoproject.com/en/5.
|
415
|
+
<https://docs.djangoproject.com/en/5.2/ref/models/querysets/#filter>`__
|
416
416
|
method.
|
417
417
|
|
418
418
|
If you allow a user to insert rows into a filtered table, you should make
|
@@ -427,7 +427,7 @@ method in order to sort the rows of the queryset.
|
|
427
427
|
One advantage of :attr:`filter` over
|
428
428
|
:attr:`known_values <lino.core.actors.Actor.known_values>`
|
429
429
|
is that this can use the full range of Django's `field lookup methods
|
430
|
-
<https://docs.djangoproject.com/en/5.
|
430
|
+
<https://docs.djangoproject.com/en/5.2/topics/db/queries/#field-lookups>`_
|
431
431
|
|
432
432
|
"""
|
433
433
|
|
@@ -435,7 +435,7 @@ method in order to sort the rows of the queryset.
|
|
435
435
|
"""
|
436
436
|
If specified, this must be a :class:`django.db.models.Q` object that will be
|
437
437
|
passed to Django's `exclude
|
438
|
-
<https://docs.djangoproject.com/en/5.
|
438
|
+
<https://docs.djangoproject.com/en/5.2/ref/models/querysets/#exclude>`__
|
439
439
|
method.
|
440
440
|
|
441
441
|
This is the logical opposite of :attr:`filter`.
|
@@ -485,6 +485,8 @@ method in order to sort the rows of the queryset.
|
|
485
485
|
resolve_fields_list(cls, "mobile_columns", set, {})
|
486
486
|
resolve_fields_list(cls, "popin_columns", set, {})
|
487
487
|
if cls.model is not None:
|
488
|
+
if not isinstance(cls.model, type):
|
489
|
+
raise Exception(f"{cls}.model is {repr(cls.model)}")
|
488
490
|
if not issubclass(cls.model, models.Model):
|
489
491
|
if cls.model._lino_default_table is None:
|
490
492
|
cls.model._lino_default_table = cls
|
@@ -526,13 +528,24 @@ method in order to sort the rows of the queryset.
|
|
526
528
|
@classmethod
|
527
529
|
def get_default_action(cls):
|
528
530
|
if cls.default_record_id is not None:
|
531
|
+
# return cls._detail_action_class(
|
532
|
+
# label=cls.label, help_text=cls.help_text,
|
533
|
+
# default_record_id = cls.default_record_id)
|
529
534
|
# assert cls.display_mode == ((None, constants.DISPLAY_MODE_DETAIL))
|
530
535
|
assert cls.detail_action is not None
|
531
|
-
cls.detail_action.action.
|
532
|
-
|
533
|
-
cls.detail_action.action.
|
534
|
-
|
535
|
-
|
536
|
+
# if cls.detail_action.action.defining_actor is not cls:
|
537
|
+
# raise Exception(
|
538
|
+
# f"{cls.detail_action.action.defining_actor} is not {cls}")
|
539
|
+
a = cls.detail_action.action
|
540
|
+
return a.__class__(
|
541
|
+
a.owner,
|
542
|
+
label=cls.label, help_text=cls.help_text,
|
543
|
+
default_record_id=cls.default_record_id)
|
544
|
+
# cls.detail_action.action.label = cls.label
|
545
|
+
# cls.detail_action.help_text = cls.help_text
|
546
|
+
# cls.detail_action.action.default_record_id = cls.default_record_id
|
547
|
+
# return cls.detail_action
|
548
|
+
return actions.SHOW_TABLE
|
536
549
|
|
537
550
|
@classmethod
|
538
551
|
def get_actor_editable(self):
|
@@ -611,7 +624,7 @@ method in order to sort the rows of the queryset.
|
|
611
624
|
if not isinstance(master_instance, self.master):
|
612
625
|
# e.g. a ByUser table descendant called by AnonymousUser
|
613
626
|
msg = "20240731 %r is not a %s (%s.master_key = '%s')" % (
|
614
|
-
master_instance
|
627
|
+
master_instance,
|
615
628
|
self.master,
|
616
629
|
self,
|
617
630
|
self.master_key,
|
lino/core/utils.py
CHANGED
@@ -13,6 +13,7 @@ import datetime
|
|
13
13
|
# import yaml
|
14
14
|
from importlib import import_module
|
15
15
|
from django.utils.html import format_html, mark_safe, SafeString
|
16
|
+
from django.utils.functional import Promise
|
16
17
|
from django.db import models
|
17
18
|
from django.db.models import Q
|
18
19
|
from django.core.exceptions import FieldDoesNotExist
|
@@ -90,7 +91,7 @@ def getrqdata(request):
|
|
90
91
|
"""Return the request data.
|
91
92
|
|
92
93
|
Unlike the now defunct `REQUEST
|
93
|
-
<https://docs.djangoproject.com/en/5.
|
94
|
+
<https://docs.djangoproject.com/en/5.2/ref/request-response/#django.http.HttpRequest.REQUEST>`_
|
94
95
|
attribute, this inspects the request's `method` in order to decide
|
95
96
|
what to return.
|
96
97
|
|
@@ -1163,6 +1164,8 @@ class Panel:
|
|
1163
1164
|
|
1164
1165
|
def resolve_layout(cls, k, spec, layout_class, **options):
|
1165
1166
|
# k: just for naming the culprit in error messages
|
1167
|
+
if isinstance(spec, Promise):
|
1168
|
+
spec = str(spec)
|
1166
1169
|
if isinstance(spec, str):
|
1167
1170
|
if "\n" in spec or "." not in spec:
|
1168
1171
|
return layout_class(spec, cls, **options)
|
lino/core/workflows.py
CHANGED
@@ -255,7 +255,8 @@ class ChangeStateAction(actions.Action):
|
|
255
255
|
self.button_text = target_state.button_text
|
256
256
|
|
257
257
|
if self.icon_name:
|
258
|
-
self.help_text = format_lazy(
|
258
|
+
self.help_text = format_lazy(
|
259
|
+
"{}. {}", self.label, self.help_text)
|
259
260
|
|
260
261
|
# def get_action_permission(self, ar, obj, state):
|
261
262
|
# if not super(ChangeStateAction, self).get_action_permission(ar, obj, state):
|
lino/help_texts.py
CHANGED
@@ -110,7 +110,6 @@ help_texts = {
|
|
110
110
|
'lino.modlib.bootstrap3.views.Element' : _("""Render a single record."""),
|
111
111
|
'lino.modlib.bootstrap3.views.Index' : _("""Render the main page."""),
|
112
112
|
'lino.modlib.checkdata.Plugin' : _("""The config descriptor for this plugin."""),
|
113
|
-
'lino.modlib.checkdata.Plugin.responsible_user' : _("""The username of the main checkdata responsible, i.e. a designated user who will be attributed to checkdata messages for which no specific responible could be designated (returned by the checker’s get_responsible_user method)."""),
|
114
113
|
'lino.modlib.checkdata.Plugin.on_plugins_loaded' : _("""Set responsible_user to "'robin' if this is a demo site (is_demo_site)."""),
|
115
114
|
'lino.modlib.checkdata.roles.CheckdataUser' : _("""Can see checkdata messages."""),
|
116
115
|
'lino.modlib.comments.Plugin' : _("""See /dev/plugins."""),
|
@@ -560,7 +559,7 @@ help_texts = {
|
|
560
559
|
'lino.modlib.jinja.XMLMaker.xml_validator_file' : _("""The name of a “validator” to use for validating the XML content."""),
|
561
560
|
'lino.modlib.jinja.XMLMaker.get_xml_file' : _("""Get the name of the XML file to be generated for this database row."""),
|
562
561
|
'lino.modlib.jinja.XMLMaker.make_xml_file' : _("""Make the XML file for this database row."""),
|
563
|
-
'lino.modlib.memo.Previewable' : _("""
|
562
|
+
'lino.modlib.memo.Previewable' : _("""See dg.memo.Previewable."""),
|
564
563
|
'lino.modlib.memo.Previewable.body' : _("""An editable text body."""),
|
565
564
|
'lino.modlib.memo.Previewable.body_short_preview' : _("""A read-only preview of the first paragraph of body."""),
|
566
565
|
'lino.modlib.memo.Previewable.body_full_preview' : _("""A read-only full preview of body."""),
|