lino 25.2.3__py3-none-any.whl → 25.3.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (81) hide show
  1. lino/__init__.py +1 -1
  2. lino/api/dd.py +11 -48
  3. lino/api/doctest.py +34 -36
  4. lino/core/actions.py +25 -23
  5. lino/core/actors.py +37 -17
  6. lino/core/choicelists.py +10 -8
  7. lino/core/dbtables.py +1 -1
  8. lino/core/elems.py +46 -30
  9. lino/core/fields.py +19 -9
  10. lino/core/inject.py +7 -6
  11. lino/core/kernel.py +26 -66
  12. lino/core/model.py +44 -31
  13. lino/core/plugin.py +4 -4
  14. lino/core/requests.py +76 -55
  15. lino/core/site.py +84 -30
  16. lino/core/store.py +5 -2
  17. lino/core/utils.py +12 -7
  18. lino/help_texts.py +3 -8
  19. lino/management/commands/prep.py +1 -1
  20. lino/mixins/duplicable.py +6 -4
  21. lino/mixins/sequenced.py +17 -6
  22. lino/modlib/__init__.py +0 -2
  23. lino/modlib/changes/models.py +21 -10
  24. lino/modlib/checkdata/models.py +59 -24
  25. lino/modlib/comments/fixtures/demo2.py +12 -3
  26. lino/modlib/comments/models.py +7 -7
  27. lino/modlib/comments/ui.py +8 -5
  28. lino/modlib/export_excel/models.py +7 -5
  29. lino/modlib/extjs/views.py +39 -20
  30. lino/modlib/help/management/commands/makehelp.py +5 -2
  31. lino/modlib/jinja/mixins.py +25 -14
  32. lino/modlib/linod/__init__.py +1 -0
  33. lino/modlib/linod/choicelists.py +21 -0
  34. lino/modlib/linod/consumers.py +13 -4
  35. lino/modlib/linod/management/commands/linod.py +6 -2
  36. lino/modlib/linod/mixins.py +16 -11
  37. lino/modlib/linod/models.py +4 -2
  38. lino/modlib/notify/models.py +18 -10
  39. lino/modlib/printing/actions.py +41 -30
  40. lino/modlib/printing/choicelists.py +11 -9
  41. lino/modlib/printing/mixins.py +25 -20
  42. lino/modlib/publisher/models.py +5 -5
  43. lino/modlib/summaries/models.py +3 -2
  44. lino/modlib/system/models.py +28 -29
  45. lino/modlib/uploads/__init__.py +5 -5
  46. lino/modlib/uploads/actions.py +2 -8
  47. lino/modlib/uploads/choicelists.py +10 -10
  48. lino/modlib/uploads/fixtures/std.py +17 -0
  49. lino/modlib/uploads/mixins.py +20 -8
  50. lino/modlib/uploads/models.py +60 -35
  51. lino/modlib/uploads/ui.py +10 -7
  52. lino/utils/media.py +45 -23
  53. lino/utils/report.py +5 -4
  54. lino/utils/soup.py +22 -4
  55. {lino-25.2.3.dist-info → lino-25.3.1.dist-info}/METADATA +1 -1
  56. {lino-25.2.3.dist-info → lino-25.3.1.dist-info}/RECORD +59 -80
  57. lino/mixins/uploadable.py +0 -3
  58. lino/sandbox/bcss/PerformInvestigation.py +0 -2260
  59. lino/sandbox/bcss/SSDNReply.py +0 -3924
  60. lino/sandbox/bcss/SSDNRequest.py +0 -3723
  61. lino/sandbox/bcss/__init__.py +0 -0
  62. lino/sandbox/bcss/readme.txt +0 -1
  63. lino/sandbox/bcss/test.py +0 -92
  64. lino/sandbox/bcss/test2.py +0 -128
  65. lino/sandbox/bcss/test3.py +0 -161
  66. lino/sandbox/bcss/test4.py +0 -167
  67. lino/sandbox/contacts/__init__.py +0 -0
  68. lino/sandbox/contacts/fixtures/__init__.py +0 -0
  69. lino/sandbox/contacts/fixtures/demo.py +0 -365
  70. lino/sandbox/contacts/manage.py +0 -10
  71. lino/sandbox/contacts/models.py +0 -395
  72. lino/sandbox/contacts/settings.py +0 -67
  73. lino/sandbox/tx25/XSD/RetrieveTIGroupsV3.wsdl +0 -65
  74. lino/sandbox/tx25/XSD/RetrieveTIGroupsV3.xsd +0 -286
  75. lino/sandbox/tx25/XSD/rn25_Release201104.xsd +0 -2855
  76. lino/sandbox/tx25/xsd2py1.py +0 -68
  77. lino/sandbox/tx25/xsd2py2.py +0 -62
  78. lino/sandbox/tx25/xsd2py3.py +0 -56
  79. {lino-25.2.3.dist-info → lino-25.3.1.dist-info}/WHEEL +0 -0
  80. {lino-25.2.3.dist-info → lino-25.3.1.dist-info}/licenses/AUTHORS.rst +0 -0
  81. {lino-25.2.3.dist-info → lino-25.3.1.dist-info}/licenses/COPYING +0 -0
