lino 25.3.1__py3-none-any.whl → 25.3.3__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 (49) hide show
  1. lino/__init__.py +6 -7
  2. lino/api/dd.py +1 -0
  3. lino/api/doctest.py +4 -68
  4. lino/api/rt.py +2 -4
  5. lino/core/actors.py +22 -16
  6. lino/core/boundaction.py +17 -7
  7. lino/core/dashboard.py +5 -4
  8. lino/core/dbtables.py +15 -16
  9. lino/core/fields.py +3 -3
  10. lino/core/menus.py +1 -1
  11. lino/core/model.py +1 -1
  12. lino/core/renderer.py +10 -11
  13. lino/core/requests.py +11 -7
  14. lino/core/site.py +9 -76
  15. lino/core/tables.py +2 -2
  16. lino/core/utils.py +3 -3
  17. lino/core/views.py +3 -3
  18. lino/help_texts.py +1 -0
  19. lino/management/commands/buildsite.py +67 -0
  20. lino/management/commands/dump2py.py +5 -7
  21. lino/management/commands/initdb.py +12 -14
  22. lino/management/commands/install.py +2 -2
  23. lino/management/commands/prep.py +3 -7
  24. lino/mixins/sequenced.py +1 -1
  25. lino/modlib/comments/models.py +1 -1
  26. lino/modlib/comments/ui.py +2 -2
  27. lino/modlib/extjs/ext_renderer.py +2 -2
  28. lino/modlib/extjs/views.py +50 -48
  29. lino/modlib/help/config/makehelp/model.tpl.rst +1 -1
  30. lino/modlib/help/management/commands/makehelp.py +6 -5
  31. lino/modlib/jinja/renderer.py +2 -2
  32. lino/modlib/linod/management/commands/linod.py +5 -2
  33. lino/modlib/memo/__init__.py +1 -1
  34. lino/modlib/publisher/choicelists.py +3 -3
  35. lino/modlib/publisher/views.py +2 -2
  36. lino/modlib/search/models.py +5 -5
  37. lino/modlib/system/choicelists.py +6 -3
  38. lino/modlib/tinymce/views.py +1 -1
  39. lino/modlib/uploads/models.py +1 -1
  40. lino/modlib/users/ui.py +2 -3
  41. lino/utils/__init__.py +5 -1
  42. lino/utils/dbhash.py +112 -0
  43. lino/utils/diag.py +1 -1
  44. lino/utils/fieldutils.py +79 -0
  45. {lino-25.3.1.dist-info → lino-25.3.3.dist-info}/METADATA +1 -1
  46. {lino-25.3.1.dist-info → lino-25.3.3.dist-info}/RECORD +49 -46
  47. {lino-25.3.1.dist-info → lino-25.3.3.dist-info}/WHEEL +0 -0
  48. {lino-25.3.1.dist-info → lino-25.3.3.dist-info}/licenses/AUTHORS.rst +0 -0
  49. {lino-25.3.1.dist-info → lino-25.3.3.dist-info}/licenses/COPYING +0 -0
lino/core/site.py CHANGED
@@ -3,14 +3,12 @@
3
3
  # License: GNU Affero General Public License v3 (see file COPYING for details)
4
4
  # doctest lino/core/site.py
5
5
 
6
- import json
7
6
  from lino.core.exceptions import ChangedAPI
8
- from lino.core.utils import get_models, is_logserver
7
+ from django.apps import apps
9
8
  from lino.utils.html import E, tostring
10
9
  from lino import assert_django_code, DJANGO_DEFAULT_LANGUAGE
11
10
  # from lino.core import constants
12
11
  from lino.core.plugin import Plugin
13
- from lino.core.utils import full_model_name as fmn
14
12
  from rstgen.confparser import ConfigParser
15
13
  from django.utils import translation
16
14
  from django.utils.html import mark_safe
@@ -657,7 +655,6 @@ class Site(object):
657
655
  log_file_path = logdir / self.logger_filename
658
656
  # print("20231019 logging", file_level, "to", log_file_path)
659
657
  self.log_sock_path = logdir / (self.logger_filename + ".sock")
660
- # if is_logserver() or not self.log_sock_path.exists():
661
658
  if self.log_sock_path.exists():
