lino 25.2.2__py3-none-any.whl → 25.3.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 (70) hide show
  1. lino/__init__.py +8 -3
  2. lino/api/dd.py +11 -35
  3. lino/api/doctest.py +49 -17
  4. lino/api/selenium.py +1 -1
  5. lino/core/actions.py +25 -23
  6. lino/core/actors.py +52 -23
  7. lino/core/choicelists.py +10 -8
  8. lino/core/dbtables.py +1 -1
  9. lino/core/elems.py +47 -31
  10. lino/core/fields.py +19 -9
  11. lino/core/kernel.py +26 -20
  12. lino/core/model.py +27 -16
  13. lino/core/renderer.py +2 -2
  14. lino/core/requests.py +103 -56
  15. lino/core/site.py +5 -5
  16. lino/core/store.py +5 -2
  17. lino/core/utils.py +12 -7
  18. lino/help_texts.py +7 -8
  19. lino/mixins/duplicable.py +6 -4
  20. lino/mixins/sequenced.py +17 -6
  21. lino/modlib/__init__.py +0 -2
  22. lino/modlib/changes/models.py +21 -10
  23. lino/modlib/checkdata/models.py +59 -24
  24. lino/modlib/comments/fixtures/demo2.py +12 -3
  25. lino/modlib/comments/models.py +7 -7
  26. lino/modlib/comments/ui.py +8 -5
  27. lino/modlib/export_excel/models.py +7 -5
  28. lino/modlib/extjs/__init__.py +2 -2
  29. lino/modlib/extjs/views.py +66 -22
  30. lino/modlib/help/config/makehelp/conf.tpl.py +1 -1
  31. lino/modlib/jinja/mixins.py +73 -0
  32. lino/modlib/jinja/models.py +6 -0
  33. lino/modlib/linod/__init__.py +1 -0
  34. lino/modlib/linod/choicelists.py +21 -0
  35. lino/modlib/linod/consumers.py +13 -4
  36. lino/modlib/linod/fixtures/__init__.py +0 -0
  37. lino/modlib/linod/fixtures/linod.py +32 -0
  38. lino/modlib/linod/management/commands/linod.py +6 -2
  39. lino/modlib/linod/mixins.py +18 -14
  40. lino/modlib/linod/models.py +4 -2
  41. lino/modlib/memo/mixins.py +2 -1
  42. lino/modlib/memo/parser.py +1 -1
  43. lino/modlib/notify/models.py +19 -11
  44. lino/modlib/printing/actions.py +47 -42
  45. lino/modlib/printing/choicelists.py +17 -15
  46. lino/modlib/printing/mixins.py +22 -20
  47. lino/modlib/publisher/models.py +5 -5
  48. lino/modlib/summaries/models.py +3 -2
  49. lino/modlib/system/models.py +28 -29
  50. lino/modlib/uploads/__init__.py +14 -11
  51. lino/modlib/uploads/actions.py +2 -8
  52. lino/modlib/uploads/choicelists.py +10 -10
  53. lino/modlib/uploads/fixtures/std.py +17 -0
  54. lino/modlib/uploads/mixins.py +20 -8
  55. lino/modlib/uploads/models.py +62 -38
  56. lino/modlib/uploads/ui.py +15 -9
  57. lino/utils/__init__.py +0 -1
  58. lino/utils/jscompressor.py +4 -4
  59. lino/utils/media.py +45 -23
  60. lino/utils/report.py +5 -4
  61. lino/utils/restify.py +2 -2
  62. lino/utils/soup.py +26 -8
  63. lino/utils/xml.py +19 -5
  64. {lino-25.2.2.dist-info → lino-25.3.0.dist-info}/METADATA +1 -1
  65. {lino-25.2.2.dist-info → lino-25.3.0.dist-info}/RECORD +68 -65
  66. lino/mixins/uploadable.py +0 -3
  67. lino/utils/requests.py +0 -55
  68. {lino-25.2.2.dist-info → lino-25.3.0.dist-info}/WHEEL +0 -0
  69. {lino-25.2.2.dist-info → lino-25.3.0.dist-info}/licenses/AUTHORS.rst +0 -0
  70. {lino-25.2.2.dist-info → lino-25.3.0.dist-info}/licenses/COPYING +0 -0
lino/core/elems.py CHANGED
@@ -5,6 +5,7 @@
5
5
 