lino/core/site.py CHANGED
@@ -3,17 +3,37 @@
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
+ from lino.core.exceptions import ChangedAPI
8
+ from lino.core.utils import get_models, is_logserver
9
+ from lino.utils.html import E, tostring
10
+ from lino import assert_django_code, DJANGO_DEFAULT_LANGUAGE
11
+ # from lino.core import constants
12
+ from lino.core.plugin import Plugin
13
+ from lino.core.utils import full_model_name as fmn
14
+ from rstgen.confparser import ConfigParser
15
+ from django.utils import translation
16
+ from django.utils.html import mark_safe
17
+ from django.utils.translation import get_language
18
+ from django.utils.translation import gettext_lazy as _
19
+ from django.conf import settings
20
+ import rstgen
21
+ from lino.utils import AttrDict, date_offset, i2d, buildurl
22
+ from lino import logger, __version__
23
+ from importlib.util import find_spec
24
+ from importlib import import_module, reload
25
+ from pathlib import Path
6
26
  import os
7
27
  import re
8
28
  import sys
9
- from os.path import normpath, dirname, join, isdir, relpath, exists, abspath
29
+ from os.path import dirname, join, isdir, relpath, exists
10
30
  import inspect
11
31
  import datetime
12
32
  import warnings
13
33
  import collections
14
34
  import locale
15
35
  import logging
16
- from pprint import pprint
36
+ # from pprint import pprint
17
37
  from logging.handlers import SocketHandler
18
38
  import time
19
39
 
@@ -30,34 +50,12 @@ ASYNC_LOGGING = False
30
50
  # activated, accesses settings.DEFAULT_EXCEPTION_REPORTER, which fails at this
31
51
  # moment because the settings aren't yet loaded.
32
52
 
33
- from pathlib import Path
34
- from importlib import import_module, reload
35
- from importlib.util import find_spec
36
-
37
- from lino import logger, __version__
38
- from lino.utils import AttrDict, date_offset, i2d, buildurl
39
- import rstgen
40
-
41
- from django.conf import settings
42
- from django.utils.translation import gettext_lazy as _
43
- from django.utils.translation import get_language
44
- from django.utils.html import mark_safe
45
- from django.db.utils import DatabaseError
46
- from django.utils import translation
47
53
 
48
54
  has_socialauth = find_spec("social_django") is not None
49
55
  has_elasticsearch = find_spec("elasticsearch_django") is not None
50
56
  has_haystack = find_spec("haystack") is not None
51
57
 
52
- from rstgen.confparser import ConfigParser
53
- from lino.core.plugin import Plugin
54
- from lino.core import constants
55
-
56
- from lino import assert_django_code, DJANGO_DEFAULT_LANGUAGE
57
- from lino.utils.html import E, join_elems, tostring
58
- from lino.core.utils import get_models, is_logserver
59
58
 
