lino 25.2.3__py3-none-any.whl → 25.3.1__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 (81) hide show
  1. lino/__init__.py +1 -1
  2. lino/api/dd.py +11 -48
  3. lino/api/doctest.py +34 -36
  4. lino/core/actions.py +25 -23
  5. lino/core/actors.py +37 -17
  6. lino/core/choicelists.py +10 -8
  7. lino/core/dbtables.py +1 -1
  8. lino/core/elems.py +46 -30
  9. lino/core/fields.py +19 -9
  10. lino/core/inject.py +7 -6
  11. lino/core/kernel.py +26 -66
  12. lino/core/model.py +44 -31
  13. lino/core/plugin.py +4 -4
  14. lino/core/requests.py +76 -55
  15. lino/core/site.py +84 -30
  16. lino/core/store.py +5 -2
  17. lino/core/utils.py +12 -7
  18. lino/help_texts.py +3 -8
  19. lino/management/commands/prep.py +1 -1
  20. lino/mixins/duplicable.py +6 -4
  21. lino/mixins/sequenced.py +17 -6
  22. lino/modlib/__init__.py +0 -2
  23. lino/modlib/changes/models.py +21 -10
  24. lino/modlib/checkdata/models.py +59 -24
  25. lino/modlib/comments/fixtures/demo2.py +12 -3
  26. lino/modlib/comments/models.py +7 -7
  27. lino/modlib/comments/ui.py +8 -5
  28. lino/modlib/export_excel/models.py +7 -5
  29. lino/modlib/extjs/views.py +39 -20
  30. lino/modlib/help/management/commands/makehelp.py +5 -2
  31. lino/modlib/jinja/mixins.py +25 -14
  32. lino/modlib/linod/__init__.py +1 -0
  33. lino/modlib/linod/choicelists.py +21 -0
  34. lino/modlib/linod/consumers.py +13 -4
  35. lino/modlib/linod/management/commands/linod.py +6 -2
  36. lino/modlib/linod/mixins.py +16 -11
  37. lino/modlib/linod/models.py +4 -2
  38. lino/modlib/notify/models.py +18 -10
  39. lino/modlib/printing/actions.py +41 -30
  40. lino/modlib/printing/choicelists.py +11 -9
  41. lino/modlib/printing/mixins.py +25 -20
  42. lino/modlib/publisher/models.py +5 -5
  43. lino/modlib/summaries/models.py +3 -2
  44. lino/modlib/system/models.py +28 -29
  45. lino/modlib/uploads/__init__.py +5 -5
  46. lino/modlib/uploads/actions.py +2 -8
  47. lino/modlib/uploads/choicelists.py +10 -10
  48. lino/modlib/uploads/fixtures/std.py +17 -0
  49. lino/modlib/uploads/mixins.py +20 -8
  50. lino/modlib/uploads/models.py +60 -35
  51. lino/modlib/uploads/ui.py +10 -7
  52. lino/utils/media.py +45 -23
  53. lino/utils/report.py +5 -4
  54. lino/utils/soup.py +22 -4
  55. {lino-25.2.3.dist-info → lino-25.3.1.dist-info}/METADATA +1 -1
  56. {lino-25.2.3.dist-info → lino-25.3.1.dist-info}/RECORD +59 -80
  57. lino/mixins/uploadable.py +0 -3
  58. lino/sandbox/bcss/PerformInvestigation.py +0 -2260
  59. lino/sandbox/bcss/SSDNReply.py +0 -3924
  60. lino/sandbox/bcss/SSDNRequest.py +0 -3723
  61. lino/sandbox/bcss/__init__.py +0 -0
  62. lino/sandbox/bcss/readme.txt +0 -1
  63. lino/sandbox/bcss/test.py +0 -92
  64. lino/sandbox/bcss/test2.py +0 -128
  65. lino/sandbox/bcss/test3.py +0 -161
  66. lino/sandbox/bcss/test4.py +0 -167
  67. lino/sandbox/contacts/__init__.py +0 -0
  68. lino/sandbox/contacts/fixtures/__init__.py +0 -0
  69. lino/sandbox/contacts/fixtures/demo.py +0 -365
  70. lino/sandbox/contacts/manage.py +0 -10
  71. lino/sandbox/contacts/models.py +0 -395
  72. lino/sandbox/contacts/settings.py +0 -67
  73. lino/sandbox/tx25/XSD/RetrieveTIGroupsV3.wsdl +0 -65
  74. lino/sandbox/tx25/XSD/RetrieveTIGroupsV3.xsd +0 -286
  75. lino/sandbox/tx25/XSD/rn25_Release201104.xsd +0 -2855
  76. lino/sandbox/tx25/xsd2py1.py +0 -68
  77. lino/sandbox/tx25/xsd2py2.py +0 -62
  78. lino/sandbox/tx25/xsd2py3.py +0 -56
  79. {lino-25.2.3.dist-info → lino-25.3.1.dist-info}/WHEEL +0 -0
  80. {lino-25.2.3.dist-info → lino-25.3.1.dist-info}/licenses/AUTHORS.rst +0 -0
  81. {lino-25.2.3.dist-info → lino-25.3.1.dist-info}/licenses/COPYING +0 -0
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,
@@ -527,6 +534,7 @@ class Model(models.Model, fields.TableRow):
527
534
  """
528
535
  watcher = ChangeWatcher(row)
529
536
  # assert hasattr(row, state_field.attname)
537
+ row.before_ui_save(ar, watcher) # added 20250312 for #5976
530
538
  old = getattr(row, state_field.attname)
531
539
  target_state.choicelist.before_state_change(row, ar, old, target_state)
532
540
  row.before_state_change(ar, old, target_state)
@@ -628,7 +636,8 @@ class Model(models.Model, fields.TableRow):
628
636
  # print("20210325", navinfo)
629
637
  # raise Exception("20")
630
638
 
631
- qs = sliced_data_iterator(ar.data_iterator, navinfo["offset"], ar.limit)
639
+ qs = sliced_data_iterator(
640
+ ar.data_iterator, navinfo["offset"], ar.limit)
632
641
  for obj in qs:
633
642
  if obj.pk == self.pk:
634
643
  items.append(E.b(str(obj)))
@@ -672,21 +681,22 @@ class Model(models.Model, fields.TableRow):
672
681
 
673
682
  df = actor.get_disabled_fields(obj, ar)
674
683
  # print(20170909, df)
675
- for ba in actor.get_actions():
676
- assert ba.actor == actor # 20170102
677
- if ba.action.show_in_workflow:
678
- # if actor.model.__name__ == 'Vote':
679
- # if ba.action.__class__.__name__ == 'MarkVoteAssigned':
680
- # print(20170115, actor, ar.get_user())
681
- if ba.action.action_name not in df:
682
- if actor.get_row_permission(obj, ar, state, ba):
683
- if show and isinstance(ba.action, ChangeStateAction):
684
- show_state()
685
- sep = " \u2192 " # "→"
686
- show = False
687
- l.append(sep)
688
- l.append(ar.action_button(ba, obj))
689
- sep = " "
684
+ if 'workflow_buttons' not in df:
685
+ for ba in actor.get_actions():
686
+ assert ba.actor == actor # 20170102
687
+ if ba.action.show_in_workflow:
688
+ # if actor.model.__name__ == 'Vote':
689
+ # if ba.action.__class__.__name__ == 'MarkVoteAssigned':
690
+ # print(20170115, actor, ar.get_user())
691
+ if ba.action.action_name not in df:
692
+ if actor.get_row_permission(obj, ar, state, ba):
693
+ if show and isinstance(ba.action, ChangeStateAction):
694
+ show_state()
695
+ sep = " \u2192 " # "→"
696
+ show = False
697
+ l.append(sep)
698
+ l.append(ar.action_button(ba, obj))
699
+ sep = " "
690
700
  if state and show:
691
701
  show_state()
692
702
  return E.span(*l)
@@ -710,7 +720,6 @@ class Model(models.Model, fields.TableRow):
710
720
  # return self.preview(ar)
711
721
  # #~ username = kw.pop('username',None)
712
722
 
713
-
714
723
  @classmethod
715
724
  def get_chooser_for_field(cls, fieldname):
716
725
  d = getattr(cls, "_choosers_dict", {})
@@ -718,7 +727,7 @@ class Model(models.Model, fields.TableRow):
718
727
  # print("20200425 Model.get_chooser_for_field", cls, fieldname, d)
719
728
  return d.get(fieldname, None)
720
729
 
721
- def filename_root(self):
730
+ def get_printable_target_stem(self):
722
731
  return self._meta.app_label + "." + self.__class__.__name__ + "-" + str(self.pk)
723
732
 
724
733
  @classmethod
@@ -743,7 +752,8 @@ class Model(models.Model, fields.TableRow):
743
752
  return set([fld.choicelist.get_by_name(x) for x in states.split()])
744
753
  elif isinstance(states, set):
745
754
  return states
746
- raise Exception("Cannot resolve stateset specifier {!r}".format(states))
755
+ raise Exception(
756
+ "Cannot resolve stateset specifier {!r}".format(states))
747
757
 
748
758
  @classmethod
749
759
  def add_picker(model, fldname):
@@ -879,7 +889,8 @@ class Model(models.Model, fields.TableRow):
879
889
  collect(self)
880
890
  s = "\n".join(
881
891
  [
882
- ' "%s" -> "%s"' % (p._meta.verbose_name, c._meta.verbose_name)
892
+ ' "%s" -> "%s"' % (p._meta.verbose_name,
893
+ c._meta.verbose_name)
883
894
  for p, c in pairs
884
895
  ]
885
896
  )
@@ -970,6 +981,7 @@ LINO_MODEL_ATTRIBS = (
970
981
  "error2str",
971
982
  "print_subclasses_graph",
972
983
  "disable_create",
984
+ "override_column_headers",
973
985
  "grid_post",
974
986
  "submit_insert",
975
987
  "delete_veto_message",
@@ -1021,7 +1033,8 @@ def pre_delete_handler(sender, instance=None, **kw):
1021
1033
  for obj in qs:
1022
1034
  setattr(obj, gfk.name, None)
1023
1035
  elif qs.count():
1024
- raise Warning(instance.delete_veto_message(qs.model, qs.count()))
1036
+ raise Warning(instance.delete_veto_message(
1037
+ qs.model, qs.count()))
1025
1038
  for qs in must_cascade:
1026
1039
  if qs.count():
1027
1040
  logger.info(
lino/core/plugin.py CHANGED
@@ -1,10 +1,9 @@
1
1
  # -*- coding: UTF-8 -*-
2
- # Copyright 2008-2024 Rumma & Ko Ltd
2
+ # Copyright 2008-2025 Rumma & Ko Ltd
3
3
  # License: GNU Affero General Public License v3 (see file COPYING for details)
4
4
 
5
- import os
6
5
  import inspect
7
- from os.path import exists, join, dirname, isdir, abspath
6
+ from os.path import join, dirname, isdir, abspath
8
7
  from collections.abc import Iterable
9
8
  from urllib.parse import urlencode
10
9
  from lino.core.exceptions import ChangedAPI
@@ -56,7 +55,8 @@ class Plugin:
56
55
 
57
56
  def hide(self):
58
57
  if self.site._startup_done:
59
- raise Exception("Tried to deactivate plugin {} after startup".format(self))
58
+ raise Exception(
59
+ "Tried to deactivate plugin {} after startup".format(self))
60
60
  self.hidden = True
61
61
 
62
62
  def configure(self, **kw):
lino/core/requests.py CHANGED
@@ -1,5 +1,5 @@
1
1
  # -*- coding: UTF-8 -*-
2
- # Copyright 2009-2024 Rumma & Ko Ltd
2
+ # Copyright 2009-2025 Rumma & Ko Ltd
3
3
  # License: GNU Affero General Public License v3 (see file COPYING for details)
4
4
  """
