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/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(
|
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!" % (
|
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(
|
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
|
-
|
490
|
-
|
491
|
-
|
492
|
-
|
493
|
-
|
494
|
-
|
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(
|
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)" %
|
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
|
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(
|
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(
|
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(
|
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(
|
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(
|
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...") %
|
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(
|
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(
|
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(
|
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:
|
@@ -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(
|
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(
|
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(
|
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(
|
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
|
-
|
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__,
|
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(
|
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(
|
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(
|
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
|
-
"""
|
1308
|
-
|
1309
|
-
|
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(
|
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/inject.py
CHANGED
@@ -132,9 +132,9 @@ def check_pending_injects(sender, models_list=None, **kw):
|
|
132
132
|
# ~ logger.info("20131110 no pending injects")
|
133
133
|
"""
|
134
134
|
20130106
|
135
|
-
now we loop a last time over each model and fill
|
136
|
-
otherwise if some
|
137
|
-
has subclasses,
|
135
|
+
now we loop a last time over each model and fill its _meta._field_cache
|
136
|
+
otherwise if some plugin used inject_field() on a model that
|
137
|
+
has subclasses, the new field would not be seen by subclasses
|
138
138
|
"""
|
139
139
|
for model in models_list:
|
140
140
|
model._meta._expire_cache()
|
@@ -225,7 +225,8 @@ def update_model(model_spec, **actions):
|
|
225
225
|
def todo(model):
|
226
226
|
for k, v in actions.items():
|
227
227
|
if not hasattr(model, k):
|
228
|
-
raise Exception(
|
228
|
+
raise Exception(
|
229
|
+
"%s has no attribute %s to update." % (model, k))
|
229
230
|
setattr(model, k, v)
|
230
231
|
|
231
232
|
if isinstance(model_spec, models.Model):
|
@@ -389,8 +390,8 @@ def inject_quick_add_buttons(model, name, target):
|
|
389
390
|
inject_field(
|
390
391
|
model,
|
391
392
|
name,
|
392
|
-
fields.VirtualField(fields.DisplayField(
|
393
|
-
|
393
|
+
fields.VirtualField(fields.DisplayField(
|
394
|
+
tm._meta.verbose_name_plural), fn))
|
394
395
|
|
395
396
|
|
396
397
|
# def django_patch():
|
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``
|
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,19 +21,16 @@ 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
|
29
27
|
import threading
|
30
28
|
from importlib import import_module
|
31
|
-
import json
|
32
29
|
|
33
|
-
from django.apps import AppConfig
|
30
|
+
# from django.apps import AppConfig
|
34
31
|
from django.apps import apps
|
35
32
|
from django.conf import settings
|
36
33
|
from django.http import HttpResponse
|
37
|
-
from django.utils.encoding import force_str
|
38
34
|
from django.utils.text import format_lazy
|
39
35
|
from django.utils.translation import gettext_lazy as _
|
40
36
|
from django.core.exceptions import PermissionDenied, ValidationError
|
@@ -454,7 +450,8 @@ class Kernel(object):
|
|
454
450
|
try:
|
455
451
|
a.setup_columns()
|
456
452
|
except DatabaseError:
|
457
|
-
logger.debug(
|
453
|
+
logger.debug(
|
454
|
+
"Ignoring DatabaseError in %s.setup_columns", a)
|
458
455
|
if (
|
459
456
|
issubclass(a, dbtables.Table)
|
460
457
|
and a.model is not None
|
@@ -533,7 +530,8 @@ class Kernel(object):
|
|
533
530
|
# for p in site.installed_plugins:
|
534
531
|
# p.after_discover()
|
535
532
|
|
536
|
-
self.reserved_names = [getattr(constants, n)
|
533
|
+
self.reserved_names = [getattr(constants, n)
|
534
|
+
for n in constants.URL_PARAMS]
|
537
535
|
|
538
536
|
names = set()
|
539
537
|
for n in self.reserved_names:
|
@@ -565,7 +563,8 @@ class Kernel(object):
|
|
565
563
|
found = True
|
566
564
|
break
|
567
565
|
if not found:
|
568
|
-
raise Exception(
|
566
|
+
raise Exception(
|
567
|
+
f"Invalid modname {modname} in Site.web_front_ends")
|
569
568
|
self.web_front_ends = lst
|
570
569
|
# print("20230407 web front ends:")
|
571
570
|
# print("\n".join(["- {0.url_prefix} --> {0.app_name} {1}".format(p, hash(p))
|
@@ -623,7 +622,8 @@ class Kernel(object):
|
|
623
622
|
|
624
623
|
return get_welcome_messages
|
625
624
|
|
626
|
-
site.add_welcome_handler(
|
625
|
+
site.add_welcome_handler(
|
626
|
+
handler(a), a, "welcome_message_when_count")
|
627
627
|
|
628
628
|
# Remove "meaningless" user roles, i.e. those which are either used by all
|
629
629
|
# types or by no type. This seems to be
|
@@ -712,7 +712,8 @@ class Kernel(object):
|
|
712
712
|
msg = (
|
713
713
|
"{0}.{1} has on_delete SET_NULL but " "is not nullable "
|
714
714
|
)
|
715
|
-
msg = msg.format(
|
715
|
+
msg = msg.format(
|
716
|
+
fmn(m), fk.name, fk.remote_field.model)
|
716
717
|
raise Exception(msg)
|
717
718
|
|
718
719
|
else:
|
@@ -785,7 +786,8 @@ class Kernel(object):
|
|
785
786
|
pointed_model.objects.get(pk=fk)
|
786
787
|
except pointed_model.DoesNotExist:
|
787
788
|
msg = "Invalid primary key {1} for {2} in `{0}`"
|
788
|
-
obj._message = msg.format(
|
789
|
+
obj._message = msg.format(
|
790
|
+
gfk.fk_field, fk, fmn(pointed_model))
|
789
791
|
if gfk.name in model.allow_cascaded_delete:
|
790
792
|
obj._todo = "delete"
|
791
793
|
elif fk_field.null:
|
@@ -807,7 +809,8 @@ class Kernel(object):
|
|
807
809
|
"""
|
808
810
|
if not ar.get_permission():
|
809
811
|
if False:
|
810
|
-
msg = "{} has no permission to run {}".format(
|
812
|
+
msg = "{} has no permission to run {}".format(
|
813
|
+
ar.get_user(), ar)
|
811
814
|
else:
|
812
815
|
msg = "No permission to run {}".format(ar)
|
813
816
|
# raise Exception(msg)
|
@@ -842,7 +845,8 @@ class Kernel(object):
|
|
842
845
|
and ar.master_instance is not None
|
843
846
|
and len(ar.selected_rows)
|
844
847
|
):
|
845
|
-
ar.set_response(
|
848
|
+
ar.set_response(
|
849
|
+
master_data=ar.selected_rows[0].get_master_data(ar))
|
846
850
|
if (
|
847
851
|
a.parameters
|
848
852
|
and not a.no_params_window
|
@@ -852,15 +856,16 @@ class Kernel(object):
|
|
852
856
|
except Warning as e:
|
853
857
|
ar.error(e, alert=True)
|
854
858
|
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
859
|
# print(f"20240911 oops {repr(e)}")
|
860
|
+
msg = ar.ah.actor.error2str(e)
|
863
861
|
ar.error(msg, alert=True)
|
862
|
+
if not isinstance(e, (ValidationError, IntegrityError)):
|
863
|
+
logger.info(
|
864
|
+
"Error during kernel.run_action() in %s: %s", ar, msg)
|
865
|
+
if is_devserver():
|
866
|
+
# can be interesting during development but disturbs on a
|
867
|
+
# production server
|
868
|
+
logger.exception(e)
|
864
869
|
|
865
870
|
return ar.renderer.render_action_response(ar)
|
866
871
|
|
@@ -947,51 +952,6 @@ class Kernel(object):
|
|
947
952
|
# def setup_static_link(self, urlpatterns, short_name,
|
948
953
|
# attr_name=None, source=None):
|
949
954
|
|
950
|
-
def mark_virgin(self):
|
951
|
-
dbhash = self.get_dbhash()
|
952
|
-
fn = self.site.site_dir / "dbhash.json"
|
953
|
-
with fn.open("w") as fp:
|
954
|
-
json.dump(dbhash, fp)
|
955
|
-
|
956
|
-
def check_virgin(self):
|
957
|
-
new = self.get_dbhash()
|
958
|
-
fn = self.site.site_dir / "dbhash.json"
|
959
|
-
if not fn.exists():
|
960
|
-
raise Exception(
|
961
|
-
"No `dbhash.json` in {} " "(did you run `django-admin prep`?)".format(
|
962
|
-
self.site.site_dir
|
963
|
-
)
|
964
|
-
)
|
965
|
-
with fn.open("r") as fp:
|
966
|
-
old = json.load(fp)
|
967
|
-
|
968
|
-
# noi1r has noi1e as master_site, but the react front end removes the
|
969
|
-
# tinymce plugin, i.e. noi1r doesn't care about
|
970
|
-
# tinymce.TextFieldTemplate model.
|
971
|
-
|
972
|
-
diffs = {}
|
973
|
-
for k, v in new.items():
|
974
|
-
oldv = old.get(k, None)
|
975
|
-
if oldv != v:
|
976
|
-
diffs[k] = (oldv, v)
|
977
|
-
# diff = set(old.items()) ^ set(new.items())
|
978
|
-
if diffs:
|
979
|
-
# db = self.site.django_settings.get('SETTINGS_MODULE')
|
980
|
-
db = self.site.site_dir
|
981
|
-
raise Exception("Database {} isn't virgin: {}".format(db, diffs))
|
982
|
-
|
983
|
-
# logger.info("Database certified as virgin")
|
984
|
-
|
985
|
-
def get_dbhash(self):
|
986
|
-
# TODO: we currently check only the number of rows per model. That's
|
987
|
-
# very naive.
|
988
|
-
rv = dict()
|
989
|
-
for m in get_models(include_auto_created=True):
|
990
|
-
k = fmn(m)
|
991
|
-
if k != "sessions.Session":
|
992
|
-
rv[k] = m.objects.count()
|
993
|
-
return rv
|
994
|
-
|
995
955
|
|
996
956
|
def site_startup(self):
|
997
957
|
"""This is being imported and called from
|