60
- from lino.core.exceptions import ChangedAPI
61
59
  # from .roles import SiteUser
62
60
 
63
61
 
@@ -88,14 +86,14 @@ def to_locale(language):
88
86
  p = language.find("-")
89
87
  if p >= 0:
90
88
  # Get correct locale for sr-latn
91
- if len(language[p + 1 :]) > 2:
89
+ if len(language[p + 1:]) > 2:
92
90
  return (
93
91
  language[:p].lower()
94
92
  + "_"
95
93
  + language[p + 1].upper()
96
- + language[p + 2 :].lower()
94
+ + language[p + 2:].lower()
97
95
  )
98
- return language[:p].lower() + "_" + language[p + 1 :].upper()
96
+ return language[:p].lower() + "_" + language[p + 1:].upper()
99
97
  return language.lower()
100
98
 
101
99
 
@@ -103,7 +101,8 @@ def class2str(cl):
103
101
  return cl.__module__ + "." + cl.__name__
104
102
 
105
103
 
106
- gettext_noop = lambda s: s
104
+ def gettext_noop(s): return s
105
+
107
106
 
108
107
  PLUGIN_CONFIGS = {}
109
108
 
@@ -718,7 +717,6 @@ class Site(object):
718
717
  else:
719
718
  d["logger_ok"] = True
720
719
  # self.update_settings(LOGGING=d)
721
- # from pprint import pprint
722
720
  # pprint(d)
723
721
  # print("20161126 Site %s " % d['loggers'].keys())
724
722
  # import yaml
@@ -1796,6 +1794,61 @@ class Site(object):
1796
1794
  # ~ return v
1797
1795
  # ~ return getattr(obj,attrname,*args)
1798
1796
 
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
+
1799
1852
  def diagnostic_report_rst(self, *args):
1800
1853
  """Returns a string with a diagnostic report about this
1801
1854
  site. :manage:`diag` is a command-line shortcut to this.
@@ -1992,7 +2045,8 @@ class Site(object):
1992
2045
  ):
1993
2046
  yield "social_django.middleware.SocialAuthExceptionMiddleware"
1994
2047
 
1995
- if False: # removed 20240921, see #5755 (Should we remove AjaxExceptionResponse?)
2048
+ # removed 20240921, see #5755 (Should we remove AjaxExceptionResponse?)
2049
+ if False:
1996
2050
  yield "lino.utils.ajax.AjaxExceptionResponse"
1997
2051
 
1998
2052
  if self.use_security_features:
lino/core/store.py CHANGED
@@ -343,7 +343,8 @@ class ForeignKeyStoreField(RelatedMixin, ComboStoreField):
343
343
 
344
344
  relto_model = self.get_rel_to(obj)
345
345
  if not relto_model:
346
- raise Warning("extract_form_data found no relto_model for %s" % self)
346
+ raise Warning(
347
+ "extract_form_data found no relto_model for %s" % self)
347
348
  # logger.info("20111209 get_value_text: no relto_model")
348
349
  # return
349
350
 
@@ -1134,7 +1135,7 @@ class ParameterStore(BaseStore):
1134
1135
  data = getrqdata(request)
1135
1136
  # print(20160329, data)
1136
1137
  # assert 'pv' in data
1137
- pv = data.getlist(self.url_param) #'fv', 'pv', post[fn] post[fv][fn]
1138
+ pv = data.getlist(self.url_param) # 'fv', 'pv', post[fn] post[fv][fn]
1138
1139
 
1139
1140
  # logger.info("20120221 ParameterStore.parse_params(%s) --> %s",self.url_param,pv)
1140
1141
 
@@ -1220,6 +1221,8 @@ class Store(BaseStore):
1220
1221
 
1221
1222
  form = rh.actor.insert_layout
1222
1223
  if form:
1224
+ if isinstance(form, str):
1225
+ raise Exception(f"20250306 insert_layout {repr(rh.actor)}")
1223
1226
  dh = form.get_layout_handle()
1224
1227
  self.collect_fields(self.detail_fields, dh)
1225
1228
 
lino/core/utils.py CHANGED
@@ -6,6 +6,8 @@ A collection of utilities which require Django settings to be
6
6
  importable.
7
7
  """