5
5
  See introduction in :doc:`/dev/ar` and API reference in
@@ -10,13 +10,12 @@ See introduction in :doc:`/dev/ar` and API reference in
10
10
 
11
11
  import sys
12
12
  import json
13
- import html
14
13
  import logging
15
14
  from io import StringIO
16
15
  from lino import logger
17
16
  from contextlib import contextmanager
18
17
  from types import GeneratorType
19
- from copy import copy, deepcopy
18
+ from copy import deepcopy
20
19
  from xml.sax.saxutils import escape
21
20
  from etgen import html as xghtml
22
21
 
@@ -34,7 +33,7 @@ from django.db import models
34
33
  from django.db.models.query import QuerySet
35
34
  from asgiref.sync import sync_to_async
36
35
 
37
- from lino.utils.html import E, tostring, iselement
36
+ from lino.utils.html import E, tostring
38
37
  from lino.utils import AttrDict
39
38
  from lino.utils import capture_output
40
39
  from lino.utils import MissingRow
@@ -49,11 +48,9 @@ from lino.core.diff import ChangeWatcher
49
48
  from lino.core.utils import getrqdata
50
49
  from lino.core.utils import obj2unicode
51
50
  from lino.core.utils import obj2str
52
- from lino.core.utils import UnresolvedModel
53
51
  from lino.core.utils import format_request
