lino 24.11.0__py3-none-any.whl → 25.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. lino/__init__.py +1 -1
  2. lino/core/actions.py +2 -4
  3. lino/core/actors.py +35 -14
  4. lino/core/dbtables.py +12 -12
  5. lino/core/fields.py +27 -18
  6. lino/core/inject.py +9 -2
  7. lino/core/kernel.py +11 -12
  8. lino/core/model.py +25 -39
  9. lino/core/renderer.py +3 -4
  10. lino/core/requests.py +91 -92
  11. lino/core/site.py +5 -46
  12. lino/core/store.py +4 -4
  13. lino/core/tables.py +0 -16
  14. lino/core/utils.py +5 -2
  15. lino/help_texts.py +7 -5
  16. lino/locale/bn/LC_MESSAGES/django.po +1225 -909
  17. lino/locale/de/LC_MESSAGES/django.mo +0 -0
  18. lino/locale/de/LC_MESSAGES/django.po +1748 -1392
  19. lino/locale/django.pot +1150 -909
  20. lino/locale/es/LC_MESSAGES/django.po +1721 -1347
  21. lino/locale/et/LC_MESSAGES/django.po +1267 -954
  22. lino/locale/fr/LC_MESSAGES/django.mo +0 -0
  23. lino/locale/fr/LC_MESSAGES/django.po +1207 -925
  24. lino/locale/nl/LC_MESSAGES/django.po +1261 -942
  25. lino/locale/pt_BR/LC_MESSAGES/django.po +1204 -906
  26. lino/locale/zh_Hant/LC_MESSAGES/django.po +1204 -906
  27. lino/mixins/dupable.py +8 -7
  28. lino/mixins/periods.py +10 -8
  29. lino/modlib/checkdata/__init__.py +1 -1
  30. lino/modlib/comments/choicelists.py +1 -1
  31. lino/modlib/comments/fixtures/demo2.py +4 -1
  32. lino/modlib/comments/mixins.py +9 -10
  33. lino/modlib/comments/models.py +4 -4
  34. lino/modlib/comments/ui.py +11 -2
  35. lino/modlib/dupable/models.py +10 -14
  36. lino/modlib/gfks/mixins.py +8 -1
  37. lino/modlib/jinja/choicelists.py +3 -3
  38. lino/modlib/jinja/renderer.py +2 -0
  39. lino/modlib/languages/fixtures/all_languages.py +4 -6
  40. lino/modlib/linod/mixins.py +3 -2
  41. lino/modlib/memo/mixins.py +17 -215
  42. lino/modlib/memo/models.py +41 -12
  43. lino/modlib/memo/parser.py +7 -3
  44. lino/modlib/notify/mixins.py +35 -34
  45. lino/modlib/periods/choicelists.py +46 -0
  46. lino/modlib/periods/mixins.py +26 -1
  47. lino/modlib/periods/models.py +17 -45
  48. lino/modlib/printing/choicelists.py +3 -3
  49. lino/modlib/publisher/choicelists.py +1 -4
  50. lino/modlib/search/models.py +17 -11
  51. lino/modlib/system/fixtures/__init__.py +0 -0
  52. lino/modlib/system/fixtures/std.py +5 -0
  53. lino/modlib/system/models.py +3 -2
  54. lino/modlib/uploads/__init__.py +10 -1
  55. lino/modlib/uploads/choicelists.py +3 -3
  56. lino/modlib/uploads/mixins.py +49 -51
  57. lino/modlib/uploads/models.py +144 -61
  58. lino/modlib/uploads/ui.py +12 -6
  59. lino/modlib/uploads/utils.py +113 -0
  60. lino/modlib/users/mixins.py +9 -6
  61. lino/modlib/weasyprint/choicelists.py +17 -7
  62. lino/utils/__init__.py +6 -0
  63. lino/utils/choosers.py +21 -8
  64. lino/utils/html.py +1 -1
  65. lino/utils/instantiator.py +9 -0
  66. lino/utils/media.py +2 -3
  67. lino/utils/soup.py +311 -0
  68. {lino-24.11.0.dist-info → lino-25.1.0.dist-info}/METADATA +2 -2
  69. {lino-24.11.0.dist-info → lino-25.1.0.dist-info}/RECORD +72 -67
  70. {lino-24.11.0.dist-info → lino-25.1.0.dist-info}/WHEEL +1 -1
  71. {lino-24.11.0.dist-info → lino-25.1.0.dist-info}/licenses/AUTHORS.rst +0 -0
  72. {lino-24.11.0.dist-info → lino-25.1.0.dist-info}/licenses/COPYING +0 -0
