lino 24.10.3__py3-none-any.whl → 24.11.1__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/actions.py +2 -4
- lino/core/actors.py +70 -35
- lino/core/choicelists.py +2 -2
- lino/core/dashboard.py +2 -1
- lino/core/dbtables.py +15 -15
- lino/core/elems.py +8 -4
- lino/core/fields.py +12 -3
- lino/core/inject.py +9 -2
- lino/core/kernel.py +11 -11
- lino/core/layouts.py +1 -1
- lino/core/model.py +25 -36
- lino/core/plugin.py +1 -0
- lino/core/renderer.py +21 -21
- lino/core/requests.py +94 -83
- lino/core/site.py +9 -90
- lino/core/store.py +16 -19
- lino/core/tables.py +0 -17
- lino/core/utils.py +32 -2
- lino/core/views.py +2 -1
- lino/help_texts.py +10 -5
- lino/locale/bn/LC_MESSAGES/django.po +1210 -907
- lino/locale/de/LC_MESSAGES/django.po +1760 -1375
- lino/locale/django.pot +1136 -906
- lino/locale/es/LC_MESSAGES/django.po +1709 -1347
- lino/locale/et/LC_MESSAGES/django.po +1206 -906
- lino/locale/fr/LC_MESSAGES/django.mo +0 -0
- lino/locale/fr/LC_MESSAGES/django.po +1193 -923
- lino/locale/nl/LC_MESSAGES/django.po +1247 -942
- lino/locale/pt_BR/LC_MESSAGES/django.po +1190 -903
- lino/locale/zh_Hant/LC_MESSAGES/django.po +1190 -903
- lino/management/commands/show.py +2 -4
- lino/mixins/periods.py +15 -7
- lino/mixins/polymorphic.py +3 -3
- lino/mixins/ref.py +6 -3
- lino/modlib/checkdata/__init__.py +3 -3
- lino/modlib/comments/choicelists.py +1 -1
- lino/modlib/comments/fixtures/demo2.py +4 -1
- lino/modlib/comments/mixins.py +9 -10
- lino/modlib/comments/models.py +4 -4
- lino/modlib/comments/ui.py +5 -0
- lino/modlib/extjs/ext_renderer.py +1 -1
- lino/modlib/linod/consumers.py +2 -3
- lino/modlib/linod/mixins.py +3 -2
- lino/modlib/memo/mixins.py +11 -209
- lino/modlib/notify/mixins.py +33 -32
- lino/modlib/periods/__init__.py +12 -1
- lino/modlib/periods/fixtures/std.py +2 -1
- lino/modlib/periods/mixins.py +0 -1
- lino/modlib/periods/models.py +79 -75
- lino/modlib/printing/actions.py +2 -0
- lino/modlib/printing/choicelists.py +3 -3
- lino/modlib/publisher/ui.py +2 -2
- lino/modlib/search/models.py +17 -11
- lino/modlib/system/__init__.py +0 -2
- lino/modlib/system/choicelists.py +55 -1
- lino/modlib/system/fixtures/__init__.py +0 -0
- lino/modlib/system/fixtures/std.py +5 -0
- lino/modlib/system/models.py +4 -2
- lino/modlib/uploads/__init__.py +10 -1
- lino/modlib/uploads/choicelists.py +3 -3
- lino/modlib/uploads/mixins.py +30 -32
- lino/modlib/uploads/models.py +89 -56
- lino/modlib/uploads/ui.py +12 -6
- lino/modlib/uploads/utils.py +107 -0
- lino/modlib/users/models.py +2 -2
- lino/modlib/weasyprint/__init__.py +2 -0
- lino/utils/__init__.py +14 -9
- lino/utils/djangotest.py +2 -1
- lino/utils/html.py +32 -1
- lino/utils/media.py +2 -3
- lino/utils/soup.py +311 -0
- {lino-24.10.3.dist-info → lino-24.11.1.dist-info}/METADATA +1 -3
- {lino-24.10.3.dist-info → lino-24.11.1.dist-info}/RECORD +80 -76
- {lino-24.10.3.dist-info → lino-24.11.1.dist-info}/WHEEL +1 -1
- {lino-24.10.3.dist-info → lino-24.11.1.dist-info}/licenses/AUTHORS.rst +0 -0
- {lino-24.10.3.dist-info → lino-24.11.1.dist-info}/licenses/COPYING +0 -0
lino/modlib/memo/mixins.py
CHANGED
@@ -2,8 +2,6 @@
|
|
2
2
|
# Copyright 2016-2024 Rumma & Ko Ltd
|
3
3
|
# License: GNU Affero General Public License v3 (see file COPYING for details)
|
4
4
|
|
5
|
-
from bs4 import BeautifulSoup, NavigableString
|
6
|
-
from bs4.element import Tag
|
7
5
|
from lxml.html import fragments_fromstring
|
8
6
|
from lino.utils.html import E, tostring, mark_safe
|
9
7
|
import lxml
|
@@ -22,212 +20,24 @@ from lino.core.gfks import gfk2lookup
|
|
22
20
|
from lino.core.model import Model
|
23
21
|
from lino.core.fields import fields_list, RichTextField, PreviewTextField
|
24
22
|
from lino.utils.restify import restify
|
23
|
+
from lino.utils.soup import truncate_comment
|
25
24
|
from lino.utils.mldbc.fields import BabelTextField
|
26
25
|
from lino.core.exceptions import ChangedAPI
|
27
26
|
from lino.modlib.checkdata.choicelists import Checker
|
28
27
|
from lino.api import rt, dd, _
|
29
28
|
|
30
29
|
|
31
|
-
def old_truncate_comment(html_str, max_p_len=None):
|
32
|
-
# returns a single paragraph with a maximum number of visible chars.
|
33
|
-
# No longer used. Replaced by new truncate_comment() below
|
34
|
-
if max_p_len is None:
|
35
|
-
max_p_len = settings.SITE.plugins.memo.short_preview_length
|
36
|
-
html_str = html_str.strip() # remove leading or trailing newlines
|
37
|
-
|
38
|
-
if not html_str.startswith("<"):
|
39
|
-
if len(html_str) > max_p_len:
|
40
|
-
txt = html_str[:max_p_len] + "..."
|
41
|
-
else:
|
42
|
-
txt = html_str
|
43
|
-
return txt
|
44
|
-
soup = BeautifulSoup(html_str, "html.parser")
|
45
|
-
ps = soup.find_all(
|
46
|
-
["p", "h1", "h2", "h3", "h4", "h5", "h6", "h7", "h8", "h9", "pre"]
|
47
|
-
)
|
48
|
-
if len(ps) > 0:
|
49
|
-
anchor_end = "</a>"
|
50
|
-
txt = ""
|
51
|
-
for p in ps:
|
52
|
-
text = ""
|
53
|
-
for c in p.contents:
|
54
|
-
if isinstance(c, Tag):
|
55
|
-
if c.name == "a":
|
56
|
-
text += str(c)
|
57
|
-
max_p_len = max_p_len + len(text) - len(c.text)
|
58
|
-
else:
|
59
|
-
# text += str(c)
|
60
|
-
text += c.text
|
61
|
-
else:
|
62
|
-
text += str(c)
|
63
|
-
|
64
|
-
if len(txt) + len(text) > max_p_len:
|
65
|
-
txt += text
|
66
|
-
if anchor_end in txt:
|
67
|
-
ae_index = txt.index(anchor_end) + len(anchor_end)
|
68
|
-
if ae_index >= max_p_len:
|
69
|
-
txt = txt[:ae_index]
|
70
|
-
txt += "..."
|
71
|
-
break
|
72
|
-
txt = txt[:max_p_len]
|
73
|
-
txt += "..."
|
74
|
-
break
|
75
|
-
else:
|
76
|
-
txt += text + "\n\n"
|
77
|
-
return txt
|
78
|
-
return html_str
|
79
|
-
|
80
|
-
|
81
30
|
def django_truncate_comment(html_str):
|
82
31
|
# works, but we don't use it because (...)
|
83
32
|
return Truncator(html_str).chars(
|
84
33
|
settings.SITE.plugins.memo.short_preview_length, html=True
|
85
34
|
)
|
86
35
|
|
87
|
-
|
88
|
-
PARAGRAPH_TAGS = {
|
89
|
-
"p",
|
90
|
-
"h1",
|
91
|
-
"h2",
|
92
|
-
"h3",
|
93
|
-
"h4",
|
94
|
-
"h5",
|
95
|
-
"h6",
|
96
|
-
"h7",
|
97
|
-
"h8",
|
98
|
-
"h9",
|
99
|
-
"pre",
|
100
|
-
"li",
|
101
|
-
"div",
|
102
|
-
}
|
103
|
-
WHITESPACE_TAGS = PARAGRAPH_TAGS | {
|
104
|
-
"[document]",
|
105
|
-
"span",
|
106
|
-
"ul",
|
107
|
-
"html",
|
108
|
-
"head",
|
109
|
-
"body",
|
110
|
-
"base",
|
111
|
-
}
|
112
|
-
|
113
|
-
|
114
36
|
MARKDOWNCFG = dict(
|
115
37
|
extensions=["toc"], extension_configs=dict(toc=dict(toc_depth=3, permalink=True))
|
116
38
|
)
|
117
39
|
|
118
40
|
|
119
|
-
class Style:
|
120
|
-
# TODO: Extend rstgen.sphinxconf.sigal_image.Format to incoroporate this.
|
121
|
-
def __init__(self, s):
|
122
|
-
self._map = {}
|
123
|
-
if s:
|
124
|
-
for i in s.split(";"):
|
125
|
-
k, v = i.split(":", maxsplit=1)
|
126
|
-
self._map[k.strip()] = v.strip()
|
127
|
-
self.is_dirty = False
|
128
|
-
|
129
|
-
def __contains__(self, *args):
|
130
|
-
return self._map.__contains__(*args)
|
131
|
-
|
132
|
-
def __setitem__(self, k, v):
|
133
|
-
if k in self._map and self._map[k] == v:
|
134
|
-
return
|
135
|
-
self._map[k] = v
|
136
|
-
self.is_dirty = True
|
137
|
-
|
138
|
-
def __delitem__(self, k):
|
139
|
-
if k in self._map:
|
140
|
-
self.is_dirty = True
|
141
|
-
return self._map.__delitem__(k)
|
142
|
-
|
143
|
-
def adjust_size(self):
|
144
|
-
# if self['float'] == "none":
|
145
|
-
# return
|
146
|
-
if "width" in self._map:
|
147
|
-
del self["width"]
|
148
|
-
self["height"] = dd.plugins.memo.short_preview_image_height
|
149
|
-
|
150
|
-
def as_string(self):
|
151
|
-
return ";".join(["{}:{}".format(*kv) for kv in self._map.items()])
|
152
|
-
|
153
|
-
|
154
|
-
class TextCollector:
|
155
|
-
def __init__(self, max_length=None):
|
156
|
-
self.text = ""
|
157
|
-
self.sep = "" # becomes "\n\n" after a PARAGRAPH_TAGS
|
158
|
-
self.remaining = max_length or settings.SITE.plugins.memo.short_preview_length
|
159
|
-
self.image = None
|
160
|
-
|
161
|
-
def add_chunk(self, ch):
|
162
|
-
# print("20230712 add_chunk", ch.name, ch)
|
163
|
-
|
164
|
-
if ch.name in WHITESPACE_TAGS:
|
165
|
-
for c in ch.children:
|
166
|
-
if not self.add_chunk(c):
|
167
|
-
return False
|
168
|
-
if ch.name in PARAGRAPH_TAGS:
|
169
|
-
self.sep = "\n\n"
|
170
|
-
else:
|
171
|
-
self.sep = " "
|
172
|
-
return True
|
173
|
-
|
174
|
-
assert ch.name != "IMG"
|
175
|
-
|
176
|
-
if ch.name == "img":
|
177
|
-
if self.image is not None:
|
178
|
-
# Ignore all images except the first one.
|
179
|
-
self.text += self.sep
|
180
|
-
return True
|
181
|
-
style = Style(ch.get("style", None))
|
182
|
-
if not "float" in style:
|
183
|
-
style["float"] = "right"
|
184
|
-
style.adjust_size()
|
185
|
-
if style.is_dirty:
|
186
|
-
ch["style"] = style.as_string()
|
187
|
-
self.image = ch
|
188
|
-
# print("20231023 a", ch)
|
189
|
-
|
190
|
-
we_want_more = True
|
191
|
-
if ch.string is not None:
|
192
|
-
if len(ch.string) > self.remaining:
|
193
|
-
# print("20231023", len(ch.string), '>', self.remaining)
|
194
|
-
ch.string = ch.string[: self.remaining] + "..."
|
195
|
-
we_want_more = False
|
196
|
-
# print("20230927", ch.string, ch)
|
197
|
-
# self.text += str(ch.string) + "..."
|
198
|
-
# return False
|
199
|
-
self.remaining -= len(ch.string)
|
200
|
-
|
201
|
-
if isinstance(ch, NavigableString):
|
202
|
-
self.text += self.sep + ch.string
|
203
|
-
else:
|
204
|
-
self.text += self.sep + str(ch)
|
205
|
-
|
206
|
-
self.remaining -= len(self.sep)
|
207
|
-
self.sep = ""
|
208
|
-
return we_want_more
|
209
|
-
|
210
|
-
|
211
|
-
def truncate_comment(html_str, max_length=300):
|
212
|
-
# new implementation since 20230713
|
213
|
-
html_str = html_str.strip() # remove leading or trailing newlines
|
214
|
-
|
215
|
-
if not html_str.startswith("<"):
|
216
|
-
# print("20231023 c", html_str)
|
217
|
-
if len(html_str) > max_length:
|
218
|
-
return html_str[:max_length] + "..."
|
219
|
-
return html_str
|
220
|
-
|
221
|
-
# if "choose one or the other" in html_str:
|
222
|
-
# print(html_str)
|
223
|
-
# raise Exception("20230928 {} {}".format(len(html_str), max_length))
|
224
|
-
|
225
|
-
soup = BeautifulSoup(html_str, "html.parser")
|
226
|
-
tc = TextCollector(max_length)
|
227
|
-
tc.add_chunk(soup)
|
228
|
-
return tc.text
|
229
|
-
|
230
|
-
|
231
41
|
def rich_text_to_elems(ar, description):
|
232
42
|
if description.startswith("<"):
|
233
43
|
# desc = E.raw('<div>%s</div>' % self.description)
|
@@ -267,32 +77,24 @@ class MemoReferrable(dd.Model):
|
|
267
77
|
|
268
78
|
memo_command = None
|
269
79
|
|
270
|
-
|
271
|
-
def on_analyze(cls, site):
|
272
|
-
super().on_analyze(site)
|
80
|
+
if dd.is_installed("memo"):
|
273
81
|
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
# """A text to be used as title of the ``<a href>``."""
|
283
|
-
# return None
|
284
|
-
# return str(self)
|
82
|
+
@classmethod
|
83
|
+
def on_analyze(cls, site):
|
84
|
+
super().on_analyze(site)
|
85
|
+
if cls.memo_command is None:
|
86
|
+
return
|
87
|
+
mp = site.plugins.memo.parser
|
88
|
+
mp.register_django_model(cls.memo_command, cls)
|
89
|
+
# mp.add_suggester("[" + cls.memo_command + " ", cls.objects.all(), 'pk')
|
285
90
|
|
286
91
|
def memo2html(self, ar, txt, **kwargs):
|
287
92
|
if txt:
|
288
93
|
kwargs.update(title=txt)
|
289
|
-
e = self.as_summary_item(ar
|
94
|
+
e = self.as_summary_item(ar)
|
290
95
|
return tostring(e)
|
291
96
|
# return ar.obj2str(self, **kwargs)
|
292
97
|
|
293
|
-
# return "<p>Oops, undefined memo2html()</p>"
|
294
|
-
|
295
|
-
# def obj2memo(self, title=str):
|
296
98
|
def obj2memo(self, text=None):
|
297
99
|
"""Render the given database object as memo markup."""
|
298
100
|
if self.memo_command is None:
|
lino/modlib/notify/mixins.py
CHANGED
@@ -8,46 +8,47 @@ from lino.api import dd, rt, _
|
|
8
8
|
|
9
9
|
|
10
10
|
class ChangeNotifier(dd.Model):
|
11
|
-
class Meta(object):
|
12
|
-
abstract = True
|
13
11
|
|
14
|
-
|
12
|
+
class Meta:
|
13
|
+
abstract = True
|
15
14
|
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
# return "{} {}".format(self, msg)
|
22
|
-
if len(list(cw.get_updates())) == 0:
|
23
|
-
return
|
24
|
-
return _("{user} modified {what}").format(**ctx)
|
25
|
-
# msg = _("has been modified by {user}").format(**ctx)
|
15
|
+
def get_change_subject(self, ar, cw):
|
16
|
+
ctx = dict(user=ar.user, what=str(self))
|
17
|
+
if cw is None:
|
18
|
+
return _("{user} created {what}").format(**ctx)
|
19
|
+
# msg = _("has been created by {user}").format(**ctx)
|
26
20
|
# return "{} {}".format(self, msg)
|
21
|
+
if len(list(cw.get_updates())) == 0:
|
22
|
+
return
|
23
|
+
return _("{user} modified {what}").format(**ctx)
|
24
|
+
# msg = _("has been modified by {user}").format(**ctx)
|
25
|
+
# return "{} {}".format(self, msg)
|
26
|
+
|
27
|
+
def get_change_body(self, ar, cw):
|
28
|
+
ctx = dict(user=ar.user, what=ar.obj2htmls(self))
|
29
|
+
if cw is None:
|
30
|
+
html = _("{user} created {what}").format(**ctx)
|
31
|
+
html += self.get_change_info(ar, cw)
|
32
|
+
html = "<p>{}</p>.".format(html)
|
33
|
+
else:
|
34
|
+
items = list(cw.get_updates_html(["_user_cache"]))
|
35
|
+
if len(items) == 0:
|
36
|
+
return
|
37
|
+
html = _("{user} modified {what}").format(**ctx)
|
38
|
+
html = "<p>{}:</p>".format(html)
|
39
|
+
html += tostring(E.ul(*items))
|
40
|
+
html += self.get_change_info(ar, cw)
|
41
|
+
return "<div>{}</div>".format(html)
|
42
|
+
|
43
|
+
def get_change_info(self, ar, cw):
|
44
|
+
return ""
|
45
|
+
|
46
|
+
if dd.is_installed("notify"):
|
27
47
|
|
28
48
|
def add_change_watcher(self, user):
|
29
49
|
pass
|
30
50
|
# raise NotImplementedError()
|
31
51
|
|
32
|
-
def get_change_body(self, ar, cw):
|
33
|
-
ctx = dict(user=ar.user, what=ar.obj2htmls(self))
|
34
|
-
if cw is None:
|
35
|
-
html = _("{user} created {what}").format(**ctx)
|
36
|
-
html += self.get_change_info(ar, cw)
|
37
|
-
html = "<p>{}</p>.".format(html)
|
38
|
-
else:
|
39
|
-
items = list(cw.get_updates_html(["_user_cache"]))
|
40
|
-
if len(items) == 0:
|
41
|
-
return
|
42
|
-
html = _("{user} modified {what}").format(**ctx)
|
43
|
-
html = "<p>{}:</p>".format(html)
|
44
|
-
html += tostring(E.ul(*items))
|
45
|
-
html += self.get_change_info(ar, cw)
|
46
|
-
return "<div>{}</div>".format(html)
|
47
|
-
|
48
|
-
def get_change_info(self, ar, cw):
|
49
|
-
return ""
|
50
|
-
|
51
52
|
def get_change_owner(self):
|
52
53
|
return self
|
53
54
|
|
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/mixins.py
CHANGED
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:
|
@@ -46,14 +46,14 @@ class BuildMethod(Choice):
|
|
46
46
|
names, self.__class__.__name__, names, **kwargs
|
47
47
|
)
|
48
48
|
|
49
|
-
def get_target(self, action,
|
50
|
-
|
49
|
+
def get_target(self, action, obj):
|
50
|
+
# Used by get_target_name()
|
51
51
|
# assert self.name is not None
|
52
52
|
return MediaFile(
|
53
53
|
self.use_webdav,
|
54
54
|
self.cache_name,
|
55
55
|
self.value,
|
56
|
-
|
56
|
+
obj.filename_root() + self.target_ext,
|
57
57
|
)
|
58
58
|
|
59
59
|
def get_target_name(self, action, elem):
|
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))
|