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.
- lino/__init__.py +1 -1
- lino/core/actions.py +2 -4
- lino/core/actors.py +35 -14
- lino/core/dbtables.py +12 -12
- lino/core/fields.py +27 -18
- lino/core/inject.py +9 -2
- lino/core/kernel.py +11 -12
- lino/core/model.py +25 -39
- lino/core/renderer.py +3 -4
- lino/core/requests.py +91 -92
- lino/core/site.py +5 -46
- lino/core/store.py +4 -4
- lino/core/tables.py +0 -16
- lino/core/utils.py +5 -2
- lino/help_texts.py +7 -5
- lino/locale/bn/LC_MESSAGES/django.po +1225 -909
- lino/locale/de/LC_MESSAGES/django.mo +0 -0
- lino/locale/de/LC_MESSAGES/django.po +1748 -1392
- lino/locale/django.pot +1150 -909
- lino/locale/es/LC_MESSAGES/django.po +1721 -1347
- lino/locale/et/LC_MESSAGES/django.po +1267 -954
- lino/locale/fr/LC_MESSAGES/django.mo +0 -0
- lino/locale/fr/LC_MESSAGES/django.po +1207 -925
- lino/locale/nl/LC_MESSAGES/django.po +1261 -942
- lino/locale/pt_BR/LC_MESSAGES/django.po +1204 -906
- lino/locale/zh_Hant/LC_MESSAGES/django.po +1204 -906
- lino/mixins/dupable.py +8 -7
- lino/mixins/periods.py +10 -8
- lino/modlib/checkdata/__init__.py +1 -1
- lino/modlib/comments/choicelists.py +1 -1
- lino/modlib/comments/fixtures/demo2.py +4 -1
- lino/modlib/comments/mixins.py +9 -10
- lino/modlib/comments/models.py +4 -4
- lino/modlib/comments/ui.py +11 -2
- lino/modlib/dupable/models.py +10 -14
- lino/modlib/gfks/mixins.py +8 -1
- lino/modlib/jinja/choicelists.py +3 -3
- lino/modlib/jinja/renderer.py +2 -0
- lino/modlib/languages/fixtures/all_languages.py +4 -6
- lino/modlib/linod/mixins.py +3 -2
- lino/modlib/memo/mixins.py +17 -215
- lino/modlib/memo/models.py +41 -12
- lino/modlib/memo/parser.py +7 -3
- lino/modlib/notify/mixins.py +35 -34
- lino/modlib/periods/choicelists.py +46 -0
- lino/modlib/periods/mixins.py +26 -1
- lino/modlib/periods/models.py +17 -45
- lino/modlib/printing/choicelists.py +3 -3
- lino/modlib/publisher/choicelists.py +1 -4
- lino/modlib/search/models.py +17 -11
- lino/modlib/system/fixtures/__init__.py +0 -0
- lino/modlib/system/fixtures/std.py +5 -0
- lino/modlib/system/models.py +3 -2
- lino/modlib/uploads/__init__.py +10 -1
- lino/modlib/uploads/choicelists.py +3 -3
- lino/modlib/uploads/mixins.py +49 -51
- lino/modlib/uploads/models.py +144 -61
- lino/modlib/uploads/ui.py +12 -6
- lino/modlib/uploads/utils.py +113 -0
- lino/modlib/users/mixins.py +9 -6
- lino/modlib/weasyprint/choicelists.py +17 -7
- lino/utils/__init__.py +6 -0
- lino/utils/choosers.py +21 -8
- lino/utils/html.py +1 -1
- lino/utils/instantiator.py +9 -0
- lino/utils/media.py +2 -3
- lino/utils/soup.py +311 -0
- {lino-24.11.0.dist-info → lino-25.1.0.dist-info}/METADATA +2 -2
- {lino-24.11.0.dist-info → lino-25.1.0.dist-info}/RECORD +72 -67
- {lino-24.11.0.dist-info → lino-25.1.0.dist-info}/WHEEL +1 -1
- {lino-24.11.0.dist-info → lino-25.1.0.dist-info}/licenses/AUTHORS.rst +0 -0
- {lino-24.11.0.dist-info → lino-25.1.0.dist-info}/licenses/COPYING +0 -0
lino/__init__.py
CHANGED
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'
|
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
|
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
|
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
|
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
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
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.
|
73
|
-
|
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
|
-
|
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
|
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
|
1373
|
-
text =
|
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
|
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
|
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
|
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
|
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:
|
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
|
-
|
313
|
-
|
314
|
-
|
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
|
-
|
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(
|
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-
|
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
|
-
|
432
|
-
|
433
|
-
|
434
|
-
|
435
|
-
|
436
|
-
|
437
|
-
|
438
|
-
|
439
|
-
|
440
|
-
|
441
|
-
|
442
|
-
|
443
|
-
|
444
|
-
|
445
|
-
|
446
|
-
|
447
|
-
|
448
|
-
|
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
|
-
|
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:
|