54
52
  from lino.core.utils import PhantomRow
55
53
  from lino.core.store import get_atomizer
56
- from lino.core.exceptions import ChangedAPI
57
54
 
58
55
  # try:
59
56
  # from django.contrib.contenttypes.models import ContentType
@@ -82,6 +79,7 @@ Subject: {subject}
82
79
  def noop(ar):
83
80
  return ar.success(gettext("Aborted"))
84
81
 
82
+
85
83
  def mi2bp(master_instance, bp):
86
84
  if master_instance is not None:
87
85
  if isinstance(master_instance, (models.Model, TableRow)):
@@ -114,6 +112,7 @@ class PrintLogger(logging.Logger):
114
112
  self.addHandler(h)
115
113
  # print("20231012", logging.getLevelName(self.level), self.__class__)
116
114
 
115
+
117
116
  class StringLogger(logging.Logger):
118
117
  # Instantiated by BaseRequest.capture_logger()
119
118
 
@@ -271,7 +270,7 @@ class ValidActionResponses(object):
271
270
 
272
271
  inheritable_attrs = frozenset(
273
272
  ["user", "subst_user", "renderer", "requesting_panel", "master_instance",
274
- "logger", "show_urls"] )
273
+ "logger", "show_urls"])
275
274
 
276
275
 
