lino 25.4.1__py3-none-any.whl → 25.4.2__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.1'
34
+ __version__ = '25.4.2'
35
35
 
36
36
  # import setuptools # avoid UserWarning "Distutils was imported before Setuptools"?
37
37
 
lino/api/dd.py CHANGED
@@ -155,7 +155,7 @@ from lino.utils import IncompleteDate, read_exception
155
155
 
156
156
  from lino.utils.format_date import fdm, fdl, fdf, fdmy
157
157
  from lino.utils.format_date import fds as fds_
158
- from lino.utils.format_date import ftl, ftf
158
+ from lino.utils.format_date import fdtl, fdtf
159
159
 
160
160
 
161
161
  def fds(d):
@@ -175,14 +175,8 @@ field2kw = settings.SITE.field2kw
175
175
  decfmt = settings.SITE.decfmt
176
176
  str2kw = settings.SITE.str2kw
177
177
  str2dict = settings.SITE.str2dict
178
-
179
-
180
- def today(*args, **kwargs):
181
- # make it serializable for Django migrations
182
- return settings.SITE.today(*args, **kwargs)
183
-
184
-
185
- # today = settings.SITE.today
178
+ now = settings.SITE.now
179
+ # today = settings.SITE.today # see below
186
180
  strftime = settings.SITE.strftime
187
181
  demo_date = settings.SITE.demo_date
188
182
  is_abstract_model = settings.SITE.is_abstract_model
@@ -190,7 +184,6 @@ is_installed = settings.SITE.is_installed
190
184
  is_hidden_plugin = settings.SITE.is_hidden_plugin
191
185
  resolve_plugin = settings.SITE.resolve_plugin
192
186
  get_plugin_setting = settings.SITE.get_plugin_setting
193
- # get_db_overview_rst = settings.SITE.get_db_overview_rst
194
187
  add_welcome_handler = settings.SITE.add_welcome_handler
195
188
  build_media_url = settings.SITE.build_media_url
196
189
  build_site_cache_url = settings.SITE.build_site_cache_url
@@ -203,6 +196,11 @@ plugins = settings.SITE.plugins
203
196
  format_currency = settings.SITE.format_currency
204
197
 
205
198
 
199
+ def today(*args, **kwargs):
200
+ # make it serializable for Django migrations
201
+ return settings.SITE.today(*args, **kwargs)
202
+
203
+
206
204
  get_language = translation.get_language
207
205
 
208
206
 
lino/core/dbtables.py CHANGED
@@ -613,7 +613,7 @@ class Table(AbstractTable):
613
613
  qs = qs.filter(**kw)
614
614
 
615
615
  if ar.exclude:
616
- qs = qs.exclude(**ar.exclude)
616
+ qs = qs.exclude(ar.exclude)
617
617
  # qs = qs.exclude(ar.exclude)
618
618
 
619
619
  if ar.param_values is not None:
@@ -654,8 +654,7 @@ class Table(AbstractTable):
654
654
  qs = qs.filter(**d)
655
655
 
656
656
  if self.exclude:
657
- qs = qs.exclude(**self.exclude)
658
- # TODO: use Q object instead of dict
657
+ qs = qs.exclude(self.exclude)
659
658
 
660
659
  if ar.quick_search:
661
660
  qs = self.add_quick_search_filter(qs, ar.quick_search)
lino/core/fields.py CHANGED
@@ -6,6 +6,8 @@ Defines extended database field classes and utility functions
6
6
  related to fields.
7
7
  """
8
8
 
9
+ #fmt: off
10
+
9
11
  from lino import logger
10
12
  import datetime
11
13
  from decimal import Decimal
@@ -112,10 +114,10 @@ class PercentageField(models.DecimalField):
112
114
  defaults = dict(
113
115
  max_length=5,
114
116
  max_digits=5,
115
- decimal_places=2,
117
+ decimal_places=0,
116
118
  )
117
119
  defaults.update(kwargs)
118
- super(PercentageField, self).__init__(*args, **defaults)
120
+ super().__init__(*args, **defaults)
119
121
 
120
122
 
121
123
  class TimeField(models.TimeField):
@@ -818,7 +820,7 @@ def virtualfield(return_type, **kwargs):
818
820
 
819
821
  def decorator(fn):
820
822
  if isinstance(return_type, DummyField):
821
- rv = DummyField(fn)
823
+ rv = DummyField(return_type.get_default())
822
824
  else:
823
825
  rv = VirtualField(return_type, fn, **kwargs)
824
826
  rv.__doc__ = fn.__doc__
@@ -1001,10 +1003,8 @@ class QuantityField(models.CharField):
1001
1003
  if self.overflow_value:
1002
1004
  return self.overflow_value
1003
1005
  raise ValidationError(
1004
- "Cannot accept quantity {} " "because max_length is {}".format(
1005
- raw_value, self.max_length
1006
- )
1007
- )
1006
+ f"Cannot accept quantity {raw_value} "
1007
+ + f"because max_length is {self.max_length}")
1008
1008
  # print("20230129 Can't store {}={} in {}".format(self.name, raw_value, obj))
1009
1009
  # return -1
1010
1010
  return super().clean(raw_value, obj)
@@ -1111,8 +1111,20 @@ class Dummy(object):
1111
1111
  class DummyField(FakeField):
1112
1112
  """
1113
1113
  Represents a field that doesn't exist in the current configuration
1114
- but might exist in other configurations. The "value" of a
1115
- DummyField is always `None`.
1114
+ but might exist in other configurations.
1115
+
1116
+ The "value" of a DummyField is always `None` or any other value to be
1117
+ optionally specified at instantiation.
1118
+
1119
+ .. attribute:: dummy_value
1120
+
1121
+ The value to be returned by this field. Default value is `None`.
1122
+
1123
+ Usage examples:
1124
+
1125
+ - The value of :attr:`lino.modlib.users.User.nickname` is always ``""`` when
1126
+ :data:`lino.modlib.users.with_nickname` is `False`.
1127
+
1116
1128
 
1117
1129
  See e.g. :func:`ForeignKey` and :func:`fields_list`.
1118
1130
  """
@@ -1120,8 +1132,9 @@ class DummyField(FakeField):
1120
1132
  # choices = []
1121
1133
  # primary_key = False
1122
1134
 
1123
- def __init__(self, *args, **kw):
1124
- pass
1135
+ def __init__(self, dummy_value=None):
1136
+ super().__init__()
1137
+ self.dummy_value = dummy_value
1125
1138
 
1126
1139
  # def __init__(self, name, *args, **kw):
1127
1140
  # self.name = name
@@ -1132,10 +1145,10 @@ class DummyField(FakeField):
1132
1145
  def __get__(self, instance, owner):
1133
1146
  if instance is None:
1134
1147
  return self
1135
- return None
1148
+ return self.dummy_value
1136
1149
 
1137
1150
  def get_default(self):
1138
- return None
1151
+ return self.dummy_value
1139
1152
 
1140
1153
  def contribute_to_class(self, cls, name):
1141
1154
  self.name = name
@@ -1548,10 +1561,12 @@ def pointer_factory(cls, othermodel, *args, **kw):
1548
1561
 
1549
1562
  """
1550
1563
  if othermodel is None:
1551
- return DummyField(othermodel, *args, **kw)
1564
+ # return DummyField(othermodel, *args, **kw)
1565
+ return DummyField(None)
1552
1566
  if isinstance(othermodel, str):
1553
1567
  if not settings.SITE.is_installed_model_spec(othermodel):
1554
- return DummyField(othermodel, *args, **kw)
1568
+ # return DummyField(othermodel, *args, **kw)
1569
+ return DummyField(None)
1555
1570
 
1556
1571
  kw.setdefault("on_delete", models.CASCADE)
1557
1572
  return cls(othermodel, *args, **kw)
lino/core/requests.py CHANGED
@@ -829,7 +829,7 @@ class BaseRequest:
829
829
  unicode=str, # backwards-compatibility. In new templates
830
830
  # you should prefer `str`.
831
831
  pgettext=pgettext,
832
- now=timezone.now(),
832
+ now=dd.now(),
833
833
  getattr=getattr,
834
834
  restify=restify,
835
835
  activate_language=activate,
@@ -1805,6 +1805,8 @@ class ActionRequest(BaseRequest):
1805
1805
  selected_rows=None,
1806
1806
  **kw
1807
1807
  ):
1808
+ if exclude is not None:
1809
+ assert isinstance(exclude, models.Q)
1808
1810
  if title is not None:
1809
1811
  self.title = title
1810
1812
  if offset is not None:
@@ -1908,7 +1910,11 @@ class ActionRequest(BaseRequest):
1908
1910
  # raise Exception("20230426 b {}".format(self.master))
1909
1911
 
1910
1912
  if issubclass(self.actor, AbstractTable):
1911
-
1913
+ if self.actor.exclude is not None:
1914
+ if not isinstance(self.actor.exclude, models.Q):
1915
+ raise Exception(
1916
+ f"{self.actor}.exclude must be a Q object",
1917
+ f" not {self.actor.exclude}")
1912
1918
  self.exclude = exclude or self.actor.exclude
1913
1919
  self.page_length = self.actor.page_length
1914
1920
 
@@ -2019,8 +2025,7 @@ class ActionRequest(BaseRequest):
2019
2025
  # often and since exception loggers usually send an email to the
2020
2026
  # local system admin, make sure to log each exception only once.
2021
2027
  e = str(e)
2022
- self.no_data_text = f"{e} (set catch_layout_exceptions to see \
2023
- details)"
2028
+ self.no_data_text = e + " (set catch_layout_exceptions to see details)"
2024
2029
  self._data_iterator = []
2025
2030
  w = WARNINGS_LOGGED.get(e)
2026
2031
  if w is None:
@@ -2042,8 +2047,18 @@ class ActionRequest(BaseRequest):
2042
2047
  self._data_iterator, self)
2043
2048
  )
2044
2049
  else:
2050
+ offset = self.offset
2051
+
2052
+ if self.actor.start_at_bottom and offset is None and self.limit is not None:
2053
+ count = self.get_total_count()
2054
+ if not self.actor.no_phantom_row:
2055
+ count += 1
2056
+ offset = (count // self.limit) * self.limit
2057
+ if (count % self.limit) == 0 and count != 0:
2058
+ offset -= self.limit
2059
+
2045
2060
  self._sliced_data_iterator = sliced_data_iterator(
2046
- self._data_iterator, self.offset, self.limit
2061
+ self._data_iterator, offset, self.limit
2047
2062
  )
2048
2063
  # logger.info("20171116 executed : %s", self._sliced_data_iterator)
2049
2064
 
@@ -2069,11 +2084,11 @@ class ActionRequest(BaseRequest):
2069
2084
  assert issubclass(self.actor, AbstractTable)
2070
2085
  self.actor.check_params(self.param_values)
2071
2086
  if self.actor.get_data_rows is not None:
2072
- l = []
2087
+ lst = []
2073
2088
  for row in self.actor.get_data_rows(self):
2074
2089
  group = self.actor.group_from_row(row)
2075
- group.process_row(l, row)
2076
- return l
2090
+ group.process_row(lst, row)
2091
+ return lst
2077
2092
  # ~ logger.info("20120914 tables.get_data_iterator %s",self)
2078
2093
  # ~ logger.info("20120914 tables.get_data_iterator %s",self.actor)
2079
2094
  # print("20181121 get_data_iterator", self.actor)
@@ -2145,7 +2160,7 @@ class ActionRequest(BaseRequest):
2145
2160
  if self.known_values:
2146
2161
  # kw.update(self.known_values)
2147
2162
  for k, v in self.known_values.items():
2148
- if not "__" in k:
2163
+ if "__" not in k:
2149
2164
  kw[k] = v
2150
2165
  obj = self.actor.create_instance(self, **kw)
2151
2166
  return obj
lino/core/site.py CHANGED
@@ -3,23 +3,8 @@
3
3
  # License: GNU Affero General Public License v3 (see file COPYING for details)
4
4
  # doctest lino/core/site.py
5
5
 
6
- from lino.core.exceptions import ChangedAPI
7
- from django.apps import apps
8
- from lino.utils.html import E, tostring
9
- from lino import assert_django_code, DJANGO_DEFAULT_LANGUAGE
10
- # from lino.core import constants
11
- from lino.core.plugin import Plugin
12
- from rstgen.confparser import ConfigParser
13
- from django.utils import translation
14
- from django.utils.html import mark_safe
15
- from django.utils.translation import get_language
16
- from django.utils.translation import gettext_lazy as _
17
- from django.conf import settings
18
- import rstgen
19
- from lino.utils import AttrDict, date_offset, i2d, buildurl
20
- from lino import logger, __version__
21
6
  from importlib.util import find_spec
22
- from importlib import import_module, reload
7
+ from importlib import import_module
23
8
  from pathlib import Path
24
9
  import os
25
10
  import re
@@ -34,6 +19,22 @@ import logging
34
19
  # from pprint import pprint
35
20
  from logging.handlers import SocketHandler
36
21
  import time
22
+ import rstgen
23
+ from rstgen.confparser import ConfigParser
24
+ from django.apps import apps
25
+ from django.utils import timezone
26
+ from django.utils import translation
27
+ from django.utils.html import mark_safe
28
+ from django.utils.translation import get_language
29
+ from django.utils.translation import gettext_lazy as _
30
+ from django.conf import settings
31
+ from lino import assert_django_code, DJANGO_DEFAULT_LANGUAGE
32
+ from lino import logger, __version__
33
+ from lino.core.exceptions import ChangedAPI
34
+ from lino.utils.html import E, tostring
35
+ # from lino.core import constants
36
+ from lino.core.plugin import Plugin
37
+ from lino.utils import AttrDict, date_offset, i2d, buildurl
37
38
 
38
39
  NO_REMOTE_AUTH = True
39
40
  # 20240518 We have only one production site still using remote http
@@ -1386,6 +1387,11 @@ class Site(object):
1386
1387
  )
1387
1388
  return date_offset(base, *args, **kwargs)
1388
1389
 
1390
+ def now(self, *args, **kwargs):
1391
+ t = self.today()
1392
+ now = timezone.now()
1393
+ return now.replace(year=t.year, month=t.month, day=t.day)
1394
+
1389
1395
  def welcome_text(self):
1390
1396
  return "This is %s using %s." % (self.site_version(), self.using_text())
1391
1397
 
@@ -2082,6 +2088,18 @@ class Site(object):
2082
2088
  time.time() - started,
2083
2089
  )
2084
2090
 
2091
+ _top_link_generator = []
2092
+
2093
+ def get_top_links(self, ar):
2094
+ messages = []
2095
+ for l in self._top_link_generator:
2096
+ for msg in l(ar):
2097
+ messages.append(msg)
2098
+ return tostring(E.div(*messages))
2099
+
2100
+ def add_top_link_generator(self, func):
2101
+ self._top_link_generator.append(func)
2102
+
2085
2103
  def get_welcome_messages(self, ar):
2086
2104
  for h in self._welcome_handlers:
2087
2105
  for msg in h(ar):
lino/core/tables.py CHANGED
@@ -1,12 +1,10 @@
1
1
  # -*- coding: UTF-8 -*-
2
- # Copyright 2009-2024 Rumma & Ko Ltd
2
+ # Copyright 2009-2025 Rumma & Ko Ltd
3
3
  # License: GNU Affero General Public License v3 (see file COPYING for details)
4
4
  """Defines the classes :class:`AbstractTable` and
5
5
  :class:`VirtualTable`.
6
6
 
7
7
  """
8
- from lino import logger
9
- from future.utils import with_metaclass
10
8
 
11
9
  import os
12
10
  import yaml
@@ -15,8 +13,9 @@ from django.db import models
15
13
  from django.db.utils import OperationalError, ProgrammingError
16
14
  from django.conf import settings
17
15
  from django.utils.translation import gettext_lazy as _
18
- from django.core.exceptions import BadRequest
16
+ # from django.core.exceptions import BadRequest
19
17
 
18
+ from lino import logger
20
19
  from lino.core import actors
21
20
  from lino.core import actions
22
21
  from lino.core import fields
@@ -191,17 +190,15 @@ class AbstractTable(actors.Actor):
191
190
  will not pop-in.
192
191
  """
193
192
 
194
- # start_at_bottom = False
195
- # """Set this to `True` if you want your table to *start at the
196
- # bottom*. Unlike reverse ordering, the rows remain in their
197
- # natural order, but when we open a grid on this table, we want it
198
- # to start on the last page.
199
- #
200
- # Use cases would be :class:`lino_xl.lib.trading.InvoicesByJournal` and
201
- # :class:`lino_xl.lib.accounting.InvoicesByJournal` but the result is not yet
202
- # satisfying.
203
- #
204
- # """
193
+ start_at_bottom = False
194
+ """Set this to `True` if you want your table to *start at the
195
+ bottom*. Unlike reverse ordering, the rows remain in their
196
+ natural order, but when we open a grid on this table, we want it
197
+ to start on the last page.
198
+
199
+ Use cases are in :class:`lino_xl.lib.trading.InvoicesByJournal` and
200
+ :class:`lino_xl.lib.accounting.InvoicesByJournal`.
201
+ """
205
202
 
206
203
  group_by = None
207
204
  """
@@ -397,21 +394,20 @@ method in order to sort the rows of the queryset.
397
394
  """
398
395
 
399
396
  filter = None
400
- """If specified, this must be a `models.Q` object (not a dict of
401
- (fieldname -> value) pairs) which will be passed to Django's
402
- `filter
397
+ """
398
+ If specified, this must be a :class:`django.db.models.Q` object that will be
399
+ passed to Django's `filter
403
400
  <https://docs.djangoproject.com/en/5.0/ref/models/querysets/#filter>`__
404
401
  method.
405
402
 
406
- Note that if you allow a user to insert rows into a filtered
407
- table, you should make sure that new records satisfy your filter
408
- condition, otherwise you can get surprising behaviour if the user
409
- creates a new row.
403
+ If you allow a user to insert rows into a filtered table, you should make
404
+ sure that new records satisfy your filter condition, otherwise you can get
405
+ surprising behaviour if the user creates a new row.
410
406
 
411
- If your filter consists of simple static values on some known
412
- field, then you might prefer to use
413
- :attr:`known_values <lino.core.actors.Actor.known_values>`
414
- instead because this will add automatic behaviour.
407
+ If your filter consists of simple static values on some known field, then
408
+ you might prefer to use :attr:`known_values
409
+ <lino.core.actors.Actor.known_values>` instead because this will add
410
+ automatic behaviour.
415
411
 
416
412
  One advantage of :attr:`filter` over
417
413
  :attr:`known_values <lino.core.actors.Actor.known_values>`
@@ -421,11 +417,13 @@ method in order to sort the rows of the queryset.
421
417
  """
422
418
 
423
419
  exclude = None
424
- """If specified, this must be dict which will be passed to Django's
425
- `exclude
420
+ """
421
+ If specified, this must be a :class:`django.db.models.Q` object that will be
422
+ passed to Django's `exclude
426
423
  <https://docs.djangoproject.com/en/5.0/ref/models/querysets/#exclude>`__
427
- method on the queryset.
424
+ method.
428
425
 
