lino 25.4.5__py3-none-any.whl → 25.5.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/doctest.py +14 -0
- lino/core/actions.py +2 -8
- lino/core/actors.py +5 -3
- lino/core/choicelists.py +8 -11
- lino/core/dbtables.py +7 -7
- lino/core/elems.py +16 -11
- lino/core/fields.py +67 -19
- lino/core/kernel.py +6 -0
- lino/core/layouts.py +2 -2
- lino/core/model.py +15 -12
- lino/core/plugin.py +2 -2
- lino/core/site.py +11 -13
- lino/core/store.py +5 -6
- lino/core/tables.py +13 -9
- lino/core/utils.py +4 -23
- lino/help_texts.py +9 -1
- lino/mixins/duplicable.py +9 -3
- lino/mixins/ref.py +9 -0
- lino/mixins/registrable.py +2 -2
- lino/mixins/sequenced.py +12 -8
- lino/modlib/extjs/ext_renderer.py +1 -1
- lino/modlib/gfks/fields.py +1 -1
- lino/modlib/help/models.py +2 -1
- lino/modlib/jinja/renderer.py +1 -0
- lino/modlib/linod/models.py +1 -1
- lino/modlib/notify/fixtures/demo2.py +7 -2
- lino/modlib/periods/fixtures/std.py +1 -3
- lino/modlib/periods/models.py +26 -5
- lino/modlib/publisher/fixtures/synodalworld.py +0 -2
- lino/modlib/weasyprint/__init__.py +1 -0
- lino/modlib/weasyprint/choicelists.py +2 -11
- lino/modlib/weasyprint/config/weasyprint/base.weasy.html +1 -1
- lino/utils/__init__.py +20 -0
- lino/utils/choosers.py +21 -15
- lino/utils/quantities.py +21 -3
- {lino-25.4.5.dist-info → lino-25.5.1.dist-info}/METADATA +1 -1
- {lino-25.4.5.dist-info → lino-25.5.1.dist-info}/RECORD +41 -41
- {lino-25.4.5.dist-info → lino-25.5.1.dist-info}/WHEEL +0 -0
- {lino-25.4.5.dist-info → lino-25.5.1.dist-info}/licenses/AUTHORS.rst +0 -0
- {lino-25.4.5.dist-info → lino-25.5.1.dist-info}/licenses/COPYING +0 -0
lino/__init__.py
CHANGED
@@ -31,7 +31,7 @@ from django import VERSION
|
|
31
31
|
from django.apps import AppConfig
|
32
32
|
from django.conf import settings
|
33
33
|
import warnings
|
34
|
-
__version__ = '25.
|
34
|
+
__version__ = '25.5.1'
|
35
35
|
|
36
36
|
# import setuptools # avoid UserWarning "Distutils was imported before Setuptools"?
|
37
37
|
|
lino/api/doctest.py
CHANGED
@@ -524,6 +524,20 @@ def show_choicelists():
|
|
524
524
|
print(rstgen.table(headers, rows))
|
525
525
|
|
526
526
|
|
527
|
+
def show_parent_layouts():
|
528
|
+
"""
|
529
|
+
Show all actors having a parent layout.
|
530
|
+
"""
|
531
|
+
headers = ["name", "parent layout"]
|
532
|
+
rows = []
|
533
|
+
for a in actors.actors_list:
|
534
|
+
if issubclass(a, AbstractTable) and a.parent_layout:
|
535
|
+
parent = a.parent_layout._datasource
|
536
|
+
row = [str(a), str(parent)] # , parent.__module__]
|
537
|
+
rows.append(row)
|
538
|
+
print(rstgen.table(headers, rows))
|
539
|
+
|
540
|
+
|
527
541
|
def show_permissions(*args):
|
528
542
|
print(visible_for(*args))
|
529
543
|
|
lino/core/actions.py
CHANGED
@@ -31,18 +31,12 @@ from django.core.exceptions import BadRequest
|
|
31
31
|
|
32
32
|
from django.apps import apps
|
33
33
|
|
34
|
-
get_models = apps.get_models
|
35
|
-
|
36
34
|
|
37
35
|
def discover_choosers():
|
38
36
|
logger.debug("Discovering choosers for database fields...")
|
39
|
-
|
40
|
-
|
41
|
-
# ~ n = 0
|
42
|
-
allfields = model._meta.fields
|
43
|
-
for field in allfields:
|
37
|
+
for model in apps.get_models():
|
38
|
+
for field in model._meta.fields:
|
44
39
|
check_for_chooser(model, field)
|
45
|
-
# ~ logger.debug("Discovered %d choosers in model %s.",n,model)
|
46
40
|
|
47
41
|
|
48
42
|
def resolve_layout(cls, k, spec, layout_class, **options):
|
lino/core/actors.py
CHANGED
@@ -17,10 +17,8 @@ from django.db import models
|
|
17
17
|
from django.conf import settings
|
18
18
|
from django.utils.translation import gettext_lazy as _
|
19
19
|
from django.utils.html import format_html, mark_safe, SafeString
|
20
|
-
from django.core.exceptions import BadRequest
|
21
20
|
|
22
21
|
from lino import logger
|
23
|
-
from lino.utils import isiterable
|
24
22
|
from lino.utils import MissingRow
|
25
23
|
from lino.core import fields
|
26
24
|
from lino.core import actions
|
@@ -532,7 +530,11 @@ class Actor(actions.Parametrizable, Permittable, metaclass=ActorMetaClass):
|
|
532
530
|
@classmethod
|
533
531
|
def get_chooser_for_field(cls, fieldname):
|
534
532
|
d = getattr(cls, "_choosers_dict", {})
|
535
|
-
|
533
|
+
ch = d.get(fieldname, None)
|
534
|
+
if ch is not None:
|
535
|
+
return ch
|
536
|
+
if cls.model is not None:
|
537
|
+
return cls.model.get_chooser_for_field(fieldname)
|
536
538
|
|
537
539
|
# @classmethod
|
538
540
|
# def inject_field(cls, name, fld):
|
lino/core/choicelists.py
CHANGED
@@ -1,25 +1,19 @@
|
|
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
5
|
Defines the classes :class:`Choice` and :class:`ChoiceList`. See
|
6
6
|
:doc:`/dev/choicelists`.
|
7
7
|
"""
|
8
8
|
|
9
|
-
from future.utils import with_metaclass
|
10
|
-
|
11
9
|
import warnings
|
12
|
-
|
13
10
|
from django.utils.translation import gettext_lazy as _
|
14
11
|
from django.utils.functional import lazy
|
15
|
-
|
16
12
|
# from django.utils.deconstruct import deconstructible
|
17
13
|
from django.db import models
|
18
14
|
from django.conf import settings
|
19
|
-
from django.db.models import NOT_PROVIDED
|
20
15
|
from django.db.migrations.serializer import BaseSerializer
|
21
16
|
from django.db.migrations.writer import MigrationWriter
|
22
|
-
|
23
17
|
from lino.utils import MissingRow
|
24
18
|
from lino.core.utils import resolve_field
|
25
19
|
from lino.core import actions
|
@@ -33,8 +27,9 @@ STRICT = True
|
|
33
27
|
VALUE_FIELD = models.CharField(_("value"), max_length=20)
|
34
28
|
VALUE_FIELD.attname = "value"
|
35
29
|
|
36
|
-
|
37
30
|
# @deconstructible
|
31
|
+
|
32
|
+
|
38
33
|
class Choice(fields.TableRow):
|
39
34
|
"""
|
40
35
|
A constant value whose string representation depends on the current language
|
@@ -345,7 +340,7 @@ class PointingChoice(Choice):
|
|
345
340
|
return not isinstance(self.get_object(severe=False), MissingRow)
|
346
341
|
|
347
342
|
|
348
|
-
class ChoiceList(
|
343
|
+
class ChoiceList(tables.AbstractTable, metaclass=ChoiceListMeta):
|
349
344
|
"""
|
350
345
|
User-defined choice lists must inherit from this base class.
|
351
346
|
|
@@ -743,12 +738,14 @@ class ChoiceList(with_metaclass(ChoiceListMeta, tables.AbstractTable)):
|
|
743
738
|
|
744
739
|
Override this to customize the display text of choices.
|
745
740
|
|
741
|
+
Usage example: :class:`lino.modlib.system.DisplayColors`.
|
742
|
+
|
746
743
|
:class:`lino.modlib.users.UserGroups` and
|
747
744
|
:class:`lino.modlib.cv.models.CefLevel` used to do this before
|
748
745
|
we had the :attr:`ChoiceList.show_values` option.
|
749
746
|
|
750
|
-
|
751
|
-
|
747
|
+
The return value must be lazily translatable because it is also used to
|
748
|
+
build the `choices` attribute of ChoiceListFields on this choicelist.
|
752
749
|
|
753
750
|
Note that Django's `lazy` function has a list of "resultclasses" that
|
754
751
|
are used "so that the automatic forcing of the lazy evaluation code is
|
lino/core/dbtables.py
CHANGED
@@ -228,13 +228,13 @@ class Table(AbstractTable):
|
|
228
228
|
return qs.model.objects.none()
|
229
229
|
return qs.filter(flt)
|
230
230
|
|
231
|
-
@classmethod
|
232
|
-
def get_chooser_for_field(self, fieldname):
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
231
|
+
# @classmethod
|
232
|
+
# def get_chooser_for_field(self, fieldname):
|
233
|
+
# ch = super().get_chooser_for_field(fieldname)
|
234
|
+
# if ch is not None:
|
235
|
+
# return ch
|
236
|
+
# if self.model is not None:
|
237
|
+
# return self.model.get_chooser_for_field(fieldname)
|
238
238
|
|
239
239
|
@classmethod
|
240
240
|
def column_choices(self):
|
lino/core/elems.py
CHANGED
@@ -5,8 +5,6 @@
|
|
5
5
|
|
6
6
|
"""
|
7
7
|
|
8
|
-
from lino.utils.jsgen import VisibleComponent
|
9
|
-
import types
|
10
8
|
import decimal
|
11
9
|
from lxml.html import fromstring
|
12
10
|
from lxml import etree
|
@@ -15,7 +13,7 @@ from django.db import models
|
|
15
13
|
from django.utils.translation import gettext as _
|
16
14
|
from django.utils.functional import Promise
|
17
15
|
from django.utils.text import format_lazy
|
18
|
-
from django.utils.html import SafeString
|
16
|
+
from django.utils.html import SafeString
|
19
17
|
from django.conf import settings
|
20
18
|
from django.db.models.fields.related import (
|
21
19
|
ReverseOneToOneDescriptor as SingleRelatedObjectDescriptor,
|
@@ -40,16 +38,15 @@ from lino.core.gfks import GenericRelation, GenericRel
|
|
40
38
|
from lino.core import choicelists
|
41
39
|
from lino.modlib.bootstrap3.views import table2html
|
42
40
|
|
43
|
-
from lino.utils.
|
44
|
-
from lino.utils.html import html2text
|
41
|
+
from lino.utils.jsgen import VisibleComponent
|
42
|
+
from lino.utils.html import E, tostring, forcetext, html2text
|
45
43
|
from lino.utils.ranges import constrain
|
46
44
|
from lino.utils import jsgen
|
47
45
|
from lino.utils import mti
|
48
46
|
from lino.utils.jsgen import py2js, js_code
|
49
|
-
from lino.utils.html2xhtml import html2xhtml
|
47
|
+
# from lino.utils.html2xhtml import html2xhtml
|
50
48
|
from lino.utils import join_elems
|
51
49
|
from lino.core.actors import qs2summary
|
52
|
-
from lino.core.store import get_atomizer
|
53
50
|
|
54
51
|
from lino.core.layouts import (
|
55
52
|
FormLayout,
|
@@ -64,9 +61,9 @@ from lino.core import tables
|
|
64
61
|
from lino.core.gfks import GenericForeignKey
|
65
62
|
from lino.utils.format_date import fds
|
66
63
|
from lino.modlib.users.utils import get_user_profile
|
64
|
+
from lino.modlib.gfks.fields import GenericForeignKeyIdField
|
67
65
|
|
68
66
|
# from etgen import etree
|
69
|
-
from lino.utils.html import E, forcetext
|
70
67
|
from etgen.html2rst import html2rst
|
71
68
|
|
72
69
|
EXT_CHAR_WIDTH = 9
|
@@ -2554,6 +2551,7 @@ class TabPanel(Panel):
|
|
2554
2551
|
|
2555
2552
|
_FIELD2ELEM = [
|
2556
2553
|
# (dd.Constant, ConstantElement),
|
2554
|
+
(GenericForeignKeyIdField, ComplexRemoteComboFieldElement),
|
2557
2555
|
(fields.RecurrenceField, RecurrenceElement),
|
2558
2556
|
(fields.DelayedHtmlBox, DisplayElement),
|
2559
2557
|
(fields.HtmlBox, HtmlBoxElement),
|
@@ -2609,6 +2607,9 @@ def field2elem(layout_handle, field, **kw):
|
|
2609
2607
|
holder = layout_handle.layout.get_chooser_holder()
|
2610
2608
|
ch = holder.get_chooser_for_field(field.name)
|
2611
2609
|
|
2610
|
+
# if field.name == "answer":
|
2611
|
+
# print(f"20250511 {field} {ch} {field.choices}")
|
2612
|
+
|
2612
2613
|
if ch:
|
2613
2614
|
kw.update(can_create_choice=ch.can_create_choice)
|
2614
2615
|
if ch.can_create_choice or not ch.force_selection:
|
@@ -2623,6 +2624,7 @@ def field2elem(layout_handle, field, **kw):
|
|
2623
2624
|
return ForeignKeyElement(layout_handle, field, **kw)
|
2624
2625
|
else:
|
2625
2626
|
return ComplexRemoteComboFieldElement(layout_handle, field, **kw)
|
2627
|
+
|
2626
2628
|
if field.choices:
|
2627
2629
|
if isinstance(field, choicelists.ChoiceListField):
|
2628
2630
|
if field.choicelist.preferred_width is None:
|
@@ -2756,6 +2758,7 @@ def get_summary_element(lh, de, name, **kw):
|
|
2756
2758
|
def create_layout_element(lh, name, **kw):
|
2757
2759
|
"""
|
2758
2760
|
Create a layout element from the named data element.
|
2761
|
+
`lh` is the layout handle of the actor who is the datasource of the data element.
|
2759
2762
|
"""
|
2760
2763
|
|
2761
2764
|
if True: # settings.SITE.catch_layout_exceptions:
|
@@ -2850,6 +2853,8 @@ def create_layout_element(lh, name, **kw):
|
|
2850
2853
|
# The data element refers to a slave table. Slave tables make
|
2851
2854
|
# no sense in an insert window because the master does not yet
|
2852
2855
|
# exist.
|
2856
|
+
de.attach_to_parent_layout(lh.layout)
|
2857
|
+
|
2853
2858
|
if settings.SITE.is_hidden_plugin(de.app_label):
|
2854
2859
|
return None
|
2855
2860
|
|
@@ -2858,8 +2863,8 @@ def create_layout_element(lh, name, **kw):
|
|
2858
2863
|
|
2859
2864
|
# print("20240317", de, lh)
|
2860
2865
|
if isinstance(lh.layout, FormLayout):
|
2861
|
-
# When a table is specified in the layout of a
|
2862
|
-
#
|
2866
|
+
# When a table is specified in the layout of a detail window, then it
|
2867
|
+
# is rendered as a :term:`slave panel`. The panel will have a
|
2863
2868
|
# tool button to "open that table in its own window". The display
|
2864
2869
|
# mode of that summary is defined by the `default_display_modes` of
|
2865
2870
|
# the table.
|
@@ -2884,7 +2889,7 @@ def create_layout_element(lh, name, **kw):
|
|
2884
2889
|
|
2885
2890
|
if (
|
2886
2891
|
len(de.default_display_modes) > 1
|
2887
|
-
and lh.ui.
|
2892
|
+
and lh.ui.media_name == "react"
|
2888
2893
|
):
|
2889
2894
|
# print(f"20240928 {de} in {lh} gets SlaveContainer")
|
2890
2895
|
return SlaveContainer(lh, name, de, **kw)
|
lino/core/fields.py
CHANGED
@@ -8,7 +8,6 @@ related to fields.
|
|
8
8
|
|
9
9
|
#fmt: off
|
10
10
|
|
11
|
-
from lino import logger
|
12
11
|
import datetime
|
13
12
|
from decimal import Decimal
|
14
13
|
|
@@ -22,6 +21,7 @@ from django.core.exceptions import FieldDoesNotExist
|
|
22
21
|
from django.db.models.fields import NOT_PROVIDED
|
23
22
|
from django.utils.functional import cached_property
|
24
23
|
|
24
|
+
from lino import logger
|
25
25
|
from lino.utils.html import E, forcetext, tostring, SafeString, escape, mark_safe
|
26
26
|
|
27
27
|
from lino.core.utils import (
|
@@ -38,8 +38,8 @@ from lino.utils import isiterable
|
|
38
38
|
from lino.utils import get_class_attr
|
39
39
|
from lino.utils import IncompleteDate
|
40
40
|
from lino.utils import quantities
|
41
|
-
from lino.utils import choosers
|
42
41
|
from lino.utils.quantities import Duration
|
42
|
+
from lino.modlib.gfks.fields import GenericForeignKeyIdField
|
43
43
|
|
44
44
|
from .signals import pre_ui_save
|
45
45
|
|
@@ -167,9 +167,17 @@ class PriceField(models.DecimalField):
|
|
167
167
|
`max_digits`.
|
168
168
|
"""
|
169
169
|
|
170
|
+
# def __init__(self, verbose_name=None, max_digits=10, **kwargs):
|
171
|
+
# defaults = dict(
|
172
|
+
# max_length=max_digits,
|
173
|
+
# max_digits=max_digits,
|
174
|
+
# decimal_places=2,
|
175
|
+
# )
|
176
|
+
# defaults.update(kwargs)
|
177
|
+
# super().__init__(verbose_name, **defaults)
|
178
|
+
|
170
179
|
def __init__(self, verbose_name=None, max_digits=10, **kwargs):
|
171
180
|
defaults = dict(
|
172
|
-
max_length=max_digits,
|
173
181
|
max_digits=max_digits,
|
174
182
|
decimal_places=2,
|
175
183
|
)
|
@@ -535,7 +543,7 @@ VFIELD_ATTRIBS = frozenset(
|
|
535
543
|
"""to_python choices save_form_data
|
536
544
|
value_to_string max_length remote_field
|
537
545
|
max_digits verbose_name decimal_places wildcard_data_elem
|
538
|
-
blank""".split()
|
546
|
+
blank choices""".split()
|
539
547
|
)
|
540
548
|
|
541
549
|
|
@@ -610,6 +618,7 @@ class VirtualField(FakeField):
|
|
610
618
|
"Invalid return type spec {} for {} : {}".format(
|
611
619
|
f, self, e)
|
612
620
|
)
|
621
|
+
self.field = f
|
613
622
|
|
614
623
|
if isinstance(f, FakeField):
|
615
624
|
# sortable_by = f.sortable_by
|
@@ -620,6 +629,11 @@ class VirtualField(FakeField):
|
|
620
629
|
# if sortable_by and isinstance(sortable_by, list):
|
621
630
|
# sortable_by = sortable_by[0]
|
622
631
|
|
632
|
+
# if isinstance(f, VirtualField):
|
633
|
+
# delegate = f.return_type
|
634
|
+
# else:
|
635
|
+
# delegate = f
|
636
|
+
|
623
637
|
if isinstance(f, models.ForeignKey):
|
624
638
|
f.remote_field.model = resolve_model(f.remote_field.model)
|
625
639
|
set_default_verbose_name(f)
|
@@ -947,7 +961,7 @@ class QuantityField(models.CharField):
|
|
947
961
|
"""
|
948
962
|
|
949
963
|
description = _("Quantity (Decimal or Duration)")
|
950
|
-
overflow_value = None
|
964
|
+
# overflow_value = None
|
951
965
|
|
952
966
|
def __init__(self, *args, **kw):
|
953
967
|
kw.setdefault("max_length", settings.SITE.quantity_max_length)
|
@@ -974,16 +988,18 @@ class QuantityField(models.CharField):
|
|
974
988
|
I'd add "Any value allowed for this field when instantiating a model."
|
975
989
|
|
976
990
|
"""
|
977
|
-
if value:
|
991
|
+
if isinstance(value, quantities.Quantity):
|
992
|
+
return value
|
993
|
+
elif isinstance(value, Decimal):
|
994
|
+
return quantities.Quantity(value)
|
995
|
+
elif isinstance(value, str):
|
996
|
+
return quantities.parse(value)
|
997
|
+
elif value:
|
978
998
|
# try:
|
979
|
-
if isinstance(value, str):
|
980
|
-
return quantities.parse(value)
|
981
999
|
return quantities.Quantity(value)
|
982
1000
|
# except Exception as e:
|
983
1001
|
# raise ValidationError(
|
984
1002
|
# "Invalid value {} for {} : {}".format(value, self, e))
|
985
|
-
elif isinstance(value, Decimal):
|
986
|
-
return quantities.Quantity(value)
|
987
1003
|
return None
|
988
1004
|
|
989
1005
|
def from_db_value(self, value, expression, connection, context=None):
|
@@ -1001,14 +1017,18 @@ class QuantityField(models.CharField):
|
|
1001
1017
|
return str(value) # if value is None else ''
|
1002
1018
|
|
1003
1019
|
def clean(self, raw_value, obj):
|
1004
|
-
if
|
1005
|
-
|
1006
|
-
|
1007
|
-
|
1008
|
-
|
1009
|
-
|
1010
|
-
|
1011
|
-
|
1020
|
+
# if isinstance(raw_value, quantities.Quantity):
|
1021
|
+
raw_value = self.to_python(raw_value)
|
1022
|
+
if raw_value is not None:
|
1023
|
+
raw_value = raw_value.limit_length(self.max_length, ValidationError)
|
1024
|
+
# if len(str(raw_value)) > self.max_length:
|
1025
|
+
# if self.overflow_value:
|
1026
|
+
# return self.overflow_value
|
1027
|
+
# raise ValidationError(
|
1028
|
+
# f"Cannot accept quantity {raw_value} "
|
1029
|
+
# + f"because max_length is {self.max_length}")
|
1030
|
+
# # print("20230129 Can't store {}={} in {}".format(self.name, raw_value, obj))
|
1031
|
+
# # return -1
|
1012
1032
|
return super().clean(raw_value, obj)
|
1013
1033
|
|
1014
1034
|
|
@@ -1133,6 +1153,7 @@ class DummyField(FakeField):
|
|
1133
1153
|
|
1134
1154
|
# choices = []
|
1135
1155
|
# primary_key = False
|
1156
|
+
field = None # Used e.g. to test whether it's a dummy field
|
1136
1157
|
|
1137
1158
|
def __init__(self, dummy_value=None):
|
1138
1159
|
super().__init__()
|
@@ -1252,6 +1273,13 @@ class TableRow(object):
|
|
1252
1273
|
|
1253
1274
|
"""
|
1254
1275
|
|
1276
|
+
@classmethod
|
1277
|
+
def get_chooser_for_field(cls, fieldname):
|
1278
|
+
d = getattr(cls, "_choosers_dict", {})
|
1279
|
+
# if fieldname.endswith("__municipality"):
|
1280
|
+
# print("20200425 Model.get_chooser_for_field", cls, fieldname, d)
|
1281
|
+
return d.get(fieldname, None)
|
1282
|
+
|
1255
1283
|
@classmethod
|
1256
1284
|
def setup_parameters(cls, params):
|
1257
1285
|
"""Inheritable hook for defining parameters for every actor on this model.
|
@@ -1326,6 +1354,14 @@ class TableRow(object):
|
|
1326
1354
|
def disable_create(self, ar):
|
1327
1355
|
return None
|
1328
1356
|
|
1357
|
+
def before_ui_save(self, ar, cw):
|
1358
|
+
# Needed for polls.AnswerRemarkField
|
1359
|
+
pass
|
1360
|
+
|
1361
|
+
def get_master_data(self, ar, master_instance=None):
|
1362
|
+
# Needed for polls.AnswerRemarkField
|
1363
|
+
return
|
1364
|
+
|
1329
1365
|
def get_detail_action(self, ar):
|
1330
1366
|
"""
|
1331
1367
|
Return the (bound) detail action to use for showing this database row in
|
@@ -1575,6 +1611,7 @@ def pointer_factory(cls, othermodel, *args, **kw):
|
|
1575
1611
|
|
1576
1612
|
|
1577
1613
|
def make_remote_field(model, name):
|
1614
|
+
from lino.utils import choosers
|
1578
1615
|
parts = name.split("__")
|
1579
1616
|
if len(parts) == 1:
|
1580
1617
|
return
|
@@ -1793,7 +1830,6 @@ def choices_for_field(ar, holder, field):
|
|
1793
1830
|
t = m.get_default_table()
|
1794
1831
|
# qs = t.create_request(request=ar.request).data_iterator
|
1795
1832
|
qs = t.create_request(parent=ar).data_iterator
|
1796
|
-
|
1797
1833
|
# logger.info('20120710 choices_view(FK) %s --> %s', t, qs.query)
|
1798
1834
|
|
1799
1835
|
def row2dict(obj, d):
|
@@ -1801,6 +1837,18 @@ def choices_for_field(ar, holder, field):
|
|
1801
1837
|
obj, ar, field)
|
1802
1838
|
d[constants.CHOICES_VALUE_FIELD] = obj.pk
|
1803
1839
|
return d
|
1840
|
+
# elif isinstance(field, GenericForeignKeyIdField):
|
1841
|
+
# ct = getattr(ar.selected_rows[0], field.type_field)
|
1842
|
+
# m = ct.model_class()
|
1843
|
+
# # print(f"20250511 {field.remote_field} {repr(field.type_field)}")
|
1844
|
+
# t = m.get_default_table()
|
1845
|
+
# qs = t.create_request(parent=ar).data_iterator
|
1846
|
+
#
|
1847
|
+
# def row2dict(obj, d):
|
1848
|
+
# d[constants.CHOICES_TEXT_FIELD] = holder.get_choices_text(
|
1849
|
+
# obj, ar, field)
|
1850
|
+
# d[constants.CHOICES_VALUE_FIELD] = obj.pk
|
1851
|
+
# return d
|
1804
1852
|
else:
|
1805
1853
|
raise http.Http404("No choices for %s" % field)
|
1806
1854
|
return (qs, row2dict)
|
lino/core/kernel.py
CHANGED
@@ -44,6 +44,7 @@ from django.db import models
|
|
44
44
|
# import lino # for is_testing
|
45
45
|
from lino import logger
|
46
46
|
from lino.utils import codetime
|
47
|
+
from lino.utils.choosers import check_for_chooser
|
47
48
|
from lino.utils.html import E
|
48
49
|
# from lino.core.utils import format_request
|
49
50
|
# from lino.utils import isiterable
|
@@ -470,6 +471,11 @@ class Kernel(object):
|
|
470
471
|
# ~ choosers.discover()
|
471
472
|
actions.discover_choosers()
|
472
473
|
|
474
|
+
for a in actors.actors_list:
|
475
|
+
for name, field in a.virtual_fields.items():
|
476
|
+
assert name == field.name
|
477
|
+
check_for_chooser(a, field)
|
478
|
+
|
473
479
|
for a in actors.actors_list:
|
474
480
|
a.on_analyze(site)
|
475
481
|
|
lino/core/layouts.py
CHANGED
@@ -99,7 +99,7 @@ class LayoutHandle(object):
|
|
99
99
|
assert isinstance(layout, BaseLayout)
|
100
100
|
assert isinstance(ui, Plugin)
|
101
101
|
self.layout = layout
|
102
|
-
self.ui = ui
|
102
|
+
self.ui = ui # TODO: rename ui to front_end
|
103
103
|
self.hidden_elements = layout.hidden_elements
|
104
104
|
self._data_elems = []
|
105
105
|
self._names = {}
|
@@ -299,7 +299,7 @@ class LayoutHandle(object):
|
|
299
299
|
else:
|
300
300
|
e.hidden = True
|
301
301
|
|
302
|
-
self.ui.setup_layout_element(e)
|
302
|
+
# self.ui.setup_layout_element(e)
|
303
303
|
self.layout.setup_element(self, e)
|
304
304
|
self._names[name] = e
|
305
305
|
return e
|
lino/core/model.py
CHANGED
@@ -7,7 +7,7 @@ See :doc:`/dev/models`, :doc:`/dev/delete`, :doc:`/dev/disable`,
|
|
7
7
|
:doc:`/dev/hide`, :doc:`/dev/format`
|
8
8
|
|
9
9
|
"""
|
10
|
-
|
10
|
+
# from bs4 import BeautifulSoup
|
11
11
|
from lino import logger
|
12
12
|
import copy
|
13
13
|
|
@@ -25,6 +25,7 @@ from lino.utils.soup import sanitize
|
|
25
25
|
from lino.core import fields
|
26
26
|
from lino.core import signals
|
27
27
|
from lino.core import actions
|
28
|
+
from lino.core import inject
|
28
29
|
|
29
30
|
from .fields import make_remote_field, RichTextField, displayfield
|
30
31
|
from .utils import error2str
|
@@ -331,9 +332,9 @@ class Model(models.Model, fields.TableRow):
|
|
331
332
|
@classmethod
|
332
333
|
def lookup_or_create(model, lookup_field, value, **known_values):
|
333
334
|
"""
|
334
|
-
|
335
|
-
|
336
|
-
(and optionally other `known_values` matching exactly).
|
335
|
+
|
336
|
+
Look up whether there is a row having value `value` for field
|
337
|
+
`lookup_field` (and optionally other `known_values` matching exactly).
|
337
338
|
|
338
339
|
If it doesn't exist, create it and emit an
|
339
340
|
:attr:`auto_create <lino.core.signals.auto_create>` signal.
|
@@ -425,7 +426,12 @@ class Model(models.Model, fields.TableRow):
|
|
425
426
|
old = getattr(self, f.name)
|
426
427
|
if old is None:
|
427
428
|
continue
|
428
|
-
|
429
|
+
|
430
|
+
if getattr(f, "format") == "plain":
|
431
|
+
# assert BeautifulSoup(old, "html.parser").find() is None
|
432
|
+
new = old
|
433
|
+
else:
|
434
|
+
new = sanitize(old, **kwargs)
|
429
435
|
# try:
|
430
436
|
# new = bleach.clean(
|
431
437
|
# new,
|
@@ -720,13 +726,6 @@ class Model(models.Model, fields.TableRow):
|
|
720
726
|
# return self.preview(ar)
|
721
727
|
# #~ username = kw.pop('username',None)
|
722
728
|
|
723
|
-
@classmethod
|
724
|
-
def get_chooser_for_field(cls, fieldname):
|
725
|
-
d = getattr(cls, "_choosers_dict", {})
|
726
|
-
# if fieldname.endswith("__municipality"):
|
727
|
-
# print("20200425 Model.get_chooser_for_field", cls, fieldname, d)
|
728
|
-
return d.get(fieldname, None)
|
729
|
-
|
730
729
|
def get_printable_target_stem(self):
|
731
730
|
return self._meta.app_label + "." + self.__class__.__name__ + "-" + str(self.pk)
|
732
731
|
|
@@ -819,6 +818,10 @@ class Model(models.Model, fields.TableRow):
|
|
819
818
|
|
820
819
|
setattr(model, vfield_name, pick_choice)
|
821
820
|
|
821
|
+
@classmethod
|
822
|
+
def update_field(cls, *args, **kwargs):
|
823
|
+
inject.update_field(cls, *args, **kwargs)
|
824
|
+
|
822
825
|
@classmethod
|
823
826
|
def django2lino(cls, model):
|
824
827
|
"""
|
lino/core/plugin.py
CHANGED
@@ -223,8 +223,8 @@ class Plugin:
|
|
223
223
|
def get_dashboard_items(self, user):
|
224
224
|
return []
|
225
225
|
|
226
|
-
def setup_layout_element(self, el):
|
227
|
-
|
226
|
+
# def setup_layout_element(self, el):
|
227
|
+
# pass
|
228
228
|
|
229
229
|
def get_detail_url(self, ar, actor, pk, *args, **kw):
|
230
230
|
from lino.core.renderer import TextRenderer
|
lino/core/site.py
CHANGED
@@ -139,7 +139,6 @@ class Site(object):
|
|
139
139
|
quantity_max_length = 6
|
140
140
|
# upload_to_tpl = "uploads/%Y/%m"
|
141
141
|
auto_fit_column_widths = True
|
142
|
-
# locale = 'en_GB.utf-8'
|
143
142
|
site_locale = None
|
144
143
|
confdirs = None
|
145
144
|
kernel = None
|
@@ -978,7 +977,7 @@ class Site(object):
|
|
978
977
|
self.update_settings(ROOT_URLCONF=self.root_urlconf)
|
979
978
|
self.update_settings(MEDIA_URL="/media/")
|
980
979
|
|
981
|
-
if
|
980
|
+
if "STATIC_ROOT" not in self.django_settings:
|
982
981
|
# cache_root = os.environ.get("LINO_CACHE_ROOT", None)
|
983
982
|
# if cache_root:
|
984
983
|
# p = Path(cache_root)
|
@@ -986,10 +985,10 @@ class Site(object):
|
|
986
985
|
# p = self.site_dir
|
987
986
|
p = self.site_dir
|
988
987
|
self.update_settings(STATIC_ROOT=str(p / "static_root"))
|
989
|
-
if
|
988
|
+
if "STATIC_URL" not in self.django_settings:
|
990
989
|
self.update_settings(STATIC_URL="/static/")
|
991
990
|
|
992
|
-
if
|
991
|
+
if "USE_TZ" not in self.django_settings:
|
993
992
|
# django.utils.deprecation.RemovedInDjango50Warning: The default
|
994
993
|
# value of USE_TZ will change from False to True in Django 5.0. Set
|
995
994
|
# USE_TZ to False in your project settings if you want to keep the
|
@@ -1235,11 +1234,13 @@ class Site(object):
|
|
1235
1234
|
from lino.core.kernel import site_startup
|
1236
1235
|
|
1237
1236
|
site_startup(self)
|
1238
|
-
|
1239
|
-
|
1240
|
-
|
1241
|
-
|
1242
|
-
|
1237
|
+
|
1238
|
+
if self.site_locale is None:
|
1239
|
+
self.site_locale = '.'.join(locale.getlocale())
|
1240
|
+
try:
|
1241
|
+
locale.setlocale(locale.LC_ALL, self.site_locale)
|
1242
|
+
except locale.Error as e:
|
1243
|
+
self.logger.warning("%s : %s", self.site_locale, e)
|
1243
1244
|
self.clear_site_config()
|
1244
1245
|
|
1245
1246
|
def register_shutdown_task(self, task):
|
@@ -2257,10 +2258,7 @@ class Site(object):
|
|
2257
2258
|
return moneyfmt(v, places=places, **kw)
|
2258
2259
|
|
2259
2260
|
def format_currency(self, *args, **kwargs):
|
2260
|
-
|
2261
|
-
# if six.PY2:
|
2262
|
-
# res = res.decode(locale.nl_langinfo(locale.CODESET))
|
2263
|
-
return res
|
2261
|
+
return locale.currency(*args, **kwargs)
|
2264
2262
|
|
2265
2263
|
LOOKUP_OP = "__iexact"
|
2266
2264
|
|
lino/core/store.py
CHANGED
@@ -447,7 +447,7 @@ class PreviewTextStoreField(StoreField):
|
|
447
447
|
class VirtStoreField(StoreField):
|
448
448
|
def __init__(self, vf, delegate, name):
|
449
449
|
self.vf = vf
|
450
|
-
|
450
|
+
super().__init__(vf.return_type, name)
|
451
451
|
self.as_js = delegate.as_js
|
452
452
|
self.column_names = delegate.column_names
|
453
453
|
self.list_values_count = delegate.list_values_count
|
@@ -647,13 +647,12 @@ class RowClassStoreField(SpecialStoreField):
|
|
647
647
|
name = "row_class"
|
648
648
|
|
649
649
|
def full_value_from_object(self, obj, ar=None):
|
650
|
-
|
651
|
-
|
652
|
-
ar.renderer.row_classes_map.get(s, "")
|
650
|
+
lst = [
|
651
|
+
ar.renderer.row_classes_map.get(s, s)
|
653
652
|
for s in self.store.actor.get_row_classes(obj, ar)
|
654
653
|
]
|
655
|
-
)
|
656
|
-
|
654
|
+
# print(f"20250509x {obj} -> {lst}")
|
655
|
+
return " ".join(lst)
|
657
656
|
|
658
657
|
|
659
658
|
class DisableEditingStoreField(SpecialStoreField):
|