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.
Files changed (41) hide show
  1. lino/__init__.py +1 -1
  2. lino/api/doctest.py +14 -0
  3. lino/core/actions.py +2 -8
  4. lino/core/actors.py +5 -3
  5. lino/core/choicelists.py +8 -11
  6. lino/core/dbtables.py +7 -7
  7. lino/core/elems.py +16 -11
  8. lino/core/fields.py +67 -19
  9. lino/core/kernel.py +6 -0
  10. lino/core/layouts.py +2 -2
  11. lino/core/model.py +15 -12
  12. lino/core/plugin.py +2 -2
  13. lino/core/site.py +11 -13
  14. lino/core/store.py +5 -6
  15. lino/core/tables.py +13 -9
  16. lino/core/utils.py +4 -23
  17. lino/help_texts.py +9 -1
  18. lino/mixins/duplicable.py +9 -3
  19. lino/mixins/ref.py +9 -0
  20. lino/mixins/registrable.py +2 -2
  21. lino/mixins/sequenced.py +12 -8
  22. lino/modlib/extjs/ext_renderer.py +1 -1
  23. lino/modlib/gfks/fields.py +1 -1
  24. lino/modlib/help/models.py +2 -1
  25. lino/modlib/jinja/renderer.py +1 -0
  26. lino/modlib/linod/models.py +1 -1
  27. lino/modlib/notify/fixtures/demo2.py +7 -2
  28. lino/modlib/periods/fixtures/std.py +1 -3
  29. lino/modlib/periods/models.py +26 -5
  30. lino/modlib/publisher/fixtures/synodalworld.py +0 -2
  31. lino/modlib/weasyprint/__init__.py +1 -0
  32. lino/modlib/weasyprint/choicelists.py +2 -11
  33. lino/modlib/weasyprint/config/weasyprint/base.weasy.html +1 -1
  34. lino/utils/__init__.py +20 -0
  35. lino/utils/choosers.py +21 -15
  36. lino/utils/quantities.py +21 -3
  37. {lino-25.4.5.dist-info → lino-25.5.1.dist-info}/METADATA +1 -1
  38. {lino-25.4.5.dist-info → lino-25.5.1.dist-info}/RECORD +41 -41
  39. {lino-25.4.5.dist-info → lino-25.5.1.dist-info}/WHEEL +0 -0
  40. {lino-25.4.5.dist-info → lino-25.5.1.dist-info}/licenses/AUTHORS.rst +0 -0
  41. {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.4.5'
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
- # ~ logger.debug("Instantiate model reports...")
40
- for model in get_models():
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
- return d.get(fieldname, None)
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-2023 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
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(with_metaclass(ChoiceListMeta, tables.AbstractTable)):
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
- This must be lazily translatable because the return value is also used
751
- to build the `choices` attribute of ChoiceListFields on this choicelist.
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
- 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)
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, escape
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.html import E, py2html, tostring
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 DetailWindow, then it
2862
- # will be rendered as a :term:`slave panel`. The panel will have a
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.renderer.front_end.media_name == "react"
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 len(str(raw_value)) > self.max_length:
1005
- if self.overflow_value:
1006
- return self.overflow_value
1007
- raise ValidationError(
1008
- f"Cannot accept quantity {raw_value} "
1009
- + f"because max_length is {self.max_length}")
1010
- # print("20230129 Can't store {}={} in {}".format(self.name, raw_value, obj))
1011
- # return -1
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
- Look up whether there is a model instance having
335
- `lookup_field` with value `value`
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
- new = sanitize(old, **kwargs)
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
- pass
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 not "STATIC_ROOT" in self.django_settings:
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 not "STATIC_URL" in self.django_settings:
988
+ if "STATIC_URL" not in self.django_settings:
990
989
  self.update_settings(STATIC_URL="/static/")
991
990
 
992
- if not "USE_TZ" in self.django_settings:
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
- if self.site_locale:
1239
- try:
1240
- locale.setlocale(locale.LC_ALL, self.site_locale)
1241
- except locale.Error as e:
1242
- self.logger.warning("%s : %s", self.site_locale, e)
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
- res = locale.currency(*args, **kwargs)
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
- StoreField.__init__(self, vf.return_type, name)
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
- return " ".join(
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
- # return ar.renderer.row_classes_map.get('x-grid3-row-%s' % s
654
+ # print(f"20250509x {obj} -> {lst}")
655
+ return " ".join(lst)
657
656
 
658
657
 
659
658
  class DisableEditingStoreField(SpecialStoreField):