lino 25.4.1__py3-none-any.whl → 25.4.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.
Files changed (52) hide show
  1. lino/__init__.py +1 -1
  2. lino/api/dd.py +8 -10
  3. lino/core/dbtables.py +2 -3
  4. lino/core/fields.py +30 -15
  5. lino/core/kernel.py +33 -7
  6. lino/core/renderer.py +3 -3
  7. lino/core/requests.py +24 -9
  8. lino/core/site.py +38 -29
  9. lino/core/tables.py +27 -29
  10. lino/help_texts.py +7 -3
  11. lino/management/commands/demotest.py +16 -22
  12. lino/mixins/__init__.py +32 -35
  13. lino/mixins/dupable.py +2 -4
  14. lino/mixins/registrable.py +5 -2
  15. lino/modlib/about/models.py +2 -2
  16. lino/modlib/changes/models.py +2 -2
  17. lino/modlib/checkdata/choicelists.py +4 -4
  18. lino/modlib/checkdata/models.py +2 -2
  19. lino/modlib/comments/fixtures/demo2.py +4 -0
  20. lino/modlib/comments/models.py +1 -1
  21. lino/modlib/dupable/mixins.py +3 -5
  22. lino/modlib/export_excel/__init__.py +6 -8
  23. lino/modlib/export_excel/models.py +1 -3
  24. lino/modlib/extjs/ext_renderer.py +1 -1
  25. lino/modlib/extjs/views.py +1 -1
  26. lino/modlib/help/fixtures/demo2.py +3 -2
  27. lino/modlib/jinja/mixins.py +20 -4
  28. lino/modlib/linod/mixins.py +17 -12
  29. lino/modlib/linod/models.py +1 -1
  30. lino/modlib/memo/mixins.py +3 -2
  31. lino/modlib/notify/api.py +33 -14
  32. lino/modlib/notify/mixins.py +16 -1
  33. lino/modlib/notify/models.py +10 -25
  34. lino/modlib/printing/mixins.py +1 -1
  35. lino/modlib/publisher/models.py +55 -6
  36. lino/modlib/publisher/ui.py +3 -3
  37. lino/modlib/publisher/views.py +9 -2
  38. lino/modlib/system/models.py +1 -1
  39. lino/modlib/uploads/mixins.py +1 -1
  40. lino/modlib/uploads/models.py +2 -2
  41. lino/modlib/users/actions.py +2 -4
  42. lino/modlib/users/models.py +13 -19
  43. lino/modlib/users/ui.py +1 -1
  44. lino/sphinxcontrib/logo/templates/part-of-synodalsoft.html +0 -1
  45. lino/utils/format_date.py +10 -5
  46. lino/utils/media.py +16 -31
  47. {lino-25.4.1.dist-info → lino-25.4.3.dist-info}/METADATA +1 -1
  48. {lino-25.4.1.dist-info → lino-25.4.3.dist-info}/RECORD +51 -52
  49. lino/management/commands/monitor.py +0 -160
  50. {lino-25.4.1.dist-info → lino-25.4.3.dist-info}/WHEEL +0 -0
  51. {lino-25.4.1.dist-info → lino-25.4.3.dist-info}/licenses/AUTHORS.rst +0 -0
  52. {lino-25.4.1.dist-info → lino-25.4.3.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.1'
34
+ __version__ = '25.4.3'
35
35
 
36
36
  # import setuptools # avoid UserWarning "Distutils was imported before Setuptools"?
37
37
 
lino/api/dd.py CHANGED
@@ -155,7 +155,7 @@ from lino.utils import IncompleteDate, read_exception
155
155
 
156
156
  from lino.utils.format_date import fdm, fdl, fdf, fdmy
157
157
  from lino.utils.format_date import fds as fds_
158
- from lino.utils.format_date import ftl, ftf
158
+ from lino.utils.format_date import fdtl, fdtf
159
159
 
160
160
 
161
161
  def fds(d):
@@ -175,14 +175,8 @@ field2kw = settings.SITE.field2kw
175
175
  decfmt = settings.SITE.decfmt
176
176
  str2kw = settings.SITE.str2kw
177
177
  str2dict = settings.SITE.str2dict
178
-
179
-
180
- def today(*args, **kwargs):
181
- # make it serializable for Django migrations
182
- return settings.SITE.today(*args, **kwargs)
183
-
184
-
185
- # today = settings.SITE.today
178
+ now = settings.SITE.now
179
+ # today = settings.SITE.today # see below
186
180
  strftime = settings.SITE.strftime
187
181
  demo_date = settings.SITE.demo_date
188
182
  is_abstract_model = settings.SITE.is_abstract_model
@@ -190,7 +184,6 @@ is_installed = settings.SITE.is_installed
190
184
  is_hidden_plugin = settings.SITE.is_hidden_plugin
191
185
  resolve_plugin = settings.SITE.resolve_plugin
192
186
  get_plugin_setting = settings.SITE.get_plugin_setting
193
- # get_db_overview_rst = settings.SITE.get_db_overview_rst
194
187
  add_welcome_handler = settings.SITE.add_welcome_handler
195
188
  build_media_url = settings.SITE.build_media_url
196
189
  build_site_cache_url = settings.SITE.build_site_cache_url
@@ -203,6 +196,11 @@ plugins = settings.SITE.plugins
203
196
  format_currency = settings.SITE.format_currency
204
197
 
205
198
 
199
+ def today(*args, **kwargs):
200
+ # make it serializable for Django migrations
201
+ return settings.SITE.today(*args, **kwargs)
202
+
203
+
206
204
  get_language = translation.get_language
207
205
 
208
206
 
lino/core/dbtables.py CHANGED
@@ -613,7 +613,7 @@ class Table(AbstractTable):
613
613
  qs = qs.filter(**kw)
614
614
 
615
615
  if ar.exclude:
616
- qs = qs.exclude(**ar.exclude)
616
+ qs = qs.exclude(ar.exclude)
617
617
  # qs = qs.exclude(ar.exclude)
618
618
 
619
619
  if ar.param_values is not None:
@@ -654,8 +654,7 @@ class Table(AbstractTable):
654
654
  qs = qs.filter(**d)
655
655
 
656
656
  if self.exclude:
657
- qs = qs.exclude(**self.exclude)
658
- # TODO: use Q object instead of dict
657
+ qs = qs.exclude(self.exclude)
659
658
 
660
659
  if ar.quick_search:
661
660
  qs = self.add_quick_search_filter(qs, ar.quick_search)
lino/core/fields.py CHANGED
@@ -6,6 +6,8 @@ Defines extended database field classes and utility functions
6
6
  related to fields.
7
7
  """
8
8
 
9
+ #fmt: off
10
+
9
11
  from lino import logger
10
12
  import datetime
11
13
  from decimal import Decimal
@@ -112,10 +114,10 @@ class PercentageField(models.DecimalField):
112
114
  defaults = dict(
113
115
  max_length=5,
114
116
  max_digits=5,
115
- decimal_places=2,
117
+ decimal_places=0,
116
118
  )
117
119
  defaults.update(kwargs)
118
- super(PercentageField, self).__init__(*args, **defaults)
120
+ super().__init__(*args, **defaults)
119
121
 
120
122
 
121
123
  class TimeField(models.TimeField):
@@ -818,7 +820,7 @@ def virtualfield(return_type, **kwargs):
818
820
 
819
821
  def decorator(fn):
820
822
  if isinstance(return_type, DummyField):
821
- rv = DummyField(fn)
823
+ rv = DummyField(return_type.get_default())
822
824
  else:
823
825
  rv = VirtualField(return_type, fn, **kwargs)
824
826
  rv.__doc__ = fn.__doc__
@@ -1001,10 +1003,8 @@ class QuantityField(models.CharField):
1001
1003
  if self.overflow_value:
1002
1004
  return self.overflow_value
1003
1005
  raise ValidationError(
1004
- "Cannot accept quantity {} " "because max_length is {}".format(
1005
- raw_value, self.max_length
1006
- )
1007
- )
1006
+ f"Cannot accept quantity {raw_value} "
1007
+ + f"because max_length is {self.max_length}")
1008
1008
  # print("20230129 Can't store {}={} in {}".format(self.name, raw_value, obj))
1009
1009
  # return -1
1010
1010
  return super().clean(raw_value, obj)
@@ -1111,8 +1111,20 @@ class Dummy(object):
1111
1111
  class DummyField(FakeField):
1112
1112
  """
1113
1113
  Represents a field that doesn't exist in the current configuration
1114
- but might exist in other configurations. The "value" of a
1115
- DummyField is always `None`.
1114
+ but might exist in other configurations.
1115
+
1116
+ The "value" of a DummyField is always `None` or any other value to be
1117
+ optionally specified at instantiation.
1118
+
1119
+ .. attribute:: dummy_value
1120
+
1121
+ The value to be returned by this field. Default value is `None`.
1122
+
1123
+ Usage examples:
1124
+
1125
+ - The value of :attr:`lino.modlib.users.User.nickname` is always ``""`` when
1126
+ :data:`lino.modlib.users.with_nickname` is `False`.
1127
+
1116
1128
 
1117
1129
  See e.g. :func:`ForeignKey` and :func:`fields_list`.
1118
1130
  """
@@ -1120,8 +1132,9 @@ class DummyField(FakeField):
1120
1132
  # choices = []
1121
1133
  # primary_key = False
1122
1134
 
1123
- def __init__(self, *args, **kw):
1124
- pass
1135
+ def __init__(self, dummy_value=None):
1136
+ super().__init__()
1137
+ self.dummy_value = dummy_value
1125
1138
 
1126
1139
  # def __init__(self, name, *args, **kw):
1127
1140
  # self.name = name
@@ -1132,10 +1145,10 @@ class DummyField(FakeField):
1132
1145
  def __get__(self, instance, owner):
1133
1146
  if instance is None:
1134
1147
  return self
1135
- return None
1148
+ return self.dummy_value
1136
1149
 
1137
1150
  def get_default(self):
1138
- return None
1151
+ return self.dummy_value
1139
1152
 
1140
1153
  def contribute_to_class(self, cls, name):
1141
1154
  self.name = name
@@ -1548,10 +1561,12 @@ def pointer_factory(cls, othermodel, *args, **kw):
1548
1561
 
1549
1562
  """
1550
1563
  if othermodel is None:
1551
- return DummyField(othermodel, *args, **kw)
1564
+ # return DummyField(othermodel, *args, **kw)
1565
+ return DummyField(None)
1552
1566
  if isinstance(othermodel, str):
1553
1567
  if not settings.SITE.is_installed_model_spec(othermodel):
1554
- return DummyField(othermodel, *args, **kw)
1568
+ # return DummyField(othermodel, *args, **kw)
1569
+ return DummyField(None)
1555
1570
 
1556
1571
  kw.setdefault("on_delete", models.CASCADE)
1557
1572
  return cls(othermodel, *args, **kw)
lino/core/kernel.py CHANGED
@@ -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
- # from lino.utils import codetime
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
- # # On a production server we test only the timestamp of
125
- # # settings.py file because otherwise the result can differ
126
- # # depending on which modules have already been imported.
127
- # self._code_mtime = codetime(settings.SETTINGS_MODULE)
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.site.lino_version:
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/requests.py CHANGED
@@ -829,7 +829,7 @@ class BaseRequest:
829
829
  unicode=str, # backwards-compatibility. In new templates
830
830
  # you should prefer `str`.
831
831
  pgettext=pgettext,
832
- now=timezone.now(),
832
+ now=dd.now(),
833
833
  getattr=getattr,
834
834
  restify=restify,
835
835
  activate_language=activate,
@@ -1805,6 +1805,8 @@ class ActionRequest(BaseRequest):
1805
1805
  selected_rows=None,
1806
1806
  **kw
1807
1807
  ):
