lino 24.11.0__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 (54) hide show
  1. lino/__init__.py +1 -1
  2. lino/core/actions.py +2 -4
  3. lino/core/actors.py +2 -2
  4. lino/core/dbtables.py +12 -12
  5. lino/core/fields.py +1 -0
  6. lino/core/inject.py +9 -2
  7. lino/core/kernel.py +7 -6
  8. lino/core/model.py +24 -27
  9. lino/core/renderer.py +2 -2
  10. lino/core/requests.py +21 -45
  11. lino/core/site.py +4 -45
  12. lino/core/store.py +4 -4
  13. lino/core/utils.py +5 -2
  14. lino/help_texts.py +4 -3
  15. lino/locale/bn/LC_MESSAGES/django.po +1210 -907
  16. lino/locale/de/LC_MESSAGES/django.po +1760 -1375
  17. lino/locale/django.pot +1136 -906
  18. lino/locale/es/LC_MESSAGES/django.po +1709 -1347
  19. lino/locale/et/LC_MESSAGES/django.po +1206 -906
  20. lino/locale/fr/LC_MESSAGES/django.mo +0 -0
  21. lino/locale/fr/LC_MESSAGES/django.po +1193 -923
  22. lino/locale/nl/LC_MESSAGES/django.po +1247 -942
  23. lino/locale/pt_BR/LC_MESSAGES/django.po +1190 -903
  24. lino/locale/zh_Hant/LC_MESSAGES/django.po +1190 -903
  25. lino/mixins/periods.py +6 -4
  26. lino/modlib/comments/choicelists.py +1 -1
  27. lino/modlib/comments/fixtures/demo2.py +4 -1
  28. lino/modlib/comments/mixins.py +9 -10
  29. lino/modlib/comments/models.py +4 -4
  30. lino/modlib/comments/ui.py +5 -0
  31. lino/modlib/linod/mixins.py +3 -2
  32. lino/modlib/memo/mixins.py +10 -208
  33. lino/modlib/notify/mixins.py +33 -32
  34. lino/modlib/periods/mixins.py +0 -1
  35. lino/modlib/printing/choicelists.py +3 -3
  36. lino/modlib/search/models.py +17 -11
  37. lino/modlib/system/fixtures/__init__.py +0 -0
  38. lino/modlib/system/fixtures/std.py +5 -0
  39. lino/modlib/system/models.py +3 -2
  40. lino/modlib/uploads/__init__.py +10 -1
  41. lino/modlib/uploads/choicelists.py +3 -3
  42. lino/modlib/uploads/mixins.py +30 -32
  43. lino/modlib/uploads/models.py +89 -56
  44. lino/modlib/uploads/ui.py +12 -6
  45. lino/modlib/uploads/utils.py +107 -0
  46. lino/utils/__init__.py +6 -0
  47. lino/utils/html.py +1 -1
  48. lino/utils/media.py +2 -3
  49. lino/utils/soup.py +311 -0
  50. {lino-24.11.0.dist-info → lino-24.11.1.dist-info}/METADATA +1 -3
  51. {lino-24.11.0.dist-info → lino-24.11.1.dist-info}/RECORD +54 -50
  52. {lino-24.11.0.dist-info → lino-24.11.1.dist-info}/WHEEL +1 -1
  53. {lino-24.11.0.dist-info → lino-24.11.1.dist-info}/licenses/AUTHORS.rst +0 -0
  54. {lino-24.11.0.dist-info → lino-24.11.1.dist-info}/licenses/COPYING +0 -0