lino/core/requests.py CHANGED
@@ -81,6 +81,26 @@ Subject: {subject}
81
81
  def noop(ar):
82
82
  return ar.success(gettext("Aborted"))
83
83
 
84
+ def mi2bp(master_instance, bp):
85
+ if master_instance is not None:
86
+ if isinstance(master_instance, (models.Model, TableRow)):
87
+ bp[constants.URL_PARAM_MASTER_PK] = master_instance.pk
88
+ if ContentType is not None and isinstance(
89
+ master_instance, models.Model
90
+ ):
91
+ assert not master_instance._meta.abstract
92
+ mt = ContentType.objects.get_for_model(
93
+ master_instance.__class__).pk
94
+ bp[constants.URL_PARAM_MASTER_TYPE] = mt
95
+ # else:
96
+ # logger.warning("20141205 %s %s",
97
+ # self.master_instance,
98
+ # ContentType)
99
+ else: # if self.master is None:
100
+ # e.g. in accounting.MovementsByMatch the master is a `str`
101
+ bp[constants.URL_PARAM_MASTER_PK] = master_instance
102
+ # raise Exception("No master key for {} {!r}".format(
103
+ # self.master_instance.__class__, self.master_instance))
84
104
 
85
105
  class StringLogger(logging.Logger):
86
106
  # Instantiated by BaseRequest.capture_logger()
@@ -152,7 +172,7 @@ class ValidActionResponses(object):
152
172
 
153
173
  no_data_text = None
154
174
  title = None
155
- """The dynamic title to give to the window or component which shows this response.
175
+ """The dynamic title to give to the window or component that shows this response.
156
176
  TODO: is this still being used?
