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.
- lino/__init__.py +1 -1
- lino/api/dd.py +11 -48
- lino/api/doctest.py +34 -36
- lino/core/actions.py +25 -23
- lino/core/actors.py +37 -17
- lino/core/choicelists.py +10 -8
- lino/core/dbtables.py +1 -1
- lino/core/elems.py +46 -30
- lino/core/fields.py +19 -9
- lino/core/inject.py +7 -6
- lino/core/kernel.py +26 -66
- lino/core/model.py +44 -31
- lino/core/plugin.py +4 -4
- lino/core/requests.py +76 -55
- lino/core/site.py +84 -30
- lino/core/store.py +5 -2
- lino/core/utils.py +12 -7
- lino/help_texts.py +3 -8
- lino/management/commands/prep.py +1 -1
- lino/mixins/duplicable.py +6 -4
- lino/mixins/sequenced.py +17 -6
- lino/modlib/__init__.py +0 -2
- lino/modlib/changes/models.py +21 -10
- lino/modlib/checkdata/models.py +59 -24
- lino/modlib/comments/fixtures/demo2.py +12 -3
- lino/modlib/comments/models.py +7 -7
- lino/modlib/comments/ui.py +8 -5
- lino/modlib/export_excel/models.py +7 -5
- lino/modlib/extjs/views.py +39 -20
- lino/modlib/help/management/commands/makehelp.py +5 -2
- lino/modlib/jinja/mixins.py +25 -14
- lino/modlib/linod/__init__.py +1 -0
- lino/modlib/linod/choicelists.py +21 -0
- lino/modlib/linod/consumers.py +13 -4
- lino/modlib/linod/management/commands/linod.py +6 -2
- lino/modlib/linod/mixins.py +16 -11
- lino/modlib/linod/models.py +4 -2
- lino/modlib/notify/models.py +18 -10
- lino/modlib/printing/actions.py +41 -30
- lino/modlib/printing/choicelists.py +11 -9
- lino/modlib/printing/mixins.py +25 -20
- lino/modlib/publisher/models.py +5 -5
- lino/modlib/summaries/models.py +3 -2
- lino/modlib/system/models.py +28 -29
- lino/modlib/uploads/__init__.py +5 -5
- lino/modlib/uploads/actions.py +2 -8
- lino/modlib/uploads/choicelists.py +10 -10
- lino/modlib/uploads/fixtures/std.py +17 -0
- lino/modlib/uploads/mixins.py +20 -8
- lino/modlib/uploads/models.py +60 -35
- lino/modlib/uploads/ui.py +10 -7
- lino/utils/media.py +45 -23
- lino/utils/report.py +5 -4
- lino/utils/soup.py +22 -4
- {lino-25.2.3.dist-info → lino-25.3.1.dist-info}/METADATA +1 -1
- {lino-25.2.3.dist-info → lino-25.3.1.dist-info}/RECORD +59 -80
- lino/mixins/uploadable.py +0 -3
- lino/sandbox/bcss/PerformInvestigation.py +0 -2260
- lino/sandbox/bcss/SSDNReply.py +0 -3924
- lino/sandbox/bcss/SSDNRequest.py +0 -3723
- lino/sandbox/bcss/__init__.py +0 -0
- lino/sandbox/bcss/readme.txt +0 -1
- lino/sandbox/bcss/test.py +0 -92
- lino/sandbox/bcss/test2.py +0 -128
- lino/sandbox/bcss/test3.py +0 -161
- lino/sandbox/bcss/test4.py +0 -167
- lino/sandbox/contacts/__init__.py +0 -0
- lino/sandbox/contacts/fixtures/__init__.py +0 -0
- lino/sandbox/contacts/fixtures/demo.py +0 -365
- lino/sandbox/contacts/manage.py +0 -10
- lino/sandbox/contacts/models.py +0 -395
- lino/sandbox/contacts/settings.py +0 -67
- lino/sandbox/tx25/XSD/RetrieveTIGroupsV3.wsdl +0 -65
- lino/sandbox/tx25/XSD/RetrieveTIGroupsV3.xsd +0 -286
- lino/sandbox/tx25/XSD/rn25_Release201104.xsd +0 -2855
- lino/sandbox/tx25/xsd2py1.py +0 -68
- lino/sandbox/tx25/xsd2py2.py +0 -62
- lino/sandbox/tx25/xsd2py3.py +0 -56
- {lino-25.2.3.dist-info → lino-25.3.1.dist-info}/WHEEL +0 -0
- {lino-25.2.3.dist-info → lino-25.3.1.dist-info}/licenses/AUTHORS.rst +0 -0
- {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(
|
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(
|
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(
|
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(
|
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(
|
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(
|
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(
|
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(
|
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
|
-
|
676
|
-
|
677
|
-
|
678
|
-
|
679
|
-
|
680
|
-
|
681
|
-
|
682
|
-
if
|
683
|
-
if
|
684
|
-
|
685
|
-
|
686
|
-
|
687
|
-
|
688
|
-
|
689
|
-
|
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
|
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(
|
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,
|
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(
|
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-
|
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
|
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(
|
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-
|
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
|
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
|
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
|
-
|
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
|
-
|
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(
|
511
|
+
master = ContentType.objects.get(
|
512
|
+
pk=master_type).model_class()
|
513
513
|
except ContentType.DoesNotExist:
|
514
|
-
raise exceptions.BadRequest(
|
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(
|
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(
|
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,
|
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, *
|
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",
|
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
|
-
|
1540
|
-
|
1541
|
-
|
1542
|
-
|
1543
|
-
|
1544
|
-
|
1545
|
-
|
1546
|
-
|
1547
|
-
|
1548
|
-
|
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(
|
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(
|
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
|
-
|
1867
|
-
|
1868
|
-
|
1869
|
-
|
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(
|
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
|
-
#
|
2006
|
-
#
|
2007
|
-
|
2008
|
-
self.no_data_text = f"{e} (set catch_layout_exceptions to see
|
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(
|
2021
|
+
w = WARNINGS_LOGGED.get(e)
|
2011
2022
|
if w is None:
|
2012
|
-
WARNINGS_LOGGED[
|
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
|
-
|
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(
|
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(
|
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(
|
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(
|
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))
|
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,
|
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(
|
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(
|
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)
|
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]
|