lino 25.4.0__py3-none-any.whl → 25.4.2__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 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.0'
34
+ __version__ = '25.4.2'
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
@@ -41,7 +41,7 @@ from django.db import models
41
41
 
42
42
  # import lino # for is_testing
43
43
  from lino import logger
44
- from lino.utils import codetime
44
+ # from lino.utils import codetime
45
45
  from lino.utils.html import E
46
46
  # from lino.core.utils import format_request
47
47
  # from lino.utils import isiterable
@@ -111,22 +111,21 @@ class Kernel(object):
111
111
  self.GFK_LIST = []
112
112
  # logger.info("20140227 Kernel.__init__() done")
113
113
 
114
- _code_mtime = None
115
- _lino_version = None
116
-
117
- @property
118
- def code_mtime(self):
119
- if self._code_mtime is None:
120
- # packages = [os.environ['DJANGO_SETTINGS_MODULE'], 'lino']
121
- # self._code_mtime = codetime(*packages)
122
- if self.site.developer_site_cache:
123
- self._code_mtime = codetime()
124
- else:
125
- # On a production server we test only the timestamp of
126
- # settings.py file because otherwise the result can differ
127
- # depending on which modules have already been imported.
128
- self._code_mtime = codetime(settings.SETTINGS_MODULE)
129
- return self._code_mtime
114
+ # _code_mtime = None
115
+ #
116
+ # @property
117
+ # def code_mtime(self):
118
+ # if self._code_mtime is None:
119
+ # # packages = [os.environ['DJANGO_SETTINGS_MODULE'], 'lino']
120
+ # # self._code_mtime = codetime(*packages)
121
+ # if self.site.developer_site_cache:
122
+ # self._code_mtime = codetime()
123
+ # 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)
128
+ # return self._code_mtime
130
129
 
131
130
  def kernel_startup(self, site):
132
131
  """This is a part of a Lino site startup. The Django Model
@@ -908,7 +907,7 @@ class Kernel(object):
908
907
  self._must_build = True
909
908
 
910
909
  def make_cache_file(self, fn, write, force=False, verbosity=1):
911
- """Make the specified cache file. This is used internally at server
910
+ """Make the specified cache file. This is used internally at site
912
911
  startup.
913
912
 
914
913
  """
@@ -919,7 +918,8 @@ class Kernel(object):
919
918
  fn = self.site.media_root / fn
920
919
  if not force and not self._must_build and fn.exists():
921
920
  mtime = os.stat(fn).st_mtime
922
- if mtime > self.code_mtime:
921
+ # if mtime > self.code_mtime:
922
+ if mtime > self.site.lino_version:
923
923
  # logger.debug("%s (%s) is up to date.", fn, time.ctime(mtime))
924
924
  return 0
925
925
 
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
@@ -558,17 +559,18 @@ class Site(object):
558
559
  break
559
560
 
560
561
  if self.master_site is None:
561
- cache_root = os.environ.get("LINO_CACHE_ROOT", None)
562
- if cache_root:
563
- # TODO: deprecate
564
- cr = Path(cache_root).absolute()
565
- if not cr.exists():
566
- msg = "LINO_CACHE_ROOT ({0}) does not exist!".format(cr)
567
- raise Exception(msg)
568
- self.site_dir = (cr / self.project_name).resolve()
569
- self.setup_cache_directory()
570
- else:
571
- self.site_dir = self.project_dir
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
+ self.site_dir = self.project_dir
572
574
  db = self.get_database_settings()
573
575
  if db is not None:
574
576
  self.django_settings.update(DATABASES=db)
@@ -987,11 +989,12 @@ class Site(object):
987
989
  self.update_settings(MEDIA_URL="/media/")
988
990
 
989
991
  if not "STATIC_ROOT" in self.django_settings:
990
- cache_root = os.environ.get("LINO_CACHE_ROOT", None)
991
- if cache_root:
992
- p = Path(cache_root)
993
- else:
994
- p = self.site_dir
992
+ # cache_root = os.environ.get("LINO_CACHE_ROOT", None)
993
+ # if cache_root:
994
+ # p = Path(cache_root)
995
+ # else:
996
+ # p = self.site_dir
997
+ p = self.site_dir
995
998
  self.update_settings(STATIC_ROOT=str(p / "static_root"))
996
999
  if not "STATIC_URL" in self.django_settings:
997
1000
  self.update_settings(STATIC_URL="/static/")
@@ -1142,40 +1145,40 @@ class Site(object):
1142
1145
 
1143
1146
  # print(20150331, self.django_settings['FIXTURE_DIRS'])
1144
1147
 
