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 +1 -1
- lino/api/dd.py +8 -10
- lino/core/dbtables.py +2 -3
- lino/core/fields.py +30 -15
- lino/core/requests.py +24 -9
- lino/core/site.py +34 -16
- lino/core/tables.py +27 -29
- lino/help_texts.py +4 -0
- lino/mixins/__init__.py +27 -30
- lino/modlib/changes/models.py +2 -2
- lino/modlib/export_excel/__init__.py +6 -8
- lino/modlib/export_excel/models.py +1 -3
- lino/modlib/jinja/mixins.py +4 -1
- lino/modlib/linod/mixins.py +17 -12
- lino/modlib/notify/mixins.py +14 -0
- lino/modlib/notify/models.py +10 -25
- lino/modlib/uploads/mixins.py +1 -1
- lino/modlib/users/actions.py +2 -4
- lino/modlib/users/models.py +13 -19
- lino/modlib/users/ui.py +1 -1
- lino/sphinxcontrib/logo/templates/part-of-synodalsoft.html +0 -1
- lino/utils/format_date.py +10 -5
- lino/utils/media.py +16 -31
- {lino-25.4.1.dist-info → lino-25.4.2.dist-info}/METADATA +1 -1
- {lino-25.4.1.dist-info → lino-25.4.2.dist-info}/RECORD +28 -28
- {lino-25.4.1.dist-info → lino-25.4.2.dist-info}/WHEEL +0 -0
- {lino-25.4.1.dist-info → lino-25.4.2.dist-info}/licenses/AUTHORS.rst +0 -0
- {lino-25.4.1.dist-info → lino-25.4.2.dist-info}/licenses/COPYING +0 -0
lino/__init__.py
CHANGED
@@ -31,7 +31,7 @@ from django import VERSION
|
|
31
31
|
from django.apps import AppConfig
|
32
32
|
from django.conf import settings
|
33
33
|
import warnings
|
34
|
-
__version__ = '25.4.
|
34
|
+
__version__ = '25.4.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
|
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(
|
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(
|
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=
|
117
|
+
decimal_places=0,
|
116
118
|
)
|
117
119
|
defaults.update(kwargs)
|
118
|
-
super(
|
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(
|
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 {} "
|
1005
|
-
|
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.
|
1115
|
-
|
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,
|
1124
|
-
|
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
|
1148
|
+
return self.dummy_value
|
1136
1149
|
|
1137
1150
|
def get_default(self):
|
1138
|
-
return
|
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=
|
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 =
|
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,
|
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
|
-
|
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(
|
2076
|
-
return
|
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
|
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
|
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-
|
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
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
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
|
-
"""
|
401
|
-
|
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
|
-
|
407
|
-
|
408
|
-
|
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
|
-
|
413
|
-
|
414
|
-
|
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
|
-
"""
|
425
|
-
`
|
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
|
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-
|
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 =
|
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 =
|
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
|
lino/modlib/changes/models.py
CHANGED
@@ -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=
|
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=
|
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-
|
1
|
+
# Copyright 2014-2025 Rumma & Ko Ltd
|
2
2
|
# License: GNU Affero General Public License v3 (see file COPYING for details)
|
3
|
-
"""
|
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-
|
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
|
lino/modlib/jinja/mixins.py
CHANGED
@@ -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 =
|
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)
|
lino/modlib/linod/mixins.py
CHANGED
@@ -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 =
|
54
|
-
|
55
|
-
|
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
|
-
|
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
|
-
|
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.
|
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.
|
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.
|
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 =
|
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
|
-
|
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)
|
lino/modlib/notify/mixins.py
CHANGED
@@ -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:
|
lino/modlib/notify/models.py
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
# -*- coding: UTF-8 -*-
|
2
|
-
# Copyright 2011-
|
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 =
|
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 =
|
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 =
|
300
|
+
msg.sent = dd.now()
|
301
301
|
if dd.plugins.notify.mark_seen_when_sent:
|
302
|
-
msg.seen =
|
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=
|
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
|
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)
|
lino/modlib/uploads/mixins.py
CHANGED
@@ -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,
|
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'):
|
lino/modlib/users/actions.py
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
# -*- coding: UTF-8 -*-
|
2
|
-
# Copyright 2011-
|
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=
|
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:
|
lino/modlib/users/models.py
CHANGED
@@ -1,7 +1,9 @@
|
|
1
1
|
# -*- coding: UTF-8 -*-
|
2
|
-
# Copyright 2011-
|
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
|
-
|
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 =
|
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
|
-
<
|
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 =
|
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
|
113
|
-
# "format time
|
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
|
120
|
-
# "format
|
121
|
-
return "{} ({})".format(
|
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-
|
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://" +
|
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.
|
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=
|
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=
|
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
|
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=
|
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=
|
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=
|
59
|
+
lino/core/requests.py,sha256=xIq6kXhjLoYbSkW40_BH-uj0WvmfkvOVLGm2piiGtJo,95835
|
60
60
|
lino/core/roles.py,sha256=PXwk436xUupxdbJcygRSYFu7ixfKjAJPQRUQ8sy0lB0,4425
|
61
61
|
lino/core/signals.py,sha256=0JT89mkjSbRm57QZcSI9DoThoKUGkyi-egNhuLUKEds,948
|
62
|
-
lino/core/site.py,sha256=
|
62
|
+
lino/core/site.py,sha256=9Ji_aj1Q3HHY6d3VqS1La4JP5K7S0tAg17Ns7gQ_TYg,83676
|
63
63
|
lino/core/store.py,sha256=6pd4J5Y-U7Muz4mKFSL6K9KEZpJUbpM-I7RQTWyCo-8,51483
|
64
|
-
lino/core/tables.py,sha256=
|
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=
|
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=
|
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=
|
189
|
-
lino/modlib/export_excel/models.py,sha256=
|
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=
|
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=
|
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=
|
3558
|
-
lino/modlib/notify/models.py,sha256=
|
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=
|
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=
|
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=
|
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=
|
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=
|
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=
|
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=
|
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.
|
4637
|
-
lino-25.4.
|
4638
|
-
lino-25.4.
|
4639
|
-
lino-25.4.
|
4640
|
-
lino-25.4.
|
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
|
File without changes
|
File without changes
|