lino/__init__.py CHANGED
@@ -26,7 +26,7 @@ defines no models, some template files, a series of :term:`django-admin commands
26
26
 
27
27
  """
28
28
 
29
- __version__ = '24.11.0'
29
+ __version__ = '24.11.1'
30
30
 
31
31
  # import setuptools # avoid UserWarning "Distutils was imported before Setuptools"?
32
32
 
lino/core/actions.py CHANGED
@@ -87,7 +87,7 @@ def install_layout(cls, k, layout_class, **options):
87
87
  - `cls` is the actor (a class object)
88
88
 
89
89
  - `k` is one of 'grid_layout', 'detail_layout', 'insert_layout',
90
- 'params_layout', 'card_layout', 'list_layout'
90
+ 'params_layout', 'card_layout'
91
91
 
92
92
  - `layout_class`
93
93
 
@@ -575,7 +575,7 @@ class Action(Parametrizable, Permittable):
575
575
 
576
576
  def is_window_action(self):
577
577
  """Return `True` if this is a "window action" (i.e. which opens a GUI
578
- window on the client before executin).
578
+ window on the client before executing).
579
579
 
580
580
  """
581
581
  return self.opens_a_window or (self.parameters and not self.no_params_window)
@@ -805,9 +805,7 @@ class ShowTable(TableAction):
805
805
  return self.label or self.defining_actor.label
806
806
 
807
807
  def get_window_layout(self, actor):
808
- # ~ return self.actor.list_layout
809
808
  return None
810
- # return actor.card_layout or actor.list_layout # 20210910
811
809
 
812
810
  def get_window_size(self, actor):
813
811
  return actor.window_size
lino/core/actors.py CHANGED
@@ -439,7 +439,7 @@ class Actor(actions.Parametrizable, Permittable, metaclass=ActorMetaClass):
439
439
  detail_layout = None
440
440
  insert_layout = None
441
441
  card_layout = None
442
- list_layout = None
442
+ list_layout = None # no longer used
443
443
 
444
444
  detail_template = None # deprecated: use insert_layout instead
445
445
  insert_template = None # deprecated: use detail_layout instead
@@ -836,7 +836,7 @@ class Actor(actions.Parametrizable, Permittable, metaclass=ActorMetaClass):
836
836
  window_size=(cls.insert_layout_width, "auto"),
837
837
  )
838
838
  actions.install_layout(cls, "card_layout", layouts.DetailLayout)
839
- actions.install_layout(cls, "list_layout", layouts.DetailLayout)
839
+ # actions.install_layout(cls, "list_layout", layouts.DetailLayout)
840
840
 
841
841
  cls.extra_layouts = dict()
842
842
  for name, main in cls.get_extra_layouts():
lino/core/dbtables.py CHANGED
@@ -209,18 +209,18 @@ class Table(AbstractTable):
209
209
  """If True will position the quick search input to the bottom of the header and have it full width.
210
210
  Default: False"""
211
211
 
212
- @classmethod
213
- def init_layouts(cls):
214
- if cls.model is not None and issubclass(cls.model, Model):
215
- if not cls.abstract:
216
- for tbl in cls.__mro__:
217
- # print("20210629", tbl)
218
- if "list_layout" in tbl.__dict__:
219
- break
220
- if issubclass(tbl, actors.Actor):
221
- cls.list_layout = "list_item"
222
- break
223
- super().init_layouts()
212
+ # @classmethod
213
+ # def init_layouts(cls):
214
+ # if cls.model is not None and issubclass(cls.model, Model):
215
+ # if not cls.abstract:
216
+ # for tbl in cls.__mro__:
217
+ # # print("20210629", tbl)
218
+ # if "list_layout" in tbl.__dict__:
219
+ # break
220
+ # if issubclass(tbl, actors.Actor):
221
+ # cls.list_layout = "list_item"
222
+ # break
223
+ # super().init_layouts()
224
224
 
225
225
  @classmethod
226
226
  def add_quick_search_filter(cls, qs, search_text):
lino/core/fields.py CHANGED
@@ -1375,6 +1375,7 @@ class TableRow(object):
1375
1375
  return ar.obj2html(self, text, **kwargs)
1376
1376
 
1377
1377
  def as_paragraph(self, ar, **kwargs):
1378
+ # must return a safe html string
1378
1379
  if ar is None:
1379
1380
  return escape(str(self))
1380
1381
  return tostring(self.as_summary_item(ar, **kwargs))
lino/core/inject.py CHANGED
@@ -111,15 +111,22 @@ def check_pending_injects(sender, models_list=None, **kw):
111
111
  # raise Exception(20150304)
112
112
  # called from kernel.analyze_models()
113
113
  # ~ logger.info("20130212 check_pending_injects()...")
114
+ # if PENDING_INJECTS:
115
+ # for spec, todos in PENDING_INJECTS.items():
116
+ # model = resolve_model(spec, strict=True)
117
+ # if todos is not None:
118
+ # for func, caller in todos:
119
+ # func(model)
120
+
114
121
  if PENDING_INJECTS:
115
122
  msg = ""
116
- for spec, funcs in list(PENDING_INJECTS.items()):
123
+ for spec, funcs in PENDING_INJECTS.items():
117
124
  msg += spec + ": "
118
125
  msg += ", ".join([fmt(f) for f in funcs])
119
126
  # ~ msg += '\n'.join([str(dir(func)) for func in funcs])
120
127
  # ~ msg += '\n'.join([str(func.func_code.co_consts) for func in funcs])
121
128
  # ~ msg += str(funcs)
122
- raise Exception("Oops, there are pending injects: %s" % msg)
129
+ raise Exception(f"Oops, there are pending injects: {msg}")
123
130
  # ~ logger.warning("pending injects: %s", msg)
124
131
 
125
132
  # ~ logger.info("20131110 no pending injects")
lino/core/kernel.py CHANGED
@@ -298,8 +298,11 @@ class Kernel(object):
298
298
  # if f.__class__ is models.CharField and f.null:
299
299
  # msg = "Nullable CharField %s in %s" % (f.name, model)
300
300
  # raise Exception(msg)
301
+
301
302
  if isinstance(f, models.ForeignKey):
303
+ # f.remote_field.model = resolve_model(f.remote_field.model)
302
304
  if isinstance(f.remote_field.model, str):
305
+ # models_list = [f"{m}({m._meta.abstract})".format(m) for m in models_list]
303
306
  raise Exception(
304
307
  "Could not resolve target %r of "
305
308
  "ForeignKey '%s' in %s "
@@ -308,12 +311,10 @@ class Kernel(object):
308
311
  )
309
312
 
310
313
  set_default_verbose_name(f)
311
- """
312
- If JobProvider is an MTI child of Company,
313
- then mti.delete_child(JobProvider) must not fail on a
314
- JobProvider being referred only by objects that can refer
315
- to a Company as well.
316
- """
314
+ # If JobProvider is an MTI child of Company,
315
+ # then mti.delete_child(JobProvider) must not fail on a
316
+ # JobProvider being referred only by objects that can refer
317
+ # to a Company as well.
317
318
  if not hasattr(f.remote_field.model, "_lino_ddh"):
318
319
  msg = "20150824 {1} (needed by {0}) " "has no _lino_ddh"
319
320
  raise Exception(
lino/core/model.py CHANGED
@@ -20,6 +20,7 @@ from django.db.models.signals import pre_delete
20
20
  from django.utils.text import format_lazy
21
21
 
22
22
  from lino.utils.html import E, forcetext, tostring, join_elems
23
+ from lino.utils.soup import sanitize
23
24
 
24
25
  from lino.core import fields
25
26
  from lino.core import signals
@@ -36,10 +37,10 @@ from .workflows import ChangeStateAction
36
37
  from .requests import ActionRequest, sliced_data_iterator
37
38
  from .tables import AbstractTable
38
39
 
39
- try:
40
- import bleach
41
- except ImportError:
42
- bleach = None
40
+ # try:
41
+ # import bleach
42
+ # except ImportError:
43
+ # bleach = None
43
44
 
44
45
 
45
46
  class Model(models.Model, fields.TableRow):
@@ -308,8 +309,7 @@ class Model(models.Model, fields.TableRow):
308
309
  if isinstance(f, RichTextField):
309
310
  if f.editable and (
310
311
  f.bleached is True
311
- or f.bleached is None
312
- and settings.SITE.textfield_bleached
312
+ or f.bleached is None and settings.SITE.textfield_bleached
313
313
  ):
314
314
  bleached_fields.append(f)
315
315
  cls._bleached_fields = tuple(bleached_fields)
@@ -428,27 +428,24 @@ class Model(models.Model, fields.TableRow):
428
428
  setattr(self, f.name, new)
429
429
 
430
430
  def fields_to_bleach(self):
431
- if bleach:
432
- for f in self._bleached_fields:
433
- old = getattr(self, f.name)
434
- if old is None:
435
- continue
436
- # print("20190626", self, f, old)
437
- if not old.startswith("<"):
438
- continue
439
- try:
440
- new = bleach.clean(
441
- old,
442
- tags=settings.SITE.bleach_allowed_tags,
443
- attributes=settings.SITE.bleach_allowed_attributes,
444
- strip=True,
445
- )
446
- except TypeError as e:
447
- logger.warning("Could not bleach %r : %s (%s)", old, e, self)
448
- continue
449
- if old != new:
450
- logger.debug("Bleaching %s from %r to %r", f.name, old, new)
451
- yield f, old, new
431
+ for f in self._bleached_fields:
432
+ old = getattr(self, f.name)
433
+ if old is None:
434
+ continue
435
+ new = sanitize(old)
436
+ # try:
437
+ # new = bleach.clean(
438
+ # new,
439
+ # tags=settings.SITE.bleach_allowed_tags,
440
+ # attributes=settings.SITE.bleach_allowed_attributes,
441
+ # strip=True,
442
+ # )
443
+ # except TypeError as e:
444
+ # logger.warning("Could not bleach %r : %s (%s)", old, e, self)
445
+ # continue
446
+ if old != new:
447
+ logger.debug("Bleaching %s from %r to %r", f.name, old, new)
448
+ yield f, old, new
452
449
 
453
450
  updatable_panels = None
454
451
 
lino/core/renderer.py CHANGED
@@ -197,7 +197,7 @@ class HtmlRenderer(Renderer):
197
197
  import rstgen
198
198
 
199
199
  if display_mode == constants.DISPLAY_MODE_CARDS:
200
- layout = ar.actor.card_layout or ar.actor.list_layout
200
+ layout = ar.actor.card_layout # or ar.actor.list_layout
201
201
  lh = layout.get_layout_handle()
202
202
  else:
203
203
  lh = ar.bound_action.get_layout_handel()
@@ -1264,7 +1264,7 @@ class JsCacheRenderer(JsRenderer):
1264
1264
  # if res.parameters is not None:
1265
1265
  add(res, self.param_panels, res.params_layout, "%s.ParamsPanel" % res)
1266
1266
  add(res, self.other_panels, res.card_layout, "%s.CardsPanel" % res)
1267
- add(res, self.other_panels, res.list_layout, "%s.ItemsPanel" % res)
1267
+ # add(res, self.other_panels, res.list_layout, "%s.ItemsPanel" % res)
1268
1268
 
1269
1269
  for ba in res.get_actions():
1270
1270
  if ba.action.parameters:
lino/core/requests.py CHANGED
@@ -317,6 +317,11 @@ class BaseRequest:
317
317
  master_instance = None
318
318
  request = None
319
319
  selected_rows = []
320
+ order_by = None
321
+ filter = None
322
+ gridfilters = None,
323
+ quick_search = None
324
+ extra = None
320
325
  content_type = "application/json"
321
326
  requesting_panel = None
322
327
  xcallback_answers = {}
@@ -411,10 +416,20 @@ class BaseRequest:
411
416
  xcallback_answers=None,
412
417
  known_values={},
413
418
  show_urls=None,
419
+ quick_search=None,
420
+ order_by=None,
421
+ filter=None,
422
+ gridfilters=None,
423
+ extra=None,
414
424
  ):
415
425
  if logger is not None:
416
426
  self.logger = logger
417
427
  self.requesting_panel = requesting_panel
428
+ self.quick_search = quick_search
429
+ self.order_by = order_by
430
+ self.filter = filter
431
+ self.gridfilters = gridfilters
432
+ self.extra = extra
418
433
  if user is None:
419
434
  self.user = settings.SITE.get_anonymous_user()
420
435
  else:
@@ -532,36 +547,11 @@ class BaseRequest:
532
547
  master_key=rqdata.get(constants.URL_PARAM_MASTER_PK, None),
533
548
  )
534
549
 
535
- # if settings.SITE.use_filterRow:
536
- # exclude = dict()
537
- # for f in self.ah.store.fields:
538
- # if f.field:
539
- # filterOption = rqdata.get(
540
- # 'filter[%s_filterOption]' % f.field.name)
541
- # if filterOption == 'empty':
542
- # kw[f.field.name + "__isnull"] = True
543
- # elif filterOption == 'notempty':
544
- # kw[f.field.name + "__isnull"] = False
545
- # else:
546
- # filterValue = rqdata.get('filter[%s]' % f.field.name)
547
- # if filterValue:
548
- # if not filterOption:
549
- # filterOption = 'contains'
550
- # if filterOption == 'contains':
551
- # kw[f.field.name + "__icontains"] = filterValue
552
- # elif filterOption == 'doesnotcontain':
553
- # exclude[f.field.name +
554
- # "__icontains"] = filterValue
555
- # else:
556
- # print("unknown filterOption %r" % filterOption)
557
- # if len(exclude):
558
- # kw.update(exclude=exclude)
559
-
560
550
  if settings.SITE.use_gridfilters:
561
- filter = rqdata.get(constants.URL_PARAM_GRIDFILTER, None)
562
- if filter is not None:
563
- filter = json.loads(filter)
564
- kw["gridfilters"] = [constants.dict2kw(flt) for flt in filter]
551
+ v = rqdata.get(constants.URL_PARAM_GRIDFILTER, None)
552
+ if v is not None:
553
+ v = json.loads(v)
554
+ kw["gridfilters"] = [constants.dict2kw(flt) for flt in v]
565
555
 
566
556
  # kw = ActionRequest.parse_req(self, request, rqdata, **kw)
567
557
  if settings.SITE.user_model:
@@ -962,6 +952,8 @@ class BaseRequest:
962
952
  )
963
953
  return
964
954
 
955
+ self.logger.info("Send email '%s' from %s to %s", subject, sender, recipients)
956
+
965
957
  recipients = [a for a in recipients if "@example.com" not in a]
966
958
  if not len(recipients):
967
959
  self.logger.info(
@@ -970,8 +962,6 @@ class BaseRequest:
970
962
  # self.logger.info("Email body would have been %s", body)
971
963
  return
972
964
 
973
- self.logger.info("Send email '%s' from %s to %s", subject, sender, recipients)
974
-
975
965
  kw = {}
976
966
  if body.startswith("<"):
977
967
  kw["html_message"] = body
@@ -1666,11 +1656,8 @@ class ActionRequest(BaseRequest):
1666
1656
  renderer = None
1667
1657
  offset = None
1668
1658
  limit = None
1669
- order_by = None
1670
1659
  master = None
1671
- extra = None
1672
1660
  title = None
1673
- filter = None
1674
1661
  limit = None
1675
1662
  offset = None
1676
1663
 
@@ -1706,25 +1693,14 @@ class ActionRequest(BaseRequest):
1706
1693
  known_values=None,
1707
1694
  param_values=None,
1708
1695
  action_param_values={},
1709
- quick_search=None,
1710
- order_by=None,
1711
1696
  offset=None,
1712
1697
  limit=None,
1713
1698
  title=None,
1714
- filter=None,
1715
- gridfilters=None,
1716
1699
  exclude=None,
1717
1700
  selected_pks=None,
1718
1701
  selected_rows=None,
1719
- extra=None,
1720
1702
  **kw
1721
1703
  ):
1722
- self.quick_search = quick_search
1723
- self.order_by = order_by
1724
- self.filter = filter
1725
- self.gridfilters = gridfilters
1726
- self.extra = extra
1727
-
1728
1704
  if title is not None:
1729
1705
  self.title = title
1730
1706
  if offset is not None:
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
@@ -627,6 +584,8 @@ class Site(object):
627
584
  for k in ("DATABASES", "SECRET_KEY"):
628
585
  self.django_settings[k] = self.master_site.django_settings[k]
629
586
 
587
+ self.update_settings(
588
+ EMAIL_SUBJECT_PREFIX=f'[{self.project_name}] ')
630
589
  self.update_settings(
631
590
  SERIALIZATION_MODULES={
632
591
  "py": "lino.utils.dpy",
@@ -896,7 +855,7 @@ class Site(object):
896
855
  # the default_ui.
897
856
  return
898
857
  raise Exception(
899
- "Tried to install {} where {} " "is already installed.".format(
858
+ "Tried to install {}, but {} is already installed.".format(
900
859
  app_name, other
901
860
  )
902
861
  )
@@ -1009,7 +968,7 @@ class Site(object):
1009
968
  for r in p.get_requirements(self):
1010
969
  reqs.add(r)
1011
970
  if self.textfield_bleached:
1012
- reqs.add("bleach")
971
+ reqs.add("beautifulsoup4")
1013
972
  return sorted(reqs)
1014
973
 
1015
974
  def setup_plugins(self):
lino/core/store.py CHANGED
@@ -1227,10 +1227,10 @@ class Store(BaseStore):
1227
1227
  dh = form.get_layout_handle()
1228
1228
  self.collect_fields(self.card_fields, dh)
1229
1229
 
1230
- form = rh.actor.list_layout
1231
- if form:
1232
- dh = form.get_layout_handle()
1233
- 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)
1234
1234
 
1235
1235
  if self.pk is not None:
1236
1236
  self.pk_index = 0
lino/core/utils.py CHANGED
@@ -379,7 +379,7 @@ class UnresolvedModel(object):
379
379
  # ~ print(self)
380
380
 
381
381
  def __repr__(self):
382
- 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)
383
383
 
384
384
  # ~ def __getattr__(self,name):
385
385
  # ~ raise AttributeError("%s has no attribute %r" % (self,name))
@@ -421,6 +421,9 @@ def resolve_model(model_spec, app_label=None, strict=False):
421
421
  # settings.SITE.logger.info("20181230 resolve %s --> %r, %r",
422
422
  # model_spec, app, model)
423
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'>)
424
427
  from django.apps import apps
425
428
 
426
429
  try:
@@ -1113,7 +1116,7 @@ class PhantomRow(VirtualRow):
1113
1116
 
1114
1117
 
1115
1118
  def login(username=None, **kwargs):
1116
- """Return a basic :term:`action request` with the specified user signed in.
1119
+ """Return a basic :term:`action request` with the specified user signed in.
1117
1120
  """
1118
1121
  from lino.core.requests import BaseRequest # avoid circular import
1119
1122
  # settings.SITE.startup()
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."""),
@@ -515,7 +516,6 @@ help_texts = {
515
516
  'lino.modlib.comments.Commentable.add_comments_filter' : _("""Add filters to the given queryset of comments, requested by the given user."""),
516
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."""),
517
518
  'lino.modlib.comments.Commentable.on_commented' : _("""This is automatically called when a comment has been created or modified."""),
518
- 'lino.modlib.comments.Commentable.get_comment_group' : _("""(Currently not used)"""),
519
519
  'lino.modlib.files.Volume' : _("""The Django model representing a file volume."""),
520
520
  'lino.modlib.files.Volume.id' : _("""The primary key used to point to this volume from a database object."""),
521
521
  'lino.modlib.files.Volume.ref' : _("""The full path of the root folder."""),
@@ -689,6 +689,7 @@ help_texts = {
689
689
  'lino.modlib.uploads.UploadController.show_uploads' : _("""Opens a data window with the uploaded files associated to this database object."""),
690
690
  'lino.modlib.uploads.UploadsByController' : _("""Shows the uploaded files associated to this database object."""),
691
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."""),
692
693
  'lino.modlib.uploads.UploadsFolderChecker' : _("""Find orphaned files in uploads folder."""),
693
694
  'lino.modlib.weasyprint.WeasyBuildMethod' : _("""The base class for both build methods."""),
694
695
  'lino.modlib.weasyprint.WeasyHtmlBuildMethod' : _("""Renders the input template and returns the unmodified output as plain HTML."""),