lino 24.11.1__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 (51) hide show
  1. lino/__init__.py +1 -1
  2. lino/core/actors.py +33 -12
  3. lino/core/fields.py +26 -18
  4. lino/core/kernel.py +4 -6
  5. lino/core/model.py +5 -16
  6. lino/core/renderer.py +1 -2
  7. lino/core/requests.py +70 -47
  8. lino/core/site.py +1 -1
  9. lino/core/tables.py +0 -16
  10. lino/help_texts.py +4 -3
  11. lino/locale/bn/LC_MESSAGES/django.po +58 -45
  12. lino/locale/de/LC_MESSAGES/django.mo +0 -0
  13. lino/locale/de/LC_MESSAGES/django.po +79 -108
  14. lino/locale/django.pot +55 -44
  15. lino/locale/es/LC_MESSAGES/django.po +56 -44
  16. lino/locale/et/LC_MESSAGES/django.po +58 -45
  17. lino/locale/fr/LC_MESSAGES/django.mo +0 -0
  18. lino/locale/fr/LC_MESSAGES/django.po +60 -48
  19. lino/locale/nl/LC_MESSAGES/django.po +59 -45
  20. lino/locale/pt_BR/LC_MESSAGES/django.po +55 -44
  21. lino/locale/zh_Hant/LC_MESSAGES/django.po +55 -44
  22. lino/mixins/dupable.py +8 -7
  23. lino/mixins/periods.py +4 -4
  24. lino/modlib/checkdata/__init__.py +1 -1
  25. lino/modlib/comments/ui.py +7 -3
  26. lino/modlib/dupable/models.py +10 -14
  27. lino/modlib/gfks/mixins.py +8 -1
  28. lino/modlib/jinja/choicelists.py +3 -3
  29. lino/modlib/jinja/renderer.py +2 -0
  30. lino/modlib/languages/fixtures/all_languages.py +4 -6
  31. lino/modlib/memo/mixins.py +7 -7
  32. lino/modlib/memo/models.py +41 -12
  33. lino/modlib/memo/parser.py +7 -3
  34. lino/modlib/notify/mixins.py +8 -8
  35. lino/modlib/periods/choicelists.py +46 -0
  36. lino/modlib/periods/mixins.py +26 -0
  37. lino/modlib/periods/models.py +17 -45
  38. lino/modlib/publisher/choicelists.py +1 -4
  39. lino/modlib/uploads/mixins.py +37 -37
  40. lino/modlib/uploads/models.py +68 -18
  41. lino/modlib/uploads/utils.py +6 -0
  42. lino/modlib/users/mixins.py +9 -6
  43. lino/modlib/weasyprint/choicelists.py +17 -7
  44. lino/utils/choosers.py +21 -8
  45. lino/utils/instantiator.py +9 -0
  46. lino/utils/soup.py +5 -5
  47. {lino-24.11.1.dist-info → lino-25.1.0.dist-info}/METADATA +4 -2
  48. {lino-24.11.1.dist-info → lino-25.1.0.dist-info}/RECORD +51 -50
  49. {lino-24.11.1.dist-info → lino-25.1.0.dist-info}/WHEEL +1 -1
  50. {lino-24.11.1.dist-info → lino-25.1.0.dist-info}/licenses/AUTHORS.rst +0 -0
  51. {lino-24.11.1.dist-info → lino-25.1.0.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.1'
29
+ __version__ = '25.1.0'
30
30
 
31
31
  # import setuptools # avoid UserWarning "Distutils was imported before Setuptools"?
32
32
 
lino/core/actors.py CHANGED
@@ -261,6 +261,9 @@ class Actor(actions.Parametrizable, Permittable, metaclass=ActorMetaClass):
261
261
 
262
262
  _detail_action_class = actions.ShowDetail
263
263
 
264
+ obvious_fields = set()
265
+ """A set of names of fields that are considered :term:`obvious field`. """
266
+
264
267
  required_roles = set([SiteUser])
265
268
  """See :doc:`/dev/perms`."""
266
269
 
@@ -443,8 +446,7 @@ class Actor(actions.Parametrizable, Permittable, metaclass=ActorMetaClass):
443
446
 
444
447
  detail_template = None # deprecated: use insert_layout instead
445
448
  insert_template = None # deprecated: use detail_layout instead
446
-
447
- row_template = "{row}"
449
+ row_template = None # "{row}"
448
450
 
449
451
  help_text = None
450
452
  detail_action = None
@@ -722,12 +724,6 @@ class Actor(actions.Parametrizable, Permittable, metaclass=ActorMetaClass):
722
724
  def on_analyze(self, site):
723
725
  pass
724
726
 
725
- @classmethod
726
- def row_as_summary(cls, ar, obj, text=None, **kwargs):
727
- if ar is None:
728
- return text or str(obj)
729
- return obj.as_summary_item(ar, text, **kwargs)
730
-
731
727
  @classmethod
732
728
  def do_setup(self):
733
729
  pass
@@ -1014,11 +1010,14 @@ class Actor(actions.Parametrizable, Permittable, metaclass=ActorMetaClass):
1014
1010
  An example is described in :ref:`avanti.specs.get_request_detail_action`
1015
1011
 
1016
1012
  """
1013
+ if (ba := cls.detail_action) is None:
1014
+ return None
1015
+ if ar is None:
1016
+ return ba
1017
1017
  ut = ar.get_user().user_type
1018
- ba = cls.detail_action
1019
1018
  if (
1020
- ar.actor.detail_action is not None
1021
- and ar.actor
1019
+ ar.actor is not None
1020
+ and ar.actor.detail_action is not None
1022
1021
  and ba.action is ar.actor.detail_action.action
1023
1022
  ):
1024
1023
  # 20210223 When this actor uses the same action, don't return
@@ -1243,7 +1242,7 @@ class Actor(actions.Parametrizable, Permittable, metaclass=ActorMetaClass):
1243
1242
  window on a given row of this actor.
1244
1243
 
1245
1244
  """
1246
- return str(obj)
1245
+ return obj.as_str(ar)
1247
1246
 
1248
1247
  @classmethod
1249
1248
  def get_card_title(self, ar, obj):
@@ -1256,6 +1255,28 @@ class Actor(actions.Parametrizable, Permittable, metaclass=ActorMetaClass):
1256
1255
  ):