6
6
  """
7
7
 
8
+ from lino.utils.jsgen import VisibleComponent
8
9
  import types
9
10
  import decimal
10
11
  from lxml.html import fromstring
@@ -181,7 +182,8 @@ class GridColumn(jsgen.Component):
181
182
  elif isinstance(editor.field, fields.VirtualField):
182
183
  # kw.update(sortable=False)
183
184
  if has_fk_renderer(editor.field.return_type):
184
- rend = fk_renderer(editor.field.return_type, editor.field.name)
185
+ rend = fk_renderer(
186
+ editor.field.return_type, editor.field.name)
185
187
  if rend:
186
188
  kw.update(renderer=js_code(rend))
187
189
  kw.update(editable=editor.editable)
@@ -221,9 +223,6 @@ class Calendar(jsgen.Component):
221
223
  value_template = "new Lino.CalendarPanel(%s)"
222
224
 
223
225
 
224
- from lino.utils.jsgen import VisibleComponent
225
-
226
-
227
226
  class LayoutElement(VisibleComponent):
228
227
  stored = False
229
228
  ext_name = None
@@ -358,10 +357,12 @@ class LayoutElement(VisibleComponent):
358
357
  label = self.get_label()
359
358
  if not label:
360
359
  raise Exception(
361
- "Item %s of tabbed %s has no label!" % (self, self.layout_handle)
360
+ "Item %s of tabbed %s has no label!" % (
361
+ self, self.layout_handle)
362
362
  )
363
363
  ukw = dict(title=label)
364
- ukw.update(listeners=dict(activate=js_code("Lino.on_tab_activate")))
364
+ ukw.update(listeners=dict(
365
+ activate=js_code("Lino.on_tab_activate")))
365
366
  # add_help_text(
366
367
  # ukw, self.help_text, 'title',
367
368
  # self.layout_handle.layout._datasource, self.name)
@@ -485,14 +486,13 @@ class FieldElement(LayoutElement):
485
486
  self.hide_sum = hide_sum
486
487
 
487
488
  if "listeners" not in kw:
488
- if not isinstance(layout_handle.layout, ColumnsLayout):
489
- layout_handle.ui.renderer.add_help_text(
490
- kw,
491
- self.field.help_text,
492
- self.field.verbose_name,
493
- layout_handle.layout._datasource,
494
- self.field.name,
495
- )
489
+ # if not isinstance(layout_handle.layout, ColumnsLayout):
490
+ layout_handle.ui.renderer.add_help_text(
491
+ kw,
492
+ self.field.help_text,
493
+ self.field.verbose_name,
494
+ layout_handle.layout._datasource,
495
+ self.field.name)
496
496
 
497
497
  # http://www.rowlands-bcs.com/extjs/tips/tooltips-form-fields
498
498
  # if self.field.__doc__:
@@ -747,7 +747,8 @@ class TextFieldElement(FieldElement):
747
747
  oui5_field_template = "/openui5/elems/field/TextFieldElement.xml"
748
748
 
749
749
  def __init__(self, layout_handle, field, **kw):
750
- self.format = getattr(field, "format", None) or settings.SITE.textfield_format
750
+ self.format = getattr(
751
+ field, "format", None) or settings.SITE.textfield_format
751
752
 
752
753
  if layout_handle.ui.renderer.extjs_version == 3:
753
754
  self.value_template = "new Ext.form.TextArea(%s)"
@@ -885,7 +886,8 @@ class CharFieldElement(FieldElement):
885
886
  kw.update(maxLength=self.field.max_length)
886
887
  if self.field.max_length <= 10:
887
888
  kw.update(
888
- boxMinWidth=js_code("Lino.chars2width(%d)" % self.field.max_length)
889
+ boxMinWidth=js_code("Lino.chars2width(%d)" %
890
+ self.field.max_length)
889
891
  )
890
892
  for lino_name, extjs_name in (
891
893
  ("regex", "regex"),
@@ -945,7 +947,8 @@ class ComboFieldElement(FieldElement):
945
947
  if not isinstance(self.layout_handle.layout, ColumnsLayout) and not isinstance(
946
948
  self, SimpleRemoteComboFieldElement
947
949
  ):
948
- kw.update(hiddenName=self.field.name + constants.CHOICES_HIDDEN_SUFFIX)
950
+ kw.update(hiddenName=self.field.name
951
+ + constants.CHOICES_HIDDEN_SUFFIX)
949
952
  return kw
950
953
 
951
954
 
@@ -1030,12 +1033,14 @@ class RemoteComboFieldElement(ComboFieldElement):
1030
1033
  # print repr(sto)
1031
1034
  if self.layout_handle.ui.renderer.extjs_version == 3:
1032
1035
  kw.update(
1033
- store=js_code("new Lino.ComplexRemoteComboStore(%s)" % py2js(sto))
1036
+ store=js_code(
1037
+ "new Lino.ComplexRemoteComboStore(%s)" % py2js(sto))
1034
1038
  )
1035
1039
  else:
1036
1040
  kw.update(
1037
1041
  store=js_code(
1038
- "Ext.create('Lino.ComplexRemoteComboStore',%s)" % py2js(sto)
1042
+ "Ext.create('Lino.ComplexRemoteComboStore',%s)" % py2js(
1043
+ sto)
1039
1044
  )
1040
1045
  )
1041
1046
  return kw
@@ -1088,11 +1093,13 @@ class ForeignKeyElement(ComplexRemoteComboFieldElement):
1088
1093
 
1089
1094
  if settings.SITE.kernel.web_front_ends[0].app_label == "react":
1090
1095
  options = dict(
1091
- related_actor_id=actor.actor_id, allowBlank=kw.get("allowBlank", False)
1096
+ related_actor_id=actor.actor_id, allowBlank=kw.get(
1097
+ "allowBlank", False)
1092
1098
  )
1093
1099
  da = actor.detail_action
1094
1100
  options.update(
1095
- view_permission=bool(da) and da.get_view_permission(get_user_profile())
1101
+ view_permission=bool(da) and da.get_view_permission(
1102
+ get_user_profile())
1096
1103
  )
1097
1104
  if hasattr(self.field, "model") and hasattr(
1098
1105
  self.field.model, f"create_{self.field.name}_choice"
@@ -1122,7 +1129,8 @@ class ForeignKeyElement(ComplexRemoteComboFieldElement):
1122
1129
  if self.layout_handle.ui.renderer.extjs_version is not None:
1123
1130
  if actor is None:
1124
1131
  raise Exception(
1125
- "20181229 {!r} {}".format(self, self.field.remote_field.model)
1132
+ "20181229 {!r} {}".format(
1133
+ self, self.field.remote_field.model)
1126
1134
  )
1127
1135
  a1 = actor.detail_action
1128
1136
  if a1 and not a1.get_view_permission(get_user_profile()):
@@ -1146,7 +1154,8 @@ class ForeignKeyElement(ComplexRemoteComboFieldElement):
1146
1154
 
1147
1155
  kw.update(pageSize=actor.page_length)
1148
1156
  if actor.model is not None:
1149
- kw.update(emptyText=_("Select a %s...") % actor.model._meta.verbose_name)
1157
+ kw.update(emptyText=_("Select a %s...") %
1158
+ actor.model._meta.verbose_name)
1150
1159
  return kw
1151
1160
 
1152
1161
  def cell_html(self, ar, row):
@@ -1174,7 +1183,8 @@ class TimeFieldElement(FieldElement):
1174
1183
  def get_field_options(self, **kwargs):
1175
1184
  kwargs = FieldElement.get_field_options(self, **kwargs)
1176
1185
  if settings.SITE.calendar_start_hour:
1177
- kwargs["minValue"] = "{}:00".format(settings.SITE.calendar_start_hour)
1186
+ kwargs["minValue"] = "{}:00".format(
1187
+ settings.SITE.calendar_start_hour)
1178
1188
  # kwargs['minValue'] = settings.SITE.calendar_start_hour
1179
1189
  eh = settings.SITE.calendar_end_hour
1180
1190
  if eh:
@@ -1243,7 +1253,8 @@ class DateFieldElement(FieldElement):
1243
1253
  # ~ preferred_width = 8 # 20131022
1244
1254
  preferred_width = 13
1245
1255
  filter_type = "date"
1246
- gridfilters_settings = dict(type="date", dateFormat=settings.SITE.date_format_extjs)
1256
+ gridfilters_settings = dict(
1257
+ type="date", dateFormat=settings.SITE.date_format_extjs)
1247
1258
  # todo: DateFieldElement.preferred_width should be computed from Report.date_format
1248
1259
  # ~ grid_column_template = "new Ext.grid.DateColumn(%s)"
1249
1260
 
@@ -1376,7 +1387,8 @@ class DecimalFieldElement(NumberFieldElement):
1376
1387
 
1377
1388
  def __init__(self, *args, **kw):
1378
1389
  FieldElement.__init__(self, *args, **kw)
1379
- self.preferred_width = max(5, self.field.max_digits) + self.field.decimal_places
1390
+ self.preferred_width = max(
1391
+ 5, self.field.max_digits) + self.field.decimal_places
1380
1392
  # fmt = '0,000'
1381
1393
  # fmt = '0.0'
1382
1394
  # if self.field.decimal_places > 0:
@@ -1830,7 +1842,7 @@ class SlaveSummaryPanel(LightWeightContainer):
1830
1842
  oui5_field_template = "openui5/elems/field/SlaveSummaryElement.xml"
1831
1843
 
1832
1844
  def __init__(self, lh, slave, name, **kw):
1833
- super().__init__(lh, slave, name, slave.get_table_summary, **kw)
1845
+ super().__init__(lh, slave, name, slave.get_slave_summary, **kw)
1834
1846
 
1835
1847
 
1836
1848
  class StoryElement(LightWeightContainer):
@@ -2442,9 +2454,11 @@ class SlaveContainer(GridElement):
2442
2454
  if constants.DISPLAY_MODE_LIST in rpt.extra_display_modes:
2443
2455
  slaves["list"] = get_list_element(layout_handle, rpt, name, **kw)
2444
2456
  if constants.DISPLAY_MODE_SUMMARY in rpt.extra_display_modes:
2445
- slaves["summary"] = get_summary_element(layout_handle, rpt, name, **kw)
2457
+ slaves["summary"] = get_summary_element(
2458
+ layout_handle, rpt, name, **kw)
2446
2459
  if constants.DISPLAY_MODE_HTML in rpt.extra_display_modes:
2447
- slaves["html"] = get_htmlbox_element(layout_handle, rpt, name, **kw)
2460
+ slaves["html"] = get_htmlbox_element(
2461
+ layout_handle, rpt, name, **kw)
2448
2462
  if slaves:
2449
2463
  self.slaves = slaves
2450
2464
  super().__init__(layout_handle, name, rpt, *columns, **kw)
@@ -2765,7 +2779,8 @@ def create_layout_element(lh, name, **kw):
2765
2779
  # ctx = (lh.layout.__class__, name, ', '.join(dir(lh.layout)))
2766
2780
  # raise Exception(
2767
2781
  # "Instance of %s has no data element '%s' (names are %s)" % ctx)
2768
- raise Exception("Invalid data element '{1}' in {0}".format(lh.layout, name))
2782
+ raise Exception(
2783
+ "Invalid data element '{1}' in {0}".format(lh.layout, name))
2769
2784
 
2770
2785
  if isinstance(de, type) and issubclass(de, fields.Dummy):
2771
2786
  return None
@@ -2864,7 +2879,8 @@ def create_layout_element(lh, name, **kw):
2864
2879
  title=_("Show this table in own window"),
2865
2880
  style="text-decoration:none;",
2866
2881
  )
2867
- kw.update(label="{} {}".format(de.get_label(), tostring(btn)))
2882
+ kw.update(label="{} {}".format(
2883
+ de.get_label(), tostring(btn)))
2868
2884
 
2869
2885
  if (
2870
2886
  len(de.default_display_modes) > 1
lino/core/fields.py CHANGED
@@ -70,7 +70,7 @@ def set_default_verbose_name(f):
70
70
 
71
71
  """
