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/__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__ = '25.1.0'
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
@@ -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
 
@@ -439,12 +442,11 @@ class Actor(actions.Parametrizable, Permittable, metaclass=ActorMetaClass):
439
442
  detail_layout = None
440
443
  insert_layout = None
441
444
  card_layout = None
442
- list_layout = None
445
+ list_layout = None # no longer used
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
@@ -836,7 +832,7 @@ class Actor(actions.Parametrizable, Permittable, metaclass=ActorMetaClass):
836
832
  window_size=(cls.insert_layout_width, "auto"),
837
833
  )
838
834
  actions.install_layout(cls, "card_layout", layouts.DetailLayout)
839
- actions.install_layout(cls, "list_layout", layouts.DetailLayout)
835
+ # actions.install_layout(cls, "list_layout", layouts.DetailLayout)
840
836
 
841
837
  cls.extra_layouts = dict()
842
838
  for name, main in cls.get_extra_layouts():
@@ -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/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
@@ -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,16 +1366,28 @@ 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):
1390
+ # must return a safe html string
1378
1391
  if ar is None:
1379
1392
  return escape(str(self))
1380
1393
  return tostring(self.as_summary_item(ar, **kwargs))
@@ -1709,12 +1722,10 @@ def choices_for_field(ar, holder, field):
1709
1722
  # same code as for ForeignKey
1710
1723
  def row2dict(obj, d):
1711
1724
  d[constants.CHOICES_TEXT_FIELD] = holder.get_choices_text(
1712
- obj, ar.request, field
1713
- )
1725
+ obj, ar, field)
1714
1726
  d[constants.CHOICES_VALUE_FIELD] = obj.pk
1715
1727
  return d
1716
1728
  else: # values are (value, text) tuples
1717
-
1718
1729
  def row2dict(obj, d):
1719
1730
  d[constants.CHOICES_TEXT_FIELD] = str(obj[1])
1720
1731
  d[constants.CHOICES_VALUE_FIELD] = obj[0]
@@ -1731,8 +1742,7 @@ def choices_for_field(ar, holder, field):
1731
1742
  d[constants.CHOICES_VALUE_FIELD] = obj[0]
1732
1743
  else:
1733
1744
  d[constants.CHOICES_TEXT_FIELD] = holder.get_choices_text(
1734
- obj, ar.request, field
1735
- )
1745
+ obj, ar, field)
1736
1746
  d[constants.CHOICES_VALUE_FIELD] = str(obj)
1737
1747
  return d
1738
1748
 
@@ -1756,8 +1766,7 @@ def choices_for_field(ar, holder, field):
1756
1766
 
1757
1767
  def row2dict(obj, d):
1758
1768
  d[constants.CHOICES_TEXT_FIELD] = holder.get_choices_text(
1759
- obj, ar.request, field
1760
- )
1769
+ obj, ar, field)
1761
1770
  d[constants.CHOICES_VALUE_FIELD] = obj.pk
1762
1771
  return d
1763
1772
  else:
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
@@ -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
 
