lino 24.10.3__py3-none-any.whl → 24.11.0__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/doctest.py +11 -10
- lino/api/rt.py +2 -3
- lino/config/admin_main_base.html +2 -2
- lino/core/actors.py +68 -33
- lino/core/choicelists.py +2 -2
- lino/core/dashboard.py +2 -1
- lino/core/dbtables.py +3 -3
- lino/core/elems.py +8 -4
- lino/core/fields.py +11 -3
- lino/core/kernel.py +4 -5
- lino/core/layouts.py +1 -1
- lino/core/model.py +1 -9
- lino/core/plugin.py +1 -0
- lino/core/renderer.py +19 -19
- lino/core/requests.py +73 -38
- lino/core/site.py +5 -45
- lino/core/store.py +12 -15
- lino/core/tables.py +0 -17
- lino/core/utils.py +28 -1
- lino/core/views.py +2 -1
- lino/help_texts.py +6 -2
- lino/management/commands/show.py +2 -4
- lino/mixins/periods.py +9 -3
- lino/mixins/polymorphic.py +3 -3
- lino/mixins/ref.py +6 -3
- lino/modlib/checkdata/__init__.py +3 -3
- lino/modlib/extjs/ext_renderer.py +1 -1
- lino/modlib/linod/consumers.py +2 -3
- lino/modlib/memo/mixins.py +1 -1
- lino/modlib/periods/__init__.py +12 -1
- lino/modlib/periods/fixtures/std.py +2 -1
- lino/modlib/periods/models.py +79 -75
- lino/modlib/printing/actions.py +2 -0
- lino/modlib/publisher/ui.py +2 -2
- lino/modlib/system/__init__.py +0 -2
- lino/modlib/system/choicelists.py +55 -1
- lino/modlib/system/models.py +1 -0
- lino/modlib/users/models.py +2 -2
- lino/modlib/weasyprint/__init__.py +2 -0
- lino/utils/__init__.py +8 -9
- lino/utils/djangotest.py +2 -1
- lino/utils/html.py +31 -0
- {lino-24.10.3.dist-info → lino-24.11.0.dist-info}/METADATA +1 -1
- {lino-24.10.3.dist-info → lino-24.11.0.dist-info}/RECORD +48 -48
- {lino-24.10.3.dist-info → lino-24.11.0.dist-info}/WHEEL +0 -0
- {lino-24.10.3.dist-info → lino-24.11.0.dist-info}/licenses/AUTHORS.rst +0 -0
- {lino-24.10.3.dist-info → lino-24.11.0.dist-info}/licenses/COPYING +0 -0
@@ -20,7 +20,8 @@ class Plugin(ad.Plugin):
|
|
20
20
|
"The config descriptor for this plugin."
|
21
21
|
|
22
22
|
verbose_name = _("Checkdata")
|
23
|
-
needs_plugins = ["lino.modlib.users", "lino.modlib.gfks",
|
23
|
+
needs_plugins = ["lino.modlib.users", "lino.modlib.gfks",
|
24
|
+
"lino.modlib.office", "lino.modlib.linod"]
|
24
25
|
|
25
26
|
# plugin settings
|
26
27
|
responsible_user = None # the username (a string)
|
@@ -50,8 +51,7 @@ class Plugin(ad.Plugin):
|
|
50
51
|
User = self.site.models.users.User
|
51
52
|
try:
|
52
53
|
self._responsible_user = User.objects.get(
|
53
|
-
username=self.responsible_user
|
54
|
-
)
|
54
|
+
username=self.responsible_user)
|
55
55
|
except User.DoesNotExist:
|
56
56
|
msg = "Invalid username '{0}' in `responsible_user` "
|
57
57
|
msg = msg.format(self.responsible_user)
|
@@ -986,7 +986,7 @@ class ExtRenderer(JsCacheRenderer):
|
|
986
986
|
tbl = dh.layout._datasource
|
987
987
|
yield ""
|
988
988
|
yield "Lino.%s = Ext.extend(Lino.ActionFormPanel,{" % dh.layout._formpanel_name
|
989
|
-
for k, v in
|
989
|
+
for k, v in dh.main.ext_options().items():
|
990
990
|
if k != "items":
|
991
991
|
yield " %s: %s," % (k, py2js(v))
|
992
992
|
assert tbl.action_name is not None
|
lino/modlib/linod/consumers.py
CHANGED
@@ -57,9 +57,8 @@ class LinodConsumer(AsyncConsumer):
|
|
57
57
|
async def run_background_tasks(self, event: dict):
|
58
58
|
# 'run.background.tasks' in `pm linod`
|
59
59
|
from lino.modlib.linod.mixins import start_task_runner
|
60
|
-
|
61
|
-
|
62
|
-
ar = settings.SITE.login()
|
60
|
+
from lino.core.requests import BaseRequest
|
61
|
+
ar = BaseRequest()
|
63
62
|
asyncio.ensure_future(start_task_runner(ar))
|
64
63
|
|
65
64
|
async def send_push(self, event):
|
lino/modlib/memo/mixins.py
CHANGED
@@ -286,7 +286,7 @@ class MemoReferrable(dd.Model):
|
|
286
286
|
def memo2html(self, ar, txt, **kwargs):
|
287
287
|
if txt:
|
288
288
|
kwargs.update(title=txt)
|
289
|
-
e = self.as_summary_item(ar
|
289
|
+
e = self.as_summary_item(ar)
|
290
290
|
return tostring(e)
|
291
291
|
# return ar.obj2str(self, **kwargs)
|
292
292
|
|
lino/modlib/periods/__init__.py
CHANGED
@@ -10,11 +10,22 @@ class Plugin(ad.Plugin):
|
|
10
10
|
period_name_plural = _("Accounting periods")
|
11
11
|
year_name = _("Fiscal year")
|
12
12
|
year_name_plural = _("Fiscal years")
|
13
|
-
fix_y2k = False
|
14
13
|
start_year = 2012
|
14
|
+
start_month = 1
|
15
|
+
period_type = "month"
|
16
|
+
fix_y2k = False
|
17
|
+
short_ref = False
|
15
18
|
|
16
19
|
def setup_config_menu(self, site, user_type, m, ar=None):
|
17
20
|
p = self.get_menu_group()
|
18
21
|
m = m.add_menu(p.app_label, p.verbose_name)
|
19
22
|
m.add_action("periods.StoredYears")
|
20
23
|
m.add_action("periods.StoredPeriods")
|
24
|
+
|
25
|
+
def before_analyze(self):
|
26
|
+
if self.fix_y2k and self.start_month != 1:
|
27
|
+
raise Exception("When fix_y2k is set, start_month must be 1")
|
28
|
+
if isinstance(self.period_type, str):
|
29
|
+
self.period_type = self.site.models.periods.PeriodTypes.get_by_name(
|
30
|
+
self.period_type)
|
31
|
+
super().before_analyze()
|
@@ -18,5 +18,6 @@ def objects():
|
|
18
18
|
raise Exception("plugins.periods.start_year is after the_demo_date")
|
19
19
|
today = site.the_demo_date or datetime.date.today()
|
20
20
|
for y in range(start_year, today.year + 6):
|
21
|
-
yield StoredYear.create_from_year(y)
|
21
|
+
# yield StoredYear.create_from_year(y)
|
22
|
+
yield StoredYear.get_or_create_from_date(datetime.date(y, today.month, today.day))
|
22
23
|
# StoredYears.add_item(StoredYear.year2value(y), str(y))
|
lino/modlib/periods/models.py
CHANGED
@@ -8,12 +8,45 @@ from django.utils.translation import gettext_lazy as _
|
|
8
8
|
|
9
9
|
from lino.api import dd
|
10
10
|
from lino import mixins
|
11
|
-
from lino.utils import last_day_of_month
|
11
|
+
from lino.utils import last_day_of_month, ONE_DAY
|
12
12
|
from lino.mixins.periods import DateRange
|
13
13
|
from lino.mixins import Referrable
|
14
14
|
|
15
15
|
from lino.modlib.office.roles import OfficeStaff
|
16
16
|
|
17
|
+
NEXT_YEAR_SEP = "/"
|
18
|
+
YEAR_PERIOD_SEP = "-"
|
19
|
+
|
20
|
+
class PeriodType(dd.Choice):
|
21
|
+
ref_template = None
|
22
|
+
ref_template = None
|
23
|
+
|
24
|
+
def __init__(self, value, text, duration, ref_template):
|
25
|
+
super().__init__(value, text, value)
|
26
|
+
self.ref_template = ref_template
|
27
|
+
self.duration = duration
|
28
|
+
|
29
|
+
class PeriodTypes(dd.ChoiceList):
|
30
|
+
item_class = PeriodType
|
31
|
+
verbose_name = _("Period type")
|
32
|
+
verbose_name_plural = _("Period types")
|
33
|
+
column_names = "value text duration ref_template"
|
34
|
+
|
35
|
+
@dd.displayfield(_("Duration"))
|
36
|
+
def duration(cls, p, ar):
|
37
|
+
return str(p.duration)
|
38
|
+
|
39
|
+
@dd.displayfield(_("Template for reference"))
|
40
|
+
def ref_template(cls, p, ar):
|
41
|
+
return p.ref_template
|
42
|
+
|
43
|
+
add = PeriodTypes.add_item
|
44
|
+
# value/names, text, duration, ref_template
|
45
|
+
add("month", _("Month"), 1, "{month:0>2}")
|
46
|
+
add("quarter", _("Quarter"), 3, "Q{period}")
|
47
|
+
add("trimester", _("Trimester"), 4, "T{period}")
|
48
|
+
add("semester", _("Semester"), 6, "S{period}")
|
49
|
+
|
17
50
|
|
18
51
|
class PeriodStates(dd.Workflow):
|
19
52
|
pass
|
@@ -27,8 +60,8 @@ class StoredYear(DateRange, Referrable):
|
|
27
60
|
|
28
61
|
class Meta:
|
29
62
|
app_label = 'periods'
|
30
|
-
verbose_name =
|
31
|
-
verbose_name_plural =
|
63
|
+
verbose_name = dd.plugins.periods.year_name
|
64
|
+
verbose_name_plural = dd.plugins.periods.year_name_plural
|
32
65
|
ordering = ['ref']
|
33
66
|
|
34
67
|
preferred_foreignkey_width = 10
|
@@ -41,45 +74,33 @@ class StoredYear(DateRange, Referrable):
|
|
41
74
|
yield "state"
|
42
75
|
|
43
76
|
@classmethod
|
44
|
-
def
|
77
|
+
def get_ref_for_date(cls, date):
|
78
|
+
year = date.year
|
79
|
+
if date.month < dd.plugins.periods.start_month:
|
80
|
+
year -= 1
|
45
81
|
if dd.plugins.periods.fix_y2k:
|
46
82
|
if year < 2000:
|
47
83
|
return str(year)[-2:]
|
48
84
|
elif year < 3000:
|
49
85
|
return chr(int(str(year)[-3:-1]) + 65) + str(year)[-1]
|
50
86
|
else:
|
51
|
-
raise Exception("
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
# raise Exception(20160304)
|
60
|
-
# return str(year)[2:]
|
61
|
-
return str(year)
|
62
|
-
|
63
|
-
@classmethod
|
64
|
-
def from_int(cls, year, *args):
|
65
|
-
ref = cls.year2ref(year)
|
66
|
-
return cls.get_by_ref(ref, *args)
|
67
|
-
|
68
|
-
@classmethod
|
69
|
-
def create_from_year(cls, year):
|
70
|
-
ref = cls.year2ref(year)
|
71
|
-
return cls(ref=ref,
|
72
|
-
start_date=datetime.date(year, 1, 1),
|
73
|
-
end_date=datetime.date(year, 12, 31))
|
74
|
-
# obj.full_clean()
|
75
|
-
# obj.save()
|
76
|
-
# return obj
|
87
|
+
raise Exception("fix_y2k not supported after 2999")
|
88
|
+
elif dd.plugins.periods.short_ref:
|
89
|
+
if dd.plugins.periods.start_month == 1:
|
90
|
+
return str(year)[-2:]
|
91
|
+
return str(year)[-2:] + NEXT_YEAR_SEP + str(year+1)[-2:]
|
92
|
+
elif dd.plugins.periods.start_month == 1:
|
93
|
+
return str(year)
|
94
|
+
return str(year) + NEXT_YEAR_SEP + str(year+1)[-2:]
|
77
95
|
|
78
96
|
@classmethod
|
79
97
|
def get_or_create_from_date(cls, date):
|
80
|
-
|
98
|
+
ref = cls.get_ref_for_date(date)
|
99
|
+
obj = cls.get_by_ref(ref, None)
|
81
100
|
if obj is None:
|
82
|
-
|
101
|
+
sd = datetime.date(date.year, dd.plugins.periods.start_month, 1)
|
102
|
+
ed = sd.replace(year=date.year+1) - ONE_DAY
|
103
|
+
obj = cls(ref=ref, start_date=sd, end_date=ed)
|
83
104
|
obj.full_clean()
|
84
105
|
obj.save()
|
85
106
|
return obj
|
@@ -138,43 +159,6 @@ class StoredPeriod(DateRange, Referrable):
|
|
138
159
|
fkw = dict(start_date__lte=today, end_date__gte=today)
|
139
160
|
return cls.objects.filter(**fkw)
|
140
161
|
|
141
|
-
@classmethod
|
142
|
-
def get_ref_for_date(cls, d):
|
143
|
-
"""Return a text to be used as :attr:`ref` for a new period.
|
144
|
-
|
145
|
-
Alternative implementation for usage on a site with movements
|
146
|
-
before year 2000::
|
147
|
-
|
148
|
-
@classmethod
|
149
|
-
def get_ref_for_date(cls, d):
|
150
|
-
if d.year < 2000:
|
151
|
-
y = str(d.year - 1900)
|
152
|
-
elif d.year < 2010:
|
153
|
-
y = "A" + str(d.year - 2000)
|
154
|
-
elif d.year < 2020:
|
155
|
-
y = "B" + str(d.year - 2010)
|
156
|
-
elif d.year < 2030:
|
157
|
-
y = "C" + str(d.year - 2020)
|
158
|
-
return y + "{:0>2}".format(d.month)
|
159
|
-
|
160
|
-
"""
|
161
|
-
y = StoredYear.year2ref(d.year)
|
162
|
-
return "{}-{:0>2}".format(y, d.month)
|
163
|
-
|
164
|
-
# if dd.plugins.periods.fix_y2k:
|
165
|
-
# return rt.models.periods.StoredYear.from_int(d.year).ref \
|
166
|
-
# + "{:0>2}".format(d.month)
|
167
|
-
|
168
|
-
# return "{0.year}-{0.month:0>2}".format(d)
|
169
|
-
|
170
|
-
# """The template used for building the :attr:`ref` of an
|
171
|
-
# :class:`StoredPeriod`.
|
172
|
-
#
|
173
|
-
# `Format String Syntax
|
174
|
-
# <https://docs.python.org/2/library/string.html#formatstrings>`_
|
175
|
-
#
|
176
|
-
# """
|
177
|
-
|
178
162
|
@classmethod
|
179
163
|
def get_periods_in_range(cls, p1, p2):
|
180
164
|
return cls.objects.filter(ref__gte=p1.ref, ref__lte=p2.ref)
|
@@ -197,14 +181,26 @@ class StoredPeriod(DateRange, Referrable):
|
|
197
181
|
return kwargs
|
198
182
|
|
199
183
|
@classmethod
|
200
|
-
def
|
201
|
-
|
184
|
+
def get_ref_for_date(cls, date):
|
185
|
+
pt = dd.plugins.periods.period_type
|
186
|
+
month = date.month
|
187
|
+
month_offset = month - dd.plugins.periods.start_month
|
188
|
+
if month_offset < 0:
|
189
|
+
month_offset += 12
|
190
|
+
period = int(month_offset / pt.duration) + 1
|
191
|
+
# periods_per_year = int(12 / p.duration)
|
192
|
+
# period = (month_offset % (periods_per_year-1)) + 1
|
193
|
+
return pt.ref_template.format(**locals())
|
194
|
+
|
195
|
+
@classmethod
|
196
|
+
def get_or_create_from_date(cls, date): # get_default_for_date until 20241020
|
197
|
+
ref = date2ref(date)
|
202
198
|
obj = cls.get_by_ref(ref, None)
|
203
199
|
if obj is None:
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
200
|
+
obj = cls(
|
201
|
+
ref=ref,
|
202
|
+
start_date=date.replace(day=1),
|
203
|
+
end_date=last_day_of_month(date))
|
208
204
|
obj.full_clean()
|
209
205
|
obj.save()
|
210
206
|
return obj
|
@@ -222,9 +218,17 @@ class StoredPeriod(DateRange, Referrable):
|
|
222
218
|
# "{0} {1} (#{0})".format(self.pk, self.year)
|
223
219
|
return self.ref
|
224
220
|
|
221
|
+
@property
|
222
|
+
def nickname(self):
|
223
|
+
if self.year.covers_date(dd.today()):
|
224
|
+
if len(parts := self.ref.split(YEAR_PERIOD_SEP)) == 2:
|
225
|
+
return parts[1]
|
226
|
+
return self.ref
|
225
227
|
|
226
228
|
StoredPeriod.set_widget_options('ref', width=6)
|
227
229
|
|
230
|
+
def date2ref(d):
|
231
|
+
return StoredYear.get_ref_for_date(d) + YEAR_PERIOD_SEP + StoredPeriod.get_ref_for_date(d)
|
228
232
|
|
229
233
|
class StoredYears(dd.Table):
|
230
234
|
model = 'periods.StoredYear'
|
lino/modlib/printing/actions.py
CHANGED
@@ -119,6 +119,7 @@ class BasePrintAction(Action):
|
|
119
119
|
class DirectPrintAction(BasePrintAction):
|
120
120
|
url_action_name = None
|
121
121
|
icon_name = "printer"
|
122
|
+
# button_text = "🖶" # 1F5B6
|
122
123
|
tplname = None
|
123
124
|
|
124
125
|
def __init__(self, label=None, tplname=None, build_method=None, **kw):
|
@@ -162,6 +163,7 @@ class CachedPrintAction(BasePrintAction):
|
|
162
163
|
# select_rows = False
|
163
164
|
http_method = "POST"
|
164
165
|
icon_name = "printer"
|
166
|
+
# button_text = "🖶" # 1F5B6
|
165
167
|
|
166
168
|
def before_build(self, bm, elem):
|
167
169
|
if elem.build_time:
|
lino/modlib/publisher/ui.py
CHANGED
@@ -172,6 +172,6 @@ class TranslationsByPage(Pages):
|
|
172
172
|
default_display_modes = {None: constants.DISPLAY_MODE_SUMMARY}
|
173
173
|
|
174
174
|
@classmethod
|
175
|
-
def row_as_summary(cls, ar, obj, **kwargs):
|
175
|
+
def row_as_summary(cls, ar, obj, text=None, **kwargs):
|
176
176
|
# return format_html("({}) {}", obj.language, obj.as_summary_row(ar, **kwargs))
|
177
|
-
return E.span("({}) ".format(obj.language), obj.as_summary_item(ar, **kwargs))
|
177
|
+
return E.span("({}) ".format(obj.language), obj.as_summary_item(ar, text, **kwargs))
|
lino/modlib/system/__init__.py
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
# -*- coding: UTF-8 -*-
|
2
|
-
# Copyright 2011-
|
2
|
+
# Copyright 2011-2024 Rumma & Ko Ltd
|
3
3
|
# License: GNU Affero General Public License v3 (see file COPYING for details)
|
4
4
|
|
5
5
|
import datetime
|
@@ -12,6 +12,7 @@ from django.utils.translation import gettext_lazy as _
|
|
12
12
|
|
13
13
|
from lino.utils import isidentifier
|
14
14
|
from lino.core.choicelists import ChoiceList, Choice
|
15
|
+
from lino.core.roles import login_required, Explorer
|
15
16
|
from lino.utils.dates import DateRangeValue
|
16
17
|
from lino.utils.format_date import day_and_month, fds
|
17
18
|
|
@@ -229,3 +230,56 @@ add("M", _("monthly"), "monthly", du_freq=MONTHLY)
|
|
229
230
|
add("Y", _("yearly"), "yearly", du_freq=YEARLY)
|
230
231
|
add("P", _("per weekday"), "per_weekday") # deprecated
|
231
232
|
add("E", _("Relative to Easter"), "easter")
|
233
|
+
|
234
|
+
|
235
|
+
|
236
|
+
class DisplayColor(Choice):
|
237
|
+
font_color = None
|
238
|
+
def __init__(self, value, text, names, font_color="white"):
|
239
|
+
super().__init__(value, text, names)
|
240
|
+
self.font_color = font_color
|
241
|
+
|
242
|
+
|
243
|
+
class DisplayColors(ChoiceList):
|
244
|
+
verbose_name = _("Display color")
|
245
|
+
verbose_name_plural = _("Display colors")
|
246
|
+
item_class = DisplayColor
|
247
|
+
required_roles = login_required(Explorer)
|
248
|
+
|
249
|
+
|
250
|
+
add = DisplayColors.add_item
|
251
|
+
# cssColors = 'White Silver Gray Black Red Maroon Yellow Olive Lime Green Aqua Teal Blue Navy Fuchsia Purple'
|
252
|
+
# cssColors = 'white silver gray black red maroon yellow olive lime green aqua teal blue navy fuchsia purple'
|
253
|
+
# for color in cssColors.split():
|
254
|
+
# add(color, _(color), color, font_color="white")
|
255
|
+
#
|
256
|
+
# lightColors = 'White Silver Gray'
|
257
|
+
# # lightColors = 'white silver gray'
|
258
|
+
# for color in lightColors.split():
|
259
|
+
# DisplayColors.get_by_value(color).font_color = "black"
|
260
|
+
|
261
|
+
# B&W
|
262
|
+
add("100", _("White"), "white", "black")
|
263
|
+
add("110", _("Gray"), "gray", "black")
|
264
|
+
add("120", _("Black"), "black", "white")
|
265
|
+
|
266
|
+
# Rainbow colors
|
267
|
+
add("210", _("Red"), "red", "white")
|
268
|
+
add("220", _("Orange"), "orange", "white")
|
269
|
+
add("230", _("Yellow"), "yellow", "black")
|
270
|
+
add("240", _("Green"), "green", "white")
|
271
|
+
add("250", _("Blue"), "blue", "white")
|
272
|
+
add("260", _("Magenta"), "magenta","white")
|
273
|
+
add("270", _("Violet"), "violet", "white")
|
274
|
+
|
275
|
+
# Other colors
|
276
|
+
add("300", _("Silver"), "silver", "black")
|
277
|
+
add("310", _("Maroon"), "maroon", "white")
|
278
|
+
add("311", _("Peru"), "peru", "white")
|
279
|
+
add("320", _("Olive"), "olive", "white")
|
280
|
+
add("330", _("Aqua"), "aqua", "white")
|
281
|
+
add("340", _("Navy"), "navy", "white")
|
282
|
+
add("350", _("Fuchsia"), "fuchsia","white")
|
283
|
+
add("351", _("Purple"), "purple", "white")
|
284
|
+
|
285
|
+
# List of all named colors: https://www.w3schools.com/colors/colors_names.asp
|
lino/modlib/system/models.py
CHANGED
lino/modlib/users/models.py
CHANGED
@@ -99,7 +99,7 @@ class User(AbstractBaseUser, Contactable, CreatedModified, Publishable, DateRang
|
|
99
99
|
user_type = UserTypes.field(blank=True)
|
100
100
|
initials = models.CharField(_("Initials"), max_length=10, blank=True)
|
101
101
|
if dd.plugins.users.with_nickname:
|
102
|
-
nickname = models.CharField(_("Nickname"), max_length=
|
102
|
+
nickname = models.CharField(_("Nickname"), max_length=50, blank=True)
|
103
103
|
else:
|
104
104
|
nickname = dd.DummyField()
|
105
105
|
first_name = models.CharField(_("First name"), max_length=30, blank=True)
|
@@ -144,7 +144,7 @@ class User(AbstractBaseUser, Contactable, CreatedModified, Publishable, DateRang
|
|
144
144
|
my_settings = MySettings()
|
145
145
|
|
146
146
|
def __str__(self):
|
147
|
-
return self.nickname
|
147
|
+
return self.nickname or self.get_full_name()
|
148
148
|
|
149
149
|
@property
|
150
150
|
def is_active(self):
|
lino/utils/__init__.py
CHANGED
@@ -67,7 +67,7 @@ from urllib.parse import urlencode
|
|
67
67
|
# import locale
|
68
68
|
import dateparser
|
69
69
|
from io import StringIO
|
70
|
-
import
|
70
|
+
from contextlib import redirect_stdout, contextmanager
|
71
71
|
from pathlib import Path
|
72
72
|
|
73
73
|
from etgen.utils import join_elems
|
@@ -77,13 +77,6 @@ from lino.utils.code import codefiles, codetime
|
|
77
77
|
|
78
78
|
from rstgen.utils import confirm, i2d, i2t
|
79
79
|
|
80
|
-
try:
|
81
|
-
import lino_book
|
82
|
-
DEMO_DATA = Path(lino_book.__file__).parent.parent.absolute() / 'demo_data'
|
83
|
-
"""The root directory with demo data included in the Developer Guide."""
|
84
|
-
except ImportError:
|
85
|
-
DEMO_DATA = None
|
86
|
-
|
87
80
|
DATE_TO_DIR_TPL = "%Y/%m"
|
88
81
|
|
89
82
|
def read_exception(excinfo):
|
@@ -366,6 +359,12 @@ curry = lambda func, *args, **kw: lambda *p, **n: func(
|
|
366
359
|
*args + p, **dict(list(kw.items()) + list(n.items()))
|
367
360
|
)
|
368
361
|
|
362
|
+
def capture_output(func, *args, **kwargs):
|
363
|
+
s = StringIO()
|
364
|
+
with redirect_stdout(s):
|
365
|
+
func(*args, **kwargs)
|
366
|
+
return s.getvalue()
|
367
|
+
|
369
368
|
|
370
369
|
class IncompleteDate(object):
|
371
370
|
"""Naive representation of a potentially incomplete gregorian date.
|
@@ -694,7 +693,7 @@ class MissingRow:
|
|
694
693
|
return "MissingRow({!r})".format(self.message)
|
695
694
|
|
696
695
|
|
697
|
-
@
|
696
|
+
@contextmanager
|
698
697
|
def logging_disabled(level):
|
699
698
|
try:
|
700
699
|
logging.disable(level)
|
lino/utils/djangotest.py
CHANGED
@@ -20,6 +20,7 @@ from django.db import connection, reset_queries, connections, DEFAULT_DB_ALIAS
|
|
20
20
|
from django.utils import translation
|
21
21
|
|
22
22
|
from lino.utils import AttrDict
|
23
|
+
from lino.api import rt
|
23
24
|
from lino.core.signals import testcase_setup # , database_ready
|
24
25
|
from lino.core.callbacks import applyCallbackChoice
|
25
26
|
from .test import CommonTestCase
|
@@ -112,7 +113,7 @@ class DjangoManageTestCase(DjangoTestCase, CommonTestCase):
|
|
112
113
|
the response's content (which is expected to contain a dict), convert
|
113
114
|
this dict to an AttrDict before returning it.
|
114
115
|
"""
|
115
|
-
ar =
|
116
|
+
ar = rt.login(username)
|
116
117
|
self.client.force_login(ar.user)
|
117
118
|
extra[settings.SITE.remote_user_header] = username
|
118
119
|
# extra.update(REMOTE_USER=username)
|
lino/utils/html.py
CHANGED
@@ -14,6 +14,7 @@ from html2text import HTML2Text
|
|
14
14
|
from django.utils.html import SafeString, mark_safe, escape
|
15
15
|
# from lino.utils import tostring
|
16
16
|
|
17
|
+
SAFE_EMPTY = mark_safe("")
|
17
18
|
|
18
19
|
def html2text(html, **kwargs):
|
19
20
|
"""
|
@@ -69,3 +70,33 @@ def assert_safe(s):
|
|
69
70
|
if not isinstance(s, SafeString):
|
70
71
|
raise Exception("%r is not a safe string" % s)
|
71
72
|
# assert isinstance(s, SafeString)
|
73
|
+
|
74
|
+
|
75
|
+
class Grouper:
|
76
|
+
|
77
|
+
def __init__(self, ar):
|
78
|
+
self.ar = ar
|
79
|
+
if ar.actor.group_by is None: return
|
80
|
+
self.last_values = [None for f in ar.actor.group_by]
|
81
|
+
|
82
|
+
def begin(self):
|
83
|
+
if self.ar.actor.group_by is None: return SAFE_EMPTY
|
84
|
+
return SAFE_EMPTY
|
85
|
+
|
86
|
+
def stop(self):
|
87
|
+
if self.ar.actor.group_by is None: return SAFE_EMPTY
|
88
|
+
return SAFE_EMPTY
|
89
|
+
|
90
|
+
def before_row(self, obj):
|
91
|
+
if self.ar.actor.group_by is None: return SAFE_EMPTY
|
92
|
+
self.current_values = [f(obj) for f in self.ar.actor.group_by]
|
93
|
+
if self.current_values == self.last_values:
|
94
|
+
return SAFE_EMPTY
|
95
|
+
return self.ar.actor.before_group_change(self, obj)
|
96
|
+
|
97
|
+
def after_row(self, obj):
|
98
|
+
if self.ar.actor.group_by is None: return SAFE_EMPTY
|
99
|
+
if self.current_values == self.last_values:
|
100
|
+
return SAFE_EMPTY
|
101
|
+
self.last_values = self.current_values
|
102
|
+
return self.ar.actor.after_group_change(self, obj)
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.3
|
2
2
|
Name: lino
|
3
|
-
Version: 24.
|
3
|
+
Version: 24.11.0
|
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
|