72
72
  if f.verbose_name is None or (
73
- f.name is not None and f.verbose_name == f.name.replace("_", " ")):
73
+ f.name is not None and f.verbose_name == f.name.replace("_", " ")):
74
74
  f.verbose_name = f.remote_field.model._meta.verbose_name
75
75
 
76
76
 
@@ -322,7 +322,8 @@ class FakeField(object):
322
322
 
323
323
  def __repr__(self):
324
324
  # copied from django Field
325
- path = "%s.%s" % (self.__class__.__module__, self.__class__.__qualname__)
325
+ path = "%s.%s" % (self.__class__.__module__,
326
+ self.__class__.__qualname__)
326
327
  name = getattr(self, "name", None)
327
328
  if name is not None:
328
329
  return "<%s: %s>" % (path, name)
@@ -604,7 +605,8 @@ class VirtualField(FakeField):
604
605
  f = self.return_type = resolve_field(f)
605
606
  except Exception as e:
606
607
  raise Exception(
607
- "Invalid return type spec {} for {} : {}".format(f, self, e)
608
+ "Invalid return type spec {} for {} : {}".format(
609
+ f, self, e)
608
610
  )
609
611
 
610
612
  if isinstance(f, FakeField):
@@ -947,7 +949,8 @@ class QuantityField(models.CharField):
947
949
  kw.setdefault("max_length", settings.SITE.quantity_max_length)