1808
+ if exclude is not None:
1809
+ assert isinstance(exclude, models.Q)
1808
1810
  if title is not None:
1809
1811
  self.title = title
1810
1812
  if offset is not None:
@@ -1908,7 +1910,11 @@ class ActionRequest(BaseRequest):
1908
1910
  # raise Exception("20230426 b {}".format(self.master))
1909
1911
 
1910
1912
  if issubclass(self.actor, AbstractTable):
1911
-
1913
+ if self.actor.exclude is not None:
1914
+ if not isinstance(self.actor.exclude, models.Q):
1915
+ raise Exception(
1916
+ f"{self.actor}.exclude must be a Q object",
1917
+ f" not {self.actor.exclude}")
1912
1918
  self.exclude = exclude or self.actor.exclude
1913
1919
  self.page_length = self.actor.page_length
1914
1920
 
@@ -2019,8 +2025,7 @@ class ActionRequest(BaseRequest):
2019
2025
  # often and since exception loggers usually send an email to the
2020
2026
  # local system admin, make sure to log each exception only once.
2021
2027
  e = str(e)
2022
- self.no_data_text = f"{e} (set catch_layout_exceptions to see \
2023
- details)"
2028
+ self.no_data_text = e + " (set catch_layout_exceptions to see details)"
2024
2029
  self._data_iterator = []