1145
- def setup_cache_directory(self):
1146
- stamp = self.site_dir / "lino_cache.txt"
1147
- this = class2str(self.__class__)
1148
- if stamp.exists():
1149
- other = stamp.read_file()
1150
- if other == this:
1151
- ok = True
1152
- else:
1153
- ok = False
1154
- for parent in self.__class__.__mro__:
1155
- if other == class2str(parent):
1156
- ok = True
1157
- break
1158
- if not ok:
1159
- # Can happen e.g. when `python -m lino.hello` is
1160
- # called. in certain conditions.
1161
- msg = (
1162
- "Cannot use {site_dir} for {this} "
1163
- "because it is used for {other}. (Settings {settings})"
1164
- )
1165
- msg = msg.format(
1166
- site_dir=self.site_dir,
1167
- this=this,
1168
- settings=self.django_settings.get("SETTINGS_MODULE"),
1169
- other=other,
1170
- )
1171
- if True:
1172
- raise Exception(msg)
1173
- else:
1174
- # print(msg)
1175
- self.site_dir = None
1176
- else:
1177
- self.makedirs_if_missing(self.site_dir)
1178
- stamp.write_file(this)
1148
+ # def setup_cache_directory(self):
1149
+ # stamp = self.site_dir / "lino_cache.txt"
1150
+ # this = class2str(self.__class__)
1151
+ # if stamp.exists():
1152
+ # other = stamp.read_file()
1153
+ # if other == this:
1154
+ # ok = True
1155
+ # else:
1156
+ # ok = False
1157
+ # for parent in self.__class__.__mro__:
1158
+ # if other == class2str(parent):
1159
+ # ok = True
1160
+ # break
1161
+ # if not ok:
1162
+ # # Can happen e.g. when `python -m lino.hello` is
1163
+ # # called. in certain conditions.
1164
+ # msg = (
1165
+ # "Cannot use {site_dir} for {this} "
1166
+ # "because it is used for {other}. (Settings {settings})"
1167
+ # )
1168
+ # msg = msg.format(
1169
+ # site_dir=self.site_dir,
1170
+ # this=this,
1171
+ # settings=self.django_settings.get("SETTINGS_MODULE"),
1172
+ # other=other,
1173
+ # )
1174
+ # if True:
1175
+ # raise Exception(msg)
1176
+ # else:
1177
+ # # print(msg)
1178
+ # self.site_dir = None
1179
+ # else:
1180
+ # self.makedirs_if_missing(self.site_dir)
1181
+ # stamp.write_file(this)
1179
1182
 
1180
1183
  def set_user_model(self, spec):
1181
1184
  # if self.user_model is not None:
@@ -1384,6 +1387,11 @@ class Site(object):
1384
1387
  )
1385
1388
  return date_offset(base, *args, **kwargs)
1386
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
+
1387
1395
  def welcome_text(self):
1388
1396
  return "This is %s using %s." % (self.site_version(), self.using_text())
1389
1397
 
@@ -2004,6 +2012,17 @@ class Site(object):
2004
2012
  s = self.plugins.jinja.render_from_ar(ar, "admin_main.html", **context)
2005
2013
  return mark_safe(s)
2006
2014
 
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
+
2007
2026
  def build_site_cache(self, force=False, later=False, verbosity=1):
2008
2027
  from lino.modlib.users.utils import with_user_profile
2009
2028
  from lino.modlib.users.choicelists import UserTypes
@@ -2011,10 +2030,12 @@ class Site(object):
2011
2030
  # if not self.is_prepared:
2012
2031
  # self.prepare_layouts()
2013
2032
  # self.is_prepared = True
2033
+ # settings_file = self.django_settings.get("__file__")
2034
+ # Path(settings_file).touch()
2035
+ p = self.site_dir / "lino_version.txt"
2036
+ p.touch()
2014
2037
 
2015
2038
  if later:
2016
- settings_file = self.django_settings.get("__file__")
2017
- Path(settings_file).touch()
2018
2039
  # print("20230823 later")
2019
2040
  return
2020
2041
 
@@ -2067,6 +2088,18 @@ class Site(object):
2067
2088
  time.time() - started,
2068
2089
  )
2069
2090
 
2091
+ _top_link_generator = []
2092
+
2093
+ def get_top_links(self, ar):
2094
+ messages = []
2095
+ for l in self._top_link_generator:
2096
+ for msg in l(ar):
2097
+ messages.append(msg)
2098
+ return tostring(E.div(*messages))
2099
+
2100
+ def add_top_link_generator(self, func):
2101
+ self._top_link_generator.append(func)
2102
+
2070
2103
  def get_welcome_messages(self, ar):
2071
2104
  for h in self._welcome_handlers:
2072
2105
  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