1257
1256
  return None
1258
1257
 
1258
+ @classmethod
1259
+ def row_as_summary(cls, ar, obj, text=None, **kwargs):
1260
+ if ar is None:
1261
+ return text or str(obj)
1262
+ return obj.as_summary_item(ar, text, **kwargs)
1263
+
1264
+ @classmethod
1265
+ def row_as_paragraph(cls, ar, row):
1266
+ """Return an HTML string that represents the given row as a single
1267
+ paragraph.
1268
+
1269
+ See :ref:`dev.as_paragraph`.
1270
+ """
1271
+ return row.as_paragraph(ar)
1272
+
1273
+ @classmethod
1274
+ def row_as_page(cls, ar, row, **kwargs):
1275
+ """
1276
+ Return an HTML string that represent the given row as a plain page.
1277
+ """
1278
+ return row.as_page(ar, **kwargs)
1279
+
1259
1280
  @classmethod
1260
1281
  def get_choices_text(self, obj, ar, field):
1261
1282
  """
lino/core/fields.py CHANGED
@@ -69,10 +69,8 @@ def set_default_verbose_name(f):
69
69
  later. These fields don't have a name.
70
70
 
71
71
  """
72
- if f.name is None:
73
- if f.verbose_name is None:
74
- f.verbose_name = f.remote_field.model._meta.verbose_name
75
- elif f.verbose_name == f.name.replace("_", " "):
72
+ if f.verbose_name is None or (
73
+ f.name is not None and f.verbose_name == f.name.replace("_", " ")):
76
74
  f.verbose_name = f.remote_field.model._meta.verbose_name