948
950
  super().__init__(*args, **kw)
949
951
  if self.blank and not self.null:
950
- raise ChangedAPI("When `blank` is True, `null` must be True as well.")
952
+ raise ChangedAPI(
953
+ "When `blank` is True, `null` must be True as well.")
951
954
 
952
955
  # ~ def get_internal_type(self):
953
956
  # ~ return "CharField"
@@ -1207,7 +1210,8 @@ class ImportedFields(object):
1207
1210
 
1208
1211
  @classmethod
1209
1212
  def declare_imported_fields(cls, names):
1210
- cls._imported_fields = cls._imported_fields | set(fields_list(cls, names))
1213
+ cls._imported_fields = cls._imported_fields | set(
1214
+ fields_list(cls, names))
1211
1215
  # ~ logger.info('20120801 %s.declare_imported_fields() --> %s' % (
1212
1216
  # ~ cls,cls._imported_fields))
1213
1217
 
@@ -1299,14 +1303,19 @@ class TableRow(object):
1299
1303
  # return v
1300
1304
  # raise Exception("Oops, {} on {} is {}".format(name, cls, v))
1301
1305
 
1306
+ @classmethod
1307
+ def override_column_headers(cls, ar, **headers):
1308
+ return headers
1309
+
1302
1310
  @classmethod