@@ -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(
@@ -826,7 +827,7 @@ class Kernel(object):
826
827
  A(ar.bound_action.full_name())
827
828
  A(obj2str(ar.master_instance))
828
829
  A(obj2str(ar.selected_rows))
829
- # A(format_request(ar.request))
830
+ A(format_request(ar.request))
830
831
  ar.logger.info("run_action {0}".format(" ".join(flds)))
831
832
  # logger.info("run_action {0}".format(ar))
832
833
  # if str(a) != 'grid_put':
@@ -909,8 +910,6 @@ class Kernel(object):
909
910
  # logger.info("STATIC_ROOT does not exist: %s", settings.STATIC_ROOT)
910
911
  # return 0
911
912
  fn = self.site.media_root / fn
912
- # fn = join(settings.STATIC_ROOT, fn)
913
- # fn = join(self.site.site_dir, fn)
914
913
  if not force and not self._must_build and fn.exists():
915
914
  mtime = os.stat(fn).st_mtime
916
915
  if mtime > self.code_mtime:
@@ -930,7 +929,7 @@ class Kernel(object):
930
929
  if verbosity > 1:
931
930
  logger.info("Building %s ...", fn)
932
931
 
933
- self.site.makedirs_if_missing(dirname(fn))
932
+ self.site.makedirs_if_missing(fn.parent)
934
933
  f = codecs.open(fn, "w", encoding="utf-8")
935
934
  try:
936
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
 
@@ -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,30 +37,20 @@ 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
43
-
44
40
 
45
41
  class Model(models.Model, fields.TableRow):
42
+
46
43
  class Meta(object):
47
44
  abstract = True
48
45
 
49
46
  allow_cascaded_copy = frozenset()
50
47
  allow_cascaded_delete = frozenset()
51
-
52
48
  grid_post = actions.CreateRow()
53
49
  submit_insert = actions.SubmitInsert()
54
-
55
50
  allow_merge_action = False
56
-
57
51
  master_data_fields = None
58
-
59
52
  show_in_site_search = True
60
-
61
53
  quick_search_fields = None
62
-
63
54
  quick_search_fields_digit = None
64
55
 
65
56
  active_fields = frozenset()
@@ -87,10 +78,6 @@ class Model(models.Model, fields.TableRow):
87
78
  Internally used by :mod:`lino.modlib.changes`.
88
79
  """
89
80
 
90
- # disable_create_choice = False
91
- # """Whether to disable any automatic creation by learning choosers.
92
- # """
93
-
94
81
  _lino_tables = []
95
82
  _bleached_fields = []
96
83
 
@@ -308,8 +295,7 @@ class Model(models.Model, fields.TableRow):
308
295
  if isinstance(f, RichTextField):
309
296
  if f.editable and (
310
297
  f.bleached is True
311
- or f.bleached is None
312
- and settings.SITE.textfield_bleached
298
+ or f.bleached is None and settings.SITE.textfield_bleached
313
299
  ):
314
300
  bleached_fields.append(f)
315
301
  cls._bleached_fields = tuple(bleached_fields)
@@ -428,27 +414,24 @@ class Model(models.Model, fields.TableRow):
428
414
  setattr(self, f.name, new)
429
415
 
430
416
  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
417
+ for f in self._bleached_fields:
418
+ old = getattr(self, f.name)
419
+ if old is None:
420
+ continue
421
+ new = sanitize(old)
422
+ # try:
423
+ # new = bleach.clean(
424
+ # new,
425
+ # tags=settings.SITE.bleach_allowed_tags,
426
+ # attributes=settings.SITE.bleach_allowed_attributes,
427
+ # strip=True,
428
+ # )
429
+ # except TypeError as e:
430
+ # logger.warning("Could not bleach %r : %s (%s)", old, e, self)
431
+ # continue
432
+ if old != new:
433
+ logger.debug("Bleaching %s from %r to %r", f.name, old, new)
434
+ yield f, old, new
452
435
 
453
436
  updatable_panels = None
454
437
 
@@ -959,11 +942,14 @@ LINO_MODEL_ATTRIBS = (
959
942
  "hidden_elements",
960
943
  "get_simple_parameters",
961
944
  "get_request_queryset",
945
+ "get_request_words",
962
946
  "get_title_tags",
963
947
  "get_default_table",
964
948
  "get_default_table",
965
949
  "get_layout_aliases",
966
950
  "edge_ngram_field",
951
+ "as_str",
952
+ "get_str_words",
967
953
  "as_summary_item",
968
954
  "as_paragraph",
969
955
  "as_page",
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()
@@ -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)
@@ -1264,7 +1263,7 @@ class JsCacheRenderer(JsRenderer):
1264
1263
  # if res.parameters is not None:
1265
1264
  add(res, self.param_panels, res.params_layout, "%s.ParamsPanel" % res)
1266
1265
  add(res, self.other_panels, res.card_layout, "%s.CardsPanel" % res)
1267
- add(res, self.other_panels, res.list_layout, "%s.ItemsPanel" % res)
1266
+ # add(res, self.other_panels, res.list_layout, "%s.ItemsPanel" % res)
1268
1267
 
1269
1268
  for ba in res.get_actions():
1270
1269
  if ba.action.parameters: