lino 24.10.3__py3-none-any.whl → 24.11.1__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 (80) hide show
  1. lino/__init__.py +1 -1
  2. lino/api/doctest.py +11 -10
  3. lino/api/rt.py +2 -3
  4. lino/config/admin_main_base.html +2 -2
  5. lino/core/actions.py +2 -4
  6. lino/core/actors.py +70 -35
  7. lino/core/choicelists.py +2 -2
  8. lino/core/dashboard.py +2 -1
  9. lino/core/dbtables.py +15 -15
  10. lino/core/elems.py +8 -4
  11. lino/core/fields.py +12 -3
  12. lino/core/inject.py +9 -2
  13. lino/core/kernel.py +11 -11
  14. lino/core/layouts.py +1 -1
  15. lino/core/model.py +25 -36
  16. lino/core/plugin.py +1 -0
  17. lino/core/renderer.py +21 -21
  18. lino/core/requests.py +94 -83
  19. lino/core/site.py +9 -90
  20. lino/core/store.py +16 -19
  21. lino/core/tables.py +0 -17
  22. lino/core/utils.py +32 -2
  23. lino/core/views.py +2 -1
  24. lino/help_texts.py +10 -5
  25. lino/locale/bn/LC_MESSAGES/django.po +1210 -907
  26. lino/locale/de/LC_MESSAGES/django.po +1760 -1375
  27. lino/locale/django.pot +1136 -906
  28. lino/locale/es/LC_MESSAGES/django.po +1709 -1347
  29. lino/locale/et/LC_MESSAGES/django.po +1206 -906
  30. lino/locale/fr/LC_MESSAGES/django.mo +0 -0
  31. lino/locale/fr/LC_MESSAGES/django.po +1193 -923
  32. lino/locale/nl/LC_MESSAGES/django.po +1247 -942
  33. lino/locale/pt_BR/LC_MESSAGES/django.po +1190 -903
  34. lino/locale/zh_Hant/LC_MESSAGES/django.po +1190 -903
  35. lino/management/commands/show.py +2 -4
  36. lino/mixins/periods.py +15 -7
  37. lino/mixins/polymorphic.py +3 -3
  38. lino/mixins/ref.py +6 -3
  39. lino/modlib/checkdata/__init__.py +3 -3
  40. lino/modlib/comments/choicelists.py +1 -1
  41. lino/modlib/comments/fixtures/demo2.py +4 -1
  42. lino/modlib/comments/mixins.py +9 -10
  43. lino/modlib/comments/models.py +4 -4
  44. lino/modlib/comments/ui.py +5 -0
  45. lino/modlib/extjs/ext_renderer.py +1 -1
  46. lino/modlib/linod/consumers.py +2 -3
  47. lino/modlib/linod/mixins.py +3 -2
  48. lino/modlib/memo/mixins.py +11 -209
  49. lino/modlib/notify/mixins.py +33 -32
  50. lino/modlib/periods/__init__.py +12 -1
  51. lino/modlib/periods/fixtures/std.py +2 -1
  52. lino/modlib/periods/mixins.py +0 -1
  53. lino/modlib/periods/models.py +79 -75
  54. lino/modlib/printing/actions.py +2 -0
  55. lino/modlib/printing/choicelists.py +3 -3
  56. lino/modlib/publisher/ui.py +2 -2
  57. lino/modlib/search/models.py +17 -11
  58. lino/modlib/system/__init__.py +0 -2
  59. lino/modlib/system/choicelists.py +55 -1
  60. lino/modlib/system/fixtures/__init__.py +0 -0
  61. lino/modlib/system/fixtures/std.py +5 -0
  62. lino/modlib/system/models.py +4 -2
  63. lino/modlib/uploads/__init__.py +10 -1
  64. lino/modlib/uploads/choicelists.py +3 -3
  65. lino/modlib/uploads/mixins.py +30 -32
  66. lino/modlib/uploads/models.py +89 -56
  67. lino/modlib/uploads/ui.py +12 -6
  68. lino/modlib/uploads/utils.py +107 -0
  69. lino/modlib/users/models.py +2 -2
  70. lino/modlib/weasyprint/__init__.py +2 -0
  71. lino/utils/__init__.py +14 -9
  72. lino/utils/djangotest.py +2 -1
  73. lino/utils/html.py +32 -1
  74. lino/utils/media.py +2 -3
  75. lino/utils/soup.py +311 -0
  76. {lino-24.10.3.dist-info → lino-24.11.1.dist-info}/METADATA +1 -3
  77. {lino-24.10.3.dist-info → lino-24.11.1.dist-info}/RECORD +80 -76
  78. {lino-24.10.3.dist-info → lino-24.11.1.dist-info}/WHEEL +1 -1
  79. {lino-24.10.3.dist-info → lino-24.11.1.dist-info}/licenses/AUTHORS.rst +0 -0
  80. {lino-24.10.3.dist-info → lino-24.11.1.dist-info}/licenses/COPYING +0 -0