277
276
  def bool2text(x):
@@ -280,7 +279,6 @@ def bool2text(x):
280
279
  return _("No")
281
280
 
282
281
 
283
-
284
282
  class SearchQuerySet:
285
283
  pass
286
284
 
@@ -440,7 +438,7 @@ class BaseRequest:
440
438
  self.known_values.setdefault(k, v)
441
439
 
442
440
  self.obvious_fields |= self.actor.obvious_fields
443
- # self.obvious_fields.update(obvious_fields.split())
441
+ # self.obvious_fields.update(obvious_fields.split())
444
442
  # if parent.obvious_fields is not None:
445
443
  if parent is not None and parent.actor is self.actor and parent.obvious_fields:
446
444
  self.obvious_fields |= parent.obvious_fields
@@ -453,7 +451,6 @@ class BaseRequest:
453
451
  self.actor, locals()
454
452
  )
455
453
  )
456
- # if isinstance(self.master_instance, UnresolvedModel):
457
454
  if isinstance(self.master_instance, MissingRow):
458
455
  raise exceptions.BadRequest(self.master_instance)
459
456
  # raise Exception(
@@ -465,6 +462,7 @@ class BaseRequest:
465
462
  user=None,
466
463
  subst_user=None,
467
464
  current_project=None,
465
+ display_mode=None,
468
466
  master=None,
