lino 25.6.1__py3-none-any.whl → 25.7.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 (93) hide show
  1. lino/__init__.py +1 -1
  2. lino/api/dd.py +1 -0
  3. lino/api/doctest.py +21 -0
  4. lino/core/actions.py +80 -25
  5. lino/core/actors.py +54 -27
  6. lino/core/boundaction.py +16 -0
  7. lino/core/choicelists.py +7 -7
  8. lino/core/constants.py +3 -0
  9. lino/core/dashboard.py +4 -2
  10. lino/core/dbtables.py +2 -2
  11. lino/core/elems.py +38 -13
  12. lino/core/fields.py +20 -11
  13. lino/core/kernel.py +8 -0
  14. lino/core/layouts.py +6 -2
  15. lino/core/menus.py +3 -6
  16. lino/core/model.py +5 -4
  17. lino/core/renderer.py +20 -9
  18. lino/core/requests.py +8 -7
  19. lino/core/signals.py +1 -0
  20. lino/core/site.py +48 -28
  21. lino/core/store.py +4 -2
  22. lino/core/tables.py +23 -10
  23. lino/core/utils.py +4 -1
  24. lino/core/workflows.py +2 -1
  25. lino/help_texts.py +1 -2
  26. lino/management/commands/prep.py +2 -2
  27. lino/management/commands/show.py +8 -10
  28. lino/mixins/__init__.py +14 -13
  29. lino/mixins/periods.py +2 -0
  30. lino/mixins/sequenced.py +1 -1
  31. lino/modlib/about/models.py +4 -3
  32. lino/modlib/checkdata/__init__.py +42 -36
  33. lino/modlib/checkdata/choicelists.py +9 -1
  34. lino/modlib/checkdata/fixtures/checkdata.py +4 -2
  35. lino/modlib/checkdata/management/commands/checkdata.py +3 -3
  36. lino/modlib/checkdata/models.py +9 -2
  37. lino/modlib/comments/models.py +4 -3
  38. lino/modlib/extjs/ext_renderer.py +4 -4
  39. lino/modlib/extjs/views.py +8 -2
  40. lino/modlib/gfks/fields.py +1 -1
  41. lino/modlib/help/__init__.py +3 -3
  42. lino/modlib/help/config/makehelp/conf.tpl.py +2 -2
  43. lino/modlib/help/fixtures/demo2.py +6 -1
  44. lino/modlib/help/management/commands/makehelp.py +4 -1
  45. lino/modlib/help/models.py +4 -1
  46. lino/modlib/help/utils.py +12 -6
  47. lino/modlib/linod/choicelists.py +57 -4
  48. lino/modlib/linod/fixtures/{linod.py → checkdata.py} +3 -13
  49. lino/modlib/linod/management/commands/linod.py +0 -13
  50. lino/modlib/linod/mixins.py +8 -0
  51. lino/modlib/linod/models.py +29 -30
  52. lino/modlib/memo/__init__.py +7 -7
  53. lino/modlib/memo/management/__init__,py +0 -0
  54. lino/modlib/memo/management/commands/__init__.py +0 -0
  55. lino/modlib/memo/management/commands/removeurls.py +67 -0
  56. lino/modlib/memo/mixins.py +1 -9
  57. lino/modlib/memo/parser.py +1 -1
  58. lino/modlib/notify/config/notify/summary.eml +5 -2
  59. lino/modlib/notify/fixtures/demo2.py +5 -6
  60. lino/modlib/notify/models.py +9 -10
  61. lino/modlib/periods/__init__.py +11 -8
  62. lino/modlib/periods/choicelists.py +16 -10
  63. lino/modlib/periods/models.py +45 -45
  64. lino/modlib/publisher/renderer.py +2 -5
  65. lino/modlib/summaries/fixtures/checksummaries.py +4 -2
  66. lino/modlib/system/models.py +17 -18
  67. lino/modlib/uploads/fixtures/demo.py +9 -3
  68. lino/modlib/uploads/mixins.py +5 -2
  69. lino/modlib/uploads/models.py +15 -9
  70. lino/modlib/uploads/utils.py +4 -1
  71. lino/modlib/users/__init__.py +59 -18
  72. lino/modlib/users/actions.py +24 -20
  73. lino/modlib/users/fixtures/demo_users.py +2 -35
  74. lino/modlib/users/mixins.py +3 -4
  75. lino/modlib/users/models.py +53 -13
  76. lino/modlib/users/ui.py +30 -16
  77. lino/modlib/users/utils.py +5 -6
  78. lino/projects/std/settings.py +1 -1
  79. lino/sphinxcontrib/logo/templates/footer.html +1 -0
  80. lino/utils/ajax.py +1 -1
  81. lino/utils/cycler.py +5 -0
  82. lino/utils/dbhash.py +4 -9
  83. lino/utils/dpy.py +2 -2
  84. lino/utils/format_date.py +4 -3
  85. lino/utils/html.py +13 -5
  86. lino/utils/jsgen.py +3 -2
  87. lino/utils/quantities.py +8 -0
  88. lino/utils/soup.py +75 -106
  89. {lino-25.6.1.dist-info → lino-25.7.1.dist-info}/METADATA +1 -1
  90. {lino-25.6.1.dist-info → lino-25.7.1.dist-info}/RECORD +93 -90
  91. {lino-25.6.1.dist-info → lino-25.7.1.dist-info}/WHEEL +0 -0
  92. {lino-25.6.1.dist-info → lino-25.7.1.dist-info}/licenses/AUTHORS.rst +0 -0
  93. {lino-25.6.1.dist-info → lino-25.7.1.dist-info}/licenses/COPYING +0 -0
lino/core/tables.py CHANGED
@@ -403,7 +403,7 @@ class AbstractTable(actors.Actor):
403
403
  order_by = None
404
404
  """If specified, this must be a tuple or list of field names that
405
405
  will be passed to Django's `order_by
406
- <https://docs.djangoproject.com/en/5.0/ref/models/querysets/#order-by>`__
406
+ <https://docs.djangoproject.com/en/5.2/ref/models/querysets/#order-by>`__
407
407
  method in order to sort the rows of the queryset.
408
408
 
409
409
  """
@@ -412,7 +412,7 @@ method in order to sort the rows of the queryset.
412
412
  """
413
413
  If specified, this must be a :class:`django.db.models.Q` object that will be
414
414
  passed to Django's `filter
415
- <https://docs.djangoproject.com/en/5.0/ref/models/querysets/#filter>`__
415
+ <https://docs.djangoproject.com/en/5.2/ref/models/querysets/#filter>`__
416
416
  method.
417
417
 
418
418
  If you allow a user to insert rows into a filtered table, you should make
@@ -427,7 +427,7 @@ method in order to sort the rows of the queryset.
427
427
  One advantage of :attr:`filter` over
428
428
  :attr:`known_values <lino.core.actors.Actor.known_values>`
429
429
  is that this can use the full range of Django's `field lookup methods
430
- <https://docs.djangoproject.com/en/5.0/topics/db/queries/#field-lookups>`_
430
+ <https://docs.djangoproject.com/en/5.2/topics/db/queries/#field-lookups>`_
431
431
 
432
432
  """
433
433
 
@@ -435,7 +435,7 @@ method in order to sort the rows of the queryset.
435
435
  """
436
436
  If specified, this must be a :class:`django.db.models.Q` object that will be
437
437
  passed to Django's `exclude
438
- <https://docs.djangoproject.com/en/5.0/ref/models/querysets/#exclude>`__
438
+ <https://docs.djangoproject.com/en/5.2/ref/models/querysets/#exclude>`__
439
439
  method.
440
440
 
441
441
  This is the logical opposite of :attr:`filter`.
@@ -485,6 +485,8 @@ method in order to sort the rows of the queryset.
485
485
  resolve_fields_list(cls, "mobile_columns", set, {})
486
486
  resolve_fields_list(cls, "popin_columns", set, {})
487
487
  if cls.model is not None:
488
+ if not isinstance(cls.model, type):
489
+ raise Exception(f"{cls}.model is {repr(cls.model)}")
488
490
  if not issubclass(cls.model, models.Model):
489
491
  if cls.model._lino_default_table is None:
490
492
  cls.model._lino_default_table = cls
@@ -526,13 +528,24 @@ method in order to sort the rows of the queryset.
526
528
  @classmethod
527
529
  def get_default_action(cls):
528
530
  if cls.default_record_id is not None:
531
+ # return cls._detail_action_class(
532
+ # label=cls.label, help_text=cls.help_text,
533
+ # default_record_id = cls.default_record_id)
529
534
  # assert cls.display_mode == ((None, constants.DISPLAY_MODE_DETAIL))
530
535
  assert cls.detail_action is not None
531
- cls.detail_action.action.label = cls.label
532
- cls.detail_action.action.help_text = cls.help_text
533
- cls.detail_action.action.default_record_id = cls.default_record_id
534
- return cls.detail_action
535
- return actions.ShowTable()
536
+ # if cls.detail_action.action.defining_actor is not cls:
537
+ # raise Exception(
538
+ # f"{cls.detail_action.action.defining_actor} is not {cls}")
539
+ a = cls.detail_action.action
540
+ return a.__class__(
541
+ a.owner,
542
+ label=cls.label, help_text=cls.help_text,
543
+ default_record_id=cls.default_record_id)
544
+ # cls.detail_action.action.label = cls.label
545
+ # cls.detail_action.help_text = cls.help_text
546
+ # cls.detail_action.action.default_record_id = cls.default_record_id
547
+ # return cls.detail_action
548
+ return actions.SHOW_TABLE
536
549
 
537
550
  @classmethod
538
551
  def get_actor_editable(self):
@@ -611,7 +624,7 @@ method in order to sort the rows of the queryset.
611
624
  if not isinstance(master_instance, self.master):
612
625
  # e.g. a ByUser table descendant called by AnonymousUser
613
626
  msg = "20240731 %r is not a %s (%s.master_key = '%s')" % (
614
- master_instance.__class__,
627
+ master_instance,
615
628
  self.master,
616
629
  self,
617
630
  self.master_key,
lino/core/utils.py CHANGED
@@ -13,6 +13,7 @@ import datetime
13
13
  # import yaml
14
14
  from importlib import import_module
15
15
  from django.utils.html import format_html, mark_safe, SafeString
16
+ from django.utils.functional import Promise
16
17
  from django.db import models
17
18
  from django.db.models import Q
18
19
  from django.core.exceptions import FieldDoesNotExist
@@ -90,7 +91,7 @@ def getrqdata(request):
90
91
  """Return the request data.
91
92
 
92
93
  Unlike the now defunct `REQUEST
93
- <https://docs.djangoproject.com/en/5.0/ref/request-response/#django.http.HttpRequest.REQUEST>`_
94
+ <https://docs.djangoproject.com/en/5.2/ref/request-response/#django.http.HttpRequest.REQUEST>`_
94
95
  attribute, this inspects the request's `method` in order to decide
95
96
  what to return.
96
97
 
@@ -1163,6 +1164,8 @@ class Panel:
1163
1164
 
1164
1165
  def resolve_layout(cls, k, spec, layout_class, **options):
1165
1166
  # k: just for naming the culprit in error messages
1167
+ if isinstance(spec, Promise):
1168
+ spec = str(spec)
1166
1169
  if isinstance(spec, str):
1167
1170
  if "\n" in spec or "." not in spec:
1168
1171
  return layout_class(spec, cls, **options)
lino/core/workflows.py CHANGED
@@ -255,7 +255,8 @@ class ChangeStateAction(actions.Action):
255
255
  self.button_text = target_state.button_text
256
256
 
257
257
  if self.icon_name:
258
- self.help_text = format_lazy("{}. {}", self.label, self.help_text)
258
+ self.help_text = format_lazy(
259
+ "{}. {}", self.label, self.help_text)
259
260
 
260
261
  # def get_action_permission(self, ar, obj, state):
261
262
  # if not super(ChangeStateAction, self).get_action_permission(ar, obj, state):
