lino 25.4.3__py3-none-any.whl → 25.4.4__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 +1 -1
- lino/core/site.py +6 -23
- lino/modlib/checkdata/models.py +9 -1
- lino/modlib/linod/routing.py +49 -46
- lino/modlib/publisher/choicelists.py +41 -20
- lino/modlib/publisher/config/publisher/page.pub.html +24 -0
- lino/modlib/publisher/fixtures/demo2.py +12 -0
- lino/modlib/publisher/fixtures/std.py +2 -1
- lino/modlib/publisher/fixtures/synodalworld.py +17 -0
- lino/modlib/publisher/mixins.py +7 -0
- lino/modlib/publisher/models.py +22 -1
- lino/modlib/publisher/ui.py +2 -2
- lino/static/bootstrap.css +1 -1
- lino/utils/dbfreader.py +64 -32
- lino/utils/dbhash.py +3 -2
- {lino-25.4.3.dist-info → lino-25.4.4.dist-info}/METADATA +1 -1
- {lino-25.4.3.dist-info → lino-25.4.4.dist-info}/RECORD +20 -18
- {lino-25.4.3.dist-info → lino-25.4.4.dist-info}/WHEEL +0 -0
- {lino-25.4.3.dist-info → lino-25.4.4.dist-info}/licenses/AUTHORS.rst +0 -0
- {lino-25.4.3.dist-info → lino-25.4.4.dist-info}/licenses/COPYING +0 -0
lino/__init__.py
CHANGED
@@ -31,7 +31,7 @@ from django import VERSION
|
|
31
31
|
from django.apps import AppConfig
|
32
32
|
from django.conf import settings
|
33
33
|
import warnings
|
34
|
-
__version__ = '25.4.
|
34
|
+
__version__ = '25.4.4'
|
35
35
|
|
36
36
|
# import setuptools # avoid UserWarning "Distutils was imported before Setuptools"?
|
37
37
|
|
lino/core/site.py
CHANGED
@@ -559,21 +559,13 @@ class Site(object):
|
|
559
559
|
break
|
560
560
|
|
561
561
|
if self.master_site is None:
|
562
|
-
# cache_root = os.environ.get("LINO_CACHE_ROOT", None)
|
563
|
-
# if cache_root:
|
564
|
-
# # TODO: deprecate
|
565
|
-
# cr = Path(cache_root).absolute()
|
566
|
-
# if not cr.exists():
|
567
|
-
# msg = "LINO_CACHE_ROOT ({0}) does not exist!".format(cr)
|
568
|
-
# raise Exception(msg)
|
569
|
-
# self.site_dir = (cr / self.project_name).resolve()
|
570
|
-
# self.setup_cache_directory()
|
571
|
-
# else:
|
572
|
-
# self.site_dir = self.project_dir
|
573
562
|
self.site_dir = self.project_dir
|
574
|
-
|
575
|
-
|
576
|
-
|
563
|
+
self.django_settings.update(DATABASES={
|
564
|
+
"default": {
|
565
|
+
"ENGINE": "django.db.backends.sqlite3",
|
566
|
+
"NAME": str(self.site_dir / "default.db")
|
567
|
+
}
|
568
|
+
})
|
577
569
|
else:
|
578
570
|
self.site_dir = self.master_site.site_dir
|
579
571
|
self._history_aware_logging = self.master_site._history_aware_logging
|
@@ -721,15 +713,6 @@ class Site(object):
|
|
721
713
|
# import yaml
|
722
714
|
# print("20231019", yaml.dump(d))
|
723
715
|
|
724
|
-
def get_database_settings(self):
|
725
|
-
if self.site_dir is None:
|
726
|
-
pass # raise Exception("20160516 No site_dir")
|
727
|
-
else:
|
728
|
-
dbname = self.site_dir / "default.db"
|
729
|
-
return {
|
730
|
-
"default": {"ENGINE": "django.db.backends.sqlite3", "NAME": str(dbname)}
|
731
|
-
}
|
732
|
-
|
733
716
|
def get_anonymous_user(self):
|
734
717
|
# The code below works even when users is not installed
|
735
718
|
from lino.modlib.users.choicelists import UserTypes
|
lino/modlib/checkdata/models.py
CHANGED
@@ -21,6 +21,9 @@ from lino.api import dd, rt, _
|
|
21
21
|
from .choicelists import Checker, Checkers
|
22
22
|
from .roles import CheckdataUser
|
23
23
|
|
24
|
+
MAX_LENGTH = 250
|
25
|
+
MORE = " (...)"
|
26
|
+
|
24
27
|
|
25
28
|
class CheckerAction(dd.Action):
|
26
29
|
fix_them = False
|
@@ -150,7 +153,7 @@ class Message(Controllable, UserAuthored):
|
|
150
153
|
checker = Checkers.field(verbose_name=_("Checker"))
|
151
154
|
# severity = Severities.field()
|
152
155
|
# feedback = Feedbacks.field(blank=True)
|
153
|
-
message = models.CharField(_("Message text"), max_length=
|
156
|
+
message = models.CharField(_("Message text"), max_length=MAX_LENGTH)
|
154
157
|
# fixable = models.BooleanField(_("Fixable"), default=False)
|
155
158
|
|
156
159
|
update_problem = UpdateMessage()
|
@@ -166,6 +169,11 @@ class Message(Controllable, UserAuthored):
|
|
166
169
|
def __str__(self):
|
167
170
|
return self.message
|
168
171
|
|
172
|
+
def full_clean(self):
|
173
|
+
if len(self.message) > MAX_LENGTH:
|
174
|
+
self.message = self.message[:MAX_LENGTH - len(MORE)] + MORE
|
175
|
+
super().full_clean()
|
176
|
+
|
169
177
|
@classmethod
|
170
178
|
def get_simple_parameters(cls):
|
171
179
|
for p in super(Message, cls).get_simple_parameters():
|
lino/modlib/linod/routing.py
CHANGED
@@ -1,67 +1,70 @@
|
|
1
1
|
# -*- coding: UTF-8 -*-
|
2
|
-
# Copyright 2022 Rumma & Ko Ltd
|
2
|
+
# Copyright 2022-2025 Rumma & Ko Ltd
|
3
3
|
# License: GNU Affero General Public License v3 (see file COPYING for details)
|
4
4
|
|
5
|
-
from django.
|
5
|
+
from django.conf import settings
|
6
6
|
from django.core.asgi import get_asgi_application
|
7
|
-
from django.utils.functional import LazyObject
|
8
7
|
|
9
|
-
|
10
|
-
from channels.db import database_sync_to_async
|
11
|
-
from channels.sessions import SessionMiddlewareStack
|
12
|
-
from channels.routing import ProtocolTypeRouter, URLRouter, ChannelNameRouter
|
8
|
+
if settings.SITE.plugins.linod.use_channels:
|
13
9
|
|
14
|
-
from
|
15
|
-
from
|
10
|
+
from django.urls import re_path
|
11
|
+
from django.utils.functional import LazyObject
|
12
|
+
from channels.middleware import BaseMiddleware
|
13
|
+
from channels.db import database_sync_to_async
|
14
|
+
from channels.sessions import SessionMiddlewareStack
|
15
|
+
from channels.routing import ProtocolTypeRouter, URLRouter, ChannelNameRouter
|
16
16
|
|
17
|
-
from .
|
18
|
-
from .consumers import
|
17
|
+
from lino.core.auth import get_user
|
18
|
+
from lino.modlib.notify.consumers import ClientConsumer
|
19
19
|
|
20
|
+
from .utils import CHANNEL_NAME
|
21
|
+
from .consumers import LinodConsumer
|
20
22
|
|
21
|
-
class UserLazyObject(LazyObject):
|
22
|
-
|
23
|
-
|
24
|
-
|
23
|
+
class UserLazyObject(LazyObject):
|
24
|
+
"""
|
25
|
+
Throw a more useful error message when scope['user'] is accessed before it's resolved
|
26
|
+
"""
|
25
27
|
|
26
|
-
|
27
|
-
|
28
|
+
def _setup(self):
|
29
|
+
raise ValueError("Accessing scope user before it is ready.")
|
28
30
|
|
31
|
+
async def _get_user(scope):
|
32
|
+
class Wrapper:
|
33
|
+
def __init__(self, session):
|
34
|
+
self.session = session
|
29
35
|
|
30
|
-
|
31
|
-
|
32
|
-
def __init__(self, session):
|
33
|
-
self.session = session
|
36
|
+
r = Wrapper(scope["session"])
|
37
|
+
return await database_sync_to_async(get_user)(r)
|
34
38
|
|
35
|
-
|
36
|
-
|
39
|
+
class AuthMiddleware(BaseMiddleware):
|
40
|
+
def populate_scope(self, scope):
|
41
|
+
# Make sure we have a session
|
42
|
+
if "session" not in scope:
|
43
|
+
raise ValueError("AuthMiddleware cannot find session in scope.")
|
44
|
+
# Add it to the scope if it's not there already
|
45
|
+
if "user" not in scope:
|
46
|
+
scope["user"] = UserLazyObject()
|
37
47
|
|
48
|
+
async def resolve_scope(self, scope):
|
49
|
+
scope["user"]._wrapped = await _get_user(scope)
|
38
50
|
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
raise ValueError("AuthMiddleware cannot find session in scope.")
|
44
|
-
# Add it to the scope if it's not there already
|
45
|
-
if "user" not in scope:
|
46
|
-
scope["user"] = UserLazyObject()
|
51
|
+
async def __call__(self, scope, receive=None, send=None):
|
52
|
+
self.populate_scope(scope)
|
53
|
+
await self.resolve_scope(scope)
|
54
|
+
return await self.inner(scope, receive, send)
|
47
55
|
|
48
|
-
|
49
|
-
scope["user"]._wrapped = await _get_user(scope)
|
56
|
+
routes = [re_path(r"^WS/$", ClientConsumer.as_asgi())]
|
50
57
|
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
58
|
+
protocol_mapping = dict(
|
59
|
+
websocket=SessionMiddlewareStack(AuthMiddleware(URLRouter(routes))),
|
60
|
+
channel=ChannelNameRouter({CHANNEL_NAME: LinodConsumer.as_asgi()}),
|
61
|
+
http=get_asgi_application(),
|
62
|
+
)
|
55
63
|
|
64
|
+
application = ProtocolTypeRouter(protocol_mapping)
|
56
65
|
|
57
|
-
|
66
|
+
# raise Exception("20240424")
|
58
67
|
|
59
|
-
|
60
|
-
websocket=SessionMiddlewareStack(AuthMiddleware(URLRouter(routes))),
|
61
|
-
channel=ChannelNameRouter({CHANNEL_NAME: LinodConsumer.as_asgi()}),
|
62
|
-
http=get_asgi_application(),
|
63
|
-
)
|
68
|
+
else:
|
64
69
|
|
65
|
-
application =
|
66
|
-
|
67
|
-
# raise Exception("20240424")
|
70
|
+
application = get_asgi_application()
|
@@ -1,5 +1,5 @@
|
|
1
1
|
# -*- coding: UTF-8 -*-
|
2
|
-
# Copyright 2020-
|
2
|
+
# Copyright 2020-2025 Rumma & Ko Ltd
|
3
3
|
# License: GNU Affero General Public License v3 (see file COPYING for details)
|
4
4
|
|
5
5
|
import os
|
@@ -8,7 +8,7 @@ from copy import copy
|
|
8
8
|
from django.db import models
|
9
9
|
from django.conf import settings
|
10
10
|
from django.utils import translation
|
11
|
-
from django.utils.text import format_lazy
|
11
|
+
# from django.utils.text import format_lazy
|
12
12
|
|
13
13
|
from lino.api import dd, rt, _
|
14
14
|
from lino.utils.html import E, tostring, join_elems
|
@@ -181,19 +181,40 @@ class SpecialPage(dd.Choice):
|
|
181
181
|
# pointing_field_name = 'publisher.Page.special_page'
|
182
182
|
# show_values = True
|
183
183
|
|
184
|
-
def __init__(self,
|
185
|
-
self.
|
186
|
-
|
184
|
+
def __init__(self, name, text=None, parent=None, **kwargs):
|
185
|
+
self.parent_value = parent
|
186
|
+
self._default_values = dict()
|
187
|
+
for k in ("ref", "title", "filler", "body"):
|
187
188
|
if k in kwargs:
|
188
|
-
self.
|
189
|
-
super().__init__(
|
190
|
-
if
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
189
|
+
self._default_values[k] = kwargs.pop(k)
|
190
|
+
super().__init__(name, text, name, **kwargs)
|
191
|
+
# if (filler := self.default_values.get('filler', None)):
|
192
|
+
# if "title" not in self.default_values:
|
193
|
+
# self.default_values["title"] = filler.data_view.get_actor_label()
|
194
|
+
# else:
|
195
|
+
# if "title" not in self.default_values:
|
196
|
+
# self.default_values["title"] = self.text
|
197
|
+
|
198
|
+
def on_page_created(self, obj):
|
199
|
+
for k, v in self._default_values.items():
|
200
|
+
setattr(obj, k, v)
|
201
|
+
if obj.filler and not obj.title:
|
202
|
+
obj.title = obj.filler.data_view.get_actor_label()
|
203
|
+
if not obj.title:
|
204
|
+
obj.title = self.text or "20250422"
|
205
|
+
if self.parent_value:
|
206
|
+
psp = self.choicelist.get_by_value(self.parent_value)
|
207
|
+
obj.parent = psp.get_object()
|
208
|
+
|
209
|
+
# def get_object(self, ar):
|
210
|
+
def get_object(self):
|
211
|
+
language = translation.get_language()
|
212
|
+
# if len(settings.SITE.languages) == 1:
|
213
|
+
# language = translation.get_language()
|
214
|
+
# else:
|
215
|
+
# language = ar.request.LANGUAGE_CODE
|
216
|
+
# return rt.models.publisher.Page.objects.get(ref=self.defaul_values['ref'], language=language)
|
217
|
+
return rt.models.publisher.Page.objects.get(special_page=self, language=language)
|
197
218
|
|
198
219
|
|
199
220
|
class SpecialPages(dd.ChoiceList):
|
@@ -227,9 +248,9 @@ class SpecialPages(dd.ChoiceList):
|
|
227
248
|
|
228
249
|
add = SpecialPages.add_item
|
229
250
|
|
230
|
-
add("
|
231
|
-
add("
|
232
|
-
add("
|
233
|
-
add("
|
234
|
-
add("
|
235
|
-
add("
|
251
|
+
add("home", _("Home"), ref="index", body=_("Welcome to our great website."))
|
252
|
+
add("terms", _("Terms and conditions"))
|
253
|
+
add("privacy", _("Privacy policy"))
|
254
|
+
add("cookies", _("Cookie settings"))
|
255
|
+
add("copyright", _("Copyright"))
|
256
|
+
add("about", _("About us"))
|
@@ -32,6 +32,30 @@
|
|
32
32
|
</div>
|
33
33
|
{% endblock %}
|
34
34
|
|
35
|
+
{% block navbar %}
|
36
|
+
<nav class="navbar navbar-default" role="navigation">
|
37
|
+
<div class="container-fluid">
|
38
|
+
{% set index_node, home_children = obj.home_and_children(ar) %}
|
39
|
+
<div class="navbar-header">
|
40
|
+
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#bs-navbar-collapsible-content" aria-expanded="false">
|
41
|
+
<span class="sr-only">Toggle navigation</span>
|
42
|
+
<span class="icon-bar"></span>
|
43
|
+
<span class="icon-bar"></span>
|
44
|
+
<span class="icon-bar"></span>
|
45
|
+
</button>
|
46
|
+
<a class="navbar-brand" href="{{ar.get_home_url()}}">{{ index_node.title }}</a>
|
47
|
+
</div>
|
48
|
+
<div class="collapse navbar-collapse" id="bs-navbar-collapsible-content">
|
49
|
+
<ul class="nav navbar-nav">
|
50
|
+
{% for child in home_children %}
|
51
|
+
<li>{{tostring(ar.obj2html(child))}}</li>
|
52
|
+
{% endfor %}
|
53
|
+
</ul>
|
54
|
+
</div>
|
55
|
+
</div>
|
56
|
+
</nav>
|
57
|
+
{% endblock %}
|
58
|
+
|
35
59
|
{% block content %}
|
36
60
|
<div class="row-fluid">
|
37
61
|
{% for chunk in obj.as_page(ar) %}
|
@@ -0,0 +1,12 @@
|
|
1
|
+
from asgiref.sync import async_to_sync
|
2
|
+
from django.conf import settings
|
3
|
+
from lino.modlib.linod.choicelists import Procedures
|
4
|
+
from lino.api import rt
|
5
|
+
|
6
|
+
|
7
|
+
def objects():
|
8
|
+
yield None
|
9
|
+
ar = rt.login("robin")
|
10
|
+
if True: # settings.SITE.use_linod:
|
11
|
+
Procedures.update_publisher_pages.run(ar)
|
12
|
+
# async_to_sync(Procedures.update_publisher_pages.run)(ar)
|
@@ -18,10 +18,11 @@ def objects():
|
|
18
18
|
with translation.override(lng.django_code):
|
19
19
|
kwargs = dict(language=lng.django_code, special_page=sp)
|
20
20
|
kwargs.update(publishing_state="published")
|
21
|
-
kwargs.update(sp.default_values)
|
21
|
+
# kwargs.update(sp.default_values)
|
22
22
|
if lng.suffix:
|
23
23
|
kwargs.update(translated_from=translated_from)
|
24
24
|
obj = Page(**kwargs)
|
25
|
+
sp.on_page_created(obj)
|
25
26
|
yield obj
|
26
27
|
if not lng.suffix:
|
27
28
|
translated_from = obj
|
@@ -0,0 +1,17 @@
|
|
1
|
+
from lino.api import rt, _
|
2
|
+
|
3
|
+
home_children = [
|
4
|
+
(_("Mission"), None, []),
|
5
|
+
(_("Maxim"), None, []),
|
6
|
+
(_("Propaganda"), None, []),
|
7
|
+
(_("About us"), None, [
|
8
|
+
(_("Team"), None, []),
|
9
|
+
(_("History"), None, []),
|
10
|
+
(_("Contact"), None, []),
|
11
|
+
(_("Terms & conditions"), None, []),
|
12
|
+
]),
|
13
|
+
]
|
14
|
+
|
15
|
+
|
16
|
+
def objects():
|
17
|
+
return rt.models.publisher.make_demo_pages(home_children)
|
lino/modlib/publisher/mixins.py
CHANGED
@@ -7,6 +7,7 @@ from lino.utils.html import tostring
|
|
7
7
|
from lino.api import dd, rt, _
|
8
8
|
|
9
9
|
from django import http
|
10
|
+
from django.db import models
|
10
11
|
from django.conf import settings
|
11
12
|
from django.utils.translation import get_language
|
12
13
|
from lino.core.renderer import add_user_language
|
@@ -124,6 +125,11 @@ class Publishable(Printable):
|
|
124
125
|
# # context = dict(obj=self, request=request, language=get_language())
|
125
126
|
# return template.render(**context)
|
126
127
|
|
128
|
+
def home_and_children(self, ar):
|
129
|
+
home = rt.models.publisher.SpecialPages.home.get_object()
|
130
|
+
return home, rt.models.publisher.Page.objects.filter(parent=home)
|
131
|
+
# return dv.model.objects.filter(models.Q(parent=index_node) | models.Q(ref='index'), language=language)
|
132
|
+
|
127
133
|
def get_publisher_response(self, ar):
|
128
134
|
if not self.is_public():
|
129
135
|
return http.HttpResponseNotFound(
|
@@ -147,6 +153,7 @@ class PublishableContent(Publishable):
|
|
147
153
|
language = dd.LanguageField()
|
148
154
|
publishing_state = PublishingStates.field(default="draft")
|
149
155
|
filler = PageFillers.field(blank=True, null=True)
|
156
|
+
main_image = dd.ForeignKey('uploads.Upload', blank=True, null=True, verbose_name=_("Main image"))
|
150
157
|
|
151
158
|
def get_print_language(self):
|
152
159
|
return self.language
|
lino/modlib/publisher/models.py
CHANGED
@@ -108,6 +108,17 @@ class Page(
|
|
108
108
|
# def as_story_item(self, ar, **kwargs):
|
109
109
|
# return "".join(self.as_page(ar, **kwargs))
|
110
110
|
|
111
|
+
def as_paragraph(self, ar):
|
112
|
+
title = E.b(escape(self.title))
|
113
|
+
url = ar.obj2url(self)
|
114
|
+
if url is not None:
|
115
|
+
title = E.a(title, href=url, style="text-decoration: none; color: black;")
|
116
|
+
body = self.get_body_parsed(ar, short=True)
|
117
|
+
if body:
|
118
|
+
body = " - " + body
|
119
|
+
item = E.li(title, body)
|
120
|
+
return tostring(item)
|
121
|
+
|
111
122
|
def toc_html(self, ar, max_depth=1):
|
112
123
|
def li(obj):
|
113
124
|
# return "<li>{}</li>".format(obj.memo2html(ar, str(obj)))
|
@@ -134,7 +145,7 @@ class Page(
|
|
134
145
|
else:
|
135
146
|
title = "<b>{}</b> — ".format(escape(self.title))
|
136
147
|
title += self.get_body_parsed(ar, short=True)
|
137
|
-
title = "<li>{}</
|
148
|
+
title = "<li>{}</li>".format(title)
|
138
149
|
# edit_url = ar.renderer.obj2url(ar, self)
|
139
150
|
# url = self.publisher_url(ar)
|
140
151
|
# print("20231029", ar.renderer)
|
@@ -149,6 +160,16 @@ class Page(
|
|
149
160
|
|
150
161
|
# if not self.is_public():
|
151
162
|
# return
|
163
|
+
if hlevel == 1 and self.main_image:
|
164
|
+
yield f"""
|
165
|
+
<div class="row">
|
166
|
+
<div class="center-block">
|
167
|
+
<a href="#" class="thumbnail">
|
168
|
+
<img src="{self.main_image.get_media_file().get_image_url()}">
|
169
|
+
</a>
|
170
|
+
</div>
|
171
|
+
</div>
|
172
|
+
"""
|
152
173
|
|
153
174
|
if display_mode in ("detail",):
|
154
175
|
info = self.get_node_info(ar)
|
lino/modlib/publisher/ui.py
CHANGED
@@ -131,7 +131,7 @@ class PageDetail(dd.DetailLayout):
|
|
131
131
|
right_panel = """
|
132
132
|
ref language
|
133
133
|
parent seqno
|
134
|
-
child_node_depth
|
134
|
+
child_node_depth main_image
|
135
135
|
#page_type filler
|
136
136
|
publishing_state special_page
|
137
137
|
publisher.TranslationsByPage
|
@@ -147,7 +147,7 @@ class Pages(dd.Table):
|
|
147
147
|
ref
|
148
148
|
#page_type filler
|
149
149
|
"""
|
150
|
-
default_display_modes = {None: constants.
|
150
|
+
default_display_modes = {None: constants.DISPLAY_MODE_LIST}
|
151
151
|
|
152
152
|
|
153
153
|
class PagesByParent(Pages):
|
lino/static/bootstrap.css
CHANGED
lino/utils/dbfreader.py
CHANGED
@@ -9,9 +9,10 @@ DBF and DBT files when both settings :attr:`use_dbfread
|
|
9
9
|
<lino_xl.lib.tim2lino.Plugin.use_dbf_py>` are `False` (which is default).
|
10
10
|
|
11
11
|
Based on original work by Lars Garshol
|
12
|
-
|
12
|
+
https://www.garshol.priv.no/download/software/python/dbfreader.py
|
13
13
|
|
14
|
-
Modified by Luc Saffre to add support for Clipper dialect.
|
14
|
+
Modified by Luc Saffre to add support for Clipper dialect. And to work under
|
15
|
+
Python 3. And to support char fields longer than 255 characters.
|
15
16
|
|
16
17
|
Sources of information:
|
17
18
|
|
@@ -20,6 +21,7 @@ Sources of information:
|
|
20
21
|
|
21
22
|
`Xbase & dBASE File Format Description by Erik Bachmann
|
22
23
|
<https://www.clicketyclick.dk/databases/xbase/format/>`__
|
24
|
+
|
23
25
|
"""
|
24
26
|
|
25
27
|
import datetime
|
@@ -37,16 +39,11 @@ codepages = {
|
|
37
39
|
|
38
40
|
|
39
41
|
def unpack_long(number):
|
40
|
-
|
41
|
-
return number[0] + 256 * (
|
42
|
-
number[1] + 256 * (number[2] + 256 * number[3])
|
43
|
-
)
|
42
|
+
return number[0] + 256 * (number[1] + 256 * (number[2] + 256 * number[3]))
|
44
43
|
|
45
44
|
|
46
45
|
def unpack_long_rev(number):
|
47
|
-
return number[3] + 256 * (
|
48
|
-
number[2] + 256 * (number[1] + 256 * number[0])
|
49
|
-
)
|
46
|
+
return number[3] + 256 * (number[2] + 256 * (number[1] + 256 * number[0]))
|
50
47
|
|
51
48
|
|
52
49
|
def unpack_int(number):
|
@@ -64,6 +61,7 @@ def hex_analyze(number):
|
|
64
61
|
|
65
62
|
# --- A class for the entire file
|
66
63
|
|
64
|
+
|
67
65
|
class DBFFile(object):
|
68
66
|
"Represents a single DBF file."
|
69
67
|
|
@@ -88,7 +86,10 @@ class DBFFile(object):
|
|
88
86
|
day = header[3]
|
89
87
|
self.lastUpdate = datetime.date(year, month, day)
|
90
88
|
# print(f"20250123 Loading {filename} (last updated {self.lastUpdate})")
|
89
|
+
|
90
|
+
# Number of records in data file:
|
91
91
|
self.rec_num = unpack_long(header[4:8])
|
92
|
+
# length of header structure. Stored as binary (little endian), unsigned:
|
92
93
|
self.first_rec = unpack_int(header[8:10])
|
93
94
|
self.rec_len = unpack_int(header[10:12])
|
94
95
|
self.codepage = codepage # s[header[29]]
|
@@ -99,13 +100,21 @@ class DBFFile(object):
|
|
99
100
|
while 1:
|
100
101
|
ch = infile.read(1)
|
101
102
|
# if ch == 0x0D:
|
102
|
-
if ch == b'\r':
|
103
|
+
# if ch == b'\r':
|
104
|
+
if ch == b'\x0D':
|
103
105
|
break
|
104
106
|
field = DBFField(ch + infile.read(31), self)
|
105
107
|
self.fields[field.name] = field
|
106
108
|
self.field_list.append(field)
|
107
109
|
# if len(self.field_list) > 20:
|
108
110
|
# sys.exit(1)
|
111
|
+
# print(f"20250423 {self.field_list}")
|
112
|
+
# n = 0
|
113
|
+
# for fld in self.field_list:
|
114
|
+
# print(f"20250423 {fld.name} {fld.field_type} {
|
115
|
+
# fld.field_len} {fld.field_places}")
|
116
|
+
# n += fld.get_len()
|
117
|
+
# print(f"20250423 {n}")
|
109
118
|
|
110
119
|
infile.close()
|
111
120
|
if self.has_memo():
|
@@ -155,8 +164,11 @@ class DBFFile(object):
|
|
155
164
|
def get_next_record(self):
|
156
165
|
values = {}
|
157
166
|
ch = self.infile.read(1)
|
167
|
+
if len(ch) == 0:
|
168
|
+
raise Exception("Unexpected end of file")
|
158
169
|
self.recno += 1
|
159
|
-
if ch == 0x1A or len(ch) == 0:
|
170
|
+
# if ch == 0x1A or len(ch) == 0:
|
171
|
+
if ch == b'\x1A':
|
160
172
|
return None
|
161
173
|
if ch == b"*":
|
162
174
|
deleted = True
|
@@ -170,6 +182,7 @@ class DBFFile(object):
|
|
170
182
|
values[field.get_name()] = field.interpret(data)
|
171
183
|
if deleted and not self.deleted:
|
172
184
|
return self.get_next_record()
|
185
|
+
# print(f"20250423 found {values}")
|
173
186
|
return DBFRecord(self, values, deleted)
|
174
187
|
|
175
188
|
def close(self):
|
@@ -255,11 +268,25 @@ class DBFField(object):
|
|
255
268
|
self.name = buf[:pos].decode()
|
256
269
|
# print(f"20250123 field {self.name}")
|
257
270
|
self.field_type = chr(buf[11])
|
258
|
-
self.field_pos = unpack_long(buf[12:16])
|
271
|
+
# self.field_pos = unpack_long(buf[12:16])
|
272
|
+
# self.field_pos_raw = buf[12:16]
|
259
273
|
self.field_len = buf[16]
|
260
|
-
self.
|
274
|
+
if self.field_type == "C":
|
275
|
+
self.field_places = 0
|
276
|
+
self.field_len += 256 * buf[17]
|
277
|
+
# https://www.clicketyclick.dk/databases/xbase/format/data_types.html
|
278
|
+
# Character fields can be up to 32 KB long (in Clipper and FoxPro)
|
279
|
+
# using decimal count (field_places) as high byte in field length.
|
280
|
+
# It's possible to use up to 64KB long fields by reading length as
|
281
|
+
# unsigned.
|
282
|
+
else:
|
283
|
+
self.field_places = buf[17]
|
261
284
|
self.dbf = dbf
|
262
285
|
|
286
|
+
def __repr__(self):
|
287
|
+
return (f"DBFField({self.name}, {self.field_type}, "
|
288
|
+
f"{self.field_len}, {self.field_places})")
|
289
|
+
|
263
290
|
# if self.field_type=="M" or self.field_type=="P" or \
|
264
291
|
# self.field_type=="G" :
|
265
292
|
# self.blockfile=blockfile
|
@@ -267,8 +294,8 @@ class DBFField(object):
|
|
267
294
|
def get_name(self):
|
268
295
|
return self.name
|
269
296
|
|
270
|
-
def get_pos(self):
|
271
|
-
|
297
|
+
# def get_pos(self):
|
298
|
+
# return self.field_pos
|
272
299
|
|
273
300
|
def get_type(self):
|
274
301
|
return self.field_type
|
@@ -283,7 +310,7 @@ class DBFField(object):
|
|
283
310
|
# raise Exception("20250123 good")
|
284
311
|
# print(f"20250123 interpret {repr(self.field_type)}")
|
285
312
|
if self.field_type == "C":
|
286
|
-
if
|
313
|
+
if self.dbf.codepage is not None:
|
287
314
|
data = data.decode(self.dbf.codepage)
|
288
315
|
data = data.strip()
|
289
316
|
# ~ if len(data) == 0: return None
|
@@ -301,6 +328,8 @@ class DBFField(object):
|
|
301
328
|
# ~ return None
|
302
329
|
return ""
|
303
330
|
raise Exception("bad memo block number %s" % repr(data))
|
331
|
+
# print("20250422 bad memo block number %s" % repr(data))
|
332
|
+
# return ""
|
304
333
|
return self.dbf.blockfile.get_block(num)
|
305
334
|
|
306
335
|
elif self.field_type == "N":
|
@@ -316,7 +345,10 @@ class DBFField(object):
|
|
316
345
|
try:
|
317
346
|
return dateparser.parse(data)
|
318
347
|
except ValueError as e:
|
319
|
-
raise ValueError("Invalid date value %r (%s)" %
|
348
|
+
raise ValueError("Invalid date value %r (%s) in field %s" %
|
349
|
+
(data, e, self.name))
|
350
|
+
# print("20250422 Invalid date value %r (%s) in field %s" %
|
351
|
+
# (data, e, self.name))
|
320
352
|
# ~ return data # string "YYYYMMDD", use the time module or mxDateTime
|
321
353
|
else:
|
322
354
|
raise NotImplementedError("Unknown data type " + self.field_type)
|
@@ -401,21 +433,21 @@ class DBTFile(object):
|
|
401
433
|
# primary key
|
402
434
|
|
403
435
|
|
404
|
-
class DBFHash(object):
|
405
|
-
|
406
|
-
|
407
|
-
|
408
|
-
|
409
|
-
|
410
|
-
|
411
|
-
|
412
|
-
|
413
|
-
|
414
|
-
|
415
|
-
|
416
|
-
|
417
|
-
|
418
|
-
|
436
|
+
# class DBFHash(object):
|
437
|
+
# def __init__(self, file, key):
|
438
|
+
# self.file = DBFFile(file)
|
439
|
+
# self.hash = {}
|
440
|
+
# self.key = key
|
441
|
+
#
|
442
|
+
# self.file.open()
|
443
|
+
# while 1:
|
444
|
+
# rec = self.file.get_next_record()
|
445
|
+
# if rec is None:
|
446
|
+
# break
|
447
|
+
# self.hash[rec[self.key]] = rec
|
448
|
+
#
|
449
|
+
# def __getitem__(self, key):
|
450
|
+
# return self.hash[key]
|
419
451
|
|
420
452
|
|
421
453
|
# --- Utility functions
|
lino/utils/dbhash.py
CHANGED
@@ -14,8 +14,9 @@ from django.conf import settings
|
|
14
14
|
from django.apps import apps
|
15
15
|
from django.db.models.deletion import ProtectedError
|
16
16
|
|
17
|
-
mod = import_module(settings.SETTINGS_MODULE)
|
18
|
-
HASH_FILE = Path(mod.__file__).parent / "dbhash.json"
|
17
|
+
# mod = import_module(settings.SETTINGS_MODULE)
|
18
|
+
# HASH_FILE = Path(mod.__file__).parent / "dbhash.json"
|
19
|
+
HASH_FILE = settings.SITE.site_dir / "dbhash.json"
|
19
20
|
|
20
21
|
|
21
22
|
def fmn(m):
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: lino
|
3
|
-
Version: 25.4.
|
3
|
+
Version: 25.4.4
|
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,6 +1,6 @@
|
|
1
1
|
lino/.cvsignore,sha256=1vrrWoP-WD8hPfCszHHIiJEi8KUMRCt5WvoKB9TSB1k,28
|
2
2
|
lino/SciTEDirectory.properties,sha256=rCYi_e-6h8Yx5DwXhAa6MBPlVINcl6Vv9BQDYZV2_go,28
|
3
|
-
lino/__init__.py,sha256=
|
3
|
+
lino/__init__.py,sha256=UvUTsw4pGmG-8l3eqcbQdedQ1jGLYfZdqOF3no5VCZI,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
|
@@ -59,7 +59,7 @@ lino/core/renderer.py,sha256=C00J0L41hLv9b2sAzqSSrT0Lz5Fc78JnwHiq9LqF81c,47310
|
|
59
59
|
lino/core/requests.py,sha256=xIq6kXhjLoYbSkW40_BH-uj0WvmfkvOVLGm2piiGtJo,95835
|
60
60
|
lino/core/roles.py,sha256=PXwk436xUupxdbJcygRSYFu7ixfKjAJPQRUQ8sy0lB0,4425
|
61
61
|
lino/core/signals.py,sha256=0JT89mkjSbRm57QZcSI9DoThoKUGkyi-egNhuLUKEds,948
|
62
|
-
lino/core/site.py,sha256=
|
62
|
+
lino/core/site.py,sha256=WRbYscZ7H8OnYMX0ptxjzNs9R6RvyBUy80f3kETo5Jw,82697
|
63
63
|
lino/core/store.py,sha256=6pd4J5Y-U7Muz4mKFSL6K9KEZpJUbpM-I7RQTWyCo-8,51483
|
64
64
|
lino/core/tables.py,sha256=Yoj-H2ASW9xIger8kuW8xst5A_tEmtWpV17L11Ebca0,24348
|
65
65
|
lino/core/urls.py,sha256=06QlmN1vpxjmb5snO3SPpP6lX1pMdE60bTiBiC77_vQ,2677
|
@@ -163,7 +163,7 @@ lino/modlib/changes/models.py,sha256=wflXkqKSC-0ZynPGEd6PR4agP6zMuYO6v6v2NcRJgoo
|
|
163
163
|
lino/modlib/changes/utils.py,sha256=4jz8QXgaBxfmCBUvDeZeh7lkdwfTq7OBXBiFhMmANqA,2550
|
164
164
|
lino/modlib/checkdata/__init__.py,sha256=raUCoYi4WZLKVLG3GqH0ml1eH_YJXqY-EgXsKUe6iRY,2829
|
165
165
|
lino/modlib/checkdata/choicelists.py,sha256=JlBK8VeJK6yAFiqeU1Zx7m2wpDtTpZ1o6YPjnig6g70,5260
|
166
|
-
lino/modlib/checkdata/models.py,sha256=
|
166
|
+
lino/modlib/checkdata/models.py,sha256=csN69hFVkQQcSwxPmY02ofXsWZvA7z2T-N1Pg19t3Wk,13758
|
167
167
|
lino/modlib/checkdata/roles.py,sha256=xjQ882-BxYUoHbv2pzebs7MWl6snA2fKivFDTRzW7sQ,295
|
168
168
|
lino/modlib/checkdata/fixtures/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
169
169
|
lino/modlib/checkdata/fixtures/checkdata.py,sha256=TY9yIBwB5nEw0e6RTXqRwYdiCaLkkYTwWmFGcVBw2ow,332
|
@@ -3536,7 +3536,7 @@ lino/modlib/linod/choicelists.py,sha256=Cu82s1QpcGFmKUXJsg-7TSqpaESBCZKOEfxzFlJP
|
|
3536
3536
|
lino/modlib/linod/consumers.py,sha256=XBjA1fflJ-e9yWRMKXyQAhrOklYzs5JRhEeGMOKWFqM,6730
|
3537
3537
|
lino/modlib/linod/mixins.py,sha256=gZci22_m3GNUNycLSvMelG1sUQLCCho2gniPTegopMs,13027
|
3538
3538
|
lino/modlib/linod/models.py,sha256=yqGytDq7KxkHBCZpwF-7GQdR3nGh6-rBB-gLkvqoouQ,2361
|
3539
|
-
lino/modlib/linod/routing.py,sha256=
|
3539
|
+
lino/modlib/linod/routing.py,sha256=3pYJT6FbaVMj6rKkDVv3E5VTFFc9nglIDeo6dgAKu8Q,2399
|
3540
3540
|
lino/modlib/linod/utils.py,sha256=dE973Xib6Be1DvNsZ0M5wzY_jpkk35R21WKs-jQPorM,339
|
3541
3541
|
lino/modlib/linod/fixtures/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
3542
3542
|
lino/modlib/linod/fixtures/linod.py,sha256=qCFU2IktRVEdt1lChwu6kZLLnd4Ppfq_x9_2qGYelTY,1173
|
@@ -3588,19 +3588,21 @@ lino/modlib/printing/utils.py,sha256=LUO9769wJvHCPZIqeVQ9XAS6UKJ6BfJbiwO8Dt1kHc4
|
|
3588
3588
|
lino/modlib/printing/config/report/Default.odt,sha256=4X8UD9H_5Th2CELP0C6DTe4g0ZNUPXAg1C00xP3Qluc,10930
|
3589
3589
|
lino/modlib/printing/config/report/Default.wk.html,sha256=4Ssx2LWm1gVpXf0Q4XoSY15WkhqRvx51Upab-IS1nm0,96
|
3590
3590
|
lino/modlib/publisher/__init__.py,sha256=bGQduKmgzr_lU2N6H4q4I5ZmW-3iwdPuT2D8dpiVOiI,2644
|
3591
|
-
lino/modlib/publisher/choicelists.py,sha256=
|
3592
|
-
lino/modlib/publisher/mixins.py,sha256=
|
3593
|
-
lino/modlib/publisher/models.py,sha256=
|
3591
|
+
lino/modlib/publisher/choicelists.py,sha256=gVzjyp1sJ-XewAW-I_bCrKdTLgygLzh1ZwFI1rKyPdo,9070
|
3592
|
+
lino/modlib/publisher/mixins.py,sha256=yRxAtFSNe9aVvdY-th_a5wmQ76jBfKYWzeNUn-efJMA,6651
|
3593
|
+
lino/modlib/publisher/models.py,sha256=3g_tPavQ6ZFQsHYkzbBe_LXPeq0TUgsU1JrR6bxuRT0,17149
|
3594
3594
|
lino/modlib/publisher/renderer.py,sha256=9RoyVMQcbwh_GcbDJO8BWhCKe1kpbDc9M45hrMqzpNs,1922
|
3595
|
-
lino/modlib/publisher/ui.py,sha256=
|
3595
|
+
lino/modlib/publisher/ui.py,sha256=qWp3JWhO6zN_HSZvSlolmNTgiZgoJeY2_TIDh9nYf3Q,4491
|
3596
3596
|
lino/modlib/publisher/views.py,sha256=_0ktDagke4TOWVekJOnaRJBrbKqn27Yfz3SC2UnHKTM,2423
|
3597
3597
|
lino/modlib/publisher/config/publisher/base.html,sha256=nrWuN4lhP1O4ORdYTaY3fS2pc90yfUnfDYfm2Jtr7hQ,198
|
3598
3598
|
lino/modlib/publisher/config/publisher/default_list.pub.html,sha256=3ePc4U3xZxE-pqtzlnB9CRQWu8OQrtfXKEjyuc8BBUQ,578
|
3599
3599
|
lino/modlib/publisher/config/publisher/item.pub.html,sha256=973RYgCAYG1SYIB4bwfVZFVTUo0WD2I98PsoPgjQ8XA,154
|
3600
3600
|
lino/modlib/publisher/config/publisher/login.html,sha256=_j_MmwVv_UohMnEDADo99XQmUqGvOWTlCDPW1oaSWpo,495
|
3601
|
-
lino/modlib/publisher/config/publisher/page.pub.html,sha256=
|
3601
|
+
lino/modlib/publisher/config/publisher/page.pub.html,sha256=jnWP-uhhJZ0fjNHJ1Fo-0AIsiOFopzesOdyVtuSuM_w,2264
|
3602
3602
|
lino/modlib/publisher/fixtures/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
3603
|
-
lino/modlib/publisher/fixtures/
|
3603
|
+
lino/modlib/publisher/fixtures/demo2.py,sha256=KrF672tNLlhMX6EkxKDfcvvE9pGH4k9NgF7Rk5DkiLg,366
|
3604
|
+
lino/modlib/publisher/fixtures/std.py,sha256=7xpMOr4kd-4SX8uhwxagktUlcXAr6fdZfD-H519CR6g,987
|
3605
|
+
lino/modlib/publisher/fixtures/synodalworld.py,sha256=aoHrQOu7qPWsPWbvEOfoVMdWQPFYBM85EUmJgcbS0tI,398
|
3604
3606
|
lino/modlib/restful/__init__.py,sha256=9CqWTQkJ2KpLW5MI1bKqKZU_7nXxodxl7HSgc8Agwmw,1506
|
3605
3607
|
lino/modlib/restful/fields.py,sha256=8dik0q2LaCtJhQX1lu-fe-y_38xdhAWy1ZX3EKmJWfs,567
|
3606
3608
|
lino/modlib/restful/serializers.py,sha256=AVyA19UkRUJ9AjlUN0fJyrj1LgfOyp-a2ANcVkecWtA,1143
|
@@ -4491,7 +4493,7 @@ lino/sphinxcontrib/logo/templates/links.html,sha256=SccaqiI_Fg4lYT_nxDWfGZXLLD8F
|
|
4491
4493
|
lino/sphinxcontrib/logo/templates/part-of-synodalsoft.html,sha256=tHh0SfRoIqpoxkA5ZA5_Obc_9Bs5Uh4d3qUSC3oprdw,1427
|
4492
4494
|
lino/sphinxcontrib/logo/templates/unused_copyright.html,sha256=ct3QmWrUk0IS6hvr_XH5fMbseTH7CTzKhRH1UOUTWG8,205
|
4493
4495
|
lino/static/blueprint.css,sha256=meK9oC38foNI4x_YuylIU9TJdGiZi4HM4PFAWpjoiiw,131
|
4494
|
-
lino/static/bootstrap.css,sha256
|
4496
|
+
lino/static/bootstrap.css,sha256=_fWS3mQj4XzXZVo5KGhZe31uHWjJqXHIck5gRv7AZLY,1160
|
4495
4497
|
lino/static/contacts.Person.jpg,sha256=rNjIfwxfjh0kgrQBeNWyJvBdr5j2y6fo9_VwHU9JNAE,31796
|
4496
4498
|
lino/static/favicon1.ico,sha256=0dr7FW9j_qWDl_z5kKtNhWELExVUblctxH0bfaVoNo0,2238
|
4497
4499
|
lino/static/favicon2.ico,sha256=gtVZpKmRoZqWCgP6GgJkviX1FwPocx8K1P5fTGFOPo0,3906
|
@@ -4587,8 +4589,8 @@ lino/utils/cycler.py,sha256=2LGMhMJX5At5ln4yZvmNleWROs-CA_YE_UCzxzvxK4U,1548
|
|
4587
4589
|
lino/utils/daemoncommand.py,sha256=NjGShiz09fddIV0WU0jK2nzO_CwPj1MfdmgwAOYZi4M,11525
|
4588
4590
|
lino/utils/dataserializer.py,sha256=-_xHXaGwDSO6-sYEHEa2BtEmKS8bW6gsYx4dV-GbvDs,3779
|
4589
4591
|
lino/utils/dates.py,sha256=eWF5WxA5uJf51Y9PKvDVBWD8yIf6yBF6oO6TeU3ujzw,1030
|
4590
|
-
lino/utils/dbfreader.py,sha256=
|
4591
|
-
lino/utils/dbhash.py,sha256=
|
4592
|
+
lino/utils/dbfreader.py,sha256=KrGsBAFV2tF9pAd9jsmBAFpZ-yw-CRymZHEn_q9IL90,13784
|
4593
|
+
lino/utils/dbhash.py,sha256=TMnUGhOFbaip6L2Y4ENhCOS79ujJiO0CyYcOCZyHiK4,3505
|
4592
4594
|
lino/utils/dblogger.py,sha256=kr0YxQY6veymvNg5A4tsvkqW8haRWdwqL0C-_9_QTg0,721
|
4593
4595
|
lino/utils/diag.py,sha256=8BGsPrLd1_Fympy3PKiTpX1MdMWGApXr6IBoVOkWRxE,18314
|
4594
4596
|
lino/utils/djangotest.py,sha256=Phz1qNp0wDonZRja5dxbCk0Xl3a73gZNiKK8v9tAgZg,8334
|
@@ -4632,8 +4634,8 @@ lino/utils/xml.py,sha256=EGDnO1UaREst9fS7KTESdbHnrrVCwKbRQdvut6B6GmQ,1612
|
|
4632
4634
|
lino/utils/mldbc/__init__.py,sha256=QqWRlzeXaOmFfbCk-vTY3SZMn1-FCf67XnpZdd_Nim0,1134
|
4633
4635
|
lino/utils/mldbc/fields.py,sha256=tAX8G5UKigr9c6g0F3ARIjZZtg406mdaZ--PWSbiH9E,2873
|
4634
4636
|
lino/utils/mldbc/mixins.py,sha256=CkYe5jDa7xp9fJq_V8zcZf8ocxgIjUgHc9KZccvA_Yw,1945
|
4635
|
-
lino-25.4.
|
4636
|
-
lino-25.4.
|
4637
|
-
lino-25.4.
|
4638
|
-
lino-25.4.
|
4639
|
-
lino-25.4.
|
4637
|
+
lino-25.4.4.dist-info/METADATA,sha256=PnRkn-GRvLWsBi5pyAO6iggfwDKypr8ZWdiuhYqRN0Y,42534
|
4638
|
+
lino-25.4.4.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
4639
|
+
lino-25.4.4.dist-info/licenses/AUTHORS.rst,sha256=8VEm_G4HOmYEa4oi1nVoKKsdo4JanekEJCefWd2E8vk,981
|
4640
|
+
lino-25.4.4.dist-info/licenses/COPYING,sha256=DZak_2itbUtvHzD3E7GNUYSRK6jdOJ-GqncQ2weavLA,34523
|
4641
|
+
lino-25.4.4.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|