2025
2030
  w = WARNINGS_LOGGED.get(e)
2026
2031
  if w is None:
@@ -2042,8 +2047,18 @@ class ActionRequest(BaseRequest):
2042
2047
  self._data_iterator, self)
2043
2048
  )
2044
2049
  else:
2050
+ offset = self.offset
2051
+
2052
+ if self.actor.start_at_bottom and offset is None and self.limit is not None:
2053
+ count = self.get_total_count()
2054
+ if not self.actor.no_phantom_row:
2055
+ count += 1
2056
+ offset = (count // self.limit) * self.limit
2057
+ if (count % self.limit) == 0 and count != 0:
2058
+ offset -= self.limit
2059
+
2045
2060
  self._sliced_data_iterator = sliced_data_iterator(
2046
- self._data_iterator, self.offset, self.limit
2061
+ self._data_iterator, offset, self.limit
2047
2062
  )
2048
2063
  # logger.info("20171116 executed : %s", self._sliced_data_iterator)
2049
2064
 
@@ -2069,11 +2084,11 @@ class ActionRequest(BaseRequest):
2069
2084
  assert issubclass(self.actor, AbstractTable)
2070
2085
  self.actor.check_params(self.param_values)
2071
2086
  if self.actor.get_data_rows is not None:
2072
- l = []
2087
+ lst = []
2073
2088
  for row in self.actor.get_data_rows(self):
2074
2089
  group = self.actor.group_from_row(row)
2075
- group.process_row(l, row)
2076
- return l
2090
+ group.process_row(lst, row)
2091
+ return lst
2077
2092
  # ~ logger.info("20120914 tables.get_data_iterator %s",self)
2078
2093
  # ~ logger.info("20120914 tables.get_data_iterator %s",self.actor)
2079
2094
  # print("20181121 get_data_iterator", self.actor)
@@ -2145,7 +2160,7 @@ class ActionRequest(BaseRequest):
2145
2160
  if self.known_values:
2146
2161
  # kw.update(self.known_values)
2147
2162
  for k, v in self.known_values.items():
2148
- if not "__" in k:
2163
+ if "__" not in k:
2149
2164
  kw[k] = v
2150
2165
  obj = self.actor.create_instance(self, **kw)
2151
2166
  return obj
lino/core/site.py CHANGED
@@ -3,23 +3,8 @@
3
3
  # License: GNU Affero General Public License v3 (see file COPYING for details)
4
4
  # doctest lino/core/site.py
5
5
 
6
- from lino.core.exceptions import ChangedAPI
7
- from django.apps import apps
8
- from lino.utils.html import E, tostring
9
- from lino import assert_django_code, DJANGO_DEFAULT_LANGUAGE
10
- # from lino.core import constants
11
- from lino.core.plugin import Plugin
12
- from rstgen.confparser import ConfigParser
13
- from django.utils import translation
14
- from django.utils.html import mark_safe
15
- from django.utils.translation import get_language
16
- from django.utils.translation import gettext_lazy as _
17
- from django.conf import settings
18
- import rstgen
19
- from lino.utils import AttrDict, date_offset, i2d, buildurl
20
- from lino import logger, __version__
21
6
  from importlib.util import find_spec
22
- from importlib import import_module, reload
7
+ from importlib import import_module
23
8
  from pathlib import Path
24
9
  import os
25
10
  import re
@@ -34,6 +19,22 @@ import logging
34
19
  # from pprint import pprint
35
20
  from logging.handlers import SocketHandler
36
21
  import time
22
+ import rstgen
23
+ from rstgen.confparser import ConfigParser
24
+ from django.apps import apps
25
+ from django.utils import timezone
26
+ from django.utils import translation
27
+ from django.utils.html import mark_safe
28
+ from django.utils.translation import get_language
29
+ from django.utils.translation import gettext_lazy as _
30
+ from django.conf import settings
31
+ from lino import assert_django_code, DJANGO_DEFAULT_LANGUAGE
32
+ from lino import logger, __version__
33
+ from lino.core.exceptions import ChangedAPI
34
+ from lino.utils.html import E, tostring
35
+ # from lino.core import constants
36
+ from lino.core.plugin import Plugin
37
+ from lino.utils import AttrDict, date_offset, i2d, buildurl
37
38
 
38
39
  NO_REMOTE_AUTH = True
39
40
  # 20240518 We have only one production site still using remote http
@@ -1386,6 +1387,11 @@ class Site(object):
1386
1387
  )