8
8
 
9
+ from .exceptions import ChangedAPI
10
+ from lino.utils import IncompleteDate
9
11
  import copy
10
12
  import sys
11
13
  import datetime
@@ -32,8 +34,6 @@ from django.apps import apps
32
34
 
33
35
  get_models = apps.get_models
34
36
 
35
- from lino.utils import IncompleteDate
36
- from .exceptions import ChangedAPI
37
37
 
38
38
  validate_url = URLValidator()
39
39
 
@@ -311,6 +311,7 @@ def range_filter(value, f1, f2):
311
311
  q2 = Q(**{f2 + "__isnull": True}) | Q(**{f2 + "__gte": value})
312
312
  return Q(q1, q2)
313
313
 
314
+
314
315
  def inrange_filter(fld, rng, **kw):
315
316
  """Assuming a database model with a field named `fld`, return a Q
316
317
  object to select the rows having value for `fld` within the given range `rng`.
@@ -343,7 +344,7 @@ def overlap_range_filter(sv, ev, f1, f2, **kw):
343
344
  # raise ValueError(f"{rng} is not a valid range")
344
345
  if not ev:
345
346
  ev = sv
346
- return Q(**{f1+"__lte" : ev, f2+"__gte" : sv})
347
+ return Q(**{f1+"__lte": ev, f2+"__gte": sv})
347
348
 
348
349
 
349
350
  def babelkw(*args, **kw):
@@ -544,7 +545,8 @@ def resolve_field(name, app_label=None):
544
545
  if len(l) == 2:
545
546
  model = apps.get_model(app_label, l[0])
546
547
  if model is None:
547
- raise FieldDoesNotExist("No model named '%s.%s'" % (app_label, l[0]))
548
+ raise FieldDoesNotExist(
549
+ "No model named '%s.%s'" % (app_label, l[0]))
548
550
  return model._meta.get_field(l[1])
549
551
  # fld, remote_model, direct, m2m = model._meta.get_field_by_name(l[1])
550
552
  # assert remote_model is None or issubclass(model, remote_model), \
@@ -812,7 +814,8 @@ def error2str(self, e):
812
814
  return str(getattr(de, "verbose_name", name))
813
815
 
814
816
  return "\n".join(
815
- ["%s : %s" % (fieldlabel(k), self.error2str(v)) for k, v in md.items()]
817
+ ["%s : %s" % (fieldlabel(k), self.error2str(v))
818
+ for k, v in md.items()]
816
819
  )
817
820
  return "\n".join(e.messages)
818
821
  return str(e)
@@ -1067,7 +1070,8 @@ class InstanceAction:
1067
1070
  """
1068
1071
  if len(args) and isinstance(args[0], BaseRequest):
1069
1072
  raise ChangedAPI("20181004")
1070
- ar = self.bound_action.request(renderer=settings.SITE.kernel.text_renderer)
1073
+ ar = self.bound_action.request(
1074
+ renderer=settings.SITE.kernel.text_renderer)
1071
1075
  self.run_from_code(ar, *args, **kwargs)
1072
1076
  return ar.response
1073
1077
 
@@ -1114,7 +1118,6 @@ class PhantomRow(VirtualRow):
1114
1118
  return str(self._ar.get_action_title())
1115
1119
 
1116
1120
 
1117
-
1118
1121
  def login(username=None, **kwargs):
1119
1122
  """Return a basic :term:`action request` with the specified user signed in.
1120
1123
  """
@@ -1131,10 +1134,12 @@ def login(username=None, **kwargs):
1131
1134
  # import lino.core.urls # hack: trigger ui instantiation
1132
1135
  return BaseRequest(**kwargs)
1133
1136
 
1137
+
1134
1138
  def show(*args, **kwargs):
1135
1139
  """Print the specified data table to stdout."""
1136
1140
  return login().show(*args, **kwargs)
1137
1141
 
1142
+
1138
1143
  def shows(*args, **kwargs):
1139
1144
  """Return the output of :func:`show`."""
1140
1145
  return capture_output(show, *args, **kwargs)
lino/help_texts.py CHANGED
@@ -171,10 +171,6 @@ help_texts = {
171
171
  'lino.modlib.tinymce.Plugin.window_buttons2' : _("""The second row of toolbar buttons when editing in own window."""),
172
172
  'lino.modlib.tinymce.Plugin.window_buttons3' : _("""The third row of toolbar buttons when editing in own window."""),
173
173
  'lino.modlib.tinymce.Plugin.media_name' : _("""Lino currently includes three versions of TinyMCE, but for production sites we still use the eldest version 3.4.8."""),
174
- 'lino.modlib.uploads.Plugin' : _("""See /dev/plugins."""),
175
- 'lino.modlib.uploads.Plugin.remove_orphaned_files' : _("""Whether checkdata –fix should automatically delete orphaned files in the uploads folder."""),
176
- 'lino.modlib.uploads.Plugin.with_thumbnails' : _("""Whether to use PIL, the Python Imaging Library."""),
177
- 'lino.modlib.uploads.Plugin.with_volumes' : _("""Whether to use library files (volumes)."""),
178
174
  'lino.modlib.weasyprint.Plugin' : _("""See /dev/plugins."""),
179
175
  'lino.modlib.weasyprint.Plugin.header_height' : _("""Height of header in mm. Set to None if you want no header."""),
180
176
  'lino.modlib.weasyprint.Plugin.footer_height' : _("""Height of footer in mm. Set to None if you want no header."""),
@@ -262,8 +258,7 @@ help_texts = {
262
258
  'lino.utils.jsgen.Component.walk' : _("""Walk over this component and its children."""),
263
259
  'lino.utils.jsgen.VisibleComponent' : _("""A visible component"""),
264
260
  'lino.utils.jsgen.VisibleComponent.install_permission_handler' : _("""Define the allow_read handler used by get_view_permission(). This must be done only once, but after having configured debug_permissions and required_roles."""),
265
- 'lino.utils.media.MediaFile' : _("""Represents a file on the server below MEDIA_ROOT with two properties name and url."""),
266
- 'lino.utils.media.MediaFile.get_url' : _("""return the url that points to file on the server"""),
261
+ 'lino.utils.media.MediaFile' : _("""Represents a file on the server below MEDIA_ROOT with two properties path and url."""),
267
262
  'lino.utils.mldbc.fields.BabelCharField' : _("""Define a variable number of CharField database fields, one for each language of your lino.core.site.Site.languages. See mldbc."""),
268
263
  'lino.utils.mldbc.fields.BabelTextField' : _("""Used for the clones of the master field, one for each non-default language. See mldbc."""),
269
264
  'lino.utils.mldbc.fields.LanguageField' : _("""A field that lets the user select a language from the available lino.core.site.Site.languages."""),
@@ -379,7 +374,7 @@ help_texts = {
379
374
  'lino.modlib.linod.SystemTask.disabled' : _("""Tells whether the task should be ignored."""),
380
375
  'lino.modlib.linod.SystemTask.log_level' : _("""The logging level to apply when running this task."""),
381
376
  'lino.modlib.linod.SystemTask.run' : _("""Performs a routine job."""),
382
- 'lino.modlib.linod.SystemTasks' : _("""The default actor for the SystemTask model."""),
377
+ 'lino.modlib.linod.SystemTasks' : _("""The default table for the SystemTask model."""),
383
378
  'lino.modlib.linod.Runnable' : _("""Model mixin used by SystemTask and other models."""),
384
379
  'lino.modlib.linod.Runnable.procedure' : _("""The background procedure to run in this task."""),
385
380
  'lino.modlib.periods.StoredYear' : _("""The Django model used to store a fiscal year."""),
@@ -690,7 +685,7 @@ help_texts = {
690
685
  'lino.modlib.uploads.AllUploads' : _("""Shows all upload files on this Lino site."""),
691
686
  'lino.modlib.uploads.AreaUploads' : _("""Mixin for tables of upload files where the upload area is known."""),
692
687
  'lino.modlib.uploads.MyUploads' : _("""Shows my uploads (i.e. those whose author is the requesting user)."""),
693
- 'lino.modlib.uploads.UploadBase' : _("""Abstract base class of Upload. This was named lino.mixins.uploadable.Uploadable until 20210217. It encapsulates some really basic functionality. Its usage is deprecated. If you were inheriting from lino.mixins.Uploadable, you should convert that model to point to an Upload instead."""),
688
+ 'lino.modlib.uploads.UploadBase' : _("""Abstract base class of Upload encapsulating some really basic functionality."""),
694
689
  'lino.modlib.uploads.UploadType' : _("""Django model representing an upload type."""),
695
690
  'lino.modlib.uploads.UploadType.shortcut' : _("""Optional pointer to a virtual upload shortcut field. If this is not empty, then the given shortcut field will manage uploads of this type. See also Shortcuts."""),
696
691
  'lino.modlib.uploads.UploadTypes' : _("""The table with all existing upload types."""),
@@ -66,4 +66,4 @@ class Command(BaseCommand):
66
66
  kwargs["removemedia"] = True
67
67
  call_command("initdb", *args, **kwargs)
68
68
 
69
- settings.SITE.kernel.mark_virgin()
69
+ settings.SITE.mark_virgin()
lino/mixins/duplicable.py CHANGED
@@ -29,7 +29,7 @@ class Duplicate(actions.Action):
29
29
  sort_index = 11
30
30
  show_in_workflow = False
31
31
  # readonly = False # like ShowInsert. See docs/blog/2012/0726
32
- callable_from = "td"
32
+ callable_from = "t"
33
33
 
34
34
  # required_roles = set([Expert])
35
35
 
@@ -42,7 +42,7 @@ class Duplicate(actions.Action):
42
42
  return False
43
43
  # if not user_type.has_required_roles([Expert]):
44
44
  # return False
45
- return super(Duplicate, self).get_view_permission(user_type)
45
+ return super().get_view_permission(user_type)
46
46
 
47
47
  def run_from_code(self, ar, **known_values):
48
48
  obj = ar.selected_rows[0]
@@ -97,7 +97,8 @@ class Duplicate(actions.Action):
97
97
  kw = dict()
98
98
  # kw.update(refresh=True)
99
99
  kw.update(
100
- message=_("Duplicated %(old)s to %(new)s.") % dict(old=obj, new=new)
100
+ message=_("Duplicated %(old)s to %(new)s.") % dict(
101
+ old=obj, new=new)
101
102
  )
102
103
  # ~ kw.update(new_status=dict(record_id=new.pk))
103
104
  ar2.success(**kw)
@@ -108,7 +109,8 @@ class Duplicate(actions.Action):
108
109
 
109
110
  obj = ar.selected_rows[0]
110
111
  ar.confirm(
111
- ok, _("This will create a copy of {}.").format(obj), _("Are you sure?")
112
+ ok, _("This will create a copy of {}.").format(
113
+ obj), _("Are you sure?")
112
114
  )
113
115
 
114
116
 
lino/mixins/sequenced.py CHANGED
@@ -33,7 +33,7 @@ class MoveByN(actions.Action):
33
33
  This action is available on any :class:`Sequenced` object as
34
34
  :attr:`Sequenced.move_by_n`.
35
35
 
36
- It is currently only used by React to allow for drag and drop reording.
36
+ It is currently only used by React to allow for drag and drop reordering.
37
37
 
38
38
  """
39
39
 
@@ -80,9 +80,11 @@ class MoveUp(actions.Action):
80
80
  # label = _("Up")
81
81
  # label = "\u2191" thin arrow up
82
82
  # label = "\u25b2" # triangular arrow up
83
- label = "\u25B2" # ▲ Black up-pointing triangle
84
- # label = "" #
83
+ label = _("Move up")
84
+ # button_text = "\u25B2" # ▲ Black up-pointing triangle
85
+ button_text = "↑"
85
86
  custom_handler = True
87
+ callable_from = "t"
86
88
  # icon_name = 'arrow_up'
87
89
  # ~ icon_file = 'arrow_up.png'
88
90
  readonly = False
@@ -92,9 +94,12 @@ class MoveUp(actions.Action):
92
94
  return False
93
95
  if not super().get_action_permission(ar, obj, state):
94
96
  return False
97
+ # if ar.order_by is None or "seqno" not in ar.order_by:
98
+ # return False
95
99
  if ar.get_total_count() == 0:
96
100
  return False
97
101
  if ar.data_iterator[0] == obj:
102
+ # print(f"20250305 first of {ar.data_iterator}")
98
103
  return False
99
104
  # print("20161128", obj.seqno, ar.data_iterator.count())
100
105
  return True
@@ -119,14 +124,16 @@ class MoveDown(actions.Action):
119
124
 
120
125
  """
121
126
 
127
+ label = _("Move down")
122
128
  # label = _("Down")
123
- label = "↓"
129
+ button_text = "↓"
124
130
  # label = "\u25bc" # triangular arrow down
125
131
  # label = "\u2193"
126
- label = "\u25BC" # ▼ Black down-pointing triangle
132
+ # button_text = "\u25BC" # ▼ Black down-pointing triangle
127
133
  # icon_name = 'arrow_down'
128
134
  custom_handler = True
129
135
  # ~ icon_file = 'arrow_down.png'
136
+ callable_from = "t"
130
137
  readonly = False
131
138
 
132
139
  def get_action_permission(self, ar, obj, state):
@@ -134,10 +141,13 @@ class MoveDown(actions.Action):
134
141
  return False
135
142
  if not super().get_action_permission(ar, obj, state):
136
143
  return False
144
+ # if ar.order_by is None or "seqno" not in ar.order_by:
145
+ # return False
137
146
  n = ar.get_total_count()
138
147
  if n == 0:
139
148
  return False
140
149
  if ar.data_iterator[n - 1] == obj:
150
+ # print(f"20250305 last of {ar.data_iterator}")
141
151
  return False
142
152
  # ~ if obj.__class__.__name__=='Entry' and obj.seqno == 25:
143
153
  # ~ print 20130706, ar.data_iterator.count(), ar.data_iterator
@@ -315,7 +325,8 @@ class Sequenced(Duplicable):
315
325
 
316
326
  seq_no += 1
317
327
 
318
- ar.success(message=_("Renumbered {} of {} siblings.").format(n, qs.count()))
328
+ ar.success(
329
+ message=_("Renumbered {} of {} siblings.").format(n, qs.count()))
319
330
  ar.set_response(refresh_all=True)
320
331
 
321
332
  @fields.displayfield(_("Move"))
lino/modlib/__init__.py CHANGED
@@ -67,7 +67,5 @@ Enterprise Resources
67
67
  languages
68
68
  office
69
69
  smtpd
70
- uploads
71
-
72
70
 
73
71
  """
@@ -64,7 +64,8 @@ class Change(UserAuthored):
64
64
  related_name="changes_by_object",
65
65
  )
66
66
  object_id = GenericForeignKeyIdField(object_type, blank=True, null=True)
67
- object = GenericForeignKey("object_type", "object_id", verbose_name=_("Object"))
67
+ object = GenericForeignKey(
68
+ "object_type", "object_id", verbose_name=_("Object"))
68
69
 
69
70
  master_type = dd.ForeignKey(
70
71
  "contenttypes.ContentType",
@@ -74,9 +75,11 @@ class Change(UserAuthored):
74
75
  related_name="changes_by_master",
75
76
  )
76
77
  master_id = GenericForeignKeyIdField(master_type, blank=True, null=True)
77
- master = GenericForeignKey("master_type", "master_id", verbose_name=_("Master"))
78
+ master = GenericForeignKey(
79
+ "master_type", "master_id", verbose_name=_("Master"))
78
80
 
79
- diff = dd.RichTextField(_("Changes"), format="plain", blank=True, editable=False)
81
+ diff = dd.RichTextField(_("Changes"), format="plain",
82
+ blank=True, editable=False)
80
83
  changed_fields = dd.CharField(_("Fields"), max_length=250, blank=True)
81
84
 
82
85
  def __str__(self):
@@ -129,7 +132,8 @@ class Changes(dd.Table):
129
132
  if pv.change_type:
130
133
  qs = qs.filter(type=pv.change_type)
131
134
  if pv.date:
132
- qs = qs.filter(time__range=(pv.date, pv.date + datetime.timedelta(1)))
135
+ qs = qs.filter(time__range=(
136
+ pv.date, pv.date + datetime.timedelta(1)))
133
137
  # if settings.SITE.user_model and ar.param_values.user:
134
138
  # qs = qs.filter(user=ar.param_values.user)
135
139
  if pv.object_type:
@@ -167,7 +171,9 @@ def log_change(type, request, master, obj, msg="", changed_fields=""):
167
171
  remove_after = dd.plugins.changes.remove_after
168
172
  if remove_after:
169
173
 
170
- @dd.schedule_daily()
174
+ from lino.modlib.linod.choicelists import schedule_daily
175
+
176
+ @schedule_daily()
171
177
  def delete_older_changes(ar):
172
178
  days = datetime.timedelta(days=remove_after)
173
179
  # django.core.exceptions.FieldError: Cannot resolve keyword 'time_lt' into field. Choices are: changed_fields, diff, id, list_item, master, master_id, master_type, master_type_id, name_column, navigation_panel, object, object_id, object_type, object_type_id, overview, time, type, user, user_id, workflow_buttons
@@ -206,7 +212,8 @@ def on_update(sender=None, watcher=None, request=None, **kw):
206
212
  changes = []
207
213
  for k, old, new in watcher.get_updates(cs.ignored_fields):
208
214
  changed_fields += k + " "
209
- changes.append("%s : %s --> %s" % (k, dd.obj2str(old), dd.obj2str(new)))
215
+ changes.append("%s : %s --> %s" %
216
+ (k, dd.obj2str(old), dd.obj2str(new)))
210
217
  if len(changes) == 0:
211
218
  msg = "(no changes)"
212
219
  elif len(changes) == 1:
@@ -229,7 +236,8 @@ def on_delete(sender=None, request=None, **kw):
229
236
  master = get_master(sender)
230
237
  if master is None:
231
238
  return
232
- log_change(ChangeTypes.delete, request, master, sender, dd.obj2str(sender, True))
239
+ log_change(ChangeTypes.delete, request, master,
240
+ sender, dd.obj2str(sender, True))
233
241
 
234
242
 
235
243
  @receiver(on_ui_created)
@@ -238,7 +246,8 @@ def on_ui_created(sender=None, request=None, **kw):
238
246
  master = get_master(sender)
239
247
  if master is None:
240
248
  return
241
- log_change(ChangeTypes.create, request, master, sender, dd.obj2str(sender, True))
249
+ log_change(ChangeTypes.create, request, master,
250
+ sender, dd.obj2str(sender, True))
242
251
 
243
252
 
244
253
  @receiver(pre_add_child)
@@ -247,7 +256,8 @@ def on_add_child(sender=None, request=None, child=None, **kw):
247
256
  if master is None:
248
257
  return
249
258
  log_change(
250
- ChangeTypes.add_child, request, master, sender, dd.full_model_name(child)
259
+ ChangeTypes.add_child, request, master, sender, dd.full_model_name(
260
+ child)
251
261
  )
252
262
 
253
263
 
@@ -257,7 +267,8 @@ def on_remove_child(sender=None, request=None, child=None, **kw):
257
267
  if master is None:
258
268
  return
259
269
  log_change(
260
- ChangeTypes.remove_child, request, master, sender, dd.full_model_name(child)
270
+ ChangeTypes.remove_child, request, master, sender, dd.full_model_name(
271
+ child)
261
272
  )
262
273
 
263
274