77
75
 
78
76
 
@@ -294,6 +292,7 @@ class FakeField(object):
294
292
  wildcard_data_elem = False
295
293
  """Whether to consider this field as wildcard data element.
296
294
  """
295
+
297
296
  sortable_by = None
298
297
  """
299
298
  A list of names of real fields to be used for sorting when this
@@ -658,6 +657,7 @@ class VirtualField(FakeField):
658
657
  return
659
658
  self.return_type.model = VirtualModel(model)
660
659
  self.return_type.column = None
660
+ self.return_type.name = name
661
661
 
662
662
  # if name == "overview":
663
663
  # print("20181022", self, self.verbose_name)
@@ -811,7 +811,7 @@ class VirtualBooleanField(VirtualField):
811
811
 
812
812
  def virtualfield(return_type, **kwargs):
813
813
  """
814
- Decorator to turn a method into a :class:`VirtualField`.
814
+ Decorator to turn a model method into a :class:`VirtualField`.
815
815
  """
816
816
 
817
817
  def decorator(fn):
@@ -1334,8 +1334,8 @@ class TableRow(object):
1334
1334
  # ar.actor, self.__class__, ar.actor.model))
1335
1335
  dt = self.__class__.get_default_table()
1336
1336
  if dt is not None:
1337
- # a = dt.get_request_detail_action(ar)
1338
- a = dt.detail_action
1337
+ a = dt.get_request_detail_action(ar)
1338
+ # a = dt.detail_action
1339
1339
  if a is None or ar is None:
1340
1340
  return a
1341
1341
  if a.get_view_permission(ar.get_user().user_type):
@@ -1343,7 +1343,8 @@ class TableRow(object):
1343
1343
  return a
1344
1344
 
1345
1345
  def get_choices_text(self, ar, actor, field):
1346
- return str(self)
1346
+ return self.as_str(ar)
1347
+ # return str(self)
1347
1348
 
1348
1349
  # @fields.displayfield(_("Description"))
1349
1350
  # @htmlbox(_("Overview"))
@@ -1365,13 +1366,24 @@ class TableRow(object):
1365
1366
  # return [ar.obj2html(self)]
1366
1367
  return [self.as_summary_item(ar)]
1367
1368
 
1369
+ def as_str(self, ar):
1370
+ # must return a str
1371
+ if ar.actor is None:
1372
+ return str(self)
1373
+ elif ar.actor.row_template is None or not isinstance(self, ar.actor.model):
1374
+ return " ".join(self.get_str_words(ar))
1375
+ return ar.actor.row_template.format(row=self)
1376
+
1377
+ def get_str_words(self, ar):
1378
+ # must yield a sequence of str (or Promise)
1379
+ yield str(self)
1380
+
1368
1381
  def as_summary_item(self, ar, text=None, **kwargs):
1369
1382
  # must return an ET element
1370
1383
  if ar is None:
1371
1384
  return text or str(self)
1372
- if text is None and ar.actor is not None:
1373
- text = ar.actor.row_template.format(row=self)
1374
- # raise Exception("20241029")
1385
+ if text is None:
1386
+ text = self.as_str(ar)
1375
1387
  return ar.obj2html(self, text, **kwargs)
1376
1388
 
1377
1389
  def as_paragraph(self, ar, **kwargs):
@@ -1710,12 +1722,10 @@ def choices_for_field(ar, holder, field):
1710
1722
  # same code as for ForeignKey
1711
1723
  def row2dict(obj, d):
1712
1724
  d[constants.CHOICES_TEXT_FIELD] = holder.get_choices_text(
1713
- obj, ar.request, field
1714
- )
1725
+ obj, ar, field)
1715
1726
  d[constants.CHOICES_VALUE_FIELD] = obj.pk
1716
1727
  return d
1717
1728
  else: # values are (value, text) tuples
1718
-
1719
1729
  def row2dict(obj, d):
1720
1730
  d[constants.CHOICES_TEXT_FIELD] = str(obj[1])
1721
1731
  d[constants.CHOICES_VALUE_FIELD] = obj[0]
@@ -1732,8 +1742,7 @@ def choices_for_field(ar, holder, field):
1732
1742
  d[constants.CHOICES_VALUE_FIELD] = obj[0]
1733
1743
  else:
1734
1744
  d[constants.CHOICES_TEXT_FIELD] = holder.get_choices_text(
1735
- obj, ar.request, field
1736
- )
1745
+ obj, ar, field)
1737
1746
  d[constants.CHOICES_VALUE_FIELD] = str(obj)
1738
1747
  return d
1739
1748
 
@@ -1757,8 +1766,7 @@ def choices_for_field(ar, holder, field):
1757
1766
 
1758
1767
  def row2dict(obj, d):
1759
1768
  d[constants.CHOICES_TEXT_FIELD] = holder.get_choices_text(
1760
- obj, ar.request, field
1761
- )
1769
+ obj, ar, field)
1762
1770
  d[constants.CHOICES_VALUE_FIELD] = obj.pk
1763
1771
  return d
1764
1772
  else:
lino/core/kernel.py CHANGED
@@ -21,8 +21,6 @@ application.
21
21
  """