1303
1311
  def disable_create(self, ar):
1304
1312
  return None
1305
1313
 
1306
1314
  def get_detail_action(self, ar):
1307
- """Return the (bound) detail action to use for showing this object in
1308
- a detail window. Return `None` when no detail form exists or
1309
- the requesting user has no permission to see it.
1315
+ """
1316
+ Return the (bound) detail action to use for showing this database row in
1317
+ a detail window. Return `None` when no detail window exists or the
1318
+ requesting user has no permission to see it.
1310
1319
 
1311
1320
  `ar` is the action request that asks to see the detail.
1312
1321
  If the action request's actor can be used for this model,
@@ -1511,7 +1520,8 @@ def fields_list(model, field_names):
1511
1520
  else:
1512
1521
  e = model.get_data_elem(name)
1513
1522
  if e is None:
1514
- raise FieldDoesNotExist("No data element %r in %s" % (name, model))
1523
+ raise FieldDoesNotExist(
1524
+ "No data element %r in %s" % (name, model))
1515
1525
  if not hasattr(e, "name"):
1516
1526
  raise FieldDoesNotExist(
1517
1527
  "%s %r in %s has no name" % (e.__class__, name, model)
lino/core/kernel.py CHANGED
@@ -4,8 +4,7 @@
4
4
  """This defines the :class:`Kernel` class.
5
5
 
6
6
  The "kernel" of a Lino site is (like `SITE` itself) a "de facto
7
- singleton", available to application code as ``SITE.kernel`` (and its
8
- alias for backwards compatibility: ``SITE.ui``).
7
+ singleton", available to application code as ``SITE.kernel``).
9
8
 
10
9
  The kernel is instantiated at the end of the startup process, when the
11
10
  :setting:`SITE` has been instantiated and models have been loaded. It
@@ -22,7 +21,6 @@ application.
22
21
 
23
22
  import os
24
23
  import sys
25
- import time
26
24
  import codecs
27
25
  import atexit
28
26
  import signal
@@ -30,11 +28,10 @@ import threading
30
28
  from importlib import import_module
31
29
  import json
32
30
 
33
- from django.apps import AppConfig
31
+ # from django.apps import AppConfig
34
32
  from django.apps import apps
35
33
  from django.conf import settings
36
34
  from django.http import HttpResponse
37
- from django.utils.encoding import force_str
38
35
  from django.utils.text import format_lazy
39
36
  from django.utils.translation import gettext_lazy as _
40
37
  from django.core.exceptions import PermissionDenied, ValidationError
@@ -454,7 +451,8 @@ class Kernel(object):
454
451
  try:
455
452
  a.setup_columns()
456
453
  except DatabaseError:
457
- logger.debug("Ignoring DatabaseError in %s.setup_columns", a)
454
+ logger.debug(
455
+ "Ignoring DatabaseError in %s.setup_columns", a)
458
456
  if (
459
457
  issubclass(a, dbtables.Table)
460
458
  and a.model is not None
@@ -533,7 +531,8 @@ class Kernel(object):
533
531
  # for p in site.installed_plugins:
534
532
  # p.after_discover()
535
533
 
536
- self.reserved_names = [getattr(constants, n) for n in constants.URL_PARAMS]
534
+ self.reserved_names = [getattr(constants, n)
535
+ for n in constants.URL_PARAMS]
537
536
 
538
537
  names = set()
539
538
  for n in self.reserved_names:
@@ -565,7 +564,8 @@ class Kernel(object):
565
564
  found = True
566
565
  break
567
566
  if not found:
568
- raise Exception(f"Invalid modname {modname} in Site.web_front_ends")
567
+ raise Exception(
568
+ f"Invalid modname {modname} in Site.web_front_ends")
569
569
  self.web_front_ends = lst
570
570
  # print("20230407 web front ends:")
571
571
  # print("\n".join(["- {0.url_prefix} --> {0.app_name} {1}".format(p, hash(p))
@@ -623,7 +623,8 @@ class Kernel(object):
623
623
 
624
624
  return get_welcome_messages
625
625
 
626
- site.add_welcome_handler(handler(a), a, "welcome_message_when_count")
626
+ site.add_welcome_handler(
627
+ handler(a), a, "welcome_message_when_count")
627
628
 
628
629
  # Remove "meaningless" user roles, i.e. those which are either used by all
629
630
  # types or by no type. This seems to be
@@ -712,7 +713,8 @@ class Kernel(object):
712
713
  msg = (
713
714
  "{0}.{1} has on_delete SET_NULL but " "is not nullable "
714
715
  )
715
- msg = msg.format(fmn(m), fk.name, fk.remote_field.model)
716
+ msg = msg.format(
717
+ fmn(m), fk.name, fk.remote_field.model)
716
718
  raise Exception(msg)
717
719
 
718
720
  else:
@@ -785,7 +787,8 @@ class Kernel(object):
785
787
  pointed_model.objects.get(pk=fk)
786
788
  except pointed_model.DoesNotExist:
787
789
  msg = "Invalid primary key {1} for {2} in `{0}`"
788
- obj._message = msg.format(gfk.fk_field, fk, fmn(pointed_model))
790
+ obj._message = msg.format(
791
+ gfk.fk_field, fk, fmn(pointed_model))
789
792
  if gfk.name in model.allow_cascaded_delete:
790
793
  obj._todo = "delete"
791
794
  elif fk_field.null:
@@ -807,7 +810,8 @@ class Kernel(object):
807
810
  """
808
811
  if not ar.get_permission():
809
812
  if False:
810
- msg = "{} has no permission to run {}".format(ar.get_user(), ar)
813
+ msg = "{} has no permission to run {}".format(
814
+ ar.get_user(), ar)
811
815
  else:
812
816
  msg = "No permission to run {}".format(ar)
813
817
  # raise Exception(msg)
@@ -842,7 +846,8 @@ class Kernel(object):
842
846
  and ar.master_instance is not None
843
847
  and len(ar.selected_rows)
844
848
  ):