662
659
  # print("20231019 log via socket server")
663
660
  handlers["file"] = {
@@ -1348,10 +1345,8 @@ class Site(object):
1348
1345
  setattr(obj, name, resolve_model(spec, strict=msg))
1349
1346
 
1350
1347
  def on_each_app(self, methname, *args):
1351
- from django.apps import apps
1352
-
1353
- apps = [a.models_module for a in apps.get_app_configs()]
1354
- for mod in apps:
1348
+ modules = [a.models_module for a in apps.get_app_configs()]
1349
+ for mod in modules:
1355
1350
  meth = getattr(mod, methname, None)
1356
1351
  if meth is not None:
1357
1352
  if False: # 20150925 once we will do it for good...
@@ -1470,7 +1465,7 @@ class Site(object):
1470
1465
  def setup_actions(self):
1471
1466
  from lino.core.merge import MergeAction
1472
1467
 
1473
- for m in get_models():
1468
+ for m in apps.get_models():
1474
1469
  if m.allow_merge_action:
1475
1470
  m.define_action(merge_row=MergeAction(m))
1476
1471
 
@@ -1794,61 +1789,6 @@ class Site(object):
1794
1789
  # ~ return v
1795
1790
  # ~ return getattr(obj,attrname,*args)
1796
1791
 
1797
- def mark_virgin(self):
1798
- """
1799
- Mark the database as virgin. This is called by :manage:`prep`.
1800
- """
1801
- dbhash = self.get_dbhash()
1802
- fn = self.site_dir / "dbhash.json"
1803
- with fn.open("w") as fp:
1804
- json.dump(dbhash, fp)
1805
- # self.site.logger.info("Wrote %s", fn)
1806
-
1807
- def check_virgin(self):
1808
- """
1809
- Verify whether the database is virgin. Print the differences if there
1810
- are any.
1811
- """
1812
- new = self.get_dbhash()
1813
- db = self.site_dir
1814
- fn = db / "dbhash.json"
1815
- if not fn.exists():
1816
- raise Exception(
1817
- f"No `dbhash.json` in {db} (did you run `django-admin prep`?)")
1818
- with fn.open("r") as fp:
1819
- old = json.load(fp)
1820
-
1821
- # noi1r has noi1e as master_site, but the react front end removes the
1822
- # tinymce plugin, i.e. noi1r doesn't care about
1823
- # tinymce.TextFieldTemplate model.
1824
-
1825
- ok = True
1826
- for k, v in new.items():
1827
- v = set(v)
1828
- oldv = set(old.get(k, None))
1829
- if oldv != v:
1830
- if ok:
1831
- print(f"Database {db} isn't virgin:")
1832
- ok = False
1833
- diffs = []
1834
- if (added := len(oldv-v)):
1835
- diffs.append(f"{added} rows added")
1836
- if (removed := len(v-oldv)):
1837
- diffs.append(f"{removed} rows removed")
1838
- print(f"- {k}: {', '.join(diffs)}")
1839
-
1840
- def get_dbhash(self):
1841
- """
1842
- Return a dictionary with a hash value of the current database content.
1843
- """
1844
- rv = dict()
1845
- for m in get_models(include_auto_created=True):
1846
- k = fmn(m)
1847
- if k != "sessions.Session":
1848
- # rv[k] = m.objects.count()
1849
- rv[k] = list(m.objects.values_list('pk', flat=True))
1850
- return rv
1851
-
1852
1792
  def diagnostic_report_rst(self, *args):
1853
1793
  """Returns a string with a diagnostic report about this
1854
1794
  site. :manage:`diag` is a command-line shortcut to this.
@@ -1998,18 +1938,9 @@ class Site(object):
1998
1938
  return self._sorted_plugins
1999
1939
 
2000
1940
  def setup_menu(self, user_type, main, ar=None):
2001
- # from django.apps import apps
2002
- # apps = [a.models_module for a in apps.get_app_configs()]
2003
-
2004
1941
  for k, label in self.top_level_menus:
2005
1942
  methname = "setup_{0}_menu".format(k)
2006
1943
 
2007
- # for mod in apps:
2008
- # if hasattr(mod, methname):
2009
- # msg = "{0} still has a function {1}(). \
2010
- # Please convert to Plugin method".format(mod, methname)
2011
- # raise ChangedAPI(msg)
2012
-
2013
1944
  if label is None:
2014
1945
  menu = main
2015
1946
  else:
@@ -2074,6 +2005,9 @@ class Site(object):
2074
2005
  return mark_safe(s)
2075
2006
 
2076
2007
  def build_site_cache(self, force=False, later=False, verbosity=1):
2008
+ from lino.modlib.users.utils import with_user_profile
2009
+ from lino.modlib.users.choicelists import UserTypes
2010
+ # from django.utils import translation
2077
2011
  # if not self.is_prepared:
2078
2012
  # self.prepare_layouts()
2079
2013
  # self.is_prepared = True
@@ -2108,9 +2042,8 @@ class Site(object):
2108
2042
 
2109
2043
  self.makedirs_if_missing(self.media_root / "webdav")
2110
2044
 
2111
- from lino.modlib.users.utils import with_user_profile
2112
- from lino.modlib.users.choicelists import UserTypes
2113
- # from django.utils import translation
2045
+ if verbosity > 0:
2046
+ self.logger.info("Build site cache in %s.", self.media_root)
2114
2047
 
2115
2048
  started = time.time()
2116
2049
  count = 0
lino/core/tables.py CHANGED
@@ -637,9 +637,9 @@ method in order to sort the rows of the queryset.
637
637
  ba = self.get_action_by_name(an)
638
638
  # ~ print ba
639
639
  if pk is None:
640
- ar = self.request(action=ba)
640
+ ar = self.create_request(action=ba)
641
641
  else:
642
- ar = self.request(action=ba, selected_pks=[pk])
642
+ ar = self.create_request(action=ba, selected_pks=[pk])
643
643
 
644
644
  ba.action.run_from_ui(ar)
645
645
  kw = ar.response
lino/core/utils.py CHANGED
@@ -17,7 +17,7 @@ from django.utils.html import format_html, mark_safe, SafeString
17
17
  from django.db import models
18
18
  from django.db.models import Q
19
19
  from django.core.exceptions import FieldDoesNotExist
20
- from django.utils.functional import lazy
20
+ # from django.utils.functional import lazy
21
21
  from importlib import import_module
22
22
  from django.utils.translation import gettext as _
23
23
  from django.conf import settings
@@ -1046,7 +1046,7 @@ class InstanceAction:
1046
1046
  """
1047
1047
  kwargs.update(selected_rows=[self.instance])
1048
1048
  kwargs.update(parent=ses)
1049
- ar = self.bound_action.request(**kwargs)
1049
+ ar = self.bound_action.create_request(**kwargs)
1050
1050
  return ar
1051
1051
 
1052
1052
  def run_from_session(self, ses, **kwargs):
@@ -1070,7 +1070,7 @@ class InstanceAction:
1070
1070
  """
1071
1071
  if len(args) and isinstance(args[0], BaseRequest):
1072
1072
  raise ChangedAPI("20181004")
1073
- ar = self.bound_action.request(
1073
+ ar = self.bound_action.create_request(
1074
1074
  renderer=settings.SITE.kernel.text_renderer)
1075
1075
  self.run_from_code(ar, *args, **kwargs)
1076
1076
  return ar.response
lino/core/views.py CHANGED
@@ -100,7 +100,7 @@ def action_request(app_label, actor, request, rqdata, is_list, **kw):
100
100
  # internationalized because some error handling code
101
101
  # may want to write it to a plain ascii stream.
102
102
  kw.update(renderer=settings.SITE.kernel.default_renderer)
103
- ar = rpt.request(request=request, action=a, rqdata=rqdata, **kw)
103
+ ar = rpt.create_request(request=request, action=a, rqdata=rqdata, **kw)
104
104
  # print("20210403b", a.action.__class__, rqdata)
105
105
  return ar
106
106
 
@@ -137,7 +137,7 @@ def choices_response(actor, request, qs, row2dict, emptyValue, field=None):
137
137
  count = qs.count()
138
138
 
139
139
  if offset:
140
- qs = qs[int(offset) :]
140
+ qs = qs[int(offset):]
141
141
  # ~ kw.update(offset=int(offset))
142
142
 
143
143
  if limit:
@@ -155,7 +155,7 @@ def choices_response(actor, request, qs, row2dict, emptyValue, field=None):
155
155
  row for row in rows if txt in row[constants.CHOICES_TEXT_FIELD].lower()
156
156
  ]
157
157
  count = len(rows)
158
- rows = rows[int(offset) :] if offset else rows
158
+ rows = rows[int(offset):] if offset else rows
159
159
  rows = rows[: int(limit)] if limit else rows
160
160
 
161
161
  if wt == constants.WINDOW_TYPE_PARAMS and field and field.blank:
lino/help_texts.py CHANGED
@@ -139,6 +139,7 @@ help_texts = {
139
139
  'lino.modlib.extjs.ext_renderer.ExtRenderer.goto_instance' : _("""See JsRenderer.goto_instance(), but when this is called while the detail window is already open (only on another record), then we don’t want to redirect to another page because that would take more time."""),
140
140
  'lino.modlib.extjs.views.AdminIndex' : _("""Similar to PlainIndex"""),
141
141
  'lino.modlib.extjs.views.Restful' : _("""Used to collaborate with a restful Ext.data.Store."""),
142
+ 'lino.modlib.extjs.views.ApiElement' : _("""The view that responds to r'api/(?P<app_label>\w+)/(?P<actor>\w+)/(?P<pk>[^/]+)$'."""),
142
143
  'lino.modlib.gfks.Plugin' : _("""Base class for this plugin."""),
143
144
  'lino.modlib.importfilters.Plugin' : _("""See /dev/plugins."""),
144
145
  'lino.modlib.jinja.Plugin' : _("""See /dev/plugins."""),
@@ -0,0 +1,67 @@
1
+ # -*- coding: UTF-8 -*-
2
+ # Copyright 2009-2023 Rumma & Ko Ltd.
3
+ # License: GNU Affero General Public License v3 (see file COPYING for details)
4
+
5
+ from click import confirm
6
+ from django.core.management.base import BaseCommand, CommandError
7
+ from django.core.management import call_command
8
+ from django.conf import settings
9
+ from lino import logger
10
+
11
+
12
+ class Command(BaseCommand):
13
+ """Build the site cache files and optionally run collectstatic for this Lino site."""
14
+
15
+ def add_arguments(self, parser):
16
+ super().add_arguments(parser)
17
+ parser.add_argument(
18
+ "-b", "--batch", "--noinput",
19
+ action="store_false",
20
+ dest="interactive",
21
+ default=True,
22
+ help="Do not prompt for input of any kind.",
23
+ ),
24
+ parser.add_argument(
25
+ "-c", "--collectstatic",
26
+ action="store_true",
27
+ dest="collectstatic",
28
+ default=False,
29
+ help="Also run collectstatic.",
30
+ ),
31
+
32
+ def handle(self, *args, **options):
33
+ interactive = options.get("interactive")
34
+ verbosity = options.get("verbosity")
35
+ project_dir = settings.SITE.project_dir
36
+ collectstatic = options.get("collectstatic")
37
+
38
+ options = dict(interactive=False, verbosity=verbosity)
39
+
40
+ if interactive:
41
+ msg = "Build everything for ({})".format(project_dir)
42
+ msg += ".\nAre you sure?"
43
+ if not confirm(msg, default=True):
44
+ raise CommandError("User abort.")
45
+
46
+ # the following log message was useful on Travis 20150104
47
+ if verbosity > 0:
48
+ logger.info("`buildsite` started on %s.", project_dir)
49
+
50
+ # pth = project_dir / "settings.py"
51
+ # if pth.exists():
52
+ # pth.touch()
53
+
54
+ if collectstatic:
55
+ call_command("collectstatic", **options)
56
+
57
+ settings.SITE.build_site_cache(force=True, verbosity=verbosity)
58
+
59
+ # if settings.SITE.is_installed("help"):
60
+ # call_command("makehelp", verbosity=verbosity)
61
+
62
+ # for p in settings.SITE.installed_plugins:
63
+ # p.on_buildsite(settings.SITE, verbosity=verbosity)
64
+
65
+ # settings.SITE.clear_site_config()
66
+
67
+ logger.info("`buildsite` finished on %s.", project_dir)
@@ -2,6 +2,10 @@
2
2
  # Copyright 2013-2023 by Rumma & Ko Ltd.
3
3
  # License: GNU Affero General Public License v3 (see file COPYING for details)
4
4
 
5
+ from lino.utils.mldbc.fields import BabelCharField, BabelTextField
6
+ from lino.core.choicelists import ChoiceListField
7
+ from lino.core.utils import sorted_models_list, full_model_name
8
+ from lino.utils import puts
5
9
  from io import open
6
10
  from lino import logger
7
11
 
@@ -20,12 +24,6 @@ from datetime import timezone
20
24
 
21
25
  utc = timezone.utc
22
26
 
23
- from lino.utils import puts
24
- from lino.core.utils import sorted_models_list, full_model_name
25
- from lino.core.choicelists import ChoiceListField
26
-
27
- from lino.utils.mldbc.fields import BabelCharField, BabelTextField
28
-
29
27
 
30
28
  def is_pointer_to_contenttype(f):
31
29
  if not settings.SITE.is_installed("contenttypes"):
@@ -402,7 +400,7 @@ def main(args):
402
400
  if __name__ == '__main__':
403
401
  import argparse
404
402
  parser = argparse.ArgumentParser(description='Restore the data.')
405
- parser.add_argument('--noinput', dest='interactive',
403
+ parser.add_argument('-b', '--noinput', '--batch', dest='interactive',
406
404
  action='store_false', default=True,
407
405
  help="Don't ask for confirmation before flushing the database.")
408
406
  parser.add_argument('--quick', dest='quick',
@@ -1,7 +1,16 @@
1
1
  # -*- coding: UTF-8 -*-
2
- # Copyright 2009-2023 Rumma & Ko Ltd.
2
+ # Copyright 2009-2025 Rumma & Ko Ltd.
3
3
  # License: GNU Affero General Public License v3 (see file COPYING for details)
4
4
 
5
+ from click import confirm
6
+ from lino.api import dd
7
+ from django.db import models
8
+ from django.db import connections, transaction, DEFAULT_DB_ALIAS
9
+ from django.core.management.color import no_style
10
+ from django.db.utils import IntegrityError, OperationalError
11
+ from django.core.management.base import BaseCommand, CommandError
12
+ from django.core.management import call_command
13
+ from django.conf import settings
5
14
  import os
6
15
  import shutil
7
16
  from pathlib import Path
@@ -22,20 +31,9 @@ warnings.filterwarnings(
22
31
  "django.core.management.commands.loaddata",
23
32
  )
24
33
 
25
- from django.conf import settings
26
- from django.core.management import call_command
27
- from django.core.management.base import BaseCommand, CommandError
28
- from django.db.utils import IntegrityError, OperationalError
29
- from django.db.migrations.exceptions import CircularDependencyError
30
- from django.core.management.color import no_style
31
34
 
32
35
  # ~ from django.core.management.sql import sql_reset
33
- from django.db import connections, transaction, DEFAULT_DB_ALIAS
34
- from django.db import models
35
-
36
- from lino.api import dd
37
36
 
38
- from rstgen.utils import confirm
39
37
 
40
38
  USE_SQLDELETE = True
41
39
 
@@ -198,8 +196,8 @@ class Command(BaseCommand):
198
196
  msg = "We are going to flush your database ({})".format(dbname)
199
197
  if removemedia:
200
198
  msg += "\nAND REMOVE ALL FILES BELOW {}".format(mroot)
201
- msg += ".\nAre you sure (y/n) ?"
202
- if not confirm(msg):
199
+ msg += ".\nAre you sure?"
200
+ if not confirm(msg, default=True):
203
201
  raise CommandError("User abort.")
204
202
 
205
203
  # mroot = Path(settings.MEDIA_ROOT)
@@ -4,7 +4,7 @@
4
4
 
5
5
  import sys
6
6
  import subprocess
7
- from rstgen.utils import confirm
7
+ from click import confirm
8
8
  from django.core.management.base import BaseCommand
9
9
  from django.conf import settings
10
10
 
@@ -54,5 +54,5 @@ class Command(BaseCommand):
54
54
  return
55
55
  # cmd = "pip install --upgrade --trusted-host svn.forge.pallavi.be {}".format(' '.join(reqs))
56
56
  cmd = sys.executable + " -m pip install --upgrade pip {}".format(" ".join(reqs))
57
- if not options["interactive"] or confirm("{} (y/n) ?".format(cmd)):
57
+ if not options["interactive"] or confirm("{} ?".format(cmd), default=True):
58
58
  runcmd(cmd)
@@ -1,15 +1,11 @@
1
1
  # -*- coding: UTF-8 -*-
2
- # Copyright 2013-2023 Rumma & Ko Ltd
2
+ # Copyright 2013-2025 Rumma & Ko Ltd
3
3
  # License: GNU Affero General Public License v3 (see file COPYING for details)
4
4
 
5
- from pathlib import Path
6
5
  from django.core.management import call_command
7
6
  from django.core.management.base import BaseCommand, CommandError
8
7
  from django.conf import settings
9
- from django.db import DEFAULT_DB_ALIAS
10
- from rstgen.utils import confirm
11
- # from lino.management.commands.initdb import Command as BaseCommand
12
- # from lino.management.commands.initdb import CommandError
8
+ from lino.utils import dbhash
13
9
 
14
10
 
15
11
  class Command(BaseCommand):
@@ -66,4 +62,4 @@ class Command(BaseCommand):
66
62
  kwargs["removemedia"] = True
67
63
  call_command("initdb", *args, **kwargs)
68
64
 
69
- settings.SITE.mark_virgin()
65
+ dbhash.mark_virgin()
lino/mixins/sequenced.py CHANGED
@@ -524,7 +524,7 @@ class Hierarchical(Duplicable):
524
524
  et.append(ar.goto_pk(c.id, get_text(c)))
525
525
  i -= 1
526
526
  if child == self:
527
- sar = ar.actor.request(
527
+ sar = ar.actor.create_request(
528
528
  parent=ar, master_instance=self, is_on_main_actor=False
529
529
  )
530
530
  # sar = ar.spawn_request(master_instance=self, is_on_main_actor=False)
@@ -257,7 +257,7 @@ class Comment(
257
257
  return format_html("<p>{}</p>", s)
258
258
 
259
259
  htmls = storypar(self.as_paragraph(ar))
260
- for child in RepliesByComment.request(master_instance=self, parent=ar):
260
+ for child in RepliesByComment.create_request(master_instance=self, parent=ar):
261
261
  htmls += storypar(child.as_story_item(ar, indent=indent + 1))
262
262
  # if self.replies_to_this.count():
263
263
  # # s += "<p>{}</p>".format("Replies:")
@@ -129,7 +129,7 @@ class Comments(dd.Table):
129
129
  pv = dict(user=user, start_date=sd, end_date=ed,
130
130
  observed_event=CommentEvents.created)
131
131
  # pv = dict(start_date=sd, end_date=ed, observed_event=CommentEvents.created)
132
- return cls.request(user=user, param_values=pv)
132
+ return cls.create_request(user=user, param_values=pv)
133
133
 
134
134
  # @classmethod
135
135
  # def get_card_title(cls, ar, obj):
@@ -302,7 +302,7 @@ class RepliesByComment(CommentsByX):
302
302
 
303
303
 
304
304
  def comments_by_owner(obj):
305
- return CommentsByRFC.request(master_instance=obj)
305
+ return CommentsByRFC.create_request(master_instance=obj)
306
306
 
307
307
 
308
308
  class Reactions(dd.Table):
@@ -136,7 +136,7 @@ class ExtRenderer(JsCacheRenderer):
136
136
  return self.handler_item(v, js, None)
137
137
  elif v.bound_action is not None:
138
138
  if v.params:
139
- ar = v.bound_action.request(**v.params)
139
+ ar = v.bound_action.create_request(**v.params)
140
140
  js = self.request_handler(ar)
141
141
  # print("20230513", js)
142
142
  else:
@@ -265,7 +265,7 @@ class ExtRenderer(JsCacheRenderer):
265
265
 
266
266
  """
267
267
  if ar is None:
268
- sar = ba.request(**request_kwargs)
268
+ sar = ba.create_request(**request_kwargs)
269
269
  else:
270
270
  sar = ar.spawn(ba, **request_kwargs)
271
271
  return self.ar2js(sar, obj, **status)