22
22
 
23
23
  import os
24
- from os.path import join, dirname
25
-
26
24
  import sys
27
25
  import time
28
26
  import codecs
@@ -49,6 +47,7 @@ from django.db import models
49
47
  from lino import logger
50
48
  from lino.utils import codetime
51
49
  from lino.utils.html import E
50
+ from lino.core.utils import format_request
52
51
  # from lino.utils import isiterable
53
52
  from lino.core import layouts
54
53
  from lino.core import actors
@@ -166,6 +165,7 @@ class Kernel(object):
166
165
 
167
166
  def h(signum, frame):
168
167
  site.shutdown()
168
+ sys.exit()
169
169
 
170
170
  signal.signal(sig, h)
171
171
 
@@ -827,7 +827,7 @@ class Kernel(object):
827
827
  A(ar.bound_action.full_name())
828
828
  A(obj2str(ar.master_instance))
829
829
  A(obj2str(ar.selected_rows))
830
- # A(format_request(ar.request))
830
+ A(format_request(ar.request))
831
831
  ar.logger.info("run_action {0}".format(" ".join(flds)))
832
832
  # logger.info("run_action {0}".format(ar))
833
833
  # if str(a) != 'grid_put':
@@ -910,8 +910,6 @@ class Kernel(object):
910
910
  # logger.info("STATIC_ROOT does not exist: %s", settings.STATIC_ROOT)
911
911
  # return 0
912
912
  fn = self.site.media_root / fn
913
- # fn = join(settings.STATIC_ROOT, fn)
914
- # fn = join(self.site.site_dir, fn)
915
913
  if not force and not self._must_build and fn.exists():
916
914
  mtime = os.stat(fn).st_mtime
917
915
  if mtime > self.code_mtime:
@@ -931,7 +929,7 @@ class Kernel(object):
931
929
  if verbosity > 1:
932
930
  logger.info("Building %s ...", fn)
933
931
 
934
- self.site.makedirs_if_missing(dirname(fn))
932
+ self.site.makedirs_if_missing(fn.parent)
935
933
  f = codecs.open(fn, "w", encoding="utf-8")
936
934
  try:
937
935
  write(f)
lino/core/model.py CHANGED
@@ -1,5 +1,5 @@
1
1
  # -*- coding: UTF-8 -*-
2
- # Copyright 2009-2022 Rumma & Ko Ltd
2
+ # Copyright 2009-2024 Rumma & Ko Ltd
3
3
  # License: GNU Affero General Public License v3 (see file COPYING for details)