lino/help_texts.py CHANGED
@@ -110,7 +110,6 @@ help_texts = {
110
110
  'lino.modlib.bootstrap3.views.Element' : _("""Render a single record."""),
111
111
  'lino.modlib.bootstrap3.views.Index' : _("""Render the main page."""),
112
112
  'lino.modlib.checkdata.Plugin' : _("""The config descriptor for this plugin."""),
113
- 'lino.modlib.checkdata.Plugin.responsible_user' : _("""The username of the main checkdata responsible, i.e. a designated user who will be attributed to checkdata messages for which no specific responible could be designated (returned by the checker’s get_responsible_user method)."""),
114
113
  'lino.modlib.checkdata.Plugin.on_plugins_loaded' : _("""Set responsible_user to "'robin' if this is a demo site (is_demo_site)."""),
115
114
  'lino.modlib.checkdata.roles.CheckdataUser' : _("""Can see checkdata messages."""),
116
115
  'lino.modlib.comments.Plugin' : _("""See /dev/plugins."""),
@@ -560,7 +559,7 @@ help_texts = {
560
559
  'lino.modlib.jinja.XMLMaker.xml_validator_file' : _("""The name of a “validator” to use for validating the XML content."""),
561
560
  'lino.modlib.jinja.XMLMaker.get_xml_file' : _("""Get the name of the XML file to be generated for this database row."""),
562
561
  'lino.modlib.jinja.XMLMaker.make_xml_file' : _("""Make the XML file for this database row."""),
563
- 'lino.modlib.memo.Previewable' : _("""Adds three rich text fields (lino.core.fields.RichTextField):"""),
562
+ 'lino.modlib.memo.Previewable' : _("""See dg.memo.Previewable."""),
564
563
  'lino.modlib.memo.Previewable.body' : _("""An editable text body."""),
565
564
  'lino.modlib.memo.Previewable.body_short_preview' : _("""A read-only preview of the first paragraph of body."""),
566
565
  'lino.modlib.memo.Previewable.body_full_preview' : _("""A read-only full preview of body."""),
@@ -5,7 +5,7 @@
5
5
  from django.core.management import call_command
6
6
  from django.core.management.base import BaseCommand, CommandError
7
7
  from django.conf import settings
8
- from lino.utils import dbhash
8
+ from lino.core.signals import database_prepared
9
9
 
10
10
 
11
11
  class Command(BaseCommand):
@@ -62,4 +62,4 @@ class Command(BaseCommand):
62
62
  kwargs["removemedia"] = True
63
63
  call_command("initdb", *args, **kwargs)
64
64
 
65
- dbhash.mark_virgin()
65
+ database_prepared.send(self)
@@ -2,25 +2,23 @@
2
2
  # Copyright 2013-2021 Rumma & Ko Ltd
3
3
  # License: GNU Affero General Public License v3 (see file COPYING for details)
4
4
 
5
- import argparse
6
5
  from lino.api import rt
7
6
  from django.core.management.base import BaseCommand, CommandError
8
7
 
8
+
9
9
  class Command(BaseCommand):
10
10
  help = "Show the content of a specified table to standard output."
11
11
 
12
12
  # args = "action_spec [options] [args ...]"
13
13
 
14
14
  def add_arguments(self, parser):
15
- (
16
- parser.add_argument(
17
- "-u",
18
- "--username",
19
- action="store",
20
- dest="username",
21
- default=None,
22
- help="The username to act as. Default is `None`.",
23
- ),
15
+ parser.add_argument(
16
+ "-u",
17
+ "--username",
18
+ action="store",
19
+ dest="username",
20
+ default=None,
21
+ help="The username to act as. Default is `None`.",
24
22
  )
25
23
  parser.add_argument(
26
24
  "-l",
lino/mixins/__init__.py CHANGED
@@ -61,7 +61,7 @@ class Contactable(model.Model):
61
61
  email.
62
62
  """
63
63
 
64
- class Meta(object):
64
+ class Meta:
65
65
  abstract = True
66
66
 
67
67
  email = models.EmailField(_("e-mail address"), blank=True)
@@ -78,7 +78,7 @@ class Phonable(model.Model):
78
78
  phone.
79
79
  """
80
80
 
81
- class Meta(object):
81
+ class Meta:
82
82
  abstract = True
83
83
 
84
84
  url = models.URLField(_("URL"), blank=True)
@@ -105,7 +105,7 @@ class Modified(model.Model):
105
105
  you explicitly call :meth:`touch`.
106
106
  """
107
107
 
108
- class Meta(object):
108
+ class Meta:
109
109
  abstract = True
110
110
 
111
111
  modified = models.DateTimeField(_("Modified"), editable=False, null=True)
@@ -132,7 +132,7 @@ class Created(model.Model):
132
132
  because their deserialization would be problematic.
133
133
  """
134
134
 
135
- class Meta(object):
135
+ class Meta:
136
136
  abstract = True
137
137
 
138
138
  created = models.DateTimeField(_("Created"), editable=False)
@@ -153,7 +153,7 @@ class CreatedModified(Created, Modified):
153
153
 
154
154
  """
155
155
 
156
- class Meta(object):
156
+ class Meta:
157
157
  abstract = True
158
158
 
159
159
 
@@ -175,7 +175,7 @@ class ProjectRelated(model.Model):
175
175
  <lino.core.fields.DummyField>`.
176
176
  """
177
177
 
178
- class Meta(object):
178
+ class Meta:
179
179
  abstract = True
180
180
 
181
181
  project = fields.ForeignKey(
@@ -186,11 +186,12 @@ class ProjectRelated(model.Model):
186
186
  )
187
187
 
188
188
  def get_related_project(self):
189
- if settings.SITE.project_model:
190
- return self.project
189
+ # if settings.SITE.project_model:
190
+ # When project_model is None, project is a dummy field which always returns None
191
+ return self.project
191
192
 
192
193
  # def on_create(self, ar):
193
- # super(ProjectRelated, self).on_create(ar)
194
+ # super().on_create(ar)
194
195
  # print(20200327, ar.actor.master_key, ar.master_instance)
195
196
  # if ar.actor.master_key and ar.actor.master_key == "project":
196
197
  # self.project = ar.master_instance
@@ -213,24 +214,24 @@ class ProjectRelated(model.Model):
213
214
  """
214
215
  if isinstance(controllable, ProjectRelated):
215
216
  controllable.project = self.project
216
- super(ProjectRelated, self).update_owned_instance(controllable)
217
+ super().update_owned_instance(controllable)
217
218
 
218
219
  def get_mailable_recipients(self):
219
220
  if isinstance(self.project, settings.SITE.models.contacts.Partner):
220
221
  if self.project.email:
221
222
  yield ("to", self.project)
222
- for r in super(ProjectRelated, self).get_mailable_recipients():
223
+ for r in super().get_mailable_recipients():
223
224
  yield r
224
225
 
225
226
  def get_postable_recipients(self):
226
227
  if isinstance(self.project, settings.SITE.models.contacts.Partner):
227
228
  yield self.project
228
- for p in super(ProjectRelated, self).get_postable_recipients():
229
+ for p in super().get_postable_recipients():
229
230
  yield p
230
231
 
231
232
  @classmethod
232
233
  def get_simple_parameters(cls):
233
- for p in super(ProjectRelated, cls).get_simple_parameters():
234
+ for p in super().get_simple_parameters():
234
235
  yield p
235
236
  # if settings.SITE.project_model:
236
237
  yield "project"
lino/mixins/periods.py CHANGED
@@ -18,6 +18,7 @@ from django.utils.translation import gettext_lazy as _
18
18
  from django.utils.translation import pgettext_lazy as pgettext
19
19
  from django.core.exceptions import ValidationError
20
20
  from django.utils.timezone import is_aware
21
+ from asgiref.sync import sync_to_async
21
22
 
22
23
  from lino.utils import last_day_of_month
23
24
  from lino.api import dd
@@ -276,6 +277,7 @@ class DateRange(DateRangeObservable):
276
277
  return False
277
278
  return True
278
279
 
280
+
279
281
  DateRange.set_widget_options("start_date", width=10)
280
282
  DateRange.set_widget_options("end_date", width=10)
281
283
 
lino/mixins/sequenced.py CHANGED
@@ -325,7 +325,7 @@ class Sequenced(Duplicable):
325
325
  message=_("Renumbered {} of {} siblings.").format(n, qs.count()))
326
326
  ar.set_response(refresh_all=True)
327
327
 
328
- @fields.displayfield("⇵")
328
+ @fields.displayfield("⇵", wildcard_data_elem=True)
329
329
  def dndreorder(self, ar):
330
330
  """A place holder column for drag and drop row reorder on :term:`React front end`
331
331
 
@@ -8,7 +8,7 @@ import datetime
8
8
  # from django.contrib.humanize.templatetags.humanize import naturaltime
9
9
  from django.utils.translation import gettext_lazy as _
10
10
  from django.utils.translation import gettext
11
- from django.utils.html import mark_safe
11
+ from django.utils.html import mark_safe, format_html
12
12
  from django.conf import settings
13
13
 
14
14
  from lino.utils.report import EmptyTable
@@ -64,7 +64,7 @@ class About(EmptyTable):
64
64
  body += "".join([tostring(e) for e in site.welcome_html()])
65
65
 
66
66
  for p in site.sorted_plugins:
67
- for i in p.get_site_info():
67
+ for i in p.get_site_info(ar):
68
68
  body += i
69
69
 
70
70
  if site.languages:
@@ -142,7 +142,8 @@ class About(EmptyTable):
142
142
  )
143
143
  )
144
144
  )
145
- body = "<div>{}</div>".format(body)
145
+ body = mark_safe(body)
146
+ body = format_html("<div>{}</div>", body)
146
147
  # return js_code(rt.html_text(body))
147
148
  # return rt.html_text(body)
148
149
  # return mark_safe(body)
@@ -14,6 +14,7 @@ See :doc:`/plugins/checkdata`.
14
14
  """
15
15
 
16
16
  from lino.api import ad, _
17
+ from lino.core.exceptions import ChangedAPI
17
18
 
18
19
 
19
20
  class Plugin(ad.Plugin):
@@ -21,42 +22,44 @@ class Plugin(ad.Plugin):
21
22
 
22
23
  verbose_name = _("Checkdata")
23
24
  needs_plugins = ["lino.modlib.users", "lino.modlib.gfks",
24
- "lino.modlib.office", "lino.modlib.linod"]
25
+ "lino.modlib.office", "lino.modlib.linod"]
26
+
27
+ fix_in_background = True
25
28
 
26
29
  # plugin settings
27
- responsible_user = None # the username (a string)
28
- """
29
-
30
- The :attr:`username <lino.modlib.users.User.username>`
31
- of the **main checkdata responsible**, i.e. a designated
32
- user who will be attributed to checkdata messages for which
33
- no *specific responible* could be designated (returned by the
34
- checker's :meth:`get_responsible_user
35
- <lino.modlib.checkdata.Checker.get_responsible_user>`
36
- method).
37
-
38
- The default value for this is `None`, except on a demo site
39
- (i.e. which has :attr:`is_demo_site
40
- <lino.core.site.Site.is_demo_site>` set to `True`) where it is
41
- ``"'robin'``.
42
-
43
- """
44
-
45
- _responsible_user = None # the cached User object
46
-
47
- def get_responsible_user(self, checker, obj):
48
- if self.responsible_user is None:
49
- return None
50
- if self._responsible_user is None:
51
- User = self.site.models.users.User
52
- try:
53
- self._responsible_user = User.objects.get(
54
- username=self.responsible_user)
55
- except User.DoesNotExist:
56
- msg = "Invalid username '{0}' in `responsible_user` "
57
- msg = msg.format(self.responsible_user)
58
- raise Exception(msg)
59
- return self._responsible_user
30
+ # responsible_user = None # the username (a string)
31
+ # """
32
+ #
33
+ # The :attr:`username <lino.modlib.users.User.username>`
34
+ # of the **main checkdata responsible**, i.e. a designated
35
+ # user who will be attributed to checkdata messages for which
36
+ # no *specific responible* could be designated (returned by the
37
+ # checker's :meth:`get_responsible_user
38
+ # <lino.modlib.checkdata.Checker.get_responsible_user>`
39
+ # method).
40
+ #
41
+ # The default value for this is `None`, except on a demo site
42
+ # (i.e. which has :attr:`is_demo_site
43
+ # <lino.core.site.Site.is_demo_site>` set to `True`) where it is
44
+ # ``"'robin'``.
45
+ #
46
+ # """
47
+
48
+ # _responsible_user = None # the cached User object
49
+ #
50
+ # def get_responsible_user(self, checker, obj):
51
+ # if self.responsible_user is None:
52
+ # return None
53
+ # if self._responsible_user is None:
54
+ # User = self.site.models.users.User
55
+ # try:
56
+ # self._responsible_user = User.objects.get(
57
+ # username=self.responsible_user)
58
+ # except User.DoesNotExist:
59
+ # msg = "Invalid username '{0}' in `responsible_user` "
60
+ # msg = msg.format(self.responsible_user)
61
+ # raise Exception(msg)
62
+ # return self._responsible_user
60
63
 
61
64
  def on_plugins_loaded(self, site):
62
65
  """Set :attr:`responsible_user` to ``"'robin'`` if this is a demo site
@@ -64,8 +67,11 @@ class Plugin(ad.Plugin):
64
67
 
65
68
  """
66
69
  super().on_plugins_loaded(site)
67
- if site.is_demo_site and self.responsible_user is None:
68
- self.configure(responsible_user="robin")
70
+ if hasattr(self, 'responsible_user'):
71
+ raise ChangedAPI(
72
+ "20250703 checkdata.responsible_user is replaced by users.demo_username")
73
+ # if site.is_demo_site and self.responsible_user is None:
74
+ # self.configure(responsible_user="robin")
69
75
 
70
76
  def post_site_startup(self, site):
71
77
  super().post_site_startup(site)
@@ -49,6 +49,9 @@ class Checker(dd.Choice):
49
49
  text = self.verbose_name
50
50
  super().__init__(value, text, None, **kwargs)
51
51
 
52
+ def __str__(self):
53
+ return self.value
54
+
52
55
  @classmethod
53
56
  def activate(cls, **kwargs):
54
57
  if cls.self is not None:
@@ -56,6 +59,11 @@ class Checker(dd.Choice):
56
59
  cls.self = cls(**kwargs)
57
60
  Checkers.add_item_instance(cls.self)
58
61
 
62
+ # @classmethod
63
+ # def fix_problems_silently(cls, ar, obj=None):
64
+ # for fixable, msg in cls.self.get_checkdata_problems(ar, obj, True):
65
+ # pass
66
+
59
67
  @classmethod
60
68
  def update_unbound_problems(cls, ar, **kwargs):
61
69
  assert cls.self.model is None
@@ -130,7 +138,7 @@ class Checker(dd.Choice):
130
138
  return []
131
139
 
132
140
  def get_responsible_user(self, obj):
133
- return dd.plugins.checkdata.get_responsible_user(self, obj)
141
+ return dd.plugins.users.get_demo_user(self, obj)
134
142
 
135
143
 
136
144
  class Checkers(dd.ChoiceList):
@@ -1,5 +1,5 @@
1
1
  # -*- coding: UTF-8 -*-
2
- # Copyright 2015 Rumma & Ko Ltd
2
+ # Copyright 2015-2025 Rumma & Ko Ltd
3
3
  # License: GNU Affero General Public License v3 (see file COPYING for details)
4
4
  """Runs the :manage:`checkdata` management command with `--fix`
5
5
  option.
@@ -7,8 +7,10 @@ option.
7
7
  """
8
8
 
9
9
  from django.core.management import call_command
10
+ from lino.api import dd
10
11
 
11
12
 
12
13
  def objects():
13
- call_command("checkdata", fix=True)
14
+ if not dd.is_installed("linod"):
15
+ call_command("checkdata", fix=True)
14
16
  return []
@@ -1,5 +1,5 @@
1
1
  # -*- coding: UTF-8 -*-
2
- # Copyright 2015-2023 Rumma & Ko Ltd
2
+ # Copyright 2015-2025 Rumma & Ko Ltd
3
3
  # License: GNU Affero General Public License v3 (see file COPYING for details)
4
4
 
5
5
  from django.core.management.base import BaseCommand, CommandError
@@ -7,7 +7,7 @@ from django.core.management.base import BaseCommand, CommandError
7
7
  from lino.modlib.checkdata.choicelists import Checkers
8
8
  from lino.modlib.checkdata.models import check_data
9
9
 
10
- from lino.api import rt
10
+ from lino.api import dd, rt
11
11
 
12
12
 
13
13
  class Command(BaseCommand):
@@ -56,7 +56,7 @@ class Command(BaseCommand):
56
56
  app = options.get("checkers", args)
57
57
  if app:
58
58
  args += tuple(app)
59
- ar = rt.login()
59
+ ar = rt.login(dd.plugins.users.demo_username)
60
60
  if options["list"]:
61
61
  ar.show(Checkers, column_names="value text")
62
62
  else:
@@ -317,6 +317,13 @@ def check_instance(ar, obj, **kwargs):
317
317
  print(msg)
318
318
 
319
319
 
320
+ def fix_instance(ar, obj, **kwargs):
321
+ kwargs['fix'] = True
322
+ for chk in get_checkers_for(obj.__class__):
323
+ for fixable, msg in chk.check_instance(ar, obj, **kwargs):
324
+ pass
325
+
326
+
320
327
  def get_checkable_models(*args, only_auto=False):
321
328
  checkable_models = OrderedDict()
322
329
  for chk in Checkers.get_list_items():
@@ -407,8 +414,8 @@ def check_data(ar, args=[], fix=True, prune=False):
407
414
  ar.logger.info(msg, done, what, found, fixed)
408
415
 
409
416
 
410
- @background_task(every_unit="daily", every=1)
417
+ @background_task(every_unit="daily", every=1, run_after='generate_calendar_entries')
411
418
  def checkdata(ar):
412
419
  """Run all data checkers."""
413
- check_data(ar, fix=False)
420
+ check_data(ar, fix=dd.plugins.checkdata.fix_in_background)
414
421
  # rt.login().run(settings.SITE.site_config.run_checkdata)
@@ -3,7 +3,6 @@
3
3
  # License: GNU Affero General Public License v3 (see file COPYING for details)
4
4
 
5
5
  # from html import escape
6
- from .ui import *
7
6
  from lino.modlib.checkdata.choicelists import Checker
8
7
  from django.contrib.humanize.templatetags.humanize import naturaltime
9
8
  from django.db import models
@@ -14,7 +13,7 @@ from django.contrib.contenttypes.models import ContentType
14
13
  from django.conf import settings
15
14
  # from lino.utils.html import E, tostring, fromstring
16
15
 
17
- from lino.api import dd, rt, _
16
+ from lino.api import dd, rt, gettext, _
18
17
 
19
18
  from lino.core.requests import BaseRequest
20
19
  from lino.mixins import CreatedModified, BabelNamed
@@ -30,6 +29,8 @@ from .mixins import Commentable, MyEmotionField
30
29
  from .roles import CommentsReader, CommentsStaff
31
30
  # from .choicelists import PublishAllComments, PublishComment
32
31
 
32
+ from .ui import *
33
+
33
34
 
34
35
  class CommentType(BabelNamed):
35
36
  class Meta(object):
@@ -287,7 +288,7 @@ class Comment(
287
288
 
288
289
  def after_ui_save(self, ar, cw):
289
290
  super().after_ui_save(ar, cw)
290
- if self.owner_id:
291
+ if self.owner is not None:
291
292
  self.owner.on_commented(self, ar, cw)
292
293
 
293
294
  def full_clean(self):
@@ -210,7 +210,7 @@ class ExtRenderer(JsCacheRenderer):
210
210
  label = str(label or ba.get_button_label())
211
211
  uri = self.js2url(self.action_call(ar, ba, status or {}))
212
212
  return self.href_button_action(
213
- ba, uri, label, title or ba.action.help_text, **kw
213
+ ba, uri, label, title or ba.get_help_text(), **kw
214
214
  )
215
215
 
216
216
  def quick_manage_toolbar(self, ar, obj):
@@ -866,7 +866,7 @@ class ExtRenderer(JsCacheRenderer):
866
866
  # ~ text=unicode(a.label),
867
867
  )
868
868
 
869
- if a.help_text:
869
+ if (help_text := ba.get_help_text()) is not None:
870
870
  # if a.__class__.__name__ in ('ChangePassword', 'SubmitDetail'):
871
871
  # logger.info("20160829 a2btn() %r %r", a, str(a.help_text))
872
872
 
@@ -874,7 +874,7 @@ class ExtRenderer(JsCacheRenderer):
874
874
  # iconCls. On a button which has only text we must use
875
875
  # Lino.quicktip_renderer. But I didn't find out why this
876
876
  # doesn't seem to work.
877
- kw.update(tooltip=a.help_text, tooltipType="title")
877
+ kw.update(tooltip=help_text, tooltipType="title")
878
878
  # if not a.icon_name:
879
879
  # kw.update(tooltipType='title')
880
880
  # kw.update(listen ers=dict(render=js_code(
@@ -1496,7 +1496,7 @@ class ExtRenderer(JsCacheRenderer):
1496
1496
  either `data_record` or a `record_id`.
1497
1497
 
1498
1498
  Usually `data_record`, except if it is a `file upload
1499
- <https://docs.djangoproject.com/en/5.0/topics/http/file-uploads/>`_
1499
+ <https://docs.djangoproject.com/en/5.2/topics/http/file-uploads/>`_
1500
1500
  where some mysterious decoding problems (:blogref:`20120209`)
1501
1501
  force us to return a `record_id` which has the same visible
1502
1502
  result but using an additional GET.
@@ -330,6 +330,7 @@ class ApiElement(View):
330
330
  vm = test_version_mismatch(request)
331
331
  if vm and settings.SITE.kernel.editing_front_end.app_label == "react":
332
332
  return json_response(vm)
333
+
333
334
  # this is also used by the react front end
334
335
  rpt = requested_actor(app_label, actor)
335
336
  # if not rpt.get_view_permission(request.user.user_type):
@@ -341,12 +342,17 @@ class ApiElement(View):
341
342
  if action_name:
342
343
  ba = rpt.get_url_action(action_name)
343
344
  if ba is None:
344
- raise http.Http404("%s has no action %r" % (rpt, action_name))
345
+ msg = f"{rpt} has no action {action_name}"
346
+ print(msg)
347
+ raise http.Http404(msg)
345
348
  else:
346
349
  ba = rpt.detail_action
347
350
  if ba is None:
348
- raise http.Http404("%s has no detail_action" % rpt)
351
+ msg = f"{rpt} has no detail_action"
352
+ print(msg)
353
+ raise http.Http404(msg)
349
354
 
355
+ # print(f"20250608 {rpt} {action_name}")
350
356
  fmt = request.GET.get(constants.URL_PARAM_FORMAT,
351
357
  ba.action.default_format)
352
358
 
@@ -75,7 +75,7 @@ if settings.SITE.is_installed("contenttypes"):
75
75
 
76
76
  def deconstruct(self):
77
77
  # needed for Django 1.7
78
- # https://docs.djangoproject.com/en/5.0/howto/custom-model-fields/#custom-field-deconstruct-method
78
+ # https://docs.djangoproject.com/en/5.2/howto/custom-model-fields/#custom-field-deconstruct-method
79
79
 
80
80
  name, path, args, kwargs = super().deconstruct()
81
81
  args = [self.type_field]