lino/core/site.py CHANGED
@@ -18,11 +18,6 @@ from pprint import pprint
18
18
  from logging.handlers import SocketHandler
19
19
  import time
20
20
 
21
- try:
22
- from bleach.sanitizer import ALLOWED_ATTRIBUTES
23
- except ImportError:
24
- ALLOWED_ATTRIBUTES = dict()
25
-
26
21
  NO_REMOTE_AUTH = True
27
22
  # 20240518 We have only one production site still using remote http
28
23
  # authentication, and they will migrate to sessions-based auth with their next
@@ -359,44 +354,6 @@ class Site(object):
359
354
 
360
355
  detail_main_name = "main"
361
356
 
362
- bleach_allowed_tags = [
363
- "a",
364
- "b",
365
- "i",
366
- "em",
367
- "ul",
368
- "ol",
369
- "li",
370
- "strong",
371
- "p",
372
- "br",
373
- "span",
374
- "pre",
375
- "def",
376
- "div",
377
- "img",
378
- "table",
379
- "th",
380
- "tr",
381
- "td",
382
- "thead",
383
- "tfoot",
384
- "tbody",
385
- ]
386
-
387
- ALLOWED_ATTRIBUTES["span"] = [
388
- "class",
389
- "data-index",
390
- "data-denotation-char",
391
- "data-link",
392
- "data-title",
393
- "data-value",
394
- "contenteditable",
395
- ]
396
- ALLOWED_ATTRIBUTES["p"] = ["href", "title", "align"]
397
-
398
- bleach_allowed_attributes = ALLOWED_ATTRIBUTES
399
-
400
357
  textfield_bleached = True
401
358
  textfield_format = "plain"
402
359
  verbose_client_info_message = False
@@ -621,13 +578,14 @@ class Site(object):
621
578
  db = self.get_database_settings()
622
579
  if db is not None:
623
580
  self.django_settings.update(DATABASES=db)
624
-
625
581
  else:
626
582
  self.site_dir = self.master_site.site_dir
627
583
  self._history_aware_logging = self.master_site._history_aware_logging
628
584
  for k in ("DATABASES", "SECRET_KEY"):
629
585
  self.django_settings[k] = self.master_site.django_settings[k]
630
586
 