4
4
  """Defines the :class:`Model` class.
5
5
 
@@ -37,30 +37,20 @@ from .workflows import ChangeStateAction
37
37
  from .requests import ActionRequest, sliced_data_iterator
38
38
  from .tables import AbstractTable
39
39
 
40
- # try:
41
- # import bleach
42
- # except ImportError:
43
- # bleach = None
44
-
45
40
 
46
41
  class Model(models.Model, fields.TableRow):
42
+
47
43
  class Meta(object):
48
44
  abstract = True
49
45
 
50
46
  allow_cascaded_copy = frozenset()
51
47
  allow_cascaded_delete = frozenset()
52
-
53
48
  grid_post = actions.CreateRow()
54
49
  submit_insert = actions.SubmitInsert()
55
-
56
50
  allow_merge_action = False
57
-
58
51
  master_data_fields = None
59
-
60
52
  show_in_site_search = True
61
-
62
53
  quick_search_fields = None
63
-
64
54
  quick_search_fields_digit = None
65
55
 
66
56
  active_fields = frozenset()
@@ -88,10 +78,6 @@ class Model(models.Model, fields.TableRow):
88
78
  Internally used by :mod:`lino.modlib.changes`.
89
79
  """
90
80
 
91
- # disable_create_choice = False
92
- # """Whether to disable any automatic creation by learning choosers.
93
- # """
94
-
95
81
  _lino_tables = []
96
82
  _bleached_fields = []
97
83
 