845
- ar.set_response(master_data=ar.selected_rows[0].get_master_data(ar))
849
+ ar.set_response(
850
+ master_data=ar.selected_rows[0].get_master_data(ar))
846
851
  if (
847
852
  a.parameters
848
853
  and not a.no_params_window
@@ -852,15 +857,16 @@ class Kernel(object):
852
857
  except Warning as e:
853
858
  ar.error(e, alert=True)
854
859
  except Exception as e:
855
- # except (ValidationError, IntegrityError) as e:
856
- msg = ar.ah.actor.error2str(e)
857
- logger.info("Error during kernel.run_action() in %s: %s", ar, msg)
858
- if is_devserver():
859
- # can be interesting during development but disturbs on a
860
- # production server
861
- logger.exception(e)
862
860
  # print(f"20240911 oops {repr(e)}")
861
+ msg = ar.ah.actor.error2str(e)
863
862
  ar.error(msg, alert=True)
863
+ if not isinstance(e, (ValidationError, IntegrityError)):
864
+ logger.info(
865
+ "Error during kernel.run_action() in %s: %s", ar, msg)
866
+ if is_devserver():
867
+ # can be interesting during development but disturbs on a
868
+ # production server
869
+ logger.exception(e)
864
870
 
865
871
  return ar.renderer.render_action_response(ar)
866
872
 
lino/core/model.py CHANGED
@@ -180,7 +180,8 @@ class Model(models.Model, fields.TableRow):
180
180
  and b is not models.Model
181
181
  and not b._meta.abstract
182
182
  ):