157
177
  """
158
178
 
@@ -308,7 +328,7 @@ class BaseRequest:
308
328
  action_param_values = None
309
329
  param_values = None
310
330
  bound_action = None
311
- known_values = {}
331
+ known_values = None
312
332
  no_data_text = _("No data to display")
313
333
  is_on_main_actor = True
314
334
  permalink_uris = False
@@ -317,6 +337,12 @@ class BaseRequest:
317
337
  master_instance = None
318
338
  request = None
319
339
  selected_rows = []
340
+ obvious_fields = None
341
+ order_by = None
342
+ filter = None
343
+ gridfilters = None,
344
+ quick_search = None
345
+ extra = None
320
346
  content_type = "application/json"
321
347
  requesting_panel = None
322
348
  xcallback_answers = {}
@@ -331,11 +357,19 @@ class BaseRequest:
331
357
  parent=None,
332
358
  is_on_main_actor=True,
333
359
  permalink_uris=None,
360
+ known_values=None,
361
+ obvious_fields=None,
334
362
  **kw
335
363
  ):
336
364
  self.response = dict()
365
+ self.obvious_fields = set()
366
+ if obvious_fields is not None:
367
+ self.obvious_fields |= obvious_fields
337
368
  # if str(kw.get('actor', None)) == "cal.EntriesByController":
338
369
  # if kw.get('actor', None):
370
+
371
+ self.known_values = dict()
372
+
339
373
  if request is not None:
340
374
  assert parent is None
341
375
  self.request = request
@@ -356,9 +390,11 @@ class BaseRequest:
356
390
  raise Exception("%s : %s is None" % (kw, k))
357
391
  else:
358
392
  kw[k] = getattr(parent, k)
359
- kv = kw.setdefault("known_values", {})
360
- if parent.actor is self.actor:
361
- kv.update(parent.known_values)
393
+ # kv = kw.setdefault("known_values", {})
394
+ if parent.actor is not None and parent.actor is self.actor:
395
+ kw['param_values'] = parent.param_values
396
+ if parent.known_values is not None:
397
+ self.known_values.update(parent.known_values)
362
398
  # kw.setdefault('user', parent.user)
363
399
  # kw.setdefault('subst_user', parent.subst_user)
364
400
  # kw.setdefault('renderer', parent.renderer)
@@ -377,10 +413,27 @@ class BaseRequest:
377
413
  self.is_on_main_actor = is_on_main_actor
378
414
  self.permalink_uris = permalink_uris
379
415
 
416
+ if known_values is not None:
417
+ self.known_values.update(known_values)
418
+
380
419
  self.setup(**kw)
381
420
 
382
421
  # print("20241101", self.__class__.__name__, self.show_urls)
383
422
 
423
+ self.obvious_fields |= set(self.known_values.keys())
424
+
425
+ if self.actor is not None:
426
+
427
+ for k, v in self.actor.known_values.items():
428
+ self.known_values.setdefault(k, v)
429
+
430
+ self.obvious_fields |= self.actor.obvious_fields
431
+ # self.obvious_fields.update(obvious_fields.split())
432
+ # if parent.obvious_fields is not None:
433
+ if parent is not None and parent.actor is self.actor and parent.obvious_fields:
434
+ self.obvious_fields |= parent.obvious_fields
435
+ self.obvious_fields.add(self.actor.master_key)
436
+
384
437
  if self.master is not None and settings.SITE.strict_master_check:
385
438
  if self.master_instance is None:
386
439
  raise exceptions.BadRequest(
@@ -409,12 +462,21 @@ class BaseRequest:
409
462
  requesting_panel=None,
410
463
  renderer=None,
411
464
  xcallback_answers=None,
412
- known_values={},
413
465
  show_urls=None,
466
+ quick_search=None,
467
+ order_by=None,
468
+ filter=None,
469
+ gridfilters=None,
470
+ extra=None,
414
471
  ):
415
472
  if logger is not None:
416
473
  self.logger = logger
417
474
  self.requesting_panel = requesting_panel
475
+ self.quick_search = quick_search
476
+ self.order_by = order_by
477
+ self.filter = filter
478
+ self.gridfilters = gridfilters
479
+ self.extra = extra
418
480
  if user is None:
419
481
  self.user = settings.SITE.get_anonymous_user()
420
482
  else:
@@ -532,36 +594,11 @@ class BaseRequest:
532
594
  master_key=rqdata.get(constants.URL_PARAM_MASTER_PK, None),
533
595
  )
534
596
 
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
597
  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]
598
+ v = rqdata.get(constants.URL_PARAM_GRIDFILTER, None)
599
+ if v is not None:
600
+ v = json.loads(v)
601
+ kw["gridfilters"] = [constants.dict2kw(flt) for flt in v]
565
602
 
566
603
  # kw = ActionRequest.parse_req(self, request, rqdata, **kw)
567
604
  if settings.SITE.user_model:
@@ -962,6 +999,8 @@ class BaseRequest:
962
999
  )
963
1000
  return
964
1001
 
1002
+ self.logger.info("Send email '%s' from %s to %s", subject, sender, recipients)
1003
+
965
1004
  recipients = [a for a in recipients if "@example.com" not in a]
966
1005
  if not len(recipients):
967
1006
  self.logger.info(
@@ -970,8 +1009,6 @@ class BaseRequest:
970
1009
  # self.logger.info("Email body would have been %s", body)
971
1010
  return
972
1011
 
973
- self.logger.info("Send email '%s' from %s to %s", subject, sender, recipients)
974
-
975
1012
  kw = {}
976
1013
  if body.startswith("<"):
977
1014
  kw["html_message"] = body
@@ -1128,14 +1165,17 @@ class BaseRequest:
1128
1165
  an obvious value.
1129
1166
 
1130
1167
  """
1131
- if name in self.known_values:
1132
- return True
1133
- # if self.param_values is not None and name in self.param_values:
1168
+ return name in self.obvious_fields
1169
+ # if name in self.known_values:
1134
1170
  # return True
1135
- if self.actor is not None:
1136
- if self.actor.master_key == name:
1137
- return True
1138
- return False
1171
+ # # if self.param_values is not None and name in self.param_values:
1172
+ # # return True
1173
+ # if self.actor is not None:
1174
+ # if self.actor.master_key == name:
1175
+ # return True
1176
+ # if name in self.actor.obvious_fields:
1177
+ # return True
1178
+ # return False
1139
1179
 
1140
1180
  def get_user(self):
1141
1181
  """
@@ -1338,10 +1378,8 @@ class BaseRequest:
1338
1378
  return self.renderer.obj2url(self, *args, **kwargs)
1339
1379
 
1340
1380
  def obj2html(self, obj, text=None, **kwargs):
1341
- """
1342
- Return a clickable HTML element that points to a detail view of the
1343
- given database object.
1344
- """
1381
+ # Return a clickable HTML element that points to a detail view of the
1382
+ # given database object.
1345
1383
  if obj is None:
1346
1384
  return ""
1347
1385
  return self.renderer.obj2html(self, obj, text, **kwargs)
@@ -1666,11 +1704,8 @@ class ActionRequest(BaseRequest):
1666
1704
  renderer = None
1667
1705
  offset = None
1668
1706
  limit = None
1669
- order_by = None
1670
1707
  master = None
1671
- extra = None
1672
1708
  title = None
1673
- filter = None
1674
1709
  limit = None
1675
1710
  offset = None
1676
1711
 
@@ -1703,28 +1738,16 @@ class ActionRequest(BaseRequest):
1703
1738
 
1704
1739
  def setup(
1705
1740
  self,
1706
- known_values=None,
1707
1741
  param_values=None,
1708
1742
  action_param_values={},
1709
- quick_search=None,
1710
- order_by=None,
1711
1743
  offset=None,
1712
1744
  limit=None,
1713
1745
  title=None,
1714
- filter=None,
1715
- gridfilters=None,
1716
1746
  exclude=None,
1717
1747
  selected_pks=None,
1718
1748
  selected_rows=None,
1719
- extra=None,
1720
1749
  **kw
1721
1750
  ):
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
1751
  if title is not None:
1729
1752
  self.title = title
1730
1753
  if offset is not None:
@@ -1743,12 +1766,7 @@ class ActionRequest(BaseRequest):
1743
1766
  assert self.actor is not None
1744
1767
  request = self.request
1745
1768
 
1746
- kv = dict()
1747
- for k, v in self.actor.known_values.items():
1748
- kv.setdefault(k, v)
1749
- if known_values:
1750
- kv.update(known_values)
1751
- self.known_values = kv
1769
+ self.actor.setup_request(self)
1752
1770
 
1753
1771
  if self.actor.parameters is not None:
1754
1772
  pv = self.actor.param_defaults(self)
@@ -1826,8 +1844,6 @@ class ActionRequest(BaseRequest):
1826
1844
  self.set_action_param_values(**action_param_values)
1827
1845
  self.bound_action.setup_action_request(self)
1828
1846
 
1829
- self.actor.setup_request(self)
1830
-
1831
1847
  # if str(self.actor) == 'outbox.MyOutbox':
1832
1848
  # if self.master_instance is None:
1833
1849
  # raise Exception("20230426 b {}".format(self.master))
@@ -1899,6 +1915,8 @@ class ActionRequest(BaseRequest):
1899
1915
  if target is None:
1900
1916
  target = self.actor
1901
1917
  if self._insert_sar is None:
1918
+ if target.insert_action is None:
1919
+ raise Exception(f"20241212 {target} has no insert_action")
1902
1920
  self._insert_sar = target.insert_action.request_from(self)
1903
1921
  else:
1904
1922
  assert self._insert_sar.actor is target
@@ -2132,26 +2150,7 @@ class ActionRequest(BaseRequest):
2132
2150
  for k, v in self.known_values.items():
2133
2151
  if self.actor.known_values.get(k, None) != v:
2134
2152
  bp[k] = v
2135
- if self.master_instance is not None:
2136
- if isinstance(self.master_instance, (models.Model, TableRow)):
2137
- bp[constants.URL_PARAM_MASTER_PK] = self.master_instance.pk
2138
- if ContentType is not None and isinstance(
2139
- self.master_instance, models.Model
2140
- ):
2141
- assert not self.master_instance._meta.abstract
2142
- mt = ContentType.objects.get_for_model(
2143
- self.master_instance.__class__
2144
- ).pk
2145
- bp[constants.URL_PARAM_MASTER_TYPE] = mt
2146
- # else:
2147
- # logger.warning("20141205 %s %s",
2148
- # self.master_instance,
2149
- # ContentType)
2150
- else: # if self.master is None:
2151
- # e.g. in accounting.MovementsByMatch the master is a `str`
2152
- bp[constants.URL_PARAM_MASTER_PK] = self.master_instance
2153
- # raise Exception("No master key for {} {!r}".format(
2154
- # self.master_instance.__class__, self.master_instance))
2153
+ mi2bp(self.master_instance, bp)
2155
2154
  self._status = kw
2156
2155
  return kw
2157
2156
 
@@ -2445,7 +2444,7 @@ class ActionRequest(BaseRequest):
2445
2444
  oh = ar.actor.override_column_headers(ar)
2446
2445
  if oh:
2447
2446
  for i, e in enumerate(columns):
2448
- header = oh.get(e.name, None)
2447
+ header = oh.get(e, None)
2449
2448
  if header is not None:
2450
2449
  headers[i] = header
2451
2450
  # ~ print 20120507, oh, headers
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):
@@ -2206,7 +2165,7 @@ class Site(object):
2206
2165
  site_prefix = "/"
2207
2166
  """The string to prefix to every URL of the Lino web interface.
2208
2167
 
2209
- This must *start and end with a *slash*. Default value is
2168
+ This must *start and end* with a *slash*. Default value is
2210
2169
  ``'/'``.
2211
2170
 
2212
2171
  This must be set if your project is not being served at the "root"
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/tables.py CHANGED
@@ -521,22 +521,6 @@ method in order to sort the rows of the queryset.
521
521
  return cls.detail_action
522
522
  return actions.ShowTable()
523
523
 
524
- @classmethod
525
- def row_as_paragraph(cls, ar, row):
526
- """Return an HTML string that represents the given row as a single
527
- paragraph.
528
-
529
- See :ref:`dev.as_paragraph`.
530
- """
531
- return row.as_paragraph(ar)
532
-
533
- @classmethod
534
- def row_as_page(cls, ar, row, **kwargs):
535
- """
536
- Return an HTML string that represent the given row as a plain page.
537
- """
538
- return row.as_page(ar, **kwargs)
539
-
540
524
  @classmethod
541
525
  def get_actor_editable(self):
542
526
  if self._editable is None:
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."""),
@@ -322,8 +323,10 @@ help_texts = {
322
323
  'lino.core.model.Model.allow_cascaded_delete' : _("""A set of names of ForeignKey or GenericForeignKey fields of this model that allow for cascaded delete."""),
323
324
  'lino.core.model.Model.disabled_fields' : _("""Return a set of field names that should be disabled (i.e. not editable) for this database object."""),
324
325
  'lino.core.model.Model.__str__' : _("""Return a translatable text that describes this database row."""),
325
- 'lino.core.model.Model.as_paragraph' : _("""Return a safe HTML string that represents this database row as a paragraph."""),
326
+ 'lino.core.model.Model.as_str' : _("""Return a translatable text that describes this database row. Unlike __str__() this method gets an action request when it is called, so it knows the context."""),
327
+ 'lino.core.model.Model.get_str_words' : _("""Yield a series of words that describe this database row in plain text."""),
326
328
  'lino.core.model.Model.as_summary_item' : _("""Return a HTML element that represents this database row in a data window in display mode “summary”."""),
329
+ 'lino.core.model.Model.as_paragraph' : _("""Return a safe HTML string that represents this database row as a paragraph."""),
327
330
  'lino.core.model.Model.set_widget_options' : _("""Set default values for the widget options of a given element."""),
328
331
  'lino.core.model.Model.get_overview_elems' : _("""Return a list of HTML elements to be shown in overview field."""),
329
332
  'lino.core.model.Model.merge_row' : _("""Merge this object into another object of same class."""),
@@ -376,7 +379,7 @@ help_texts = {
376
379
  'lino.modlib.linod.SystemTask.log_level' : _("""The logging level to apply when running this task."""),
377
380
  'lino.modlib.linod.SystemTask.run' : _("""Performs a routine job."""),
378
381
  'lino.modlib.linod.SystemTasks' : _("""The default actor for the SystemTask model."""),
379
- 'lino.modlib.periods.StoredYear' : _("""The Django model used to store an fiscal year."""),
382
+ 'lino.modlib.periods.StoredYear' : _("""The Django model used to store a fiscal year."""),
380
383
  'lino.modlib.periods.StoredPeriod' : _("""The Django model used to store an accounting period."""),
381
384
  'lino.modlib.periods.StoredYears' : _("""The fiscal years defined in this database."""),
382
385
  'lino.modlib.periods.StoredPeriods' : _("""The accounting periods defined in this database."""),
@@ -515,7 +518,6 @@ help_texts = {
515
518
  'lino.modlib.comments.Commentable.add_comments_filter' : _("""Add filters to the given queryset of comments, requested by the given user."""),
516
519
  '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
520
  '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
521
  'lino.modlib.files.Volume' : _("""The Django model representing a file volume."""),
520
522
  'lino.modlib.files.Volume.id' : _("""The primary key used to point to this volume from a database object."""),
521
523
  'lino.modlib.files.Volume.ref' : _("""The full path of the root folder."""),