@@ -956,11 +942,14 @@ LINO_MODEL_ATTRIBS = (
956
942
  "hidden_elements",
957
943
  "get_simple_parameters",
958
944
  "get_request_queryset",
945
+ "get_request_words",
959
946
  "get_title_tags",
960
947
  "get_default_table",
961
948
  "get_default_table",
962
949
  "get_layout_aliases",
963
950
  "edge_ngram_field",
951
+ "as_str",
952
+ "get_str_words",
964
953
  "as_summary_item",
965
954
  "as_paragraph",
966
955
  "as_page",
lino/core/renderer.py CHANGED
@@ -506,8 +506,7 @@ class HtmlRenderer(Renderer):
506
506
 
507
507
  """
508
508
  if text is None:
509
- # text = (force_str(obj),)
510
- text = (str(obj),)
509
+ text = (obj.as_str(ar), )
511
510
  elif isinstance(text, str) or iselement(text):
512
511
  text = (text,)
513
512
  url = self.obj2url(ar, obj)
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,7 @@ class BaseRequest:
317
337
  master_instance = None
318
338
  request = None
319
339
  selected_rows = []
340
+ obvious_fields = None
320
341
  order_by = None
321
342
  filter = None
322
343
  gridfilters = None,
@@ -336,11 +357,19 @@ class BaseRequest:
336
357
  parent=None,
337
358
  is_on_main_actor=True,
338
359
  permalink_uris=None,
360
+ known_values=None,
361
+ obvious_fields=None,
339
362
  **kw
340
363
  ):
341
364
  self.response = dict()
365
+ self.obvious_fields = set()
366
+ if obvious_fields is not None:
367
+ self.obvious_fields |= obvious_fields
342
368
  # if str(kw.get('actor', None)) == "cal.EntriesByController":
343
369
  # if kw.get('actor', None):
370
+
371
+ self.known_values = dict()
372
+
344
373
  if request is not None:
345
374
  assert parent is None
346
375
  self.request = request
@@ -361,9 +390,11 @@ class BaseRequest:
361
390
  raise Exception("%s : %s is None" % (kw, k))
362
391
  else:
363
392
  kw[k] = getattr(parent, k)
364
- kv = kw.setdefault("known_values", {})
365
- if parent.actor is self.actor:
366
- 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)
367
398
  # kw.setdefault('user', parent.user)
368
399
  # kw.setdefault('subst_user', parent.subst_user)
369
400
  # kw.setdefault('renderer', parent.renderer)
@@ -382,10 +413,27 @@ class BaseRequest:
382
413
  self.is_on_main_actor = is_on_main_actor
383
414
  self.permalink_uris = permalink_uris
384
415
 
416
+ if known_values is not None:
417
+ self.known_values.update(known_values)
418
+
385
419
  self.setup(**kw)
386
420
 
387
421
  # print("20241101", self.__class__.__name__, self.show_urls)
388
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
+
389
437
  if self.master is not None and settings.SITE.strict_master_check:
390
438
  if self.master_instance is None:
391
439
  raise exceptions.BadRequest(
@@ -414,7 +462,6 @@ class BaseRequest:
414
462
  requesting_panel=None,
415
463
  renderer=None,
416
464
  xcallback_answers=None,
417
- known_values={},
418
465
  show_urls=None,
419
466
  quick_search=None,
420
467
  order_by=None,
@@ -1118,14 +1165,17 @@ class BaseRequest:
1118
1165
  an obvious value.
1119
1166
 
1120
1167
  """
1121
- if name in self.known_values:
1122
- return True
1123
- # 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:
1124
1170
  # return True
1125
- if self.actor is not None:
1126
- if self.actor.master_key == name:
1127
- return True
1128
- 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
1129
1179
 
1130
1180
  def get_user(self):
1131
1181
  """
@@ -1328,10 +1378,8 @@ class BaseRequest:
1328
1378
  return self.renderer.obj2url(self, *args, **kwargs)
1329
1379
 
1330
1380
  def obj2html(self, obj, text=None, **kwargs):
1331
- """
1332
- Return a clickable HTML element that points to a detail view of the
1333
- given database object.
1334
- """
1381
+ # Return a clickable HTML element that points to a detail view of the
1382
+ # given database object.
1335
1383
  if obj is None:
1336
1384
  return ""
1337
1385
  return self.renderer.obj2html(self, obj, text, **kwargs)
@@ -1690,7 +1738,6 @@ class ActionRequest(BaseRequest):
1690
1738
 
1691
1739
  def setup(
1692
1740
  self,
1693
- known_values=None,
1694
1741
  param_values=None,
1695
1742
  action_param_values={},
1696
1743
  offset=None,
@@ -1719,12 +1766,7 @@ class ActionRequest(BaseRequest):
1719
1766
  assert self.actor is not None
1720
1767
  request = self.request
1721
1768
 
1722
- kv = dict()
1723
- for k, v in self.actor.known_values.items():
1724
- kv.setdefault(k, v)
1725
- if known_values:
1726
- kv.update(known_values)
1727
- self.known_values = kv
1769
+ self.actor.setup_request(self)
1728
1770
 
1729
1771
  if self.actor.parameters is not None:
1730
1772
  pv = self.actor.param_defaults(self)
@@ -1802,8 +1844,6 @@ class ActionRequest(BaseRequest):
1802
1844
  self.set_action_param_values(**action_param_values)
1803
1845
  self.bound_action.setup_action_request(self)
1804
1846
 
1805
- self.actor.setup_request(self)
1806
-
1807
1847
  # if str(self.actor) == 'outbox.MyOutbox':
1808
1848
  # if self.master_instance is None:
1809
1849
  # raise Exception("20230426 b {}".format(self.master))
@@ -1875,6 +1915,8 @@ class ActionRequest(BaseRequest):
1875
1915
  if target is None:
1876
1916
  target = self.actor
1877
1917
  if self._insert_sar is None:
1918
+ if target.insert_action is None:
1919
+ raise Exception(f"20241212 {target} has no insert_action")
1878
1920
  self._insert_sar = target.insert_action.request_from(self)
1879
1921
  else:
1880
1922
  assert self._insert_sar.actor is target
@@ -2108,26 +2150,7 @@ class ActionRequest(BaseRequest):
2108
2150
  for k, v in self.known_values.items():
2109
2151
  if self.actor.known_values.get(k, None) != v:
2110
2152
  bp[k] = v
2111
- if self.master_instance is not None:
2112
- if isinstance(self.master_instance, (models.Model, TableRow)):
2113
- bp[constants.URL_PARAM_MASTER_PK] = self.master_instance.pk
2114
- if ContentType is not None and isinstance(
2115
- self.master_instance, models.Model
2116
- ):
2117
- assert not self.master_instance._meta.abstract
2118
- mt = ContentType.objects.get_for_model(
2119
- self.master_instance.__class__
2120
- ).pk
2121
- bp[constants.URL_PARAM_MASTER_TYPE] = mt
2122
- # else:
2123
- # logger.warning("20141205 %s %s",
2124
- # self.master_instance,
2125
- # ContentType)
2126
- else: # if self.master is None:
2127
- # e.g. in accounting.MovementsByMatch the master is a `str`
2128
- bp[constants.URL_PARAM_MASTER_PK] = self.master_instance
2129
- # raise Exception("No master key for {} {!r}".format(
2130
- # self.master_instance.__class__, self.master_instance))
2153
+ mi2bp(self.master_instance, bp)
2131
2154
  self._status = kw
2132
2155
  return kw
2133
2156
 
@@ -2421,7 +2444,7 @@ class ActionRequest(BaseRequest):
2421
2444
  oh = ar.actor.override_column_headers(ar)
2422
2445
  if oh:
2423
2446
  for i, e in enumerate(columns):
2424
- header = oh.get(e.name, None)
2447
+ header = oh.get(e, None)
2425
2448
  if header is not None:
2426
2449
  headers[i] = header
2427
2450
  # ~ print 20120507, oh, headers
lino/core/site.py CHANGED
@@ -2165,7 +2165,7 @@ class Site(object):
2165
2165
  site_prefix = "/"
2166
2166
  """The string to prefix to every URL of the Lino web interface.
2167
2167
 
2168
- This must *start and end with a *slash*. Default value is
2168
+ This must *start and end* with a *slash*. Default value is
2169
2169
  ``'/'``.
2170
2170
 
2171
2171
  This must be set if your project is not being served at the "root"
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/help_texts.py CHANGED
@@ -323,8 +323,10 @@ help_texts = {
323
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."""),
324
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."""),
325
325
  'lino.core.model.Model.__str__' : _("""Return a translatable text that describes this database row."""),
326
- '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."""),
327
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."""),
328
330
  'lino.core.model.Model.set_widget_options' : _("""Set default values for the widget options of a given element."""),
329
331
  'lino.core.model.Model.get_overview_elems' : _("""Return a list of HTML elements to be shown in overview field."""),
330
332
  'lino.core.model.Model.merge_row' : _("""Merge this object into another object of same class."""),
@@ -377,7 +379,7 @@ help_texts = {
377
379
  'lino.modlib.linod.SystemTask.log_level' : _("""The logging level to apply when running this task."""),
378
380
  'lino.modlib.linod.SystemTask.run' : _("""Performs a routine job."""),
379
381
  'lino.modlib.linod.SystemTasks' : _("""The default actor for the SystemTask model."""),
380
- '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."""),
381
383
  'lino.modlib.periods.StoredPeriod' : _("""The Django model used to store an accounting period."""),
382
384
  'lino.modlib.periods.StoredYears' : _("""The fiscal years defined in this database."""),
383
385
  'lino.modlib.periods.StoredPeriods' : _("""The accounting periods defined in this database."""),
@@ -689,7 +691,6 @@ help_texts = {
689
691
  'lino.modlib.uploads.UploadController.show_uploads' : _("""Opens a data window with the uploaded files associated to this database object."""),
690
692
  'lino.modlib.uploads.UploadsByController' : _("""Shows the uploaded files associated to this database object."""),
691
693
  '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."""),
693
694
  'lino.modlib.uploads.UploadsFolderChecker' : _("""Find orphaned files in uploads folder."""),
694
695
  'lino.modlib.weasyprint.WeasyBuildMethod' : _("""The base class for both build methods."""),
695
696
  'lino.modlib.weasyprint.WeasyHtmlBuildMethod' : _("""Renders the input template and returns the unmodified output as plain HTML."""),