426
+ This is the logical opposite of :attr:`filter`.
429
427
  """
430
428
 
431
429
  extra = None
lino/help_texts.py CHANGED
@@ -253,6 +253,8 @@ help_texts = {
253
253
  'lino.utils.jsgen.VisibleComponent' : _("""A visible component"""),
254
254
  'lino.utils.jsgen.VisibleComponent.install_permission_handler' : _("""Define the allow_read handler used by get_view_permission(). This must be done only once, but after having configured debug_permissions and required_roles."""),
255
255
  'lino.utils.media.MediaFile' : _("""Represents a file on the server below MEDIA_ROOT with two properties path and url."""),
256
+ 'lino.utils.media.MediaFile.path' : _("""A pathlib.Path naming the file on the server’s file system."""),
257
+ 'lino.utils.media.MediaFile.url' : _("""The URL to use for getting this file from a web client."""),
256
258
  'lino.utils.mldbc.fields.BabelCharField' : _("""Define a variable number of CharField database fields, one for each language of your lino.core.site.Site.languages. See mldbc."""),
257
259
  'lino.utils.mldbc.fields.BabelTextField' : _("""Used for the clones of the master field, one for each non-default language. See mldbc."""),
258
260
  'lino.utils.mldbc.fields.LanguageField' : _("""A field that lets the user select a language from the available lino.core.site.Site.languages."""),
@@ -551,6 +553,8 @@ help_texts = {
551
553
  'lino.modlib.jinja.XMLMaker.xml_file_name' : _("""The name of the XML file to generate. This file will be overwritten without asking. The name formatted with one name self in the context."""),
552
554
  'lino.modlib.jinja.XMLMaker.xml_file_template' : _("""The name of a Jinja template to render for generating the XML content."""),
553
555
  'lino.modlib.jinja.XMLMaker.xml_validator_file' : _("""The name of a “validator” to use for validating the XML content."""),
556
+ 'lino.modlib.jinja.XMLMaker.get_xml_file' : _("""Get the name of the XML file to be generated for this database row."""),
557
+ 'lino.modlib.jinja.XMLMaker.make_xml_file' : _("""Make the XML file for this database row."""),
554
558
  'lino.modlib.memo.Previewable' : _("""Adds three rich text fields (lino.core.fields.RichTextField):"""),
555
559
  'lino.modlib.memo.Previewable.body' : _("""An editable text body."""),
556
560
  'lino.modlib.memo.Previewable.body_short_preview' : _("""A read-only preview of the first paragraph of body."""),
lino/mixins/__init__.py CHANGED
@@ -1,5 +1,5 @@
1
1
  # -*- coding: UTF-8 -*-
2
- # Copyright 2010-2021 Rumma & Ko Ltd
2
+ # Copyright 2010-2025 Rumma & Ko Ltd
3
3
  # License: GNU Affero General Public License v3 (see file COPYING for details)
4
4
  """
5
5
  This package contains model mixins, some of which are heavily used
@@ -22,18 +22,37 @@ by applications and the :ref:`xl`. But none of them is mandatory.
22
22
  from django.db import models
23
23
  from django.conf import settings
24
24
  from django.utils.translation import gettext_lazy as _
25
- from django.utils.html import format_html
26
- from django.utils.text import format_lazy
27
- from django.utils import timezone
25
+ # from django.utils.html import format_html
26
+ # from django.utils.text import format_lazy
27
+ # from django.utils import timezone
28
28
  from django.contrib.humanize.templatetags.humanize import naturaltime
29
29
 
30
+ # Note that reordering the imports here can cause the field ordering to change
31
+ # in models like lino_voga.lib.courses.TeacherType, which inherits from
32
+ # `(Referrable, BabelNamed, Printable)`. This can cause doctests like
33
+ # docs/specs/voga/courses.rst to fail because the `ref` field then came after
34
+ # the name field. The TeacherTypes table has no explicit `column_names`, so it
35
+ # uses the "natural" field ordering, which is, as this observation shows, quite
36
+ # unpredictable.
37
+
30
38
  from lino.core import actions
31
39
  from lino.core import fields
32
40
  from lino.core import model
33
- from lino.core.workflows import ChangeStateAction
41
+ # from lino.core.workflows import ChangeStateAction
42
+ # from lino.core.exceptions import ChangedAPI
34
43
  from lino.utils.mldbc.fields import LanguageField
35
- from lino.core.exceptions import ChangedAPI
36
44
  from lino.utils.html import E
45
+ from lino.utils.mldbc.mixins import BabelNamed, BabelDesignated
46
+ from lino.utils.mldbc.fields import BabelCharField, BabelTextField
47
+
48
+ from .human import Human
49
+ from .polymorphic import Polymorphic
50
+ from .periods import ObservedDateRange, Yearly, Monthly, Today
51
+ from .periods import DateRange
52
+ from .sequenced import Sequenced, Hierarchical
53
+ from .duplicable import Duplicable, Duplicate
54
+ from .registrable import Registrable, RegistrableState
55
+ from .ref import Referrable, StructuredReferrable
37
56
 
38
57
 
39
58
  class Contactable(model.Model):
@@ -97,7 +116,7 @@ class Modified(model.Model):
97
116
  super().save(*args, **kwargs)
98
117
 
99
118
  def touch(self):
100
- self.modified = timezone.now()
119
+ self.modified = settings.SITE.now()
101
120
 
102
121
 
103
122
  class Created(model.Model):
@@ -124,7 +143,7 @@ class Created(model.Model):
124
143
 
125
144
  def save(self, *args, **kwargs):
126
145
  if self.created is None and not settings.SITE.loading_from_dump:
127
- self.created = timezone.now()
146
+ self.created = settings.SITE.now()
128
147
  super().save(*args, **kwargs)
129
148
 
130
149
 
@@ -264,25 +283,3 @@ class Draggable(model.Model):
264
283
 
265
284
  def on_dropped(self, ar, **kwargs):
266
285
  pass
267
-
268
-
269
- from .ref import Referrable, StructuredReferrable
270
- from .registrable import Registrable, RegistrableState
271
- from lino.mixins.duplicable import Duplicable, Duplicate
272
- from lino.mixins.sequenced import Sequenced, Hierarchical
273
- from lino.mixins.periods import DateRange
274
- from lino.mixins.periods import ObservedDateRange, Yearly, Monthly, Today
275
- from lino.mixins.polymorphic import Polymorphic
276
-
277
- # Observation: moving the following two lines to the top (to be together with
278
- # the import of LanguageField) caused the field ordering to change in models
279
- # like `lino_voga.lib.courses.TeacherType` which inherits from `(Referrable,
280
- # BabelNamed, Printable)`. Which caused docs/specs/voga/courses.rst to fail
281
- # because the `ref` field then came after the name field. The TeacherTypes table
282
- # has no explicit `column_names`, so it uses the "natural" field ordering, which
283
- # is, as this observation shows, quite unpredictable.
284
-
285
- from lino.utils.mldbc.fields import BabelCharField, BabelTextField
286
- from lino.utils.mldbc.mixins import BabelNamed, BabelDesignated
287
-
288
- from lino.mixins.human import Human
@@ -159,7 +159,7 @@ class ChangesByMaster(Changes):
159
159
  def log_change(type, request, master, obj, msg="", changed_fields=""):
160
160
  Change(
161
161
  type=type,
162
- time=timezone.now(),
162
+ time=dd.now(),
163
163
  master=master,
164
164
  user=request.user,
165
165
  object=obj,
@@ -177,7 +177,7 @@ if remove_after:
177
177
  def delete_older_changes(ar):
178
178
  days = datetime.timedelta(days=remove_after)
179
179
  # django.core.exceptions.FieldError: Cannot resolve keyword 'time_lt' into field. Choices are: changed_fields, diff, id, list_item, master, master_id, master_type, master_type_id, name_column, navigation_panel, object, object_id, object_type, object_type_id, overview, time, type, user, user_id, workflow_buttons
180
- qs = Change.objects.filter(time__lt=timezone.now() - days)
180
+ qs = Change.objects.filter(time__lt=dd.now() - days)
181
181
  if qs.count() > 0:
182
182
  ar.logger.info(
183
183
  "Removing %d changes older than %d days.", qs.count(), remove_after
@@ -1,12 +1,7 @@
1
- # Copyright 2014-2020 Rumma & Ko Ltd
1
+ # Copyright 2014-2025 Rumma & Ko Ltd
2
2
  # License: GNU Affero General Public License v3 (see file COPYING for details)
3
- """This plugin installs a button to export any table to excel xls format.
4
-
5
- To use it, simply add the following line to your
6
- :meth:`lino.core.site.Site.get_installed_plugins`::
7
-
8
- yield 'lino.modlib.export_excel'
9
-
3
+ """
4
+ See :doc:`/plugins/export_excel`.
10
5
  """
11
6
 
12
7
  from lino import ad, _
@@ -16,3 +11,6 @@ class Plugin(ad.Plugin):
16
11
  "See :doc:`/dev/plugins`."
17
12
 
18
13
  verbose_name = _("Export to Excel xls format")
14
+
15
+ def get_requirements(self, site):
16
+ yield "openpyxl"
@@ -1,9 +1,7 @@
1
1
  # -*- coding: UTF-8 -*-
2
- # Copyright 2014-2024 Rumma & Ko Ltd
2
+ # Copyright 2014-2025 Rumma & Ko Ltd
3
3
  # License: GNU Affero General Public License v3 (see file COPYING for details)
4
- """Database models for `lino.modlib.export_excel`.
5
4
 
6
- """
7
5
  import os
8
6
 
9
7
  from django.conf import settings
@@ -35,6 +35,9 @@ class XMLMaker(dd.Model):
35
35
  yield 'xml'
36
36
  yield self.get_printable_target_stem() + ".xml"
37
37
 
38
+ def get_xml_file(self):
39
+ return MediaFile(False, *self.get_xml_file_parts())
40
+
38
41
  def make_xml_file(self, ar):
39
42
  renderer = settings.SITE.plugins.jinja.renderer
40
43
  tpl = renderer.jinja_env.get_template(self.xml_file_template)
@@ -45,7 +48,7 @@ class XMLMaker(dd.Model):
45
48
  # parts = [
46
49
  # dd.plugins.accounting.xml_media_dir,
47
50
  # self.xml_file_name.format(self=self)]
48
- xmlfile = MediaFile(False, *self.get_xml_file_parts())
51
+ xmlfile = self.get_xml_file()
49
52
  # xmlfile = Path(settings.MEDIA_ROOT, *parts)
50
53
  ar.logger.info("Make %s from %s ...", xmlfile.path, self)
51
54
  xmlfile.path.parent.mkdir(exist_ok=True, parents=True)
@@ -45,15 +45,15 @@ class RunNow(dd.Action):
45
45
 
46
46
  def run_from_ui(self, ar, **kwargs):
47
47
  # print("20231102 RunNow", ar.selected_rows)
48
+ now = dd.now()
48
49
  for obj in ar.selected_rows:
49
50
  assert issubclass(obj.__class__, Runnable)
50
51
  if True: # dd.plugins.linod.use_channels:
51
52
  obj.last_start_time = None
52
53
  obj.last_end_time = None
53
- obj.requested_at = timezone.now()
54
- obj.message = "{} requested to run this task at {}.".format(
55
- ar.get_user(), dd.ftl(timezone.now())
56
- )
54
+ obj.requested_at = now
55
+ tpl = _("{0} requested to run this task at {1}.")
56
+ obj.message = tpl.format(ar.get_user(), dd.fdtl(now))
57
57
  # obj.disabled = False
58
58
  obj.full_clean()
59
59
  obj.save()
@@ -155,10 +155,12 @@ class Runnable(Sequenced, RecurrenceSet):
155
155
  await ar.adebug("Start %s with logging level %s", self, self.log_level)
156
156
  # ar.info("Start %s with logging level %s", astr(self), self.log_level)
157
157
  # forget about any previous run:
158
- self.last_start_time = timezone.now()
158
+ now = await sync_to_async(dd.now)()
159
+ self.last_start_time = now
159
160
  self.requested_at = None
160
161
  self.last_end_time = None
161
- self.message = ""
162
+ self.message = f"Started at {self.last_start_time} " \
163
+ f"with logging level {self.log_level}"
162
164
  # print("20231102 full_clean")
163
165
  await sync_to_async(self.full_clean)()
164
166
  # self.full_clean()
@@ -184,19 +186,21 @@ class Runnable(Sequenced, RecurrenceSet):
184
186
  self.disabled = True
185
187
  await ar.awarning("Disabled %s after exception %s", self, e)
186
188
  # ar.warning("Disabled %s after exception %s", astr(self), e)
187
- self.last_end_time = timezone.now()
189
+ now = await sync_to_async(dd.now)()
190
+ self.last_end_time = now
188
191
  self.message = "<pre>" + self.message + "</pre>"
189
192
  await sync_to_async(self.full_clean)()
190
193
  # self.full_clean()
191
194
  await self.asave()
195
+ await sync_to_async(dd.post_ui_save.send)(sender=self.__class__, instance=self)
192
196
 
193
197
  @dd.displayfield("Status")
194
198
  def status(self, ar=None):
195
199
  if self.is_running():
196
- return _("Running since {}").format(dd.ftf(self.last_start_time))
200
+ return _("Running since {}").format(dd.fdtf(self.last_start_time))
197
201
  if self.requested_at is not None:
198
202
  return _("Requested to run asap (since {})").format(
199
- dd.ftf(self.requested_at))
203
+ dd.fdtf(self.requested_at))
200
204
  if self.disabled:
201
205
  return _("Disabled")
202
206
  if self.last_start_time is None or self.last_end_time is None:
@@ -206,7 +210,7 @@ class Runnable(Sequenced, RecurrenceSet):
206
210
  next_time = self.get_next_suggested_date(self.last_end_time)
207
211
  if next_time is None:
208
212
  return _("Not scheduled")
209
- return _("Scheduled to run at {}").format(dd.ftf(next_time))
213
+ return _("Scheduled to run at {}").format(dd.fdtf(next_time))
210
214
 
211
215
 
212
216
  async def start_task_runner(ar=None, max_count=None):
@@ -219,7 +223,7 @@ async def start_task_runner(ar=None, max_count=None):
219
223
  while True:
220
224
  await ar.adebug("Start next task runner loop.")
221
225
 
222
- now = timezone.now()
226
+ now = await sync_to_async(dd.now)()
223
227
  next_time = now + \
224
228
  timedelta(seconds=dd.plugins.linod.background_sleep_time)
225
229
 
@@ -282,7 +286,8 @@ async def start_task_runner(ar=None, max_count=None):
282
286
  if max_count is not None and count >= max_count:
283
287
  await ar.ainfo("Stop after %s loops.", max_count)
284
288
  return next_time
285
- if (to_sleep := (next_time - timezone.now()).total_seconds()) <= 0:
289
+ now = await sync_to_async(dd.now)()
290
+ if (to_sleep := (next_time - now).total_seconds()) <= 0:
286
291
  continue
287
292
  await ar.adebug("Let task runner sleep for %s seconds.", to_sleep)
288
293
  await asyncio.sleep(to_sleep)
@@ -7,6 +7,20 @@ from lino.api import dd, rt, _
7
7
  # PUBLIC_GROUP = "all_users_channel"
8
8
 
9
9
 
10
+ def get_updatables(instance, ar=None):
11
+ data = {
12
+ "actorIDs": instance.updatable_panels,
13
+ "pk": instance.pk,
14
+ "model": f"{instance._meta.app_label}.{instance.__class__.__name__}",
15
+ "mk": None, "master_model": None
16
+ }
17
+ if ar is None:
18
+ return data
19
+ if mi := ar.master_instance:
20
+ data.update(mk=mi.pk, master_model=f"{mi._meta.app_label}.{mi.__class__.__name__}")
21
+ return data
22
+
23
+
10
24
  class ChangeNotifier(dd.Model):
11
25
 
12
26
  class Meta:
@@ -1,5 +1,5 @@
1
1
  # -*- coding: UTF-8 -*-
2
- # Copyright 2011-2024 Rumma & Ko Ltd
2
+ # Copyright 2011-2025 Rumma & Ko Ltd
3
3
  # License: GNU Affero General Public License v3 (see file COPYING for details)
4
4
 
5
5
  # import json
@@ -9,7 +9,6 @@ from datetime import timedelta
9
9
 
10
10
  from django.db import models
11
11
  from django.conf import settings
12
- from django.utils import timezone
13
12
  from django.utils import translation
14
13
 
15
14
  from lino import logger
@@ -25,6 +24,7 @@ from lino.modlib.users.mixins import UserAuthored, My
25
24
  from lino.modlib.linod.choicelists import schedule_daily, schedule_often
26
25
  from lino.modlib.office.roles import OfficeUser
27
26
 
27
+ from .mixins import get_updatables
28
28
  from .choicelists import MessageTypes, MailModes
29
29
  from .api import send_notification, NOTIFICATION, send_panel_update
30
30
 
@@ -57,7 +57,7 @@ class MarkAllSeen(dd.Action):
57
57
  user=ar.get_user(), seen__isnull=True
58
58
  )
59
59
  for obj in qs:
60
- obj.seen = timezone.now()
60
+ obj.seen = dd.now()
61
61
  obj.save()
62
62
  ar.success(
63
63
  eval_js='window.top.document.querySelectorAll(".'
@@ -81,7 +81,7 @@ class MarkSeen(dd.Action):
81
81
 
82
82
  def run_from_ui(self, ar):
83
83
  for obj in ar.selected_rows:
84
- obj.seen = timezone.now()
84
+ obj.seen = dd.now()
85
85
  obj.save()
86
86
  ar.success(refresh_all=True)
87
87
 
@@ -297,9 +297,9 @@ class Message(UserAuthored, Controllable, Created):
297
297
  # dd.logger.info("20240902 %s", sender)
298
298
  ar.send_email(subject, sender, body, [user.email])
299
299
  for msg in messages:
300
- msg.sent = timezone.now()
300
+ msg.sent = dd.now()
301
301
  if dd.plugins.notify.mark_seen_when_sent:
302
- msg.seen = timezone.now()
302
+ msg.seen = dd.now()
303
303
  msg.save()
304
304
 
305
305
  # def send_browser_message_for_all_users(self, user):
@@ -500,7 +500,7 @@ if remove_after:
500
500
  def clear_seen_messages(ar):
501
501
  Message = rt.models.notify.Message
502
502
  qs = Message.objects.filter(
503
- created__lt=timezone.now() - timedelta(days=remove_after)
503
+ created__lt=dd.now() - timedelta(days=remove_after)
504
504
  )
505
505
  what = "notification messages older than {} days".format(remove_after)
506
506
  if dd.plugins.notify.keep_unseen:
@@ -522,26 +522,11 @@ if remove_after:
522
522
 
523
523
 
524
524
  @dd.receiver(dd.post_ui_save)
525
- def notify_panels(sender, instance, ar, **kwargs):
525
+ def notify_panels(sender, instance, ar=None, **kwargs):
526
526
  if (
527
527
  not dd.get_plugin_setting("linod", "use_channels", False)
528
- or not (ups := instance.updatable_panels)
529
- or not hasattr(ar, "rqdata")
528
+ or not instance.updatable_panels
530
529
  ):
531
530
  return
532
- data = {
533
- "actorIDs": ups,
534
- "pk": instance.pk,
535
- "model": f"{instance._meta.app_label}.{instance.__class__.__name__}",
536
- }
537
- data.update(
538
- **(
539
- {
540
- "mk": (mi := ar.master_instance).pk,
541
- "master_model": f"{mi._meta.app_label}.{mi.__class__.__name__}",
542
- }
543
- if ar.master_instance
544
- else {"mk": None, "master_model": None}
545
- )
546
- )
531
+ data = get_updatables(instance, ar)
547
532
  send_panel_update(data)
@@ -72,7 +72,7 @@ def make_uploaded_file(filename, src=None, upload_date=None):
72
72
  def base64_to_image(imgstring):
73
73
  type, file = imgstring.split(";base64,")
74
74
  imgdata = base64.b64decode(file)
75
- return make_captured_image(imgdata, timezone.now(), ext=f".{type.split('/')[1]}")
75
+ return make_captured_image(imgdata, dd.now(), ext=f".{type.split('/')[1]}")
76
76
 
77
77
 
78
78
  def make_captured_image(imgdata, upload_date=None, filename=None, ext='.jpg'):
@@ -1,5 +1,5 @@
1
1
  # -*- coding: UTF-8 -*-
2
- # Copyright 2011-2024 Rumma & Ko Ltd
2
+ # Copyright 2011-2025 Rumma & Ko Ltd
3
3
  # License: GNU Affero General Public License v3 (see file COPYING for details)
4
4
 
5
5
  import datetime
@@ -9,7 +9,6 @@ from lino.utils.html import E, tostring
9
9
  from django.db import models
10
10
  from django.conf import settings
11
11
  from django.http import HttpResponse
12
- from django.utils import timezone
13
12
  from django.utils.translation import gettext
14
13
  from django.contrib.auth.password_validation import validate_password
15
14
  from django.core.exceptions import ValidationError
@@ -115,7 +114,6 @@ class CreateAccount(dd.Action):
115
114
  obj.full_clean()
116
115
  obj.save()
117
116
 
118
-
119
117
  ar.selected_rows = [obj]
120
118
  recipients = ["{} <{}>".format(obj.get_full_name(), obj.email)]
121
119
  send_welcome_email(ar, obj, recipients)
@@ -452,7 +450,7 @@ class SignOut(dd.Action):
452
450
  def validate_sessions_limit(request):
453
451
  if dd.plugins.users.active_sessions_limit == -1:
454
452
  return
455
- qs = rt.models.sessions.Session.objects.filter(expire_date__gt=timezone.now())
453
+ qs = rt.models.sessions.Session.objects.filter(expire_date__gt=dd.now())
456
454
  if request.session.session_key:
457
455
  qs = qs.exclude(session_key=request.session.session_key)
458
456
  if qs.count() >= dd.plugins.users.active_sessions_limit:
@@ -1,7 +1,9 @@
1
1
  # -*- coding: UTF-8 -*-
2
- # Copyright 2011-2023 Rumma & Ko Ltd
2
+ # Copyright 2011-2025 Rumma & Ko Ltd
3
3
  # License: GNU Affero General Public License v3 (see file COPYING for details)
4
4
 
5
+ import random
6
+ import string
5
7
  from datetime import timedelta
6
8
  from django.db import models
7
9
  from django.db.models import Q
@@ -9,15 +11,17 @@ from django.conf import settings
9
11
  from django.contrib.auth.base_user import AbstractBaseUser, BaseUserManager
10
12
  from django.utils import timezone
11
13
 
12
- from lino.utils.html import E
13
14
  from lino.api import dd, rt, _
14
15
  from lino.core import userprefs
15
-
16
- # from lino.core.fields import NullCharField
16
+ from lino.core.roles import Supervisor
17
17
  from lino.core.roles import SiteAdmin
18
-
18
+ # from lino.core.fields import NullCharField
19
19
  from lino.mixins import CreatedModified, Contactable
20
20
  from lino.mixins import DateRange
21
+ from lino.modlib.about.choicelists import TimeZones, DateFormats
22
+ from lino.modlib.publisher.mixins import Publishable
23
+ from lino.modlib.about.models import About
24
+ from lino.utils.html import E
21
25
 
22
26
  from .choicelists import UserTypes
23
27
  from .mixins import UserAuthored # , TimezoneHolder
@@ -25,14 +29,8 @@ from .actions import ChangePassword, SignOut, CheckedSubmitInsert
25
29
  from .actions import SendWelcomeMail, SignIn, ConnectAccount
26
30
  from .actions import SendWelcomeMail, CreateAccount, ResetPassword, VerifyUser, VerifyMe
27
31
 
28
- # from .actions import SignIn
29
- from lino.modlib.about.choicelists import TimeZones, DateFormats
30
- from lino.modlib.publisher.mixins import Publishable
32
+ from .ui import *
31
33
 
32
- from lino.core.roles import Supervisor
33
-
34
- import random
35
- import string
36
34
 
37
35
  if multi_ledger := dd.is_installed("ledgers"):
38
36
  from lino_xl.lib.ledgers.actions import SubscribeToLedger
@@ -100,7 +98,7 @@ class User(AbstractBaseUser, Contactable, CreatedModified, Publishable, DateRang
100
98
  if dd.plugins.users.with_nickname:
101
99
  nickname = models.CharField(_("Nickname"), max_length=50, blank=True)
102
100
  else:
103
- nickname = dd.DummyField()
101
+ nickname = dd.DummyField("")
104
102
  first_name = models.CharField(_("First name"), max_length=30, blank=True)
105
103
  last_name = models.CharField(_("Last name"), max_length=30, blank=True)
106
104
  remarks = models.TextField(_("Remarks"), blank=True) # ,null=True)
@@ -156,7 +154,7 @@ class User(AbstractBaseUser, Contactable, CreatedModified, Publishable, DateRang
156
154
 
157
155
  def must_verify(self):
158
156
  self.verification_code = id_generator(12)
159
- self.verification_code_sent_on = timezone.now()
157
+ self.verification_code_sent_on = dd.now()
160
158
 
161
159
  def is_verified(self):
162
160
  return not self.verification_code
@@ -170,7 +168,7 @@ class User(AbstractBaseUser, Contactable, CreatedModified, Publishable, DateRang
170
168
  return (
171
169
  self.verification_code_sent_on
172
170
  + timedelta(minutes=dd.plugins.users.verification_code_expires)
173
- < timezone.now()
171
+ < dd.now()
174
172
  )
175
173
 
176
174
  def get_as_user(self):
@@ -451,8 +449,6 @@ class Permission(dd.Model):
451
449
  abstract = True
452
450
 
453
451
 
454
- from lino.modlib.about.models import About
455
-
456
452
  About.sign_in = SignIn()
457
453
  About.reset_password = ResetPassword()
458
454
  About.verify_user = VerifyUser()
@@ -478,7 +474,5 @@ def setup_memo_commands(sender=None, **kwargs):
478
474
  )
479
475
 
480
476
 
481
- from .ui import *
482
-
483
477
  if dd.get_plugin_setting("users", "third_party_authentication"):
484
478
  Me.connect_account = ConnectAccount()
lino/modlib/users/ui.py CHANGED
@@ -118,7 +118,7 @@ class AllUsers(Users):
118
118
  class UsersOverview(Users):
119
119
  required_roles = set([])
120
120
  column_names = "username user_type language"
121
- exclude = dict(user_type="")
121
+ exclude = models.Q(user_type="")
122
122
  # abstract = not settings.SITE.is_demo_site
123
123
  detail_layout = None
124
124
 
@@ -5,7 +5,6 @@ This website is part of the Synodalsoft project:
5
5
  <a class="reference external" href="https://using.lino-framework.org">User Guide</a> |
6
6
  <a class="reference external" href="https://hosting.lino-framework.org">Hosting Guide</a> |
7
7
  <a class="reference external" href="https://dev.lino-framework.org">Developer Guide</a> |
8
- <a class="reference external" href="https://community.lino-framework.org">Community Guide</a> |
9
8
  <a class="reference external" href="https://luc.lino-framework.org">Luc’s blog</a>
10
9
  </p><p>
11
10
  <a href="https://www.synodalsoft.net">
lino/utils/format_date.py CHANGED
@@ -109,13 +109,18 @@ def day_and_weekday(d):
109
109
  # return d.strftime("%a%d")
110
110
 
111
111
 
112
- def ftl(t):
113
- # "format time long"
112
+ def fts(t):
113
+ # "format time short"
114
+ return t.strftime(settings.SITE.time_format_strftime)
115
+
116
+
117
+ def fdtl(t):
118
+ # "format datetime long"
114
119
  return "{} {}".format(
115
120
  t.strftime(settings.SITE.date_format_strftime),
116
121
  t.strftime(settings.SITE.time_format_strftime))
117
122
 
118
123
 
119
- def ftf(t):
120
- # "format time full"
121
- return "{} ({})".format(ftl(t), naturaltime(t))
124
+ def fdtf(t):
125
+ # "format datetime full"
126
+ return "{} ({})".format(fdtl(t), naturaltime(t))
lino/utils/media.py CHANGED
@@ -1,14 +1,12 @@
1
1
  # -*- coding: UTF-8 -*-
2
- # Copyright 2013-2021 Rumma & Ko Ltd
2
+ # Copyright 2013-2025 Rumma & Ko Ltd
3
3
  # License: GNU Affero General Public License v3 (see file COPYING for details)
4
4
  """Defines the :class:`MediaFile` class.
5
5
  """
6
6
 
7
- from os.path import join
8
7
  from pathlib import Path
9
-
10
8
  from django.conf import settings
11
- from lino.core.utils import is_devserver
9
+ # from lino.core.utils import is_devserver
12
10
 
13
11
  # davlink = settings.SITE.plugins.get('davlink', None)
14
12
  # has_davlink = davlink is not None and settings.SITE.use_java
@@ -24,7 +22,19 @@ class MediaFile(object):
24
22
  :attr:`webdav_root <lino.core.site.Site.webdav_root>`,
25
23
  :attr:`webdav_protocol <lino.core.site.Site.webdav_protocol>`
26
24
  and
27
- :attr:`webdav_url <lino.core.site.Site.webdav_url>`
25
+ :attr:`webdav_url <lino.core.site.Site.webdav_url>`.
26
+
27
+ .. attribute:: path
28
+
29
+ A :class:`pathlib.Path` naming the file on the server's file system.
30
+
31
+ .. attribute:: url
32
+
33
+ The URL to use for getting this file from a web client.
34
+
35
+ Used by :meth:`lino.modlib.jinja.XMLMaker.get_xml_file`,
36
+ :attr:`lino.core.tables.AbstractTable.export_excel` and others.
37
+
28
38
  """
29
39
 
30
40
  def __init__(self, editable, *parts):
@@ -37,7 +47,7 @@ class MediaFile(object):
37
47
  # webdav server and a protocol handler.
38
48
  # if is_devserver():
39
49
  if False:
40
- url = "file://" + join(settings.SITE.webdav_root, *parts)
50
+ url = "file://" + settings.SITE.webdav_root + "/".join(parts)
41
51
  else:
42
52
  url = settings.SITE.webdav_url + "/".join(parts)
43
53
  if settings.SITE.webdav_protocol:
@@ -48,31 +58,6 @@ class MediaFile(object):
48
58
  self.url = url
49
59
  self.path = path
50
60
 
51
- # @property
52
- # def path(self):
53
- # "Return the full filename on the server as a Path object."
54
-
55
- # @property
56
- # def name(self):
57
- # "return the filename on the server"
58
- # if self.editable and (has_davlink or settings.SITE.webdav_protocol):
59
- # return join(settings.SITE.webdav_root, *self.parts)
60
- # return join(settings.MEDIA_ROOT, *self.parts)
61
-
62
- # def get_url(self, request):
63
- # "return the url that points to file on the server"
64
- # if self.editable and request is not None:
65
- # if is_devserver():
66
- # url = "file://" + join(settings.SITE.webdav_root, *self.parts)
67
- # else:
68
- # url = settings.SITE.webdav_url + "/".join(self.parts)
69
- # url = request.build_absolute_uri(url)
70
- # if settings.SITE.webdav_protocol:
71
- # url = settings.SITE.webdav_protocol + "://" + url
72
- # return url
73
- #
74
- # return settings.SITE.build_media_url(*self.parts)
75
-
76
61
 
77
62
  class TmpMediaFile(MediaFile):
78
63
  def __init__(self, ar, fmt):
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: lino
3
- Version: 25.4.1
3
+ Version: 25.4.2
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,13 +1,13 @@
1
1
  lino/.cvsignore,sha256=1vrrWoP-WD8hPfCszHHIiJEi8KUMRCt5WvoKB9TSB1k,28
2
2
  lino/SciTEDirectory.properties,sha256=rCYi_e-6h8Yx5DwXhAa6MBPlVINcl6Vv9BQDYZV2_go,28
3
- lino/__init__.py,sha256=CcmiXMf7kjvfR3armsMJoL2tdYjk5eeMI5a5XcwanPk,6176
3
+ lino/__init__.py,sha256=vPakvNPcc39olTexcyLdrhwTrq7ulMVBrgPP5ZFuzIA,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=MGIQvx-xvYSyfgtSHkolpJ2aQlvChEFyR9_Xw1qt49w,90988
7
+ lino/help_texts.py,sha256=RPD-0OJQz8DZtJvr4FuZxiOnnGzNK9jdVYJykoSFWRc,91431
8
8
  lino/api/__init__.py,sha256=WmzHU-rHdZ68se_nI0SmepQTGE8-cd9tPpovHRH9aag,512
9
9
  lino/api/ad.py,sha256=F6SrcKPRRalHKOZ7QLwsRWGq9hhykQIeo0b85cEk9NQ,314
10
- lino/api/dd.py,sha256=FSv9gDstKcKM1qFgSSJpJOGhvT0bDTIqwjcPA9pbMm8,7431
10
+ lino/api/dd.py,sha256=-mxiSJS27LGCWyOgboA8qDudTkE9vg9vmUGOT08Hq6A,7410
11
11
  lino/api/doctest.py,sha256=87j1_xGpomQlEmUUh8CprBFbbqKuQe1OWr5iIXABWag,24028
12
12
  lino/api/rt.py,sha256=OCYWhrWnMcL988MdvBLBEP8qKQJEGXQhVoam_X0sotU,1376
13
13
  lino/api/selenium.py,sha256=bOu8UaNz3Q7lGVvxjmvrtYtSWn1xfI1f5MN5sVcdYr8,9383
@@ -36,13 +36,13 @@ lino/core/choicelists.py,sha256=RkgDhPjjC5lS0Zew1F5yNL2y3wYTIJOmqYEAnz5prxA,3653
36
36
  lino/core/classproperty.py,sha256=_E95WPAs7BWbAuFpPvoYM2ZwW_mbq3rvF7o43WsMq_8,4316
37
37
  lino/core/constants.py,sha256=chvG1TrwD2gVMmL4nTOZtO8NcffcclcUv3zBE8mMoiQ,4503
38
38
  lino/core/dashboard.py,sha256=1ASWZ9I1LQBUJZ0JKiwW_g_1Pix3jb4SWUFC2cUM23g,6537
39
- lino/core/dbtables.py,sha256=ib3yzqeQgzxdero2DnfSxkGYncP7TfLx37uniVgl13A,29170
39
+ lino/core/dbtables.py,sha256=bV1hcr9MXCT3doauxcOrnTZeBmHtb1tAQrLlnfvQj_o,29113
40
40
  lino/core/dbutils.py,sha256=_QHcWd-ajLUwt5G8uOp8d47lZQKD3VseHnqKJke18rA,263
41
41
  lino/core/ddh.py,sha256=dYScxWKTOCDEgow7wJNJe812ESasmmITPK2ovraBQno,3172
42
42
  lino/core/diff.py,sha256=XQ-oQQDS_v3kXd4eRP9Hwr5UCgp-TPZIPVav9ZblUno,5882
43
43
  lino/core/elems.py,sha256=3PZ-0RDr6c8QoZa9sWKV36w-wn1Oj2xFpjW8eHrEpzs,108811
44
44
  lino/core/exceptions.py,sha256=QDxDo5cllSyXQ8VWet9hGXzNadxCOmwMVrFXc6V-vpE,665
45
- lino/core/fields.py,sha256=9yb4rBVebV1blvk4qdEVmhW8xqwE2fCfOtBtI7bXYXw,57645
45
+ lino/core/fields.py,sha256=ZnNMTI_1VbNbfIABa2r3hiOByZ2BF0M4TUOOFZfL72U,58106
46
46
  lino/core/frames.py,sha256=ISxgq9zyZfqW3tDZMWdKi9Ij455lT_81qBH0xex0bfE,1161
47
47
  lino/core/gfks.py,sha256=6VXn2FSIXOrwVq0stfbPevT37EWg1tg4Fn-HMNVnbmk,1970
48
48
  lino/core/help.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -56,12 +56,12 @@ lino/core/model.py,sha256=YENZ-v1sggtdTitgmC8jBmuihN1EsKXQMlbPAuTSat8,36870
56
56
  lino/core/permissions.py,sha256=Fnemz3NwWz21X0YATI9Q7ba2FcAdg-EMLHjIcbt_AVU,6840
57
57
  lino/core/plugin.py,sha256=8FdxFF5tqHK-N8aXANzpWXY97hj6Lty4ulM6PWcq5A8,6833
58
58
  lino/core/renderer.py,sha256=auQn2v9bBp6jLIH3hXRQXbrjFrTmsO1o0DwaoXBn1eE,47310
59
- lino/core/requests.py,sha256=67335dTFqcwfF1PvWFmWwDKmVyJvDW9dMRQJF_X_Q9I,95093
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=RsvMMSaCHzGcVpvUWKhkW4ugP6Fj7EfQXx5NegzJ9PQ,83164
62
+ lino/core/site.py,sha256=9Ji_aj1Q3HHY6d3VqS1La4JP5K7S0tAg17Ns7gQ_TYg,83676
63
63
  lino/core/store.py,sha256=6pd4J5Y-U7Muz4mKFSL6K9KEZpJUbpM-I7RQTWyCo-8,51483
64
- lino/core/tables.py,sha256=rMMDmDKMcWJ39fD9Q_EFV7qdcXMktwO40G4ATyKuRGg,24427
64
+ lino/core/tables.py,sha256=Yoj-H2ASW9xIger8kuW8xst5A_tEmtWpV17L11Ebca0,24348
65
65
  lino/core/urls.py,sha256=06QlmN1vpxjmb5snO3SPpP6lX1pMdE60bTiBiC77_vQ,2677
66
66
  lino/core/user_types.py,sha256=0iSYmzr2M9v2Mn2y6hzAZeqareUT-gD7l3MfIPyG9ZI,867
67
67
  lino/core/userprefs.py,sha256=cmufIS9xJErKDrw05uoWtQTUR6WRWIGkU1KdAqzNr5M,2850
@@ -116,7 +116,7 @@ lino/management/commands/run.py,sha256=MiK53KIACYKDTKF6knJGwU-uzEApGnDxByi-3_nrT
116
116
  lino/management/commands/show.py,sha256=dS_TotAEeH8zo_wufQgca6q0Yj9qeWsJUOUcss6YD7E,1439
117
117
  lino/management/commands/syncscreenshots.py,sha256=XYZhqfm5_RwJzVFNGhHJKucl4j6T6mYo2GsDaUzvjAs,1561
118
118
  lino/management/commands/update_conf.py,sha256=saAaQdPhn3mNOoHcBxOFSf_vBEgM-aopTHq1sJN20Bo,1026
119
- lino/mixins/__init__.py,sha256=ADIq0rPGlAY4ifSaMvqahXvCMnbFzq2qAWcyyUkx4Ds,8930
119
+ lino/mixins/__init__.py,sha256=tUVJjAWMZv3gNOWKWodxeHXXOmEcxqvMCNXpltmXXO4,8832
120
120
  lino/mixins/dupable.py,sha256=ofyYrcT9WiYdVTkzgXyXaBu1kS-y197YnFTnZNALUHo,10233
121
121
  lino/mixins/duplicable.py,sha256=a5-1jZZWX-kgKg5GNhV6yaSGOgfTyd2lWHtY35bVmes,4247
122
122
  lino/mixins/human.py,sha256=YDIfIHHAaVmzd3uGsJp_vDvkaBWOp09I7i4AGNy9YsQ,13255
@@ -160,7 +160,7 @@ lino/modlib/bootstrap3/static/bootstrap-3.3.4/js/bootstrap.min.js,sha256=1f0XPQD
160
160
  lino/modlib/bootstrap3/static/bootstrap-3.3.4/js/bootstrap_lino.js,sha256=PxA9wsbAmzTJYPw7fMMg9Sh3CsS-bquziVKe1kjUYY8,114
161
161
  lino/modlib/bootstrap3/static/bootstrap-3.3.4/js/npm.js,sha256=x6qCoap9RSJKONkm0q2v9_5K71vNr6Kke9rAV_RCLC0,484
162
162
  lino/modlib/changes/__init__.py,sha256=3-CO7xI-U-rKDu_-WJg7N4FaIgqmFcj81uzyQlNYrFE,1414
163
- lino/modlib/changes/models.py,sha256=LvtiDewZAx-4z0uPtsznHBXUKtngXac5t-q7a7uB6BY,9549
163
+ lino/modlib/changes/models.py,sha256=wflXkqKSC-0ZynPGEd6PR4agP6zMuYO6v6v2NcRJgoo,9537
164
164
  lino/modlib/changes/utils.py,sha256=4jz8QXgaBxfmCBUvDeZeh7lkdwfTq7OBXBiFhMmANqA,2550
165
165
  lino/modlib/checkdata/__init__.py,sha256=raUCoYi4WZLKVLG3GqH0ml1eH_YJXqY-EgXsKUe6iRY,2829
166
166
  lino/modlib/checkdata/choicelists.py,sha256=OGv3mmr5DPPahoBliAOFIx_H6ysuW3ZpcrI4fIq1SB0,5244
@@ -185,8 +185,8 @@ lino/modlib/dashboard/models.py,sha256=EgRNg88dmz-OlIdi1SyEuerWMsRqKIfqE2MgKL7kA
185
185
  lino/modlib/dupable/__init__.py,sha256=fIQ8wj-T8ZbkjwQgW_-ankJsHLjPMepOTo32mJXNCvI,532
186
186
  lino/modlib/dupable/mixins.py,sha256=B7JRVDRszf7ojFBAeb-Md_fJMc9Sp5u-emLEAtR6s2g,6318
187
187
  lino/modlib/dupable/models.py,sha256=0watviKwTiVwlArC54V3IxVVfcB1Yg5kO6ed2xCM9a0,4595
188
- lino/modlib/export_excel/__init__.py,sha256=k11dEbh1VgA7cMaUdMhiJvHNboX4BqN0Z5Wj9fV7rWw,469
189
- lino/modlib/export_excel/models.py,sha256=MoGj3RyAj8PBy75HFGBv9Y1QnsG-H_ajRK27UDpZPKo,5094
188
+ lino/modlib/export_excel/__init__.py,sha256=HrsrhXjIMvMHRGu8whH3A_WijZWrH35p2cQsFXK60DY,356
189
+ lino/modlib/export_excel/models.py,sha256=A-lFS-xFDSPHcVbY0OI7VsuGr82jJyLDhXH2Dcv80Os,5039
190
190
  lino/modlib/extjs/__init__.py,sha256=6UBWAWSROwy3DfTXQmVUVJTF6eZ_e2k3BEfE4wtqVhU,10172
191
191
  lino/modlib/extjs/ext_renderer.py,sha256=DVUoCmy-knDO_i6s8yVGy8xI2qsWBA8GByiv29Lm8ts,60609
192
192
  lino/modlib/extjs/views.py,sha256=7IlzseAwtGPZtpsbow103mEV8VuKIH9uhfZmv9StneA,26292
@@ -3518,7 +3518,7 @@ lino/modlib/ipdict/models.py,sha256=9-pjj_xXdndhQaQI8sgXcmODxurST_aFcShGwomiYKk,
3518
3518
  lino/modlib/jinja/__init__.py,sha256=XSa-e1qZGabl8EmRDKPRtmzgBRole5ZbxAUBR_m-ds0,3418
3519
3519
  lino/modlib/jinja/choicelists.py,sha256=QHjWQWLnJCKSGnLIKeGqnCw41JYvcbTkCeXjBpWh23w,1466
3520
3520
  lino/modlib/jinja/loader.py,sha256=MX027X_UuQPqq0wOUr06QnOkdTzGpksNv0Om1CGp61I,1765
3521
- lino/modlib/jinja/mixins.py,sha256=UPuVWtfw1p07h8uAaIzS7q-NLdmCpaSi-scqZofmOI0,2426
3521
+ lino/modlib/jinja/mixins.py,sha256=FU0AcDyJwcip4EAlkG5vjuLFVZFLyiXqPXU4bjGx-S0,2490
3522
3522
  lino/modlib/jinja/models.py,sha256=vXNFS78qP-qIzeSmnXJNOpvW_IfH1h6hhPgYjfkvvD0,207
3523
3523
  lino/modlib/jinja/renderer.py,sha256=PtjhJdidlOOGzRdyUQlaEIj2FhFFuthFB80-bkSfsoM,6156
3524
3524
  lino/modlib/jinja/config/jinja/status.jinja.rst,sha256=XEsfXd82B0fLsBfds5-pXdeDWVr4ccTv7WyGa9ayigk,312
@@ -3535,7 +3535,7 @@ lino/modlib/languages/fixtures/iso-639-3_20100707.tab,sha256=u8PwI2s8shy0_Val5-s
3535
3535
  lino/modlib/linod/__init__.py,sha256=efmj_Kz3OO2zF1lvs7P459iufYGimH1-6Ge6Cbq85tQ,2665
3536
3536
  lino/modlib/linod/choicelists.py,sha256=Cu82s1QpcGFmKUXJsg-7TSqpaESBCZKOEfxzFlJP06I,2626
3537
3537
  lino/modlib/linod/consumers.py,sha256=XBjA1fflJ-e9yWRMKXyQAhrOklYzs5JRhEeGMOKWFqM,6730
3538
- lino/modlib/linod/mixins.py,sha256=yE0J51bRA8u5YekrNnAlWoOBee3z5rqDE8yxHgiqGMM,12739
3538
+ lino/modlib/linod/mixins.py,sha256=gZci22_m3GNUNycLSvMelG1sUQLCCho2gniPTegopMs,13027
3539
3539
  lino/modlib/linod/models.py,sha256=tbPKNk3BLnnAPWvbNrWhz713b3IPM0z8UoctJcwVhhw,2357
3540
3540
  lino/modlib/linod/routing.py,sha256=FiG0JVqp9TWWkNpl9Y_50UCAJ7ZEImDXkQUhlg4aGL4,2094
3541
3541
  lino/modlib/linod/utils.py,sha256=dE973Xib6Be1DvNsZ0M5wzY_jpkk35R21WKs-jQPorM,339
@@ -3554,8 +3554,8 @@ lino/modlib/notify/actions.py,sha256=ClRKDjmgx3m43IZ5cx0TdxXN_pU6hLIlo_jCwEW2LhY
3554
3554
  lino/modlib/notify/api.py,sha256=dIuyn7uRF1dDgoGAqvFQvuJImLl1Q0g79skBJvnXMYs,3339
3555
3555
  lino/modlib/notify/choicelists.py,sha256=7ZkYNMOXNDfyTvdx8Sn-Gsma9f31o-6G1CtivXmQDmA,1324
3556
3556
  lino/modlib/notify/consumers.py,sha256=YaEAhAi_Oq5ovakuP5DI21YIAMvRqQcf4K8MBilcN2w,1529
3557
- lino/modlib/notify/mixins.py,sha256=oRArn5ZCJ3Ot4OAYXleyDEEkpe-y50qKdJTGmWq8ZHs,3495
3558
- lino/modlib/notify/models.py,sha256=DfhMc817tgRrEinJP2rFo-nDIK--NCdeK8-_ojcB_JU,18936
3557
+ lino/modlib/notify/mixins.py,sha256=E_PZuljGnE0Xwzxloe3vorCFTBZTsXhpTfHX0BrPvV4,3928
3558
+ lino/modlib/notify/models.py,sha256=LtefhX8wYtP2EYpFN1-VTV8ZAjSTbbbVoAh68LHU1KE,18464
3559
3559
  lino/modlib/notify/utils.py,sha256=e4TAJr8mgicwEjgyuP-bOiw1VGNA6tVo_hab1q-oSNc,328
3560
3560
  lino/modlib/notify/views.py,sha256=Ar2L_Tv1qqU5giHu2b0h_AKc3utvgfZwda4qncqoBsk,957
3561
3561
  lino/modlib/notify/config/notify/individual.eml,sha256=Ct8fLx7dz_adE_ua8S87dYFSNESCHRbRGx9Ven1MlLI,307
@@ -4396,7 +4396,7 @@ lino/modlib/uploads/actions.py,sha256=6Bi5hDUdR3cOA4ROARqPzRwQKtUcHoSl3QM44iWVrt
4396
4396
  lino/modlib/uploads/choicelists.py,sha256=-7SODzA_0afrBgPh7-j8kpz0VdzDXy2U7dLhhwE2kG8,1327
4397
4397
  lino/modlib/uploads/dummy_upload.odt,sha256=VHG2YkykCg8VqoXx8Hm37QYunvdbmg_jCyygiZseKF8,10447
4398
4398
  lino/modlib/uploads/dummy_upload.pdf,sha256=hdAx7s3V10lqVekSGugzkn_Hqxx3V4OkCNVNQI2OsFY,18730
4399
- lino/modlib/uploads/mixins.py,sha256=blYaqtbO0m9o0PdFdM7HB6QvoGC5bMBmRqFe7R8x1bM,8362
4399
+ lino/modlib/uploads/mixins.py,sha256=Vpr7BB0NvDIyG59KnR-j-4KH8CleSGmtjUSrS5YGeu0,8356
4400
4400
  lino/modlib/uploads/models.py,sha256=aqneUEaai_wgmZTtm1Tj5RNc9k3yGQVPzLE2aj7MdCw,17527
4401
4401
  lino/modlib/uploads/roles.py,sha256=ae0wf_vC4O6MffDx8abpjW1M2oWOp5VzOvt_ckk72Cc,191
4402
4402
  lino/modlib/uploads/ui.py,sha256=gVQQJUJEfBAESkU5ueKXs_XKZYiXYaIMh1CyNXBrcyk,8666
@@ -4406,12 +4406,12 @@ lino/modlib/uploads/fixtures/demo.py,sha256=igZevIAwJFjt5y90lnkzVnaWYgZLxmEtOFuF
4406
4406
  lino/modlib/uploads/fixtures/demo3.py,sha256=q0bwZrx5XtRsRlFpsa33fL0sCl7IdCYaP9E1rhCnJt4,547
4407
4407
  lino/modlib/uploads/fixtures/std.py,sha256=nb5oRcX_WrkTLaGoch6PT7GA0FPKmqbN-BdlPq-hHSc,516
4408
4408
  lino/modlib/users/__init__.py,sha256=40f-PheIyHqAzGXQbkvEKAnZ9_bb8RkaLAKIqNE96qg,3215
4409
- lino/modlib/users/actions.py,sha256=Nik2CoxKZWoGIsPT50mWESzHSTQx9WiBXWG64CUFyS8,18074
4409
+ lino/modlib/users/actions.py,sha256=nHjxh6CfpO--JN4ApWvZ-07GonahCW7r4k6xKjF6AP4,18033
4410
4410
  lino/modlib/users/choicelists.py,sha256=-X76C1NxIs5e7rFHp5Z0kjJkA1NlOP2vdLKGkI2wZRU,3876
4411
4411
  lino/modlib/users/mixins.py,sha256=muW6-LwHF2L6uvGO0h3qIh1_SeJrgaeooPvjqBiXbAI,14277
4412
- lino/modlib/users/models.py,sha256=tdROzvtd3cMxsXKtYZjcUv2XJU7hQLbyBCiE0zVvaOo,16470
4412
+ lino/modlib/users/models.py,sha256=AheMwBWhjlLJzPXNCdJax1eZfsKE0906kIhFhcjjuO0,16425
4413
4413
  lino/modlib/users/roles.py,sha256=yi29ELbWU1VtteGARaxetxmsCkZQHA2oJiD0dXujMiE,320
4414
- lino/modlib/users/ui.py,sha256=ZbA8NaAWR3SIUmC6GXbUumJfsOQlskwSE1gDYBVAXY8,13278
4414
+ lino/modlib/users/ui.py,sha256=xL3KVY_TB_OuaD5ewe5H7eZlnW_2mGJLUJySgLZ0klM,13282
4415
4415
  lino/modlib/users/utils.py,sha256=bD0DJZsUy59xw9N-tx3W_h30_R10GT2qXZVzYtGWMa8,1963
4416
4416
  lino/modlib/users/config/users/verification_response.html,sha256=8X1sEn53ploQgB6ds0UfmmkZpcvP9KSwWiQjRE_q970,772
4417
4417
  lino/modlib/users/config/users/welcome_email.eml,sha256=bPSPbJKIPFRVWPDCuhNquQYwQtXzYGZgs7wICoiutS8,1078
@@ -4489,7 +4489,7 @@ lino/sphinxcontrib/logo/templates/footer.html,sha256=2GkZgY9tUVC-6-KtD7RnuZisd0Y
4489
4489
  lino/sphinxcontrib/logo/templates/globaltoc.html,sha256=S7-Dg4sjTXhWIrcmaNQ0QugRzM9J-knpPpD9lmEaEv8,400
4490
4490
  lino/sphinxcontrib/logo/templates/layout.html,sha256=wtJBqvZyykSiiH2oiHQFOg5N_ECNTtrsy5s4JOvOATI,567
4491
4491
  lino/sphinxcontrib/logo/templates/links.html,sha256=SccaqiI_Fg4lYT_nxDWfGZXLLD8FhhX2ln7WomsDFUk,174
4492
- lino/sphinxcontrib/logo/templates/part-of-synodalsoft.html,sha256=tX8zlxJh2Pw0Uk5V4HQ-kUWAggYilDvy6z1ltWdL8zk,1523
4492
+ lino/sphinxcontrib/logo/templates/part-of-synodalsoft.html,sha256=tHh0SfRoIqpoxkA5ZA5_Obc_9Bs5Uh4d3qUSC3oprdw,1427
4493
4493
  lino/sphinxcontrib/logo/templates/unused_copyright.html,sha256=ct3QmWrUk0IS6hvr_XH5fMbseTH7CTzKhRH1UOUTWG8,205
4494
4494
  lino/static/blueprint.css,sha256=meK9oC38foNI4x_YuylIU9TJdGiZi4HM4PFAWpjoiiw,131
4495
4495
  lino/static/bootstrap.css,sha256=-8bGCqxOzajs3T4lvqWjTUGvvQ1uwVH886fr4cIKPs4,1156
@@ -4595,7 +4595,7 @@ lino/utils/diag.py,sha256=8BGsPrLd1_Fympy3PKiTpX1MdMWGApXr6IBoVOkWRxE,18314
4595
4595
  lino/utils/djangotest.py,sha256=Phz1qNp0wDonZRja5dxbCk0Xl3a73gZNiKK8v9tAgZg,8334
4596
4596
  lino/utils/dpy.py,sha256=Hw4ofFnhRPAE2PsPf9r5RpzcfVLQdIjtOe-XtMMLtuE,20661
4597
4597
  lino/utils/fieldutils.py,sha256=IfwuTpSirKYEk5h1URxQ9CF6i0ZPcsuNQHyk-LQOdRE,2874
4598
- lino/utils/format_date.py,sha256=esVElXGtmc_M5CAoFyomVr1hoi3ITn95e1ToZ09li-U,3008
4598
+ lino/utils/format_date.py,sha256=4CMkx8UHL1WZnOl77c4MRiXEX33SjBal-Vv_wKRiByk,3117
4599
4599
  lino/utils/html.py,sha256=pcE0UQmdQGxxmb-p0mBb47zNbRMXLP9cxxrXTLs4gbY,3143
4600
4600
  lino/utils/html2odf.py,sha256=Hxw4HiIHY1ZCjb4_JLykVHbr6yAMhhHrnrCnLNDYKAs,4826
4601
4601
  lino/utils/html2xhtml.py,sha256=fvrIoLBFpiXtYO3UYaIgAIDjf6ATvrxolQX4etxS57Y,2119
@@ -4605,7 +4605,7 @@ lino/utils/jscompressor.py,sha256=j9UTaaPCfRZLrWUh6PBp0KDDM0QshG7XAFzp-R_elOs,52
4605
4605
  lino/utils/jsgen.py,sha256=yN0uUNFp4FCxsSPJBDsdkvtAJVL50f7CpW4hR31Rm4k,15215
4606
4606
  lino/utils/latex.py,sha256=cv5rJjtcslTVoRNjAmlUd2CCpC0GPO-YnvJgNJJe0R8,2218
4607
4607
  lino/utils/mdbtools.py,sha256=R1LHskyDLtwBcYRDA14PU2XeSku9gRUIV52f4Sr-Hto,5310
4608
- lino/utils/media.py,sha256=_JSIL7GQONbYknMzhYNkD0LNipMKXJwOIf4V1yiklDY,3013
4608
+ lino/utils/media.py,sha256=Vk-Mt-w9agYWhKHW6zqr9p6cu5uYQaeaPQU28cbgUDU,2306
4609
4609
  lino/utils/mti.py,sha256=CSOpu0BxSQ6ii5Sky3eVcUMp_jCcBwgOHi3_ydWf3YU,11193
4610
4610
  lino/utils/mytidylib.py,sha256=38qV49amgCi4CE4zRmHt_RrqpF8JOV0SDsBj0bIYy8s,4647
4611
4611
  lino/utils/odsreader.py,sha256=odz2VYTNa0l5mO2FLgm4HW4bwA6OS78R5G4c_HGRfK8,4355
@@ -4633,8 +4633,8 @@ lino/utils/xml.py,sha256=EGDnO1UaREst9fS7KTESdbHnrrVCwKbRQdvut6B6GmQ,1612
4633
4633
  lino/utils/mldbc/__init__.py,sha256=QqWRlzeXaOmFfbCk-vTY3SZMn1-FCf67XnpZdd_Nim0,1134
4634
4634
  lino/utils/mldbc/fields.py,sha256=tAX8G5UKigr9c6g0F3ARIjZZtg406mdaZ--PWSbiH9E,2873
4635
4635
  lino/utils/mldbc/mixins.py,sha256=CkYe5jDa7xp9fJq_V8zcZf8ocxgIjUgHc9KZccvA_Yw,1945
4636
- lino-25.4.1.dist-info/METADATA,sha256=tByKXlu49CnGrkvvWX-WVmYBSYIQyjUr3rhA5AIKys4,42534
4637
- lino-25.4.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
4638
- lino-25.4.1.dist-info/licenses/AUTHORS.rst,sha256=8VEm_G4HOmYEa4oi1nVoKKsdo4JanekEJCefWd2E8vk,981
4639
- lino-25.4.1.dist-info/licenses/COPYING,sha256=DZak_2itbUtvHzD3E7GNUYSRK6jdOJ-GqncQ2weavLA,34523
4640
- lino-25.4.1.dist-info/RECORD,,
4636
+ lino-25.4.2.dist-info/METADATA,sha256=8d3X3OB46Yp6iZ6qzfVpolugwultuOMXphYCLHdK7eg,42534
4637
+ lino-25.4.2.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
4638
+ lino-25.4.2.dist-info/licenses/AUTHORS.rst,sha256=8VEm_G4HOmYEa4oi1nVoKKsdo4JanekEJCefWd2E8vk,981
4639
+ lino-25.4.2.dist-info/licenses/COPYING,sha256=DZak_2itbUtvHzD3E7GNUYSRK6jdOJ-GqncQ2weavLA,34523
4640
+ lino-25.4.2.dist-info/RECORD,,
File without changes