lino 25.7.3__py3-none-any.whl → 25.8.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
lino/__init__.py 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.7.3'
34
+ __version__ = '25.8.1'
35
35
 
36
36
  # import setuptools # avoid UserWarning "Distutils was imported before Setuptools"?
37
37
 
lino/core/actions.py CHANGED
@@ -233,7 +233,7 @@ class Action(Parametrizable, Permittable):
233
233
 
234
234
  def full_name(self, actor=None):
235
235
  if self.action_name is None:
236
- raise Exception("Tried to full_name() on %r" % self)
236
+ raise Exception(f"Tried to full_name() on {repr(self)}")
237
237
  # ~ return repr(self)
238
238
  if actor is None or (self.parameters and not self.no_params_window):
239
239
  return self.defining_actor.actor_id + "." + self.action_name
@@ -362,6 +362,7 @@ class ShowDetail(Action):
362
362
  ui5_icon_name = "sap-icon://detail-view"
363
363
  opens_a_window = True
364
364
  window_type = constants.WINDOW_TYPE_DETAIL
365
+ show_in_toolbar = False
365
366
  show_in_workflow = False
366
367
  save_action_name = "submit_detail"
367
368
  callable_from = "t"
lino/core/dbtables.py CHANGED
@@ -440,7 +440,10 @@ class Table(AbstractTable):
440
440
 
441
441
  # if self.master is not None:
442
442
  # self.master = resolve_model(self.master)
443
- if self.master_key:
443
+
444
+ # For abstract tables we don't check the master key, e.g.
445
+ # tickets.TicketsByGroup
446
+ if self.master_key and not self.abstract:
444
447
  master_model = None
445
448
  try:
446
449
  fk = self.model.get_data_elem(self.master_key)
lino/core/site.py CHANGED
@@ -670,18 +670,16 @@ class Site(object):
670
670
  "formatter": "verbose",
671
671
  }
672
672
  elif self.use_systemd:
673
- handlers["file"] = {
674
- "class": "systemd.journal.JournalHandler",
675
- "SYSLOG_IDENTIFIER": str(self.project_name),
676
- }
677
- # try:
678
- # from systemd.journal import JournalHandler
679
- # handlers["file"] = {
680
- # "class": "systemd.journal.JournalHandler",
681
- # "SYSLOG_IDENTIFIER": str(self.project_name),
682
- # }
683
- # except ImportError:
684
- # pass
673
+ try:
674
+ from systemd.journal import JournalHandler
675
+ handlers["file"] = {
676
+ "class": "systemd.journal.JournalHandler",
677
+ "SYSLOG_IDENTIFIER": str(self.project_name),
678
+ }
679
+ except ImportError:
680
+ # Silenly ignore. Can happen when use_systemd is True but
681
+ # `pm install` hasn't yet been run.
682
+ pass
685
683
 
686
684
  # when a file handler exists, we have the loggers use it even if this
687
685
  # instance didn't create it:
@@ -919,8 +917,6 @@ class Site(object):
919
917
  extends_models = pc.__dict__.get("extends_models")
920
918
  if extends_models is not None:
921
919
  for m in extends_models:
922
- if "." in m:
923
- raise Exception("extends_models in %s still uses '.'" % pc)
924
920
  for pp in plugin_parents(pc):
925
921
  if pp is pc:
926
922
  continue
lino/help_texts.py CHANGED
@@ -232,7 +232,7 @@ help_texts = {
232
232
  'lino.utils.djangotest.RestoreTestCase' : _("""Used for testing migrations from previous versions."""),
233
233
  'lino.utils.djangotest.RestoreTestCase.tested_versions' : _("""A list of strings, each string is a version for which there must be a migration dump created by makemigdump."""),
234
234
  'lino.utils.dpy.FakeDeserializedObject' : _("""Imitates DeserializedObject required by loaddata."""),
235
- 'lino.utils.dpy.FakeDeserializedObject.try_save' : _("""Try to save the specified Model instance obj. Return True on success, False if this instance wasn’t saved and should be deferred."""),
235
+ 'lino.utils.dpy.FakeDeserializedObject.try_save' : _("""Try to save this Model instance."""),
236
236
  'lino.utils.dpy.Serializer' : _("""Serializes a QuerySet to a py stream."""),
237
237
  'lino.utils.dpy.FlushDeferredObjects' : _("""Indicator class object. Fixture may yield a FlushDeferredObjects to indicate that all deferred objects should get saved before going on."""),
238
238
  'lino.utils.dpy.DpyLoader' : _("""Instantiated by restore.py."""),
@@ -0,0 +1,60 @@
1
+ # -*- coding: UTF-8 -*-
2
+ # Copyright 2025 Rumma & Ko Ltd
3
+ # License: GNU Affero General Public License v3 (see file COPYING for details)
4
+
5
+ # import shutil
6
+ from click import confirm
7
+ import subprocess
8
+ from django.db import DEFAULT_DB_ALIAS
9
+ from django.core.management import call_command
10
+ from django.core.management.base import BaseCommand, CommandError
11
+ from django.conf import settings
12
+
13
+
14
+ class Command(BaseCommand):
15
+ help = "Run a double-dump test on this site."
16
+
17
+ def add_arguments(self, parser):
18
+ super().add_arguments(parser)
19
+ (
20
+ parser.add_argument(
21
+ "-b", "--batch", "--noinput",
22
+ action="store_false",
23
+ dest="interactive",
24
+ default=True,
25
+ help="Do not prompt for input of any kind.",
26
+ ),
27
+ )
28
+
29
+ def runcmd(self, cmd, **options):
30
+ bashopts = dict(shell=True, universal_newlines=True)
31
+ cp = subprocess.run(cmd, **bashopts)
32
+ if cp.returncode != 0:
33
+ # subprocess.run("sudo journalctl -xe", **kw)
34
+ raise CommandError(f"{cmd} ended with return code {cp.returncode}")
35
+
36
+ def handle(self, *args, **options):
37
+ if len(args) > 0:
38
+ raise CommandError("This command takes no arguments (got %r)" % args)
39
+
40
+ using = options.get("database", DEFAULT_DB_ALIAS)
41
+ dbname = settings.DATABASES[using]["NAME"]
42
+ interactive = options.get("interactive")
43
+ if interactive:
44
+ msg = f"WARNING: running this test can break your database ({dbname})."
45
+ msg += "\nAre you sure?"
46
+ if not confirm(msg, default=True):
47
+ raise CommandError("User abort.")
48
+
49
+ kwargs = dict()
50
+ kwargs["interactive"] = False
51
+ kwargs["verbosity"] = options.get("verbosity")
52
+ tmpdir = settings.SITE.site_dir / "tmp"
53
+ # call_command("prep", '--keepmedia', **kwargs)
54
+ call_command("dump2py", '-o', tmpdir / "a", **kwargs)
55
+ self.runcmd(f"python manage.py run {str(tmpdir / 'a' / 'restore.py')} -b")
56
+ call_command("dump2py", '-o', tmpdir / "b", **kwargs)
57
+ self.runcmd(f"diff {tmpdir / 'a'} {tmpdir / 'b'}")
58
+ print(f"Successfully ran double-dump test in {tmpdir}.")
59
+ # print(f"The double-dump test was successful, we can remove {tmpdir}.")
60
+ # shutil.rmtree(tmpdir)
@@ -300,7 +300,7 @@ def bv2kw(fieldname, values):
300
300
  """
301
301
 
302
302
  def main(args):
303
- loader = DpyLoader(globals(), quick=args.quick)
303
+ loader = DpyLoader(globals(), quick=args.quick, strict=True)
304
304
  from django.core.management import call_command
305
305
  call_command('initdb', interactive=args.interactive)
306
306
  os.chdir(os.path.dirname(__file__))
@@ -449,35 +449,23 @@ if __name__ == '__main__':
449
449
  hope = True
450
450
  break
451
451
 
452
- # ~ ok = True
453
- # ~ for d in deps:
454
- # ~ if d in unsorted:
455
- # ~ ok = False
456
- # ~ if ok:
457
- # ~ sorted.append(model)
458
- # ~ unsorted.remove(model)
459
- # ~ hope = True
460
- # ~ break
461
- # ~ else:
462
- # ~ guilty[model] = deps
463
- # ~ print model.__name__, "depends on", [m.__name__ for m in deps]
464
- if unsorted:
452
+ if len(unsorted):
465
453
  assert len(unsorted) == len(guilty)
466
- msg = "There are %d models with circular dependencies :\n" % len(unsorted)
454
+ todo = [(m, m.objects.count(), guilty[m]) for m in unsorted]
455
+ todo.sort(key=lambda i: i[1])
456
+
457
+ msg = "There are %d models with circular dependencies :\n" % len(todo)
467
458
  msg += "- " + "\n- ".join(
468
459
  [
469
460
  full_model_name(m)
470
- + " (depends on %s)" % ", ".join([full_model_name(d) for d in deps])
471
- for m, deps in guilty.items()
472
- ]
473
- )
474
- if False:
475
- # we don't write them to the .py file because they are
476
- # in random order which would cause false ddt to fail
477
- for ln in msg.splitlines():
478
- self.stream.write("\n# %s" % ln)
461
+ + " (%d rows, depends on %s)" % (
462
+ count, ", ".join([full_model_name(d) for d in deps])
463
+ ) for m, count, deps in todo]
464
+ ) + "\n\n"
465
+ for ln in msg.splitlines():
466
+ self.stream.write("\n# %s" % ln)
479
467
  logger.info(msg)
480
- sorted.extend(unsorted)
468
+ sorted.extend([i[0] for i in todo])
481
469
  return sorted
482
470
 
483
471
  def value2string(self, obj, field):
@@ -24,7 +24,7 @@ class Command(BaseCommand):
24
24
  )
25
25
  (
26
26
  parser.add_argument(
27
- "--keepmedia",
27
+ "-k", "--keepmedia",
28
28
  action="store_true",
29
29
  dest="keepmedia",
30
30
  default=False,
@@ -123,10 +123,12 @@ class Commentable(MemoReferrable):
123
123
  return _("Created new {model} {obj}.").format(
124
124
  model=self.__class__._meta.verbose_name, obj=self)
125
125
 
126
- @classmethod
127
- def add_comments_filter(cls, qs, ar):
128
- return qs
126
+ # @classmethod
127
+ # def add_comments_filter(cls, qs, ar):
128
+ # return qs
129
129
 
130
- def is_comment_private(self, comment, ar):
131
- """Whether the given comment should be private."""
132
- return dd.plugins.comments.private_default
130
+ def get_comment_group(self):
131
+ pass
132
+
133
+ def on_create_comment(self, comment, ar):
134
+ comment.private = dd.plugins.comments.private_default
@@ -9,7 +9,7 @@ from django.db import models
9
9
  from django.db.models import Q
10
10
  from django.core import validators
11
11
  from django.utils.html import mark_safe, format_html, SafeString
12
- from django.contrib.contenttypes.models import ContentType
12
+ from django.utils.translation import ngettext
13
13
  from django.conf import settings
14
14
  # from lino.utils.html import E, tostring, fromstring
15
15
 
@@ -24,6 +24,7 @@ from lino.modlib.search.mixins import ElasticSearchable
24
24
  from lino.modlib.gfks.mixins import Controllable
25
25
  from lino.modlib.memo.mixins import Previewable, MemoReferrable
26
26
  from lino.modlib.publisher.mixins import Publishable
27
+ from lino_xl.lib.groups.mixins import Groupwise
27
28
  from .choicelists import CommentEvents, Emotions
28
29
  from .mixins import Commentable, MyEmotionField
29
30
  from .roles import CommentsReader, CommentsStaff
@@ -49,8 +50,9 @@ class Comment(
49
50
  Publishable,
50
51
  DateRangeObservable,
51
52
  MemoReferrable,
53
+ Groupwise
52
54
  ):
53
- class Meta(object):
55
+ class Meta:
54
56
  app_label = "comments"
55
57
  abstract = dd.is_abstract_model(__name__, "Comment")
56
58
  verbose_name = _("Comment")
@@ -119,8 +121,7 @@ class Comment(
119
121
  # more_text = dd.RichTextField(_("More text"), blank=True)
120
122
 
121
123
  private = models.BooleanField(
122
- _("Confidential"), default=dd.plugins.comments.private_default
123
- )
124
+ _("Confidential"), default=dd.plugins.comments.private_default)
124
125
 
125
126
  comment_type = dd.ForeignKey("comments.CommentType", blank=True, null=True)
126
127
  # reply_vote = models.BooleanField(_("Upvote"), null=True, blank=True)
@@ -187,10 +188,10 @@ class Comment(
187
188
  # if o.emotion.button_text:
188
189
  # yield o.emotion.button_text
189
190
  # yield " "
190
- if False:
191
- attrs = {"class": "bordertext"}
192
- else:
193
- attrs = {}
191
+ # if False:
192
+ # attrs = {"class": "bordertext"}
193
+ # else:
194
+ # attrs = {}
194
195
  s += ar.obj2htmls(o, naturaltime(o.created), title=t)
195
196
  s += format_html(" {} ", _("by"))
196
197
  if o.user is None:
@@ -284,7 +285,11 @@ class Comment(
284
285
  def on_create(self, ar):
285
286
  super().on_create(ar)
286
287
  if self.owner_id:
287
- self.private = self.owner.is_comment_private(self, ar)
288
+ self.owner.on_create_comment(self, ar)
289
+
290
+ def get_default_group(self):
291
+ if self.owner_id:
292
+ return self.owner.get_comment_group()
288
293
 
289
294
  def after_ui_save(self, ar, cw):
290
295
  super().after_ui_save(ar, cw)
@@ -355,9 +360,10 @@ class Comment(
355
360
  qs = qs.filter(private=False)
356
361
  elif not user.user_type.has_required_roles([CommentsStaff]):
357
362
  qs = qs.filter(Q(private=False) | Q(user=user))
358
- sar = BaseRequest(user=ar.get_user())
359
- for m in rt.models_by_base(Commentable):
360
- qs = m.add_comments_filter(qs, sar)
363
+
364
+ # sar = BaseRequest(user=ar.get_user())
365
+ # for m in rt.models_by_base(Commentable):
366
+ # qs = m.add_comments_filter(qs, sar)
361
367
 
362
368
  qs = qs.annotate(num_replies=models.Count("replies_to_this"))
363
369
  qs = qs.annotate(num_reactions=models.Count("reactions_to_this"))
@@ -3,7 +3,6 @@
3
3
  # License: GNU Affero General Public License v3 (see file COPYING for details)
4
4
 
5
5
  from lino.modlib.publisher.choicelists import PageFillers
6
- from django.utils.translation import ngettext
7
6
  from django.contrib.humanize.templatetags.humanize import naturaltime
8
7
  from django.contrib.contenttypes.models import ContentType
9
8
  from django.db import models
@@ -73,7 +72,7 @@ class CommentDetail(dd.DetailLayout):
73
72
  )
74
73
 
75
74
  more2 = """
76
- id user
75
+ id user group
77
76
  owner_type owner_id
78
77
  created modified
79
78
  comment_type
@@ -214,6 +214,8 @@ class Previewable(BasePreviewable):
214
214
  body_short_preview = RichTextField(_("Preview"), blank=True, editable=False)
215
215
  body_full_preview = RichTextField(_("Preview (full)"), blank=True, editable=False)
216
216
 
217
+ edit_body = dd.ShowEditor("body")
218
+
217
219
  def get_body_parsed(self, ar, short=False):
218
220
  if ar.renderer is settings.SITE.kernel.editing_front_end.renderer:
219
221
  return self.body_short_preview if short else self.body_full_preview
@@ -38,6 +38,7 @@ from lino.modlib.linod.choicelists import schedule_daily
38
38
  from lino.modlib.memo.mixins import Previewable
39
39
  from lino.mixins.polymorphic import Polymorphic
40
40
  from lino_xl.lib.topics.mixins import Taggable
41
+ from lino_xl.lib.groups.mixins import Groupwise
41
42
  # from .utils import render_node
42
43
 
43
44
  from lino.api import rt, dd
@@ -50,19 +51,22 @@ from .ui import *
50
51
 
51
52
 
52
53
  class Page(
53
- Hierarchical, Sequenced, Previewable, Commentable, PublishableContent, Taggable
54
+ Hierarchical, Sequenced, Previewable, Commentable, PublishableContent,
55
+ Taggable # , Groupwise
54
56
  ):
55
57
  class Meta:
56
58
  verbose_name = _("Page")
57
59
  verbose_name_plural = _("Pages")
58
60
  abstract = dd.is_abstract_model(__name__, "Page")
61
+ # if dd.is_installed("groups"):
62
+ # unique_together = ["group", "ref", "language"]
63
+ # else:
64
+ # unique_together = ["ref", "language"]
59
65
  unique_together = ["ref", "language"]
60
66
 
61
67
  memo_command = "page"
62
68
 
63
- ref = models.CharField(
64
- _("Reference"), max_length=200, blank=True, null=True)
65
-
69
+ ref = dd.CharField(_("Reference"), max_length=200, blank=True, null=True)
66
70
  title = dd.CharField(_("Title"), max_length=250, blank=True)
67
71
  child_node_depth = models.IntegerField(default=1)
68
72
  # page_type = PageTypes.field(blank=True, null=True)
@@ -362,10 +366,14 @@ class Page(
362
366
  # return True
363
367
 
364
368
  def get_absolute_url(self, **kwargs):
369
+ parts = []
370
+ if self.group is not None:
371
+ if self.group.ref is not None:
372
+ parts.append(self.group.ref)
365
373
  if self.ref:
366
374
  if self.ref != "index":
367
- return dd.plugins.publisher.build_plain_url(self.ref, **kwargs)
368
- return dd.plugins.publisher.build_plain_url(**kwargs)
375
+ parts.append(self.group.ref)
376
+ return dd.plugins.publisher.build_plain_url(*parts, **kwargs)
369
377
 
370
378
  def get_publisher_response(self, ar):
371
379
  if ar and ar.request and self.language != ar.request.LANGUAGE_CODE:
@@ -382,8 +390,7 @@ class Page(
382
390
  sources.add(p.id)
383
391
  p = p.translated_from
384
392
  qs = self.__class__.objects.filter(
385
- language=rqlang, translated_from__in=sources
386
- )
393
+ language=rqlang, translated_from__in=sources)
387
394
  obj = qs.first()
388
395
  # obj = self.translated_to.filter(language=rqlang).first()
389
396
  # print("20231027 redirect to translation", tt.language, ar.request.LANGUAGE_CODE)
lino/utils/cycler.py CHANGED
@@ -51,14 +51,17 @@ class Cycler(object):
51
51
  return item.pop()
52
52
  return item
53
53
 
54
+ def reset(self):
55
+ self.current = 0
56
+
54
57
  def __len__(self):
55
58
  return len(self.items)
56
59
 
60
+ def __iter__(self):
61
+ return self.items.__iter__()
62
+
57
63
  def __repr__(self):
58
64
  return f"Cycler({self.current} of {len(self.items)} in loop {self.loop_no})"
59
65
 
60
- def reset(self):
61
- self.current = 0
62
-
63
66
  def __getitem__(self, *args):
64
67
  return self.items.__getitem__(*args)
lino/utils/dpy.py CHANGED
@@ -6,30 +6,27 @@ Defines Lino's **Python serializer and deserializer**. See
6
6
  :doc:`Specification </specs/dpy>`.
7
7
  """
8
8
 
9
- from lino import logger
10
- from packaging.version import Version
11
-
9
+ # import traceback
12
10
  import os
13
11
  import importlib.util
14
12
  import sys
15
- from unipath import Path
13
+ from packaging.version import Version
14
+ from pathlib import Path
16
15
  # from lino import AFTER17
17
-
18
16
  from django.conf import settings
19
17
  from django.db import models
20
-
21
18
  from django.utils import translation
22
19
  from django.utils.module_loading import import_string
23
20
  from django.utils.encoding import force_str
24
-
25
21
  # from django.db import IntegrityError
26
22
  from django.core.serializers import base
27
23
  from django.core.exceptions import ValidationError
28
24
  # from django.core.exceptions import ObjectDoesNotExist
29
-
30
25
  # from lino.utils.mldbc.fields import BabelCharField, BabelTextField
31
26
  # from lino.core.choicelists import ChoiceListField
32
27
  from lino.core.utils import obj2str, full_model_name
28
+ from lino.core.fields import VirtualField
29
+ from lino import logger
33
30
 
34
31
  SUFFIX = ".py"
35
32
 
@@ -118,7 +115,7 @@ if SUPPORT_EMPTY_FIXTURES:
118
115
  class FakeDeserializedObject(base.DeserializedObject):
119
116
  """Imitates DeserializedObject required by loaddata.
120
117
 
121
- Unlike normal DeserializedObject, we *don't want* to bypass
118
+ Unlike normal DeserializedObject, we do *not* want to bypass
122
119
  pre_save and validation methods on the individual objects.
123
120
 
124
121
  """
@@ -126,7 +123,6 @@ class FakeDeserializedObject(base.DeserializedObject):
126
123
  def __init__(self, deserializer, object, **kw):
127
124
  super().__init__(object, deserializer, **kw)
128
125
  self.object = object
129
- # self.name = name
130
126
  self.deserializer = deserializer
131
127
 
132
128
  def save(self, *args, **kw):
@@ -134,16 +130,9 @@ class FakeDeserializedObject(base.DeserializedObject):
134
130
  # logger.info("Loading %s...",self.name)
135
131
 
136
132
  self.try_save(*args, **kw)
137
- # if self.try_save(*args,**kw):
138
- # self.deserializer.saved += 1
139
- # else:
140
- # self.deserializer.save_later.append(self)
141
133
 
142
134
  def try_save(self, *args, **kw):
143
- """Try to save the specified Model instance `obj`. Return `True`
144
- on success, `False` if this instance wasn't saved and should be
145
- deferred.
146
- """
135
+ """Try to save this Model instance."""
147
136
  obj = self.object
148
137
  try:
149
138
  m = getattr(obj, "before_dumpy_save", None)
@@ -152,39 +141,56 @@ class FakeDeserializedObject(base.DeserializedObject):
152
141
  if not self.deserializer.quick:
153
142
  try:
154
143
  obj.full_clean()
155
- except ValidationError as e:
144
+ except ValidationError:
156
145
  # raise Exception("{0} : {1}".format(obj2str(obj), e))
157
146
  raise # Exception("{0} : {1}".format(obj2str(obj), e))
158
147
  obj.save(*args, **kw)
159
- logger.debug("%s has been saved" % obj2str(obj))
148
+ logger.debug("%s has been saved", obj2str(obj))
160
149
  self.deserializer.register_success()
161
- return True
162
- # except ValidationError,e:
163
- # except ObjectDoesNotExist,e:
164
- # except (ValidationError,ObjectDoesNotExist), e:
165
- # except (ValidationError,ObjectDoesNotExist,IntegrityError), e:
166
150
  except Exception as e:
167
- if True:
168
- if not settings.SITE.loading_from_dump:
169
- # hand-written fixtures are expected to yield in savable
170
- # order
171
- logger.warning(
172
- "Failed to save %s from manual fixture:" % obj2str(obj)
173
- )
174
- raise
151
+ if not settings.SITE.loading_from_dump:
152
+ # hand-written fixtures are expected to yield in savable order
153
+ logger.warning(
154
+ "Failed to save %s from manual fixture:", obj2str(obj))
155
+ raise
175
156
  deps = [
176
- f.remote_field.model
177
- for f in obj._meta.fields
178
- if f.remote_field and f.remote_field.model
179
- ]
157
+ f for f in obj._meta.fields
158
+ if f.remote_field and f.remote_field.model]
180
159
  if not deps:
181
- logger.exception(e)
182
- raise Exception("Failed to save independent %s." % obj2str(obj))
160
+ # logger.exception(e)
161
+ msg = f"Failed to save independent {obj2str(obj)}."
162
+ raise Exception(msg) from e
163
+ self.on_restore_failure(obj)
183
164
  self.deserializer.register_failure(self, e)
184
- return False
185
- # except Exception,e:
186
- # logger.exception(e)
187
- # raise Exception("Failed to save %s. Abandoned." % obj2str(obj))
165
+
166
+ def on_restore_failure(self, obj):
167
+ # When a database row fails to save during restore.py, Lino looks
168
+ # whether it has nullable FK fields that aren't null, set these to None
169
+ # and try to save an intermediate row.
170
+
171
+ nullable = dict()
172
+ # required = dict()
173
+ for f in obj._meta.fields:
174
+ if f.remote_field and f.remote_field.model:
175
+ if f.null and (v := f.value_from_object(obj)) is not None:
176
+ nullable[f] = v
177
+ if not (nullable):
178
+ return
179
+ for f in nullable.keys():
180
+ setattr(obj, f.attname, None)
181
+ # f.set_value_in_object(obj, None)
182
+ try:
183
+ obj.full_clean()
184
+ obj.save()
185
+ except Exception:
186
+ pass
187
+ for f, v in nullable.items():
188
+ setattr(obj, f.attname, v)
189
+ # f.set_value_in_object(obj, v)
190
+ logger.info(
191
+ "Save intermediate %s without %s",
192
+ obj.__class__.__name__,
193
+ ", ".join([f"{f.name}={v}" for f, v in nullable.items()]))
188
194
 
189
195
 
190
196
  class Serializer(base.Serializer):
@@ -205,7 +211,7 @@ class Serializer(base.Serializer):
205
211
  raise NotImplementedError("Don't use dumpdata but `dump2py`")
206
212
 
207
213
 
208
- class FlushDeferredObjects(object):
214
+ class FlushDeferredObjects:
209
215
  """
210
216
  Indicator class object.
211
217
  Fixture may yield a `FlushDeferredObjects`
@@ -215,10 +221,11 @@ class FlushDeferredObjects(object):
215
221
  pass
216
222
 
217
223
 
218
- class LoaderBase(object):
224
+ class LoaderBase:
219
225
  quick = False
220
226
  source_version = None
221
- max_deferred_objects = 1000
227
+ max_deferred_objects = 4000
228
+ strict = False
222
229
 
223
230
  def __init__(self):
224
231
  # logger.info("20120225 DpyLoader.__init__()")
@@ -282,13 +289,13 @@ class LoaderBase(object):
282
289
  self.flush_deferred_objects()
283
290
  if count > self.max_deferred_objects + 1:
284
291
  raise Exception(
285
- "More than {} deferred objects".format(self.max_deferred_objects)
286
- )
292
+ f"More than {self.max_deferred_objects} deferred objects")
287
293
  l.append(obj)
288
294
  # report a full traceback, but only once per model and
289
295
  # exception type:
290
296
  k = (obj.object.__class__, e.__class__)
291
297
  if k not in self.reported_tracebacks:
298
+ # traceback.print_exc(e)
292
299
  logger.exception(e)
293
300
  self.reported_tracebacks.add(k)
294
301
 
@@ -333,14 +340,17 @@ class LoaderBase(object):
333
340
  ", ".join([str(o.object.pk) for o in objects]),
334
341
  )
335
342
  count += len(objects)
336
- msg = "Abandoning with {} unsaved instances:{}"
337
- logger.warning(msg.format(count, s))
338
-
339
- # Don't raise an exception. The unsaved instances got lost and
340
- # the loaddata should be done again, but meanwhile the database
341
- # is not necessarily invalid and may be used for further testing.
342
- # And anyway, loaddata would catch it and still continue.
343
- # raise Exception(msg)
343
+ msg = f"Abandoning with {count} unsaved instances:{s}"
344
+ if self.strict:
345
+ raise Exception(msg)
346
+ logger.warning(msg)
347
+
348
+ # Until 20250730: Don't raise an exception. The unsaved instances
349
+ # got lost and the loaddata should be done again, but meanwhile the
350
+ # database is not necessarily invalid and may be used for further
351
+ # testing. And anyway, loaddata would catch it and still continue.
352
+ # Since 20250730: But we need a way to make sure that the restore.py
353
+ # worked well.
344
354
 
345
355
  settings.SITE.loading_from_dump = False
346
356
  # reset to False because the same SITE might get reused by
@@ -350,11 +360,12 @@ class LoaderBase(object):
350
360
  class DpyLoader(LoaderBase):
351
361
  """Instantiated by :xfile:`restore.py`."""
352
362
 
353
- def __init__(self, globals_dict, quick=None):
363
+ def __init__(self, globals_dict, quick=None, strict=True):
354
364
  if quick is not None:
355
365
  self.quick = quick
356
366
  self.globals_dict = globals_dict
357
- super(DpyLoader, self).__init__()
367
+ self.strict = strict
368
+ super().__init__()
358
369
  self.source_version = globals_dict["SOURCE_VERSION"]
359
370
  site = globals_dict["settings"].SITE
360
371
  site.startup()
@@ -369,7 +380,7 @@ class DpyDeserializer(LoaderBase):
369
380
  """The Django deserializer for :ref:`dpy`.
370
381
 
371
382
  Note that this deserializer explicitly ignores fixtures whose
372
- source file is located in the current directory because i the case
383
+ source file is located in the current directory because in the case
373
384
  of `.py` files this can lead to side effects when importing them.
374
385
  See e.g. :ticket:`1029`. We consider it an odd behaviour of
375
386
  Django to search for fixtures also in the current directory (and
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: lino
3
- Version: 25.7.3
3
+ Version: 25.8.1
4
4
  Summary: A framework for writing desktop-like web applications using Django and ExtJS or React
5
5
  Project-URL: Homepage, https://www.lino-framework.org
6
6
  Project-URL: Repository, https://gitlab.com/lino-framework/lino
@@ -1,10 +1,10 @@
1
1
  lino/.cvsignore,sha256=1vrrWoP-WD8hPfCszHHIiJEi8KUMRCt5WvoKB9TSB1k,28
2
2
  lino/SciTEDirectory.properties,sha256=rCYi_e-6h8Yx5DwXhAa6MBPlVINcl6Vv9BQDYZV2_go,28
3
- lino/__init__.py,sha256=Z8-O-349Z6KOAuKypQdw30uEIz-6BXs2vR96MDswJSQ,6176
3
+ lino/__init__.py,sha256=SAUG9WJQxlJPY6VB5ga-HE3k5vefql6UsZTQk7kre7E,6176
4
4
  lino/ad.py,sha256=AQ-vJ4scac1mx3xegXezxnxyOQpV-a0q3VFMJSDbj2s,142
5
5
  lino/apps.py,sha256=ECq-dPARDkuhngwNrcipse3b4Irj70HxJs44uWEZFc4,27
6
6
  lino/hello.py,sha256=7-PJg7PnEiznyETqGjOwXcKh8rda0qLetpbS2gvRYy0,532
7
- lino/help_texts.py,sha256=vGF0OWysfFG0MYrTV9N1zqCQL-1T45aIS1iHR5Ypuxw,92484
7
+ lino/help_texts.py,sha256=RPw9EDHoQD7foqFTXNC8gWlnaWPqTZJQYgBTzNtDqIg,92385
8
8
  lino/api/__init__.py,sha256=WmzHU-rHdZ68se_nI0SmepQTGE8-cd9tPpovHRH9aag,512
9
9
  lino/api/ad.py,sha256=F6SrcKPRRalHKOZ7QLwsRWGq9hhykQIeo0b85cEk9NQ,314
10
10
  lino/api/dd.py,sha256=w-5qIpWdXu5yXyCC4Qh4ApB3yZTn0AwdxAjEhH8PyVE,7510
@@ -28,7 +28,7 @@ lino/config/unused/403.html,sha256=ePwDIUXhz1iD8xXWWyt5xEvpcGIHU4LMnXma8x0ik1c,2
28
28
  lino/config/unused/404.html,sha256=GOJrAyF6NcM69ETdSHgjff_-lvYs_-bOYhyZBem7x3I,220
29
29
  lino/config/unused/500.html,sha256=aWmP37uPoMS-PJgPuBloxdx0nEreU7AvkXxsex3yVYs,544
30
30
  lino/core/__init__.py,sha256=I4X69XK6Y1MZ8X6tC13Wmg13C3r5iTfYcFDiPJKpUdw,726
31
- lino/core/actions.py,sha256=opZ9_EyFaFxXk8prwzVnurFqmswFsKrf6ms38Rlc_wQ,32627
31
+ lino/core/actions.py,sha256=0ebutR3pIMXa7cNpoVJlyt6TueWrfJNNygvQL7XkOgQ,32659
32
32
  lino/core/actors.py,sha256=hbbzTpP7iBjQ2Eup4JhV5zx8w64arqPpkv4RnCptyKY,72585
33
33
  lino/core/atomizer.py,sha256=yK_l9-g8RZIjy2_iBB_efpyO1CrvegCBbjobD5mTTVc,13476
34
34
  lino/core/boundaction.py,sha256=06NNPjCesEr-R1YQKkiuy8JKzDrMJJ948x9jczOkZqY,7850
@@ -37,7 +37,7 @@ lino/core/choicelists.py,sha256=5Xu3M5ZVOis2JoNSuNiJGBHdkqCwLofUxSd19iLIlKs,3650
37
37
  lino/core/classproperty.py,sha256=_E95WPAs7BWbAuFpPvoYM2ZwW_mbq3rvF7o43WsMq_8,4316
38
38
  lino/core/constants.py,sha256=GwSyViDk3wClZzgbrCo6N-JYOa3LCP46FSof-iZ5RRU,5085
39
39
  lino/core/dashboard.py,sha256=kKUoZ1P6He0By3qUOkNIPAVF1zPkXxGFgHsCOdQ8syM,6672
40
- lino/core/dbtables.py,sha256=AqH7OGnXiup7avmFU-GQubXqVYW338tu43D5X9BCyFE,29127
40
+ lino/core/dbtables.py,sha256=b3D7gNaLpNvRgiFmOcfIwnW14Z4eCT0nWaPE8OjziOU,29258
41
41
  lino/core/dbutils.py,sha256=_QHcWd-ajLUwt5G8uOp8d47lZQKD3VseHnqKJke18rA,263
42
42
  lino/core/ddh.py,sha256=dYScxWKTOCDEgow7wJNJe812ESasmmITPK2ovraBQno,3172
43
43
  lino/core/diff.py,sha256=XQ-oQQDS_v3kXd4eRP9Hwr5UCgp-TPZIPVav9ZblUno,5882
@@ -60,7 +60,7 @@ lino/core/renderer.py,sha256=HhRC_gtrapNLw2Xl-cs67YdI_NdEdJ2ULsbvs5gb2wA,48252
60
60
  lino/core/requests.py,sha256=_kwmx8VAwdaPssIdsZt_GzCst67BTR7WwVv4z_KCsNo,96389
61
61
  lino/core/roles.py,sha256=PXwk436xUupxdbJcygRSYFu7ixfKjAJPQRUQ8sy0lB0,4425
62
62
  lino/core/signals.py,sha256=ORY2s3Krlh9n24XyHetfwbeUhCqzib6YSqWeFTTY7ps,979
63
- lino/core/site.py,sha256=HidiqVqXkTi-OKvNg8tvlR_FEKYEaowZ9h3ly0Zx-Fk,83583
63
+ lino/core/site.py,sha256=DiKF-yRzubEE7xpBntBh4LGlbIaOs6jNd2dT1uTh9dg,83401
64
64
  lino/core/store.py,sha256=bbWrbsV-hPS1FF3e2ABiQHQ_Q61tD2XSRBDSwoTW6jQ,46093
65
65
  lino/core/tables.py,sha256=sjmVu3A8gnamxwy16n76UJy2BXgiqeNeCEbI4oGd93Q,25431
66
66
  lino/core/urls.py,sha256=06QlmN1vpxjmb5snO3SPpP6lX1pMdE60bTiBiC77_vQ,2677
@@ -98,9 +98,10 @@ lino/locale/zh_Hant/LC_MESSAGES/django.po,sha256=t4Sjt5ygmJzPhmf5FqEgQmRKoFErB0R
98
98
  lino/management/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
99
99
  lino/management/commands/__init__.py,sha256=raVDRiXns3SegyXEhaLZMcxfEDs7ggy2nFUN5D0f5F0,47
100
100
  lino/management/commands/buildcache.py,sha256=MrOio6uBtQNW5gnbRrPIjarwUe18R-irxC7dLBBCpR4,1949
101
+ lino/management/commands/ddt.py,sha256=bn9DqZCztwJooa8u9lDBT96sB6o8on1aJWAUKafoQT8,2347
101
102
  lino/management/commands/demotest.py,sha256=NCUr3ttqU0oorOIc3Z6R_cNNgw5_OzHDbQf0HWlcB1g,4877
102
103
  lino/management/commands/diag.py,sha256=vt-NlZUx5gf7T4EpbM-gle3tAwMuwfPQY8lUgxjFaUw,478
103
- lino/management/commands/dump2py.py,sha256=X2u6OVkSb4MVQucF6jOKDDwRc8TCBNakW3UX_9S-n-U,20344
104
+ lino/management/commands/dump2py.py,sha256=V2weGrZnb2JzqNuJrmLWnkeVJFVHRbA-6f7b4f7cPBk,19895
104
105
  lino/management/commands/dump_settings.py,sha256=tGOR4h_ueVe2DOk84ILFvzvndt0doshvaxRkybB-rnY,2009
105
106
  lino/management/commands/initdb.py,sha256=j_cWTlugP2-UP_qnHpXmz_pzPGRK1oORymjxphhcdEg,11398
106
107
  lino/management/commands/install.py,sha256=k7lCJznVLS6swR4ayKNulj5TPpJ3qf99Fha87TZAaUk,1889
@@ -109,7 +110,7 @@ lino/management/commands/makescreenshots.py,sha256=fJF7ATZz7_s1IWkkYMEHVTLB5r5C1
109
110
  lino/management/commands/makeui.py,sha256=qYz68fnUKNXicZfGy3GXdjIZubhg1KyQnqjMblFN_LY,4813
110
111
  lino/management/commands/mergedata.py,sha256=-dPvBtyc-AqKpQeL4TUd2IKHGe8EaaW8Citcsp_hwEU,2527
111
112
  lino/management/commands/passwd.py,sha256=S-7qyUWOaBnqRo_quwWewJjzwonqK4PBP2j8fuBGy3c,3012
112
- lino/management/commands/prep.py,sha256=YPuUT8FLhK-R09N4FFckvYzKdPfou70ISC6l8IDhUEg,2150
113
+ lino/management/commands/prep.py,sha256=dsHu4owaAJB3kIlRLh-11WN3mMLR3I2EpEYOxBWJGfM,2156
113
114
  lino/management/commands/qtclient.py,sha256=ltZyz-SmIevotRmv26TmPiW8VTXKK37DUwdwwMUzrHI,4715
114
115
  lino/management/commands/resetsequences.py,sha256=v7MdzJCw-rBXljttCsgoMIi-cgubCPptXDlcNO0pCjw,978
115
116
  lino/management/commands/run.py,sha256=MiK53KIACYKDTKF6knJGwU-uzEApGnDxByi-3_nrTjQ,1115
@@ -173,10 +174,10 @@ lino/modlib/checkdata/management/commands/__init__.py,sha256=47DEQpj8HBSa-_TImW-
173
174
  lino/modlib/checkdata/management/commands/checkdata.py,sha256=z-mvg8R0G7-BiWcyeyeMUoaLEhi9n58lockOtPdmhCg,1962
174
175
  lino/modlib/comments/__init__.py,sha256=XoRLIB-pSsz4EfA2FhsbKQ27fsW_AAvS7SyA0_vhHv8,1678
175
176
  lino/modlib/comments/choicelists.py,sha256=SIA7P_KwtaayqOJxCkwyZouK0Z23-2v4ZFV9a0Zexnk,3314
176
- lino/modlib/comments/mixins.py,sha256=h1VvM4h-6rEixziz0r7vJCptM7T7K6i4r50Scz5riws,4413
177
- lino/modlib/comments/models.py,sha256=BSm9x_KSVacba7x4BfLKo2KVbBO4oy84SU0vWEPVOqA,15945
177
+ lino/modlib/comments/mixins.py,sha256=834JYMsK7qnnqSsQDNSkuHqLw8p4Cu2O7GW3qmVfU5I,4417
178
+ lino/modlib/comments/models.py,sha256=t_uQeElsbl8X-oaDxwGEIOQZhvvlbgJlImgD7nLIq2s,16090
178
179
  lino/modlib/comments/roles.py,sha256=z3gctvlTa_5PAs-D4pounyzNyuEc31jTFq9g33r6Z1w,751
179
- lino/modlib/comments/ui.py,sha256=OYeYHJZUvou3d21OcChVhCWtCd7ildGNxIgRoM37Gng,9955
180
+ lino/modlib/comments/ui.py,sha256=u46TU1Qn2jz3CN-pCmhV8D2HXezEhGGSTZjuTJ4GWME,9915
180
181
  lino/modlib/comments/config/comments/comments.js,sha256=7oAnNyx_MKM1iWPu-QSp6iKfnOVdgq7EciQPpxTvYU8,242
181
182
  lino/modlib/comments/fixtures/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
182
183
  lino/modlib/comments/fixtures/demo2.py,sha256=DSIqNtGOQlwbg8k43nE2ax1x-H_ifRsozvw6LZM330Y,25407
@@ -3541,7 +3542,7 @@ lino/modlib/linod/management/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NM
3541
3542
  lino/modlib/linod/management/commands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3542
3543
  lino/modlib/linod/management/commands/linod.py,sha256=SzJ6-yRpI1QFrXqiS8AO7_c2NmkMdV4JpFhfyGzs5eI,2587
3543
3544
  lino/modlib/memo/__init__.py,sha256=mM4xIODNczmmn43ympj_Un7sPyc9RuIr6zmGKYB5vkk,5381
3544
- lino/modlib/memo/mixins.py,sha256=gtWbmpjLEEBSB11zp30t1Kf3ukUsGXntaIjymj3Kq6k,10915
3545
+ lino/modlib/memo/mixins.py,sha256=cu5BkaYgNtFtJufqphlJrNJWO_wksZDqMdaQVBSrGAQ,10954
3545
3546
  lino/modlib/memo/models.py,sha256=zUEvWu0dK5QhkU3DeMqNqsrzSUzOl6DLZaJBNytfgrs,4388
3546
3547
  lino/modlib/memo/parser.py,sha256=h21I0EjXfV2oHvekp5LxqtfEGlHREa1eOFHU3yC3sOw,13029
3547
3548
  lino/modlib/memo/views.py,sha256=H3g_nuiMzguptXLSWaWLJClU7BefXPFn9Rh8UIVVBeg,555
@@ -3590,7 +3591,7 @@ lino/modlib/printing/config/report/Default.wk.html,sha256=4Ssx2LWm1gVpXf0Q4XoSY1
3590
3591
  lino/modlib/publisher/__init__.py,sha256=9w4cclyodBB3PO5rFzheIkzJs-tfA62PSGx2WI0NL5Q,2303
3591
3592
  lino/modlib/publisher/choicelists.py,sha256=gVzjyp1sJ-XewAW-I_bCrKdTLgygLzh1ZwFI1rKyPdo,9070
3592
3593
  lino/modlib/publisher/mixins.py,sha256=yRxAtFSNe9aVvdY-th_a5wmQ76jBfKYWzeNUn-efJMA,6651
3593
- lino/modlib/publisher/models.py,sha256=GKM31oWJ6etzughvpQKuGN1rvHSiKneB4t6dWRBvsrA,17264
3594
+ lino/modlib/publisher/models.py,sha256=BvGv6JxdrKoa1VJITOw5Dlbwj96lRdDkCGcRc3uoxG4,17584
3594
3595
  lino/modlib/publisher/renderer.py,sha256=Rl6fX8PzfX6crmnUh8mdU5Mpe44mSN5lTu_Pv8aVSCk,1845
3595
3596
  lino/modlib/publisher/ui.py,sha256=qWp3JWhO6zN_HSZvSlolmNTgiZgoJeY2_TIDh9nYf3Q,4491
3596
3597
  lino/modlib/publisher/views.py,sha256=l_GomdliB1qCylg7jKKkay3ZgAaOPfWNQQ6ZPDjAUl0,2214
@@ -4585,7 +4586,7 @@ lino/utils/ajax.py,sha256=SdMckREfEk6-0oaXZNdEUfi1pL-OwnwS5YzaGbXLjEs,3254
4585
4586
  lino/utils/choosers.py,sha256=V3Eo0VtGR0t708DeiSkGwSziVCHkipoe2i95d5wTVYc,18298
4586
4587
  lino/utils/code.py,sha256=-2vc8npHQ5jsrRqzxmjRZZ8FWY7QJ7-sRg7-ZhOsUEU,7053
4587
4588
  lino/utils/config.py,sha256=gtaJURPOwHyV_AW7i7_vin3KTj3UwZa4iviDbldwXGE,8568
4588
- lino/utils/cycler.py,sha256=hUKogflEMAwVz8niQxHrLQJN1loRHXsgJTwfbtm0YDQ,1713
4589
+ lino/utils/cycler.py,sha256=Kd4jUUaj181gZxSYWMiZTgF7iT3JXZm57pBh-ZxiOMg,1775
4589
4590
  lino/utils/daemoncommand.py,sha256=NjGShiz09fddIV0WU0jK2nzO_CwPj1MfdmgwAOYZi4M,11525
4590
4591
  lino/utils/dataserializer.py,sha256=-_xHXaGwDSO6-sYEHEa2BtEmKS8bW6gsYx4dV-GbvDs,3779
4591
4592
  lino/utils/dates.py,sha256=eWF5WxA5uJf51Y9PKvDVBWD8yIf6yBF6oO6TeU3ujzw,1030
@@ -4594,7 +4595,7 @@ lino/utils/dbhash.py,sha256=tG1IHe6Bz9MaagTI-131gpcLcNw3g642QVvv7GsJH2g,3303
4594
4595
  lino/utils/dblogger.py,sha256=kr0YxQY6veymvNg5A4tsvkqW8haRWdwqL0C-_9_QTg0,721
4595
4596
  lino/utils/diag.py,sha256=quiGcv-e0pwn_yjjt7OUMo8kr1qKYmIFBjDnHxr_5X4,18754
4596
4597
  lino/utils/djangotest.py,sha256=Phz1qNp0wDonZRja5dxbCk0Xl3a73gZNiKK8v9tAgZg,8334
4597
- lino/utils/dpy.py,sha256=8eL5SE6YfTFLvNjFJlFr2SpR_eubU0Rb3ckCbGgrsU8,20661
4598
+ lino/utils/dpy.py,sha256=pHeOmAQLqn4DLunipn5FwooTttRroqdewgUTUt_Dq5s,21220
4598
4599
  lino/utils/fieldutils.py,sha256=6GwPOfL-Jv-uh5-tZrTqC1hJccqHhdLbVSy4CAeegDA,2957
4599
4600
  lino/utils/format_date.py,sha256=zJu8PO45hGsk6Znq8_93D3vUz9HcY7CjHduAFxoU0v8,3123
4600
4601
  lino/utils/html.py,sha256=nR2h6oa_47Baq5rdSln0aGbqzS6SFsWzl-uqjnGIUWU,3273
@@ -4635,8 +4636,8 @@ lino/utils/xml.py,sha256=EGDnO1UaREst9fS7KTESdbHnrrVCwKbRQdvut6B6GmQ,1612
4635
4636
  lino/utils/mldbc/__init__.py,sha256=QqWRlzeXaOmFfbCk-vTY3SZMn1-FCf67XnpZdd_Nim0,1134
4636
4637
  lino/utils/mldbc/fields.py,sha256=tAX8G5UKigr9c6g0F3ARIjZZtg406mdaZ--PWSbiH9E,2873
4637
4638
  lino/utils/mldbc/mixins.py,sha256=CkYe5jDa7xp9fJq_V8zcZf8ocxgIjUgHc9KZccvA_Yw,1945
4638
- lino-25.7.3.dist-info/METADATA,sha256=V8-UPwVsw07yLV9_UI7YAxRmj6CntfLiivjBLfswdlo,42534
4639
- lino-25.7.3.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
4640
- lino-25.7.3.dist-info/licenses/AUTHORS.rst,sha256=8VEm_G4HOmYEa4oi1nVoKKsdo4JanekEJCefWd2E8vk,981
4641
- lino-25.7.3.dist-info/licenses/COPYING,sha256=DZak_2itbUtvHzD3E7GNUYSRK6jdOJ-GqncQ2weavLA,34523
4642
- lino-25.7.3.dist-info/RECORD,,
4639
+ lino-25.8.1.dist-info/METADATA,sha256=S4pvxNfayUCSdOc2fhkj8KdaPN-WvT5pK8PuwHU3Fd4,42534
4640
+ lino-25.8.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
4641
+ lino-25.8.1.dist-info/licenses/AUTHORS.rst,sha256=8VEm_G4HOmYEa4oi1nVoKKsdo4JanekEJCefWd2E8vk,981
4642
+ lino-25.8.1.dist-info/licenses/COPYING,sha256=DZak_2itbUtvHzD3E7GNUYSRK6jdOJ-GqncQ2weavLA,34523
4643
+ lino-25.8.1.dist-info/RECORD,,
File without changes