587
+ self.update_settings(
588
+ EMAIL_SUBJECT_PREFIX=f'[{self.project_name}] ')
631
589
  self.update_settings(
632
590
  SERIALIZATION_MODULES={
633
591
  "py": "lino.utils.dpy",
@@ -868,16 +826,13 @@ class Site(object):
868
826
  for x in self.local_apps:
869
827
  add(x)
870
828
 
871
- # actual_apps = []
872
829
  plugins = []
873
830
 
874
- # disabled_plugins = set()
875
-
876
831
  def install_plugin(app_name, needed_by=None):
877
832
  # print("20210305 install_plugin({})".format(app_name))
878
833
  # Django does not accept newstr, and we don't want to see
879
834
  # ``u'applabel'`` in doctests.
880
- app_name = str(app_name)
835
+ # app_name = str(app_name)
881
836
  # print("20160524 install_plugin(%r)" % app_name)
882
837
  app_mod = import_module(app_name)
883
838
 
@@ -900,21 +855,18 @@ class Site(object):
900
855
  # the default_ui.
901
856
  return
902
857
  raise Exception(
903
- "Tried to install {} where {} " "is already installed.".format(
858
+ "Tried to install {}, but {} is already installed.".format(
904
859
  app_name, other
905
860
  )
906
861
  )
907
862
 
908
863
  # Can an `__init__.py` file explicitly set ``Plugin =
909
864
  # None``? Is that feature being used?
910
- app_class = getattr(app_mod, "Plugin", None)
911
- if app_class is None:
912
- app_class = Plugin
865
+ app_class = getattr(app_mod, "Plugin", Plugin)
866
+ # if app_class is None:
867
+ # app_class = Plugin
913
868
  cfg = PLUGIN_CONFIGS.pop(k, None)
914
869
  ip = app_class(self, k, app_name, app_mod, needed_by, cfg or dict())
915
- # cfg = PLUGIN_CONFIGS.pop(k, None)
916
- # if cfg:
917
- # ip.configure(**cfg)
918
870
 
919
871
  self.plugins.define(k, ip)
920
872
 
@@ -928,8 +880,6 @@ class Site(object):
928
880
  # plugins.append(dep)
929
881
 
930
882
  plugins.append(ip)
931
- # for dp in ip.disables_plugins:
932
- # disabled_plugins.add(dp)
933
883
 
934
884
  # lino is always the first plugin:
935
885
  install_plugin("lino")
@@ -951,17 +901,10 @@ class Site(object):
951
901
  # afterwards.
952
902
  # if self.get_auth_method() == 'session':
953
903
  if self.user_model:
954
- k = str("django.contrib.sessions")
904
+ k = "django.contrib.sessions"
955
905
  if k not in self.plugins:
956
906
  install_plugin(k)
957
907
 
958
- # for p in plugins:
959
- # if p.app_label in disabled_plugins \
960
- # or p.app_name in disabled_plugins:
961
- # plugins.remove(p)
962
- # del self.plugins[p.app_label]
963
-
964
- # self.update_settings(INSTALLED_APPS=tuple(actual_apps))
965
908
  self.update_settings(INSTALLED_APPS=tuple([p.app_name for p in plugins]))
966
909
  self.installed_plugins = tuple(plugins)
967
910
 
@@ -1009,13 +952,6 @@ class Site(object):
1009
952
 
1010
953
  for p in self.installed_plugins:
1011
954
  reg(p.__class__)
1012
- # for pp in plugin_parents(p.__class__):
1013
- # if p.app_label == 'contacts':
1014
- # print("20160524c %s" % pp)
1015
- # reg(p.__class__)
1016
-
1017
- # for m, p in self.override_modlib_models.items():
1018
- # print("20160524 %s : %s" % (m, p))
1019
955
 
1020
956
  self.installed_plugin_modules = set()
1021
957
  for p in self.installed_plugins:
@@ -1032,7 +968,7 @@ class Site(object):
1032
968
  for r in p.get_requirements(self):
1033
969
  reqs.add(r)
1034
970
  if self.textfield_bleached:
1035
- reqs.add("bleach")
971
+ reqs.add("beautifulsoup4")
1036
972
  return sorted(reqs)
1037
973
 
1038
974
  def setup_plugins(self):
@@ -2304,23 +2240,6 @@ class Site(object):
2304
2240
 
2305
2241
  yield E.span(*p)
2306
2242
 
2307
- def login(self, username=None, **kw):
2308
- from lino.core import requests
2309
-
2310
- self.startup()
2311
- User = self.user_model
2312
- if User and username:
2313
- try:
2314
- kw.update(user=User.objects.get(username=username))
2315
- except User.DoesNotExist:
2316
- raise User.DoesNotExist("'{0}' : no such user".format(username))
2317
-
2318
- # if not 'renderer' in kw:
2319
- # kw.update(renderer=self.ui.text_renderer)
2320
-
2321
- # import lino.core.urls # hack: trigger ui instantiation
2322
- return requests.BaseRequest(**kw)
2323
-
2324
2243
  def get_letter_date_text(self, today=None):
2325
2244
  sc = self.site_config.site_company
2326
2245
  if today is None:
lino/core/store.py CHANGED
@@ -53,6 +53,8 @@ from lino.utils.format_date import fds
53
53
  from lino.utils import IncompleteDate
54
54
  from lino.core.utils import DelayedValue
55
55
 
56
+ FIELD_TYPES = {}
57
+
56
58
 
57
59
  class StoreField(object):
58
60
  """
@@ -207,6 +209,14 @@ class StoreField(object):
207
209
  setattr(instance, self.name, v)
208
210
  return True
209
211
 
212
+ @classmethod
213
+ def register_for_field(cls, dftype, elemtype):
214
+ if dftype in FIELD_TYPES:
215
+ raise Exception(f"Duplicate field type {dftype}")
216
+ FIELD_TYPES[dftype] = cls
217
+ from lino.core.elems import _FIELD2ELEM
218
+ _FIELD2ELEM.append((dftype, elemtype))
219
+
210
220
  def format_value(self, ar, v):
211
221
  """
212
222
  Return a plain textual representation as a unicode string
@@ -424,19 +434,6 @@ class PreviewTextStoreField(StoreField):
424
434
  for name in self.column_names():
425
435
  d[name] = getattr(row, name)
426
436
 
427
-
428
- class MeasurementStoreField(StoreField):
429
- def extract_form_data(self, obj, post_data, ar=None):
430
- unit = self.field.measurement.STANDARD_UNIT
431
- if hasattr(obj, "unit"):
432
- new_unit = post_data.get("unitHidden", None)
433
- if new_unit is not None:
434
- unit = new_unit
435
- elif obj.unit is not None:
436
- unit = obj.unit.name
437
- return self.field.measurement(**{unit: float(post_data.get(self.name, "0.0"))})
438
-
439
-
440
437
  # class LinkedForeignKeyField(ForeignKeyStoreField):
441
438
 
442
439
  # def get_rel_to(self,obj):
@@ -1062,13 +1059,13 @@ def create_atomizer(holder, fld, name):
1062
1059
  return DisplayStoreField(fld, name)
1063
1060
  if isinstance(fld, models.IntegerField):
1064
1061
  return IntegerStoreField(fld, name)
1065
- if isinstance(fld, fields.MeasurementField):
1066
- return MeasurementStoreField(fld, name)
1067
1062
  if isinstance(fld, fields.PreviewTextField):
1068
1063
  return PreviewTextStoreField(fld, name)
1069
1064
  if isinstance(fld, models.ManyToOneRel):
1070
1065
  # raise Exception("20190625 {} {} {}".format(holder, fld, name))
1071
1066
  return
1067
+ if (sft := FIELD_TYPES.get(fld.__class__, None)) is not None:
1068
+ return sft(fld, name)
1072
1069
  kw = {}
1073
1070
  if choosers.uses_simple_values(holder, fld):
1074
1071
  return StoreField(fld, name, **kw)
@@ -1230,10 +1227,10 @@ class Store(BaseStore):
1230
1227
  dh = form.get_layout_handle()
1231
1228
  self.collect_fields(self.card_fields, dh)
1232
1229
 
1233
- form = rh.actor.list_layout
1234
- if form:
1235
- dh = form.get_layout_handle()
1236
- self.collect_fields(self.item_fields, dh)
1230
+ # form = rh.actor.list_layout
1231
+ # if form:
1232
+ # dh = form.get_layout_handle()
1233
+ # self.collect_fields(self.item_fields, dh)
1237
1234
 
1238
1235
  if self.pk is not None:
1239
1236
  self.pk_index = 0
lino/core/tables.py CHANGED
@@ -639,23 +639,6 @@ method in order to sort the rows of the queryset.
639
639
  # # print("20181230 detail_pointer() {}".format(cls))
640
640
  # return obj.as_summary_item(ar)
641
641
 
642
- @classmethod
643
- def request(self, master_instance=None, **kw):
644
- """Return a new :class:`ActionRequest
645
- <lino.core.requests.ActionRequest>` on this table.
646
-
647
- The :attr:`master_instance
648
- <lino.core.requests.ActionRequest.master_instance>` can be
649
- specified as optional first positional argument.
650
-
651
- """
652
- from lino.core.requests import ActionRequest
653
- kw.update(actor=self)
654
- if master_instance is not None:
655
- kw.update(master_instance=master_instance)
656
- kw.setdefault("action", self.default_action)
657
- return ActionRequest(**kw)
658
-
659
642
  @classmethod
660
643
  def run_action_from_console(self, pk=None, an=None):
661
644
  """
lino/core/utils.py CHANGED
@@ -23,6 +23,7 @@ from django.core import exceptions
23
23
  from django.http import QueryDict
24
24
 
25
25
  from lino.utils.html import E, assert_safe, tostring
26
+ from lino.utils import capture_output
26
27
  from lino.utils.ranges import isrange
27
28
 
28
29
  from django.core.validators import validate_email, ValidationError, URLValidator
@@ -378,7 +379,7 @@ class UnresolvedModel(object):
378
379
  # ~ print(self)
379
380
 
380
381
  def __repr__(self):
381
- return self.__class__.__name__ + "(%s,%s)" % (self.model_spec, self.app_label)
382
+ return self.__class__.__name__ + "(%r, %s)" % (self.model_spec, self.app_label)
382
383
 
383
384
  # ~ def __getattr__(self,name):
384
385
  # ~ raise AttributeError("%s has no attribute %r" % (self,name))
@@ -420,6 +421,9 @@ def resolve_model(model_spec, app_label=None, strict=False):
420
421
  # settings.SITE.logger.info("20181230 resolve %s --> %r, %r",
421
422
  # model_spec, app, model)
422
423
  else:
424
+ # 20241112 Helped to explore #5797 (Could not resolve target
425
+ # 'uploads.UploadType' of ForeignKey 'type' in <class
426
+ # 'lino.modlib.uploads.models.Upload'>)
423
427
  from django.apps import apps
424
428
 
425
429
  try:
@@ -1063,7 +1067,7 @@ class InstanceAction:
1063
1067
  """
1064
1068
  if len(args) and isinstance(args[0], BaseRequest):
1065
1069
  raise ChangedAPI("20181004")
1066
- ar = self.bound_action.request()
1070
+ ar = self.bound_action.request(renderer=settings.SITE.kernel.text_renderer)
1067
1071
  self.run_from_code(ar, *args, **kwargs)
1068
1072
  return ar.response
1069
1073
 
@@ -1108,3 +1112,29 @@ class PhantomRow(VirtualRow):
1108
1112
 
1109
1113
  def __str__(self):
1110
1114
  return str(self._ar.get_action_title())
1115
+
1116
+
1117
+
1118
+ def login(username=None, **kwargs):
1119
+ """Return a basic :term:`action request` with the specified user signed in.
1120
+ """
1121
+ from lino.core.requests import BaseRequest # avoid circular import
1122
+ # settings.SITE.startup()
1123
+ User = settings.SITE.user_model
1124
+ if User and username:
1125
+ try:
1126
+ kwargs.update(user=User.objects.get(username=username))
1127
+ except User.DoesNotExist:
1128
+ raise User.DoesNotExist(f"'{username}' : no such user")
1129
+
1130
+ kwargs.setdefault("show_urls", False)
1131
+ # import lino.core.urls # hack: trigger ui instantiation
1132
+ return BaseRequest(**kwargs)
1133
+
1134
+ def show(*args, **kwargs):
1135
+ """Print the specified data table to stdout."""
1136
+ return login().show(*args, **kwargs)
1137
+
1138
+ def shows(*args, **kwargs):
1139
+ """Return the output of :func:`show`."""
1140
+ return capture_output(show, *args, **kwargs)
lino/core/views.py CHANGED
@@ -1,4 +1,4 @@
1
- # Copyright 2010-2020 Rumma & Ko Ltd
1
+ # Copyright 2010-2024 Rumma & Ko Ltd
2
2
  # License: GNU Affero General Public License v3 (see file COPYING for details)
3
3
  """Utility functions used by :mod:`lino.modlib.extjs.views`.
4
4
 
@@ -99,6 +99,7 @@ def action_request(app_label, actor, request, rqdata, is_list, **kw):
99
99
  # The text of an Exception may not be
100
100
  # internationalized because some error handling code
101
101
  # may want to write it to a plain ascii stream.
102
+ kw.update(renderer=settings.SITE.kernel.default_renderer)
102
103
  ar = rpt.request(request=request, action=a, rqdata=rqdata, **kw)
103
104
  # print("20210403b", a.action.__class__, rqdata)
104
105
  return ar
lino/help_texts.py CHANGED
@@ -56,8 +56,8 @@ help_texts = {
56
56
  'lino.mixins.periods.Started.save' : _("""Fills default value “today” to start_date"""),
57
57
  'lino.mixins.periods.Ended' : _("""Mixin for models with two fields end_date and end_time."""),
58
58
  'lino.mixins.periods.Ended.get_duration' : _("""Return the duration in hours."""),
59
- 'lino.mixins.periods.DateRange' : _("""Mixin for models that represent a period defined by a start date and an end date."""),
60
- 'lino.mixins.periods.ObservedDateRange' : _("""lino.core.param_panel.ParameterPanel with two fields start_date and end_date which default to empty."""),
59
+ 'lino.mixins.periods.DateRange' : _("""A model mixin that adds two fields start_date and end_date. DateRangeObservable"""),
60
+ 'lino.mixins.periods.ObservedDateRange' : _("""lino.core.param_panel.ParameterPanel with two fields start_date and end_date."""),
61
61
  'lino.mixins.periods.Yearly' : _("""An ObservedDateRange for which start_date defaults to Jan 1st and end_date to Dec 31 of the current year."""),
62
62
  'lino.mixins.periods.Monthly' : _("""An ObservedDateRange which defaults to the current month."""),
63
63
  'lino.mixins.periods.Weekly' : _("""An ObservedDateRange which defaults to the current week."""),
@@ -174,6 +174,7 @@ help_texts = {
174
174
  'lino.modlib.tinymce.Plugin.media_name' : _("""Lino currently includes three versions of TinyMCE, but for production sites we still use the eldest version 3.4.8."""),
175
175
  'lino.modlib.uploads.Plugin' : _("""See /dev/plugins."""),
176
176
  'lino.modlib.uploads.Plugin.remove_orphaned_files' : _("""Whether checkdata –fix should automatically delete orphaned files in the uploads folder."""),
177
+ 'lino.modlib.uploads.Plugin.with_thumbnails' : _("""Whether to use PIL, the Python Imaging Library."""),
177
178
  'lino.modlib.weasyprint.Plugin' : _("""See /dev/plugins."""),
178
179
  'lino.modlib.weasyprint.Plugin.header_height' : _("""Height of header in mm. Set to None if you want no header."""),
179
180
  'lino.modlib.weasyprint.Plugin.footer_height' : _("""Height of footer in mm. Set to None if you want no header."""),
@@ -376,8 +377,11 @@ help_texts = {
376
377
  'lino.modlib.linod.SystemTask.log_level' : _("""The logging level to apply when running this task."""),
377
378
  'lino.modlib.linod.SystemTask.run' : _("""Performs a routine job."""),
378
379
  'lino.modlib.linod.SystemTasks' : _("""The default actor for the SystemTask model."""),
379
- 'lino.modlib.periods.StoredPeriod' : _("""An accounting period is the smallest time slice to be declared in accounting reports. Usually it corresponds to one month. But there are except for some small companies which declare per quarter."""),
380
- 'lino.modlib.periods.StoredYears' : _("""The fiscal years available in this database."""),
380
+ 'lino.modlib.periods.StoredYear' : _("""The Django model used to store an fiscal year."""),
381
+ 'lino.modlib.periods.StoredPeriod' : _("""The Django model used to store an accounting period."""),
382
+ 'lino.modlib.periods.StoredYears' : _("""The fiscal years defined in this database."""),
383
+ 'lino.modlib.periods.StoredPeriods' : _("""The accounting periods defined in this database."""),
384
+ 'lino.modlib.periods.PeriodTypes' : _("""A list of choices for the values allowed as periods.period_type."""),
381
385
  'lino.modlib.periods.PeriodRange' : _("""Model mixin for objects that cover a range of accounting periods."""),
382
386
  'lino.modlib.periods.PeriodRange.start_period' : _("""The first period of the range to cover."""),
383
387
  'lino.modlib.periods.PeriodRange.end_period' : _("""The last period of the range to cover."""),
@@ -512,7 +516,6 @@ help_texts = {
512
516
  'lino.modlib.comments.Commentable.add_comments_filter' : _("""Add filters to the given queryset of comments, requested by the given user."""),
513
517
  '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 CommentsByOwner."""),
514
518
  'lino.modlib.comments.Commentable.on_commented' : _("""This is automatically called when a comment has been created or modified."""),
515
- 'lino.modlib.comments.Commentable.get_comment_group' : _("""(Currently not used)"""),
516
519
  'lino.modlib.files.Volume' : _("""The Django model representing a file volume."""),
517
520
  'lino.modlib.files.Volume.id' : _("""The primary key used to point to this volume from a database object."""),
518
521
  'lino.modlib.files.Volume.ref' : _("""The full path of the root folder."""),
@@ -654,6 +657,7 @@ help_texts = {
654
657
  'lino.modlib.system.DurationUnits' : _("""The list of possible duration units defined by this application."""),
655
658
  'lino.modlib.system.DurationUnit' : _("""Base class for the choices in the DurationUnits choicelist."""),
656
659
  'lino.modlib.system.DurationUnit.add_duration' : _("""Return a date or datetime obtained by adding value times this unit to the specified value orig. Returns None is orig is empty."""),
660
+ 'lino.modlib.system.DisplayColors' : _("""A list of colors to be specified for displaying."""),
657
661
  'lino.modlib.system.BleachChecker' : _("""A data checker used to find unbleached html content."""),
658
662
  'lino.modlib.system.Genders' : _("""Defines the possible choices for the gender of a person (“male”, “female” and “nonbinary”)."""),
659
663
  'lino.modlib.system.YesNo' : _("""A choicelist with two values “Yes” and “No”."""),
@@ -685,6 +689,7 @@ help_texts = {
685
689
  'lino.modlib.uploads.UploadController.show_uploads' : _("""Opens a data window with the uploaded files associated to this database object."""),
686
690
  'lino.modlib.uploads.UploadsByController' : _("""Shows the uploaded files associated to this database object."""),
687
691
  'lino.modlib.uploads.Shortcuts' : _("""The list of available upload shortcut fields in this application."""),
692
+ 'lino.modlib.uploads.Previewers' : _("""An abstract choicelist of the previewers that are available on this site."""),
688
693
  'lino.modlib.uploads.UploadsFolderChecker' : _("""Find orphaned files in uploads folder."""),
689
694
  'lino.modlib.weasyprint.WeasyBuildMethod' : _("""The base class for both build methods."""),
690
695
  'lino.modlib.weasyprint.WeasyHtmlBuildMethod' : _("""Renders the input template and returns the unmodified output as plain HTML."""),