469
467
  master_instance=None,
470
468
  master_key=None,
@@ -494,6 +492,7 @@ class BaseRequest:
494
492
  else:
495
493
  self.user = user
496
494
  self.current_project = current_project
495
+ self.display_mode = display_mode
497
496
  if renderer is None:
498
497
  renderer = settings.SITE.kernel.text_renderer
499
498
  # renderer = settings.SITE.kernel.default_renderer
@@ -509,9 +508,11 @@ class BaseRequest:
509
508
  master = self.actor.master
510
509
  if master_type and ContentType is not None:
511
510
  try:
512
- master = ContentType.objects.get(pk=master_type).model_class()
511
+ master = ContentType.objects.get(
512
+ pk=master_type).model_class()
513
513
  except ContentType.DoesNotExist:
514
- raise exceptions.BadRequest("Invalid master_type {}".format(master_type)) from None
514
+ raise exceptions.BadRequest(
515
+ "Invalid master_type {}".format(master_type)) from None
515
516
  self.master = master
516
517
 
517
518
  if self.master is not None and self.master_instance is None:
@@ -520,7 +521,8 @@ class BaseRequest:
520
521
  master_instance = self.get_master_instance(
521
522
  self.master, master_key, master_type
522
523
  )
523
- self.master_instance = self.actor.cast_master_instance(master_instance)
524
+ self.master_instance = self.actor.cast_master_instance(
525
+ master_instance)
524
526
 
525
527
  self.row_meta = dict(meta=True)
526
528
 
@@ -642,7 +644,10 @@ class BaseRequest:
642
644
  kw.update(user=request.user)
643
645
  kw.update(subst_user=request.subst_user)
644
646
  kw.update(requesting_panel=request.requesting_panel)
645
- kw.update(current_project=rqdata.get(constants.URL_PARAM_PROJECT, None))
647
+ kw.update(current_project=rqdata.get(
648
+ constants.URL_PARAM_PROJECT, None))
649
+ kw.update(display_mode=rqdata.get(
650
+ constants.URL_PARAM_DISPLAY_MODE, None))
646
651
 
647
652
  # If the incoming request specifies an active tab, then the
648
653
  # response must forward this information. Otherwise Lino would
@@ -713,7 +718,8 @@ class BaseRequest:
713
718
  offset = rqdata.get(constants.URL_PARAM_START, None)
714
719
  if offset:
715
720
  kw.update(offset=int(offset))
716
- limit = rqdata.get(constants.URL_PARAM_LIMIT, self.actor.preview_limit)
721
+ limit = rqdata.get(constants.URL_PARAM_LIMIT,
722
+ self.actor.preview_limit)
717
723
  if limit:
718
724
  kw.update(limit=int(limit))
719
725
  except ValueError:
@@ -1007,7 +1013,8 @@ class BaseRequest:
1007
1013
 
1008
1014
  debug = lambda self, *args, **kwargs: self.logger.debug(*args, **kwargs)
1009
1015
  info = lambda self, *args, **kwargs: self.logger.info(*args, **kwargs)
1010
- warning = lambda self, *args, **kwargs: self.logger.warning(*args, **kwargs)
1016
+ warning = lambda self, * \
1017
+ args, **kwargs: self.logger.warning(*args, **kwargs)
1011
1018
 
1012
1019
  async def adebug(self, *args, **kwargs):
1013
1020
  return await self.alogger.debug(*args, **kwargs)
@@ -1043,7 +1050,8 @@ class BaseRequest:
1043
1050
  )
1044
1051
  return
1045
1052
 
1046
- self.logger.info("Send email '%s' from %s to %s", subject, sender, recipients)
1053
+ self.logger.info("Send email '%s' from %s to %s",
1054
+ subject, sender, recipients)
1047
1055
 
1048
1056
  recipients = [a for a in recipients if "@example.com" not in a]
1049
1057
  if not len(recipients):
@@ -1535,18 +1543,17 @@ class BaseRequest:
1535
1543
  if cls is not None:
1536
1544
  user_type = self.get_user().user_type
1537
1545
  for ba in cls.get_toolbar_actions(self.bound_action.action, user_type):
1538
- if not ba.action.select_rows:
1539
- if ba.action.show_in_plain:
1540
- ir = ba.request_from(self)
1541
- # assert ir.user is self.user
1542
- if ir.get_permission():
1543
- # try:
1544
- # btn = ir.ar2button(**btnattrs)
1545
- # except AttributeError:
1546
- # raise Exception("20200513 {}".format(ir))
1547
- btn = ir.ar2button(**btnattrs)
1548
- # assert iselement(btn)
1549
- buttons.append(btn)
1546
+ if ba.action.show_in_plain and not ba.action.select_rows:
1547
+ ir = ba.request_from(self)
1548
+ # assert ir.user is self.user
1549
+ if ir.get_permission():
1550
+ # try:
1551
+ # btn = ir.ar2button(**btnattrs)
1552
+ # except AttributeError:
1553
+ # raise Exception("20200513 {}".format(ir))
1554
+ btn = ir.ar2button(**btnattrs)
1555
+ # assert iselement(btn)
1556
+ buttons.append(btn)
1550
1557
  # print("20181106", cls, self.bound_action, buttons)
1551
1558
  return buttons
1552
1559
  # if len(buttons) == 0:
@@ -1662,7 +1669,8 @@ class BaseRequest:
1662
1669
  else:
1663
1670
  # print("20190703", self.actor, self.actor.default_action)
1664
1671
  sar = self.spawn_request(actor=self.actor)
1665
- list_title = tostring(sar.href_to_request(sar, list_title, icon_name=None))
1672
+ list_title = tostring(sar.href_to_request(
1673
+ sar, list_title, icon_name=None))
1666
1674
  # return list_title + " » " + self.get_detail_title(elem)
1667
1675
  return format_html("{} » {}", list_title, self.get_detail_title(elem))
1668
1676
 
@@ -1858,16 +1866,17 @@ class ActionRequest(BaseRequest):
1858
1866
  else:
1859
1867
  raise Exception(
1860
1868
  "20160329 params_layout {0} has no params_store "
1861
- "in {1!r}".format(self.actor.params_layout, self.actor)
1869
+ "in {1!r}".format(
1870
+ self.actor.params_layout, self.actor)
1862
1871
  )
1863
1872
  else:
1864
1873
  for k in param_values.keys():
1865
1874
  if k not in pv:
1866
- raise Exception(
1867
- "Invalid key '%s' in param_values of %s "
1868
- "request (possible keys are %s)"
1869
- % (k, self.actor, list(pv.keys()))
1870
- )
1875
+ msg = ("Invalid key '%s' in param_values of %s "
1876
+ "request (possible keys are %s)"
1877
+ % (k, self.actor, list(pv.keys())))
1878
+ # print(msg) # 20250309
1879
+ raise Exception(msg)
1871
1880
  pv.update(param_values)
1872
1881
  # print("20160329 ok", pv)
1873
1882
  self.param_values = AttrDict(**pv)
@@ -1883,7 +1892,8 @@ class ActionRequest(BaseRequest):
1883
1892
  # self.selected_rows, action)
1884
1893
  # raise Exception(msg)
1885
1894
  if request is not None:
1886
- apv.update(action.params_layout.params_store.parse_params(request))
1895
+ apv.update(
1896
+ action.params_layout.params_store.parse_params(request))
1887
1897
  self.action_param_values = AttrDict(**apv)