1387
1388
  return date_offset(base, *args, **kwargs)
1388
1389
 
1390
+ def now(self, *args, **kwargs):
1391
+ t = self.today()
1392
+ now = timezone.now()
1393
+ return now.replace(year=t.year, month=t.month, day=t.day)
1394
+
1389
1395
  def welcome_text(self):
1390
1396
  return "This is %s using %s." % (self.site_version(), self.using_text())
1391
1397
 
@@ -1823,6 +1829,7 @@ class Site(object):
1823
1829
  self.install_settings()
1824
1830
 
1825
1831
  def is_imported_partner(self, obj):
1832
+ # Deprecated.
1826
1833
  # ~ return obj.id is not None and (obj.id < 200000 or obj.id > 299999)
1827
1834
  return False
1828
1835
  # ~ return obj.id is not None and (obj.id > 10 and obj.id < 21)
@@ -2006,17 +2013,6 @@ class Site(object):
2006
2013
  s = self.plugins.jinja.render_from_ar(ar, "admin_main.html", **context)
2007
2014
  return mark_safe(s)
2008
2015
 
2009
- _lino_version = None
2010
-
2011
- @property
2012
- def lino_version(self):
2013
- p = self.site_dir / "lino_version.txt"
2014
- if self._lino_version is None:
2015
- if not p.exists():
2016
- p.touch()
2017
- self._lino_version = p.stat().st_mtime
2018
- return self._lino_version
2019
-
2020
2016
  def build_site_cache(self, force=False, later=False, verbosity=1):