183
- msg = b._lino_ddh.disable_delete_on_object(self, [self.__class__])
183
+ msg = b._lino_ddh.disable_delete_on_object(
184
+ self, [self.__class__])
184
185
  if msg is not None:
185
186
  return msg
186
187
  return self.__class__._lino_ddh.disable_delete_on_object(self)
@@ -243,7 +244,8 @@ class Model(models.Model, fields.TableRow):
243
244
  @classmethod
244
245
  def add_active_field(cls, names):
245
246
  if isinstance(cls.active_fields, str):
246
- cls.active_fields = frozenset(fields.fields_list(cls, cls.active_fields))
247
+ cls.active_fields = frozenset(
248
+ fields.fields_list(cls, cls.active_fields))
247
249
  cls.active_fields = cls.active_fields | fields.fields_list(cls, names)
248
250
 
249
251
  @classmethod
@@ -274,9 +276,11 @@ class Model(models.Model, fields.TableRow):
274
276
  @classmethod
275
277
  def on_analyze(cls, site):
276
278
  if isinstance(cls.workflow_owner_field, str):
277
- cls.workflow_owner_field = cls.get_data_elem(cls.workflow_owner_field)
279
+ cls.workflow_owner_field = cls.get_data_elem(
280
+ cls.workflow_owner_field)
278
281
  if isinstance(cls.workflow_state_field, str):
279
- cls.workflow_state_field = cls.get_data_elem(cls.workflow_state_field)
282
+ cls.workflow_state_field = cls.get_data_elem(
283
+ cls.workflow_state_field)
280
284
  # for vf in cls._meta.private_fields:
281
285
  # if vf.name == 'detail_link':
282
286
  # if vf.verbose_name is None:
@@ -300,7 +304,8 @@ class Model(models.Model, fields.TableRow):
300
304
  bleached_fields.append(f)
301
305
  cls._bleached_fields = tuple(bleached_fields)
302
306
  if hasattr(cls, "bleached_fields"):
303
- raise ChangedAPI("Replace bleached_fields by bleached=True on each field")
307
+ raise ChangedAPI(
308
+ "Replace bleached_fields by bleached=True on each field")
304
309
 
305
310
  @classmethod
306
311
  def create_from_choice(cls, text, ar=None, save=True):
@@ -344,7 +349,8 @@ class Model(models.Model, fields.TableRow):
344
349
  if isinstance(lookup_field, str):
345
350
  lookup_field = model._meta.get_field(lookup_field)
346
351
  if isinstance(lookup_field, BabelCharField):
347
- flt = settings.SITE.lookup_filter(lookup_field.name, value, **known_values)
352
+ flt = settings.SITE.lookup_filter(
353
+ lookup_field.name, value, **known_values)
348
354
  else:
349
355
  if isinstance(lookup_field, models.CharField):
350
356
  fkw[lookup_field.name + "__iexact"] = value
@@ -369,7 +375,8 @@ class Model(models.Model, fields.TableRow):
369
375
  try:
370
376
  obj.full_clean()
371
377
  except ValidationError as e:
372
- raise ValidationError("Failed to auto_create %s : %s" % (obj2str(obj), e))
378
+ raise ValidationError(
379
+ "Failed to auto_create %s : %s" % (obj2str(obj), e))
373
380
  obj.save()
374
381
  signals.auto_create.send(obj, known_values=known_values)
375
382
  return obj
@@ -410,15 +417,15 @@ class Model(models.Model, fields.TableRow):
410
417
  pass
411
418
 
412
419
  def before_ui_save(self, ar, cw):
413
- for f, old, new in self.fields_to_bleach():
420
+ for f, old, new in self.fields_to_bleach(save=True, ar=ar):
414
421
  setattr(self, f.name, new)
415
422
 
416
- def fields_to_bleach(self):
423
+ def fields_to_bleach(self, **kwargs):
417
424
  for f in self._bleached_fields:
418
425
  old = getattr(self, f.name)
419
426
  if old is None:
420
427
  continue
421
- new = sanitize(old)
428
+ new = sanitize(old, **kwargs)
422
429
  # try:
423
430
  # new = bleach.clean(
424
431
  # new,
@@ -628,7 +635,8 @@ class Model(models.Model, fields.TableRow):
628
635
  # print("20210325", navinfo)
629
636
  # raise Exception("20")
630
637
 
631
- qs = sliced_data_iterator(ar.data_iterator, navinfo["offset"], ar.limit)
638
+ qs = sliced_data_iterator(
639
+ ar.data_iterator, navinfo["offset"], ar.limit)
632
640
  for obj in qs:
633
641
  if obj.pk == self.pk:
634
642
  items.append(E.b(str(obj)))
@@ -710,7 +718,6 @@ class Model(models.Model, fields.TableRow):
710
718
  # return self.preview(ar)
711
719
  # #~ username = kw.pop('username',None)
712
720
 
713
-
714
721
  @classmethod
715
722
  def get_chooser_for_field(cls, fieldname):
716
723
  d = getattr(cls, "_choosers_dict", {})
@@ -718,7 +725,7 @@ class Model(models.Model, fields.TableRow):
718
725
  # print("20200425 Model.get_chooser_for_field", cls, fieldname, d)
719
726
  return d.get(fieldname, None)
720
727
 
721
- def filename_root(self):
728
+ def get_printable_target_stem(self):
722
729
  return self._meta.app_label + "." + self.__class__.__name__ + "-" + str(self.pk)
723
730
 
724
731
  @classmethod
@@ -743,7 +750,8 @@ class Model(models.Model, fields.TableRow):
743
750
  return set([fld.choicelist.get_by_name(x) for x in states.split()])
744
751
  elif isinstance(states, set):
745
752
  return states
746
- raise Exception("Cannot resolve stateset specifier {!r}".format(states))
753
+ raise Exception(
754
+ "Cannot resolve stateset specifier {!r}".format(states))
747
755
 
748
756
  @classmethod
749
757
  def add_picker(model, fldname):
@@ -879,7 +887,8 @@ class Model(models.Model, fields.TableRow):
879
887
  collect(self)
880
888
  s = "\n".join(
881
889
  [
882
- ' "%s" -> "%s"' % (p._meta.verbose_name, c._meta.verbose_name)
890
+ ' "%s" -> "%s"' % (p._meta.verbose_name,
891
+ c._meta.verbose_name)
883
892
  for p, c in pairs
884
893
  ]
885
894
  )
@@ -970,6 +979,7 @@ LINO_MODEL_ATTRIBS = (
970
979
  "error2str",
971
980
  "print_subclasses_graph",
972
981
  "disable_create",
982
+ "override_column_headers",
973
983
  "grid_post",
974
984
  "submit_insert",
975
985
  "delete_veto_message",
@@ -1021,7 +1031,8 @@ def pre_delete_handler(sender, instance=None, **kw):
1021
1031
  for obj in qs:
1022
1032
  setattr(obj, gfk.name, None)
1023
1033
  elif qs.count():
1024
- raise Warning(instance.delete_veto_message(qs.model, qs.count()))
1034
+ raise Warning(instance.delete_veto_message(
1035
+ qs.model, qs.count()))
1025
1036
  for qs in must_cascade:
1026
1037
  if qs.count():
1027
1038
  logger.info(
lino/core/renderer.py CHANGED
@@ -284,7 +284,7 @@ class HtmlRenderer(Renderer):
284
284
  raise Exception("Both nosummary and display_mode were specified")
285
285
 
286
286
  if display_mode == constants.DISPLAY_MODE_SUMMARY:
287
- yield ar.actor.get_table_summary(ar.master_instance, ar)
287
+ yield ar.actor.get_table_summary(ar)
288
288
  return
289
289
 
290
290
  if header_level is not None:
@@ -834,7 +834,7 @@ class TextRenderer(HtmlRenderer):
834
834
  # yield "20240506 {}".format(ar)
835
835
  if display_mode == constants.DISPLAY_MODE_SUMMARY:
836
836
  s = to_rst(
837
- ar.actor.get_table_summary(ar.master_instance, ar),
837
+ ar.actor.get_table_summary(ar),
838
838
  stripped=stripped,
839
839
  )
840
840
  if stripped: