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 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.3'
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
- db = self.get_database_settings()
575
- if db is not None:
576
- self.django_settings.update(DATABASES=db)
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
@@ -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=250)
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():
@@ -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.urls import re_path
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
- from channels.middleware import BaseMiddleware
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 lino.core.auth import get_user
15
- from lino.modlib.notify.consumers import ClientConsumer
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 .utils import CHANNEL_NAME
18
- from .consumers import LinodConsumer
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
- Throw a more useful error message when scope['user'] is accessed before it's resolved
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
- def _setup(self):
27
- raise ValueError("Accessing scope user before it is ready.")
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
- async def _get_user(scope):
31
- class Wrapper:
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
- r = Wrapper(scope["session"])
36
- return await database_sync_to_async(get_user)(r)
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
- 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()
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
- async def resolve_scope(self, scope):
49
- scope["user"]._wrapped = await _get_user(scope)
56
+ routes = [re_path(r"^WS/$", ClientConsumer.as_asgi())]
50
57
 
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)
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
- routes = [re_path(r"^WS/$", ClientConsumer.as_asgi())]
66
+ # raise Exception("20240424")
58
67
 
59
- protocol_mapping = dict(
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 = ProtocolTypeRouter(protocol_mapping)
66
-
67
- # raise Exception("20240424")
70
+ application = get_asgi_application()
@@ -1,5 +1,5 @@
1
1
  # -*- coding: UTF-8 -*-
2
- # Copyright 2020-2022 Rumma & Ko Ltd
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, *args, **kwargs):
185
- self.default_values = dict()
186
- for k in ("ref", "title"):
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.default_values[k] = kwargs.pop(k)
189
- super().__init__(*args, **kwargs)
190
- if not "title" in self.default_values:
191
- self.default_values["title"] = self.text
192
-
193
- def create_object(self, **kwargs):
194
- kwargs.update(self.default_values)
195
- kwargs.update(special_page=self)
196
- return self.pointing_field.model(**kwargs)
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("100", _("Home"), "home", ref="index", body=_("Welcome to our great website."))
231
- add("200", _("Terms and conditions"), "terms")
232
- add("300", _("Privacy policy"), "privacy")
233
- add("400", _("Cookie settings"), "cookies")
234
- add("500", _("Copyright"), "copyright")
235
- add("600", _("About us"), "about")
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)
@@ -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
@@ -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>{}</i>".format(title)
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)
@@ -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.DISPLAY_MODE_STORY}
150
+ default_display_modes = {None: constants.DISPLAY_MODE_LIST}
151
151
 
152
152
 
153
153
  class PagesByParent(Pages):
lino/static/bootstrap.css CHANGED
@@ -1,7 +1,7 @@
1
1
  /* styles to be applied after bootstrap css */
2
2
 
3
3
  .publisher-document {
4
- width: 840px;
4
+ max-width: 840px;
5
5
  }
6
6
 
7
7
  .publisher-toc {
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
- http://www.garshol.priv.no/download/software/python/dbfreader.py
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
- # print(f"20250123 unpack_long {number}")
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.field_places = buf[17]
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
- return self.field_pos
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 not self.dbf.codepage is None:
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)" % (data, e))
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
- def __init__(self, file, key):
406
- self.file = DBFFile(file)
407
- self.hash = {}
408
- self.key = key
409
-
410
- self.file.open()
411
- while 1:
412
- rec = self.file.get_next_record()
413
- if rec is None:
414
- break
415
- self.hash[rec[self.key]] = rec
416
-
417
- def __getitem__(self, key):
418
- return self.hash[key]
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
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=NIz99QDcp3WGCLbrw0KdBjHI4-fjQbLrCUifvYsLnOE,6176
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=IBC8XyyUTq7Qt3LSkAidsyPG3sd5eBevBA5abUQgQas,83446
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=MNfvEqwHVw8K9Aoqd2rVvcdRo67vpsCufctj9B6g4qM,13546
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=FiG0JVqp9TWWkNpl9Y_50UCAJ7ZEImDXkQUhlg4aGL4,2094
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=fxvf0-1Y9YnBXaJVs8ZaNfLLQwyJO4BR3Uz_UeZtM7Q,8066
3592
- lino/modlib/publisher/mixins.py,sha256=eMEZeMQdaEp9tDkA5_YdqFGQec1q7Z1DAJZvU1cmoRM,6229
3593
- lino/modlib/publisher/models.py,sha256=4DbS4-CeMyNnAetEiuWpY9-mrGS0HsXovKiE5DMHOm4,16404
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=l4QNzMjCdBDaY8qa20S8TWmt9rW_oYR2Qw_HMhSUHtg,4481
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=Oxo9FD968sNRgY4GZulRMBZ4A2LHj2oVoWBVNxS7imE,1279
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/std.py,sha256=PtFH0JoN5rCGHCzFTYeyX9bBidD3cLdkXPygCrUGdwU,945
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=-8bGCqxOzajs3T4lvqWjTUGvvQ1uwVH886fr4cIKPs4,1156
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=4sXOGBKX6DFQCEPkCMfnJAVneHMyDzJQB5tsYAq90vQ,12205
4591
- lino/utils/dbhash.py,sha256=S2jCCYUItovF8Qkc2kVk5WS14U1c3RjijkUGqYs20_c,3450
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.3.dist-info/METADATA,sha256=qsQfxaXAH1QDVcf9nmSab5NgN8yYfHHpyUz8BqGt12E,42534
4636
- lino-25.4.3.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
4637
- lino-25.4.3.dist-info/licenses/AUTHORS.rst,sha256=8VEm_G4HOmYEa4oi1nVoKKsdo4JanekEJCefWd2E8vk,981
4638
- lino-25.4.3.dist-info/licenses/COPYING,sha256=DZak_2itbUtvHzD3E7GNUYSRK6jdOJ-GqncQ2weavLA,34523
4639
- lino-25.4.3.dist-info/RECORD,,
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