2021
2017
  from lino.modlib.users.utils import with_user_profile
2022
2018
  from lino.modlib.users.choicelists import UserTypes
@@ -2026,8 +2022,9 @@ class Site(object):
2026
2022
  # self.is_prepared = True
2027
2023
  # settings_file = self.django_settings.get("__file__")
2028
2024
  # Path(settings_file).touch()
2029
- p = self.site_dir / "lino_version.txt"
2030
- p.touch()
2025
+ # p = self.site_dir / "lino_version.txt"
2026
+ # p.touch()
2027
+ self.kernel.touch_lino_version()
2031
2028
 
2032
2029
  if later:
2033
2030
  # print("20230823 later")
@@ -2082,6 +2079,18 @@ class Site(object):
2082
2079
  time.time() - started,
2083
2080
  )
2084
2081
 
2082
+ _top_link_generator = []
2083
+
2084
+ def get_top_links(self, ar):
2085
+ messages = []
2086
+ for l in self._top_link_generator:
2087
+ for msg in l(ar):
2088
+ messages.append(msg)
2089
+ return tostring(E.div(*messages))
2090
+
2091
+ def add_top_link_generator(self, func):
2092
+ self._top_link_generator.append(func)
2093
+
2085
2094
  def get_welcome_messages(self, ar):