1888
1898
  # action.check_params(action_param_values)
1889
1899
  self.set_action_param_values(**action_param_values)
@@ -2001,19 +2011,20 @@ class ActionRequest(BaseRequest):
2001
2011
  except Exception as e:
2002
2012
  if not settings.SITE.catch_layout_exceptions:
2003
2013
  raise
2004
- # Report this exception. But since such errors may occur
2005
- # rather often and since exception loggers usually send an
2006
- # email to the local system admin, make sure to log each
2007
- # exception only once.
2008
- self.no_data_text = f"{e} (set catch_layout_exceptions to see details)"
2014
+ # Report this exception. But since such errors may occur rather
2015
+ # often and since exception loggers usually send an email to the
2016
+ # local system admin, make sure to log each exception only once.
2017
+ e = str(e)
2018
+ self.no_data_text = f"{e} (set catch_layout_exceptions to see \
2019
+ details)"
2009
2020
  self._data_iterator = []
2010
- w = WARNINGS_LOGGED.get(str(e))
2021
+ w = WARNINGS_LOGGED.get(e)
2011
2022
  if w is None:
2012
- WARNINGS_LOGGED[str(e)] = True
2023
+ WARNINGS_LOGGED[e] = True
2013
2024
  # raise
2014
2025
  # logger.exception(e)
2015
2026
  logger.warning(f"Error while executing {repr(self)}: {e}\n"
2016
- "(Subsequent warnings will be silenced.)")
2027
+ "(Subsequent warnings will be silenced.)")
2017
2028
 
2018
2029
  if self._data_iterator is None:
2019
2030
  raise Exception(f"No data iterator for {self}")
@@ -2023,7 +2034,8 @@ class ActionRequest(BaseRequest):
2023
2034
  self._data_iterator = tuple(self._data_iterator)
2024
2035
  if isinstance(self._data_iterator, (SearchQuery, SearchQuerySet)):
2025
2036
  self._sliced_data_iterator = tuple(
2026
- self.actor.get_rows_from_search_query(self._data_iterator, self)
2037
+ self.actor.get_rows_from_search_query(
2038
+ self._data_iterator, self)
2027
2039
  )
2028
2040
  else:
2029
2041
  self._sliced_data_iterator = sliced_data_iterator(
@@ -2147,7 +2159,8 @@ class ActionRequest(BaseRequest):
2147
2159
 
2148
2160
  if self.request is not None:
2149
2161
  for e in elems:
2150
- self.ah.store.form2obj(self, self.request.POST or self.rqdata, e, True)
2162
+ self.ah.store.form2obj(
2163
+ self, self.request.POST or self.rqdata, e, True)
2151
2164
  for e in elems:
2152
2165
  e.full_clean()
2153
2166
  return elems
@@ -2158,7 +2171,8 @@ class ActionRequest(BaseRequest):
2158
2171
  self.actor.handle_uploaded_files(elem, self.request)
2159
2172
 
2160
2173
  if self.request is not None:
2161
- self.ah.store.form2obj(self, self.request.POST or self.rqdata, elem, True)
2174
+ self.ah.store.form2obj(
2175
+ self, self.request.POST or self.rqdata, elem, True)
2162
2176
  elem.full_clean()
2163
2177
  return elem
2164
2178
 
@@ -2171,7 +2185,8 @@ class ActionRequest(BaseRequest):
2171
2185
  if self._status is not None and not kw:
2172
2186
  return self._status
2173
2187
  if self.actor.parameters:
2174
- pv = self.actor.params_layout.params_store.pv2dict(self, self.param_values)
2188
+ pv = self.actor.params_layout.params_store.pv2dict(
2189
+ self, self.param_values)
2175
2190
  # print(f"20250121c {self}\n{self.actor.params_layout.params_store.__class__}\n{self.actor.params_layout.params_store.param_fields}")
2176
2191
  # print(f"20250121c {self} {self.param_values.keys()}\n{self.actor.params_layout}\n{pv.keys()}")
2177
2192
  # print(f"20250121c {self}|{self.actor.params_layout.params_store}\n{}")
@@ -2181,6 +2196,8 @@ class ActionRequest(BaseRequest):
2181
2196
  bp = kw.setdefault("base_params", {})
2182
2197
  if self.current_project is not None:
2183
2198
  bp[constants.URL_PARAM_PROJECT] = self.current_project
2199
+ if self.display_mode is not None:
2200
+ bp[constants.URL_PARAM_DISPLAY_MODE] = self.display_mode
2184
2201
  if self.subst_user is not None:
2185
2202
  # raise Exception("20230331")
2186
2203
  bp[constants.URL_PARAM_SUBST_USER] = self.subst_user.id
@@ -2284,7 +2301,6 @@ class ActionRequest(BaseRequest):
2284
2301
  # ~ s = self.get_title()
2285
2302
  # ~ return s.encode('us-ascii','replace')
2286
2303
 
2287
-
2288
2304
  def to_rst(self, *args, **kw):
2289
2305
  """Returns a string representing this table request in
2290
2306
  reStructuredText markup.
@@ -2373,7 +2389,8 @@ class ActionRequest(BaseRequest):
2373
2389
  # if cellwidths and self.renderer.is_interactive:
2374
2390
  if cellwidths:
2375
2391
  totwidth = sum([int(w) for w in cellwidths])
2376
- widths = [str(int(int(w) * 100 / totwidth)) + "%" for w in cellwidths]
2392
+ widths = [str(int(int(w) * 100 / totwidth))
2393
+ + "%" for w in cellwidths]
2377
2394
  for i, td in enumerate(headers):
2378
2395
  # td.set('width', six.text_type(cellwidths[i]))
2379
2396
  td.set("width", widths[i])
@@ -2381,7 +2398,8 @@ class ActionRequest(BaseRequest):
2381
2398
  # ~ print 20120623, ar.actor
2382
2399
  recno = 0
2383
2400
  for obj in data_iterator:
2384
- cells = ar.row2html(recno, columns, obj, sums, **self.renderer.cellattrs)
2401
+ cells = ar.row2html(recno, columns, obj, sums,
2402
+ **self.renderer.cellattrs)
2385
2403
  if cells is not None:
2386
2404
  recno += 1
2387
2405
  tble.body.append(xghtml.E.tr(*cells))
@@ -2420,7 +2438,8 @@ class ActionRequest(BaseRequest):
2420
2438
  columns = None
2421
2439
  else:
2422
2440
  data = getrqdata(ar.request)
2423
- columns = [str(x) for x in data.getlist(constants.URL_PARAM_COLUMNS)]
2441
+ columns = [str(x) for x in data.getlist(
2442
+ constants.URL_PARAM_COLUMNS)]
2424
2443
  if columns:
2425
2444
  all_widths = data.getlist(constants.URL_PARAM_WIDTHS)
2426
2445
  hiddens = [
@@ -2464,10 +2483,12 @@ class ActionRequest(BaseRequest):
2464
2483
  # except AttributeError as ex:
2465
2484
  # raise AttributeError("20160529 %s : %s" % (e, ex))
2466
2485
  #
2467
- fields = [e for e in fields if not e.value.get("hidden", False)]
2486
+ fields = [e for e in fields if not e.value.get(
2487
+ "hidden", False)]
2468
2488
  fields = [e for e in fields if not e.hidden]
2469
2489
 
2470
- widths = ["%d" % (e.width or e.preferred_width) for e in fields]
2490
+ widths = ["%d" % (e.width or e.preferred_width)
2491
+ for e in fields]
2471
2492
  columns = [e.name for e in fields]
2472
2493
 
2473
2494
  headers = [column_header(col) for col in fields]