2086
2095
  for h in self._welcome_handlers:
2087
2096
  for msg in h(ar):
lino/core/tables.py CHANGED
@@ -1,12 +1,10 @@
1
1
  # -*- coding: UTF-8 -*-
2
- # Copyright 2009-2024 Rumma & Ko Ltd
2
+ # Copyright 2009-2025 Rumma & Ko Ltd
3
3
  # License: GNU Affero General Public License v3 (see file COPYING for details)
4
4
  """Defines the classes :class:`AbstractTable` and
5
5
  :class:`VirtualTable`.
6
6
 
7
7
  """
8
- from lino import logger
9
- from future.utils import with_metaclass
10
8
 
11
9
  import os
12
10
  import yaml
@@ -15,8 +13,9 @@ from django.db import models
15
13
  from django.db.utils import OperationalError, ProgrammingError
16
14
  from django.conf import settings
17
15
  from django.utils.translation import gettext_lazy as _
18
- from django.core.exceptions import BadRequest
16
+ # from django.core.exceptions import BadRequest
19
17
 
18
+ from lino import logger
20
19
  from lino.core import actors
21
20
  from lino.core import actions
22
21
  from lino.core import fields
@@ -191,17 +190,15 @@ class AbstractTable(actors.Actor):
191
190
  will not pop-in.
192
191
  """
193
192
 
194
- # start_at_bottom = False
195
- # """Set this to `True` if you want your table to *start at the
196
- # bottom*. Unlike reverse ordering, the rows remain in their
197
- # natural order, but when we open a grid on this table, we want it
198
- # to start on the last page.
199
- #
200
- # Use cases would be :class:`lino_xl.lib.trading.InvoicesByJournal` and
201
- # :class:`lino_xl.lib.accounting.InvoicesByJournal` but the result is not yet
202
- # satisfying.
203
- #
204
- # """
193
+ start_at_bottom = False
194
+ """Set this to `True` if you want your table to *start at the
195
+ bottom*. Unlike reverse ordering, the rows remain in their
196
+ natural order, but when we open a grid on this table, we want it
197
+ to start on the last page.
198
+
199
+ Use cases are in :class:`lino_xl.lib.trading.InvoicesByJournal` and
200
+ :class:`lino_xl.lib.accounting.InvoicesByJournal`.
201
+ """
205
202
 
206
203
  group_by = None
207
204
  """
@@ -397,21 +394,20 @@ method in order to sort the rows of the queryset.
397
394
  """
398
395
 
399
396
  filter = None
400
- """If specified, this must be a `models.Q` object (not a dict of
401
- (fieldname -> value) pairs) which will be passed to Django's
402
- `filter
397
+ """
398
+ If specified, this must be a :class:`django.db.models.Q` object that will be
399
+ passed to Django's `filter
403
400
  <https://docs.djangoproject.com/en/5.0/ref/models/querysets/#filter>`__
404
401
  method.
405
402
 
406
- Note that if you allow a user to insert rows into a filtered
407
- table, you should make sure that new records satisfy your filter
408
- condition, otherwise you can get surprising behaviour if the user
409
- creates a new row.
403
+ If you allow a user to insert rows into a filtered table, you should make
404
+ sure that new records satisfy your filter condition, otherwise you can get
405
+ surprising behaviour if the user creates a new row.
410
406
 
411
- If your filter consists of simple static values on some known
412
- field, then you might prefer to use
413
- :attr:`known_values <lino.core.actors.Actor.known_values>`
414
- instead because this will add automatic behaviour.
407
+ If your filter consists of simple static values on some known field, then
408
+ you might prefer to use :attr:`known_values
409
+ <lino.core.actors.Actor.known_values>` instead because this will add
410
+ automatic behaviour.
415
411
 
416
412
  One advantage of :attr:`filter` over
417
413
  :attr:`known_values <lino.core.actors.Actor.known_values>`
@@ -421,11 +417,13 @@ method in order to sort the rows of the queryset.
421
417
  """
422
418
 
423
419
  exclude = None
424
- """If specified, this must be dict which will be passed to Django's
425
- `exclude
420
+ """
421
+ If specified, this must be a :class:`django.db.models.Q` object that will be
422
+ passed to Django's `exclude
426
423
  <https://docs.djangoproject.com/en/5.0/ref/models/querysets/#exclude>`__
427
- method on the queryset.
424
+ method.
428
425
 
426
+ This is the logical opposite of :attr:`filter`.
429
427
  """
430
428
 
431
429
  extra = None
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 which holds the creation time of every individual database object."""),
15
- 'lino.mixins.Created.created' : _("""The time when this object was created."""),
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."""),
@@ -253,6 +253,8 @@ help_texts = {
253
253
  'lino.utils.jsgen.VisibleComponent' : _("""A visible component"""),
254
254
  '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."""),
255
255
  'lino.utils.media.MediaFile' : _("""Represents a file on the server below MEDIA_ROOT with two properties path and url."""),
256
+ 'lino.utils.media.MediaFile.path' : _("""A pathlib.Path naming the file on the server’s file system."""),
257
+ 'lino.utils.media.MediaFile.url' : _("""The URL to use for getting this file from a web client."""),
256
258
  '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."""),
257
259
  'lino.utils.mldbc.fields.BabelTextField' : _("""Used for the clones of the master field, one for each non-default language. See mldbc."""),
258
260
  'lino.utils.mldbc.fields.LanguageField' : _("""A field that lets the user select a language from the available lino.core.site.Site.languages."""),
@@ -551,6 +553,8 @@ help_texts = {
551
553
  '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."""),
552
554
  'lino.modlib.jinja.XMLMaker.xml_file_template' : _("""The name of a Jinja template to render for generating the XML content."""),
553
555
  'lino.modlib.jinja.XMLMaker.xml_validator_file' : _("""The name of a “validator” to use for validating the XML content."""),
556
+ 'lino.modlib.jinja.XMLMaker.get_xml_file' : _("""Get the name of the XML file to be generated for this database row."""),
557
+ 'lino.modlib.jinja.XMLMaker.make_xml_file' : _("""Make the XML file for this database row."""),
554
558
  'lino.modlib.memo.Previewable' : _("""Adds three rich text fields (lino.core.fields.RichTextField):"""),
555
559
  'lino.modlib.memo.Previewable.body' : _("""An editable text body."""),
556
560
  'lino.modlib.memo.Previewable.body_short_preview' : _("""A read-only preview of the first paragraph of body."""),
@@ -642,7 +646,7 @@ help_texts = {
642
646
  'lino.modlib.system.SiteConfig.simulate_today' : _("""A constant user-defined date to be substituted as current system date."""),
643
647
  'lino.modlib.system.SiteConfig.site_company' : _("""The site operator, i.e. the legal person that operates this Lino site."""),
644
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."""),
645
- 'lino.modlib.system.SiteConfigManager' : _("""Always return the cached instance which holds the one and only database instance."""),
649
+ 'lino.modlib.system.SiteConfigManager' : _("""Returns the cached instance, which holds the one and only database instance."""),
646
650
  'lino.modlib.system.Lockable' : _("""Mixin to add row-level edit locking to any model."""),
647
651
  'lino.modlib.system.BuildSiteCache' : _("""Rebuild the site cache. This action is available on About."""),
648
652
  'lino.modlib.system.SiteConfigs' : _("""The table used to present the SiteConfig row in a Detail form."""),