lino 24.11.1__py3-none-any.whl → 25.1.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/core/actors.py +33 -12
- lino/core/fields.py +26 -18
- lino/core/kernel.py +4 -6
- lino/core/model.py +5 -16
- lino/core/renderer.py +1 -2
- lino/core/requests.py +70 -47
- lino/core/site.py +1 -1
- lino/core/tables.py +0 -16
- lino/help_texts.py +4 -3
- lino/locale/bn/LC_MESSAGES/django.po +58 -45
- lino/locale/de/LC_MESSAGES/django.mo +0 -0
- lino/locale/de/LC_MESSAGES/django.po +79 -108
- lino/locale/django.pot +55 -44
- lino/locale/es/LC_MESSAGES/django.po +56 -44
- lino/locale/et/LC_MESSAGES/django.po +58 -45
- lino/locale/fr/LC_MESSAGES/django.mo +0 -0
- lino/locale/fr/LC_MESSAGES/django.po +60 -48
- lino/locale/nl/LC_MESSAGES/django.po +59 -45
- lino/locale/pt_BR/LC_MESSAGES/django.po +55 -44
- lino/locale/zh_Hant/LC_MESSAGES/django.po +55 -44
- lino/mixins/dupable.py +8 -7
- lino/mixins/periods.py +4 -4
- lino/modlib/checkdata/__init__.py +1 -1
- lino/modlib/comments/ui.py +7 -3
- lino/modlib/dupable/models.py +10 -14
- lino/modlib/gfks/mixins.py +8 -1
- lino/modlib/jinja/choicelists.py +3 -3
- lino/modlib/jinja/renderer.py +2 -0
- lino/modlib/languages/fixtures/all_languages.py +4 -6
- lino/modlib/memo/mixins.py +7 -7
- lino/modlib/memo/models.py +41 -12
- lino/modlib/memo/parser.py +7 -3
- lino/modlib/notify/mixins.py +8 -8
- lino/modlib/periods/choicelists.py +46 -0
- lino/modlib/periods/mixins.py +26 -0
- lino/modlib/periods/models.py +17 -45
- lino/modlib/publisher/choicelists.py +1 -4
- lino/modlib/uploads/mixins.py +37 -37
- lino/modlib/uploads/models.py +68 -18
- lino/modlib/uploads/utils.py +6 -0
- lino/modlib/users/mixins.py +9 -6
- lino/modlib/weasyprint/choicelists.py +17 -7
- lino/utils/choosers.py +21 -8
- lino/utils/instantiator.py +9 -0
- lino/utils/soup.py +5 -5
- {lino-24.11.1.dist-info → lino-25.1.0.dist-info}/METADATA +4 -2
- {lino-24.11.1.dist-info → lino-25.1.0.dist-info}/RECORD +51 -50
- {lino-24.11.1.dist-info → lino-25.1.0.dist-info}/WHEEL +1 -1
- {lino-24.11.1.dist-info → lino-25.1.0.dist-info}/licenses/AUTHORS.rst +0 -0
- {lino-24.11.1.dist-info → lino-25.1.0.dist-info}/licenses/COPYING +0 -0
lino/modlib/memo/parser.py
CHANGED
@@ -159,7 +159,7 @@ class Parser:
|
|
159
159
|
)
|
160
160
|
self.commands[cmdname] = func
|
161
161
|
|
162
|
-
def register_django_model(self, name, model, cmd=None):
|
162
|
+
def register_django_model(self, name, model, cmd=None, rnd=None):
|
163
163
|
"""
|
164
164
|
Register the given string `name` as command for referring to
|
165
165
|
database rows of the given Django database model `model`.
|
@@ -172,6 +172,8 @@ class Parser:
|
|
172
172
|
# if rnd is None:
|
173
173
|
# def rnd(obj):
|
174
174
|
# return "[{} {}] ({})".format(name, obj.id, title(obj))
|
175
|
+
if rnd is None:
|
176
|
+
rnd = model.memo2html
|
175
177
|
if cmd is None:
|
176
178
|
|
177
179
|
def cmd(ar, s, cmdname, mentions, context):
|
@@ -200,7 +202,8 @@ class Parser:
|
|
200
202
|
# caption = obj.get_memo_title()
|
201
203
|
# txt = "#{0}".format(obj.id)
|
202
204
|
# kw.update(title=title(obj))
|
203
|
-
return obj.memo2html(ar, text)
|
205
|
+
# return obj.memo2html(ar, text)
|
206
|
+
return rnd(obj, ar, text)
|
204
207
|
# e = ar.obj2html(obj, txt, **kw)
|
205
208
|
# # return str(ar)
|
206
209
|
# return etree.tostring(e)
|
@@ -210,7 +213,8 @@ class Parser:
|
|
210
213
|
# pass
|
211
214
|
|
212
215
|
cmd._for_model = model
|
213
|
-
cmd.__doc__
|
216
|
+
if cmd.__doc__ is None:
|
217
|
+
cmd.__doc__ = rnd.__doc__ or """
|
214
218
|
Insert a reference to the specified {}.
|
215
219
|
|
216
220
|
The first argument is mandatory and specifies the primary key.
|
lino/modlib/notify/mixins.py
CHANGED
@@ -1,7 +1,7 @@
|
|
1
|
-
# Copyright 2016-
|
1
|
+
# Copyright 2016-2024 Rumma & Ko Ltd
|
2
2
|
# License: GNU Affero General Public License v3 (see file COPYING for details)
|
3
3
|
|
4
|
-
from lino.utils.html import E, tostring
|
4
|
+
from lino.utils.html import E, tostring, format_html, mark_safe
|
5
5
|
from lino.api import dd, rt, _
|
6
6
|
|
7
7
|
# PUBLIC_GROUP = "all_users_channel"
|
@@ -27,21 +27,21 @@ class ChangeNotifier(dd.Model):
|
|
27
27
|
def get_change_body(self, ar, cw):
|
28
28
|
ctx = dict(user=ar.user, what=ar.obj2htmls(self))
|
29
29
|
if cw is None:
|
30
|
-
html = _("{user} created {what}")
|
30
|
+
html = format_html(_("{user} created {what}"), **ctx)
|
31
31
|
html += self.get_change_info(ar, cw)
|
32
|
-
html = "<p>{}</p>."
|
32
|
+
html = format_html("<p>{}</p>.", html)
|
33
33
|
else:
|
34
34
|
items = list(cw.get_updates_html(["_user_cache"]))
|
35
35
|
if len(items) == 0:
|
36
36
|
return
|
37
|
-
|
38
|
-
html = "<p>{}:</p>"
|
37
|
+
txt = format_html(_("{user} modified {what}"), **ctx)
|
38
|
+
html = format_html("<p>{}:</p>", txt)
|
39
39
|
html += tostring(E.ul(*items))
|
40
40
|
html += self.get_change_info(ar, cw)
|
41
|
-
return "<div>{}</div>"
|
41
|
+
return format_html("<div>{}</div>", html)
|
42
42
|
|
43
43
|
def get_change_info(self, ar, cw):
|
44
|
-
return ""
|
44
|
+
return mark_safe("")
|
45
45
|
|
46
46
|
if dd.is_installed("notify"):
|
47
47
|
|
@@ -0,0 +1,46 @@
|
|
1
|
+
# -*- coding: UTF-8 -*-
|
2
|
+
# Copyright 2008-2024 Rumma & Ko Ltd
|
3
|
+
# License: GNU Affero General Public License v3 (see file COPYING for details)
|
4
|
+
|
5
|
+
from django.utils.translation import gettext_lazy as _
|
6
|
+
|
7
|
+
from lino.api import dd
|
8
|
+
from lino.utils import ONE_DAY
|
9
|
+
|
10
|
+
|
11
|
+
class PeriodType(dd.Choice):
|
12
|
+
ref_template = None
|
13
|
+
|
14
|
+
def __init__(self, value, text, duration, ref_template):
|
15
|
+
super().__init__(value, text, value)
|
16
|
+
self.ref_template = ref_template
|
17
|
+
self.duration = duration
|
18
|
+
|
19
|
+
class PeriodTypes(dd.ChoiceList):
|
20
|
+
item_class = PeriodType
|
21
|
+
verbose_name = _("Period type")
|
22
|
+
verbose_name_plural = _("Period types")
|
23
|
+
column_names = "value text duration ref_template"
|
24
|
+
|
25
|
+
@dd.displayfield(_("Duration"))
|
26
|
+
def duration(cls, p, ar):
|
27
|
+
return str(p.duration)
|
28
|
+
|
29
|
+
@dd.displayfield(_("Template for reference"))
|
30
|
+
def ref_template(cls, p, ar):
|
31
|
+
return p.ref_template
|
32
|
+
|
33
|
+
add = PeriodTypes.add_item
|
34
|
+
# value/names, text, duration, ref_template
|
35
|
+
add("month", _("Month"), 1, "{month:0>2}")
|
36
|
+
add("quarter", _("Quarter"), 3, "Q{period}")
|
37
|
+
add("trimester", _("Trimester"), 4, "T{period}")
|
38
|
+
add("semester", _("Semester"), 6, "S{period}")
|
39
|
+
|
40
|
+
|
41
|
+
class PeriodStates(dd.Workflow):
|
42
|
+
pass
|
43
|
+
|
44
|
+
add = PeriodStates.add_item
|
45
|
+
add('10', _("Open"), 'open')
|
46
|
+
add('20', _("Closed"), 'closed')
|
lino/modlib/periods/mixins.py
CHANGED
@@ -2,14 +2,40 @@
|
|
2
2
|
# Copyright 2008-2024 Rumma & Ko Ltd
|
3
3
|
# License: GNU Affero General Public License v3 (see file COPYING for details)
|
4
4
|
|
5
|
+
import datetime
|
5
6
|
from django.db import models
|
6
7
|
from django.utils.translation import gettext_lazy as _
|
7
8
|
|
8
9
|
from lino.api import dd, rt
|
9
10
|
from lino import mixins
|
10
11
|
from lino.mixins import Referrable
|
12
|
+
from lino.utils import ONE_DAY
|
11
13
|
|
12
14
|
from lino.modlib.office.roles import OfficeStaff
|
15
|
+
from lino.modlib.system.choicelists import DurationUnits
|
16
|
+
|
17
|
+
|
18
|
+
def get_range_for_date(date):
|
19
|
+
"""
|
20
|
+
Return the default start and end date of the period to create for the given
|
21
|
+
date.
|
22
|
+
"""
|
23
|
+
pt = dd.plugins.periods.period_type
|
24
|
+
month = date.month
|
25
|
+
year = date.year
|
26
|
+
month -= dd.plugins.periods.start_month
|
27
|
+
if month < 0:
|
28
|
+
month += 12
|
29
|
+
year -= 1
|
30
|
+
period = int(month / pt.duration)
|
31
|
+
month = dd.plugins.periods.start_month + period * pt.duration
|
32
|
+
if month > 12:
|
33
|
+
month -= 12
|
34
|
+
year += 1
|
35
|
+
sd = datetime.date(year, month, 1)
|
36
|
+
# ed = sd.replace(month=sd.month + pt.duration + 1, 1) - ONE_DAY
|
37
|
+
ed = DurationUnits.months.add_duration(sd, pt.duration) - ONE_DAY
|
38
|
+
return (sd, ed)
|
13
39
|
|
14
40
|
|
15
41
|
class PeriodRange(dd.Model):
|
lino/modlib/periods/models.py
CHANGED
@@ -8,52 +8,17 @@ 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
|
11
|
+
from lino.utils import 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
|
+
from .mixins import get_range_for_date
|
17
|
+
from .choicelists import PeriodTypes, PeriodStates
|
16
18
|
|
17
19
|
NEXT_YEAR_SEP = "/"
|
18
20
|
YEAR_PERIOD_SEP = "-"
|
19
21
|
|
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
|
-
|
50
|
-
|
51
|
-
class PeriodStates(dd.Workflow):
|
52
|
-
pass
|
53
|
-
|
54
|
-
add = PeriodStates.add_item
|
55
|
-
add('10', _("Open"), 'open')
|
56
|
-
add('20', _("Closed"), 'closed')
|
57
22
|
|
58
23
|
|
59
24
|
class StoredYear(DateRange, Referrable):
|
@@ -120,7 +85,7 @@ class StoredPeriod(DateRange, Referrable):
|
|
120
85
|
preferred_foreignkey_width = 10
|
121
86
|
|
122
87
|
state = PeriodStates.field(default='open')
|
123
|
-
year = dd.ForeignKey('periods.StoredYear', blank=True, null=True)
|
88
|
+
year = dd.ForeignKey('periods.StoredYear', blank=True, null=True, related_name="periods")
|
124
89
|
remark = models.CharField(_("Remark"), max_length=250, blank=True)
|
125
90
|
|
126
91
|
@classmethod
|
@@ -197,10 +162,8 @@ class StoredPeriod(DateRange, Referrable):
|
|
197
162
|
ref = date2ref(date)
|
198
163
|
obj = cls.get_by_ref(ref, None)
|
199
164
|
if obj is None:
|
200
|
-
|
201
|
-
|
202
|
-
start_date=date.replace(day=1),
|
203
|
-
end_date=last_day_of_month(date))
|
165
|
+
sd, ed = get_range_for_date(date)
|
166
|
+
obj = cls(ref=ref, start_date=sd, end_date=ed)
|
204
167
|
obj.full_clean()
|
205
168
|
obj.save()
|
206
169
|
return obj
|
@@ -208,7 +171,7 @@ class StoredPeriod(DateRange, Referrable):
|
|
208
171
|
def full_clean(self, *args, **kwargs):
|
209
172
|
if self.start_date is None:
|
210
173
|
self.start_date = dd.today().replace(day=1)
|
211
|
-
if not self.
|
174
|
+
if not self.year_id:
|
212
175
|
self.year = StoredYear.get_or_create_from_date(self.start_date)
|
213
176
|
super().full_clean(*args, **kwargs)
|
214
177
|
|
@@ -218,10 +181,19 @@ class StoredPeriod(DateRange, Referrable):
|
|
218
181
|
# "{0} {1} (#{0})".format(self.pk, self.year)
|
219
182
|
return self.ref
|
220
183
|
|
184
|
+
# def get_str_words(self, ar):
|
185
|
+
# # if ar.is_obvious_field("year"):
|
186
|
+
# if self.year.covers_date(dd.today()):
|
187
|
+
# # yield self.nickname
|
188
|
+
# yield f"{dd.plugins.periods.period_name} {self.nickname}"
|
189
|
+
# else:
|
190
|
+
# yield str(self)
|
191
|
+
|
221
192
|
@property
|
222
193
|
def nickname(self):
|
223
194
|
if self.year.covers_date(dd.today()):
|
224
|
-
if len(parts := self.ref.split(YEAR_PERIOD_SEP)) == 2:
|
195
|
+
if self.ref and len(parts := self.ref.split(YEAR_PERIOD_SEP)) == 2:
|
196
|
+
# return "{} {}".format(dd.plugins.periods.period_name, parts[1])
|
225
197
|
return parts[1]
|
226
198
|
return self.ref
|
227
199
|
|
@@ -51,12 +51,9 @@ class PublisherBuildMethod(JinjaBuildMethod):
|
|
51
51
|
)
|
52
52
|
context = elem.get_printable_context(ar)
|
53
53
|
html = tpl.render(context)
|
54
|
-
self.html2file(html, filename)
|
54
|
+
self.html2file(html, filename, context)
|
55
55
|
return os.path.getmtime(filename)
|
56
56
|
|
57
|
-
def html2file(self, html, filename):
|
58
|
-
raise NotImplementedError()
|
59
|
-
|
60
57
|
|
61
58
|
BuildMethods.add_item_instance(PublisherBuildMethod())
|
62
59
|
|
lino/modlib/uploads/mixins.py
CHANGED
@@ -7,10 +7,10 @@ import shutil
|
|
7
7
|
import uuid
|
8
8
|
from pathlib import Path
|
9
9
|
|
10
|
-
from rstgen.sphinxconf.sigal_image import parse_image_spec
|
11
|
-
from rstgen.sphinxconf.sigal_image import Standard, Thumb, Tiny, Wide, Solo, Duo, Trio
|
12
|
-
SMALL_FORMATS = (Thumb, Tiny, Duo, Trio)
|
13
|
-
|
10
|
+
# from rstgen.sphinxconf.sigal_image import parse_image_spec
|
11
|
+
# from rstgen.sphinxconf.sigal_image import Standard, Thumb, Tiny, Wide, Solo, Duo, Trio
|
12
|
+
# SMALL_FORMATS = (Thumb, Tiny, Duo, Trio)
|
13
|
+
#
|
14
14
|
from django.db import models
|
15
15
|
from django.conf import settings
|
16
16
|
from django.core.files.storage import default_storage
|
@@ -198,36 +198,36 @@ class UploadBase(Commentable, GalleryViewable):
|
|
198
198
|
return text
|
199
199
|
return E.a(text, href=mf.get_download_url(), target="_blank")
|
200
200
|
|
201
|
-
def memo2html(self, ar, text, **ctx):
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
201
|
+
# def memo2html(self, ar, text, **ctx):
|
202
|
+
# mf = self.get_media_file()
|
203
|
+
# if mf is None:
|
204
|
+
# return format_html("<em>{}</em>", text or str(self))
|
205
|
+
# ctx.update(src=mf.get_download_url())
|
206
|
+
# ctx.update(href=ar.renderer.obj2url(ar, self))
|
207
|
+
# small_url = mf.get_image_url()
|
208
|
+
# if small_url is None or small_url == mf.url: # non-previewable mimetype
|
209
|
+
# if not text:
|
210
|
+
# text = str(self)
|
211
|
+
# ctx.update(text=text)
|
212
|
+
# tpl = '(<a href="{src}" target="_blank">{text}</a>'
|
213
|
+
# tpl += '| <a href="{href}">detail</a>)'
|
214
|
+
# return format_html(tpl, **ctx)
|
215
|
+
#
|
216
|
+
# fmt = parse_image_spec(text, **ctx)
|
217
|
+
# if isinstance(fmt, SMALL_FORMATS):
|
218
|
+
# fmt.context.update(src=small_url)
|
219
|
+
#
|
220
|
+
# if not fmt.context["caption"]:
|
221
|
+
# fmt.context["caption"] = self.description or str(self)
|
222
|
+
#
|
223
|
+
# rv = format_html(
|
224
|
+
# '<a href="{href}" target="_blank"><img src="{src}"'
|
225
|
+
# + ' style="{style}" title="{caption}"/></a>', **fmt.context)
|
226
|
+
# # if ar.renderer.front_end.media_name == 'react':
|
227
|
+
# # return ('<figure class="lino-memo-image"><img src="{src}" ' +
|
228
|
+
# # 'style="{style}" title="{caption}"/><figcaption' +
|
229
|
+
# # ' style="text-align: center;">{caption}</figcaption>' +
|
230
|
+
# # '</figure>').format(**kwargs)
|
231
|
+
#
|
232
|
+
# # print("20230325", rv)
|
233
|
+
# return rv
|
lino/modlib/uploads/models.py
CHANGED
@@ -14,6 +14,10 @@ from django.utils.text import format_lazy
|
|
14
14
|
from django.utils.html import format_html, mark_safe
|
15
15
|
from django.utils.translation import pgettext_lazy as pgettext
|
16
16
|
|
17
|
+
from rstgen.sphinxconf.sigal_image import parse_image_spec
|
18
|
+
# from rstgen.sphinxconf.sigal_image import Standard, Thumb, Tiny, Wide, Solo, Duo, Trio
|
19
|
+
# SMALL_FORMATS = (Thumb, Tiny, Duo, Trio)
|
20
|
+
|
17
21
|
from lino.utils.html import E, join_elems
|
18
22
|
from lino.api import dd, rt, _
|
19
23
|
from lino.modlib.gfks.mixins import Controllable
|
@@ -112,7 +116,7 @@ class Upload(UploadBase, UserAuthored, Controllable, Publishable):
|
|
112
116
|
verbose_name = _("Upload file")
|
113
117
|
verbose_name_plural = _("Upload files")
|
114
118
|
|
115
|
-
memo_command = "
|
119
|
+
memo_command = "upload"
|
116
120
|
|
117
121
|
upload_area = UploadAreas.field(default="general")
|
118
122
|
type = dd.ForeignKey("uploads.UploadType", blank=True, null=True)
|
@@ -129,7 +133,8 @@ class Upload(UploadBase, UserAuthored, Controllable, Publishable):
|
|
129
133
|
elif self.file:
|
130
134
|
s = filename_leaf(self.file.name)
|
131
135
|
elif self.library_file:
|
132
|
-
s =
|
136
|
+
s = filename_leaf(self.library_file)
|
137
|
+
# s = "{}:{}".format(self.volume.ref, self.library_file)
|
133
138
|
else:
|
134
139
|
s = str(self.id)
|
135
140
|
if self.type:
|
@@ -158,12 +163,13 @@ class Upload(UploadBase, UserAuthored, Controllable, Publishable):
|
|
158
163
|
|
159
164
|
def get_memo_command(self, ar=None):
|
160
165
|
if dd.is_installed("memo"):
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
166
|
+
return f"[{self.memo_command} {self.pk} {self}]"
|
167
|
+
# cmd = f"[{self.memo_command} {self.pk}"
|
168
|
+
# if self.description:
|
169
|
+
# cmd += " " + self.description + "]"
|
170
|
+
# else:
|
171
|
+
# cmd += "]"
|
172
|
+
# return cmd
|
167
173
|
return None
|
168
174
|
|
169
175
|
def get_real_file_size(self):
|
@@ -180,16 +186,6 @@ class Upload(UploadBase, UserAuthored, Controllable, Publishable):
|
|
180
186
|
df.add("camera_stream")
|
181
187
|
return df
|
182
188
|
|
183
|
-
def full_clean(self):
|
184
|
-
super().full_clean()
|
185
|
-
for i in self.check_previews(True):
|
186
|
-
pass
|
187
|
-
|
188
|
-
def check_previews(self, fix):
|
189
|
-
for p in [rt.models.uploads.previewer]:
|
190
|
-
for i in p.check_preview(self, fix):
|
191
|
-
yield i
|
192
|
-
|
193
189
|
@dd.displayfield(_("Description"))
|
194
190
|
def description_link(self, ar):
|
195
191
|
s = str(self)
|
@@ -214,6 +210,13 @@ class Upload(UploadBase, UserAuthored, Controllable, Publishable):
|
|
214
210
|
super().full_clean(*args, **kw)
|
215
211
|
if self.type is not None:
|
216
212
|
self.upload_area = self.type.upload_area
|
213
|
+
for i in self.check_previews(True):
|
214
|
+
pass
|
215
|
+
|
216
|
+
def check_previews(self, fix):
|
217
|
+
p = rt.models.uploads.previewer
|
218
|
+
for i in p.check_preview(self, fix):
|
219
|
+
yield i
|
217
220
|
|
218
221
|
def get_gallery_item(self, ar):
|
219
222
|
d = super().get_gallery_item(ar)
|
@@ -415,3 +418,50 @@ def before_analyze(sender, **kwargs):
|
|
415
418
|
from .ui import *
|
416
419
|
|
417
420
|
# raise Exception("20241112")
|
421
|
+
|
422
|
+
|
423
|
+
@dd.receiver(dd.post_startup)
|
424
|
+
def setup_memo_commands(sender=None, **kwargs):
|
425
|
+
# Adds another memo command for Upload
|
426
|
+
# See :doc:`/specs/memo`
|
427
|
+
|
428
|
+
if not sender.is_installed('memo'):
|
429
|
+
return
|
430
|
+
|
431
|
+
def file2html(self, ar, text, **ctx):
|
432
|
+
"""
|
433
|
+
Insert an image tag of the specified upload file.
|
434
|
+
"""
|
435
|
+
mf = self.get_media_file()
|
436
|
+
if mf is None:
|
437
|
+
return format_html("<em>{}</em>", text or str(self))
|
438
|
+
ctx.update(src=mf.get_download_url())
|
439
|
+
ctx.update(href=ar.renderer.obj2url(ar, self))
|
440
|
+
if not mf.is_image():
|
441
|
+
if not text:
|
442
|
+
text = str(self)
|
443
|
+
ctx.update(text=text)
|
444
|
+
tpl = '(<a href="{src}" target="_blank">{text}</a>'
|
445
|
+
tpl += '| <a href="{href}">Detail</a>)'
|
446
|
+
return format_html(tpl, **ctx)
|
447
|
+
|
448
|
+
fmt = parse_image_spec(text, **ctx)
|
449
|
+
# TODO: When an image is inserted with format "wide", we should not use
|
450
|
+
# the thumbnail but the original file. But for a PDF file we must always
|
451
|
+
# use the img_src because the download_url is not an image.
|
452
|
+
fmt.context.update(src=mf.get_image_url())
|
453
|
+
# if isinstance(fmt, SMALL_FORMATS):
|
454
|
+
# fmt.context.update(src=img_src)
|
455
|
+
# else:
|
456
|
+
# print(f"20241116 {fmt} {fmt.context}")
|
457
|
+
|
458
|
+
if not fmt.context["caption"]:
|
459
|
+
fmt.context["caption"] = self.description or str(self)
|
460
|
+
|
461
|
+
rv = format_html(
|
462
|
+
'<a href="{href}" target="_blank"><img src="{src}"'
|
463
|
+
+ ' style="{style}" title="{caption}"/></a>', **fmt.context)
|
464
|
+
return rv
|
465
|
+
|
466
|
+
mp = sender.plugins.memo.parser
|
467
|
+
mp.register_django_model('file', rt.models.uploads.Upload, rnd=file2html)
|
lino/modlib/uploads/utils.py
CHANGED
@@ -32,6 +32,12 @@ class UploadMediaFile:
|
|
32
32
|
url += ".png"
|
33
33
|
return previewer.base_dir + "/" + url
|
34
34
|
|
35
|
+
def is_image(self):
|
36
|
+
# whether this can be rendered in an <img> tag
|
37
|
+
if self.get_image_name() is None:
|
38
|
+
return False
|
39
|
+
return self.suffix in previewer.PREVIEW_SUFFIXES
|
40
|
+
|
35
41
|
def get_mimetype_description(self):
|
36
42
|
if self.suffix == ".pdf":
|
37
43
|
return _("PDF file")
|
lino/modlib/users/mixins.py
CHANGED
@@ -159,13 +159,16 @@ class My(dbtables.Table):
|
|
159
159
|
|
160
160
|
@classmethod
|
161
161
|
def get_actor_label(self):
|
162
|
+
if self._label is not None:
|
163
|
+
return self._label
|
162
164
|
if self.model is None:
|
163
|
-
return self.
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
)
|
165
|
+
return self.__name__
|
166
|
+
return format_lazy(_("My {}"), self.model._meta.verbose_name_plural)
|
167
|
+
|
168
|
+
@classmethod
|
169
|
+
def setup_request(cls, ar):
|
170
|
+
super().setup_request(ar)
|
171
|
+
ar.obvious_fields.add("user")
|
169
172
|
|
170
173
|
@classmethod
|
171
174
|
def param_defaults(self, ar, **kw):
|
@@ -1,8 +1,9 @@
|
|
1
1
|
# -*- coding: UTF-8 -*-
|
2
|
-
# Copyright 2016-
|
2
|
+
# Copyright 2016-2024 Rumma & Ko Ltd
|
3
3
|
# License: GNU Affero General Public License v3 (see file COPYING for details)
|
4
4
|
|
5
5
|
import os
|
6
|
+
from pathlib import Path
|
6
7
|
from copy import copy
|
7
8
|
|
8
9
|
try:
|
@@ -10,11 +11,20 @@ try:
|
|
10
11
|
except ImportError:
|
11
12
|
HTML = None
|
12
13
|
|
14
|
+
try:
|
15
|
+
import bulma
|
16
|
+
from weasyprint import CSS
|
17
|
+
BULMA_CSS = Path(bulma.__file__).parent / "static/bulma/css/style.min.css"
|
18
|
+
assert BULMA_CSS.exists()
|
19
|
+
except ImportError:
|
20
|
+
BULMA_CSS = None
|
21
|
+
|
22
|
+
|
23
|
+
|
13
24
|
from django.conf import settings
|
14
25
|
from django.utils import translation
|
15
26
|
|
16
27
|
from lino.api import dd
|
17
|
-
|
18
28
|
from lino.modlib.jinja.choicelists import JinjaBuildMethod
|
19
29
|
from lino.modlib.printing.choicelists import BuildMethods
|
20
30
|
|
@@ -29,17 +39,17 @@ class WeasyHtmlBuildMethod(WeasyBuildMethod):
|
|
29
39
|
target_ext = ".html"
|
30
40
|
name = "weasy2html"
|
31
41
|
|
32
|
-
def html2file(self, html, filename):
|
33
|
-
open(filename, "w").write(html)
|
34
|
-
|
35
42
|
|
36
43
|
class WeasyPdfBuildMethod(WeasyBuildMethod):
|
37
44
|
target_ext = ".pdf"
|
38
45
|
name = "weasy2pdf"
|
39
46
|
|
40
|
-
def html2file(self, html, filename):
|
47
|
+
def html2file(self, html, filename, context):
|
41
48
|
pdf = HTML(string=html)
|
42
|
-
|
49
|
+
if BULMA_CSS and context.get('use_bulma_css', False):
|
50
|
+
pdf.write_pdf(filename, stylesheets=[CSS(filename=BULMA_CSS)])
|
51
|
+
else:
|
52
|
+
pdf.write_pdf(filename)
|
43
53
|
|
44
54
|
|
45
55
|
add = BuildMethods.add_item_instance
|
lino/utils/choosers.py
CHANGED
@@ -108,6 +108,7 @@ class ChoiceConverter(Converter):
|
|
108
108
|
|
109
109
|
def convert(self, **kw):
|
110
110
|
value = kw.get(self.field.name)
|
111
|
+
# print(f"20241203 convert {self.field.name}")
|
111
112
|
|
112
113
|
if value is not None:
|
113
114
|
if not isinstance(value, self.field.choicelist.item_class):
|
@@ -190,7 +191,16 @@ class ManyToManyConverter(LookupConverter):
|
|
190
191
|
|
191
192
|
def make_converter(f, lookup_fields={}):
|
192
193
|
from lino.core.gfks import GenericForeignKey
|
194
|
+
from lino.core.fields import VirtualField
|
193
195
|
|
196
|
+
# if f.name == 'rating_type':
|
197
|
+
# print(f"20241203 {f.__class__}")
|
198
|
+
|
199
|
+
# selector = f
|
200
|
+
|
201
|
+
if isinstance(f, VirtualField):
|
202
|
+
# print(f"20241203 {f.name} {f.return_type.name}")
|
203
|
+
f = f.return_type
|
194
204
|
if isinstance(f, models.ForeignKey):
|
195
205
|
return ForeignKeyConverter(f, lookup_fields.get(f.name, "pk"))
|
196
206
|
if isinstance(f, GenericForeignKey):
|
@@ -206,8 +216,7 @@ def make_converter(f, lookup_fields={}):
|
|
206
216
|
from lino.core import choicelists
|
207
217
|
|
208
218
|
if isinstance(f, choicelists.ChoiceListField):
|
209
|
-
#
|
210
|
-
# print "20131012 b", f
|
219
|
+
# print(f"20241203c {f.__class__}")
|
211
220
|
return ChoiceConverter(f)
|
212
221
|
|
213
222
|
|
@@ -244,18 +253,22 @@ class Chooser(FieldChooser):
|
|
244
253
|
from lino.core.gfks import is_foreignkey
|
245
254
|
from lino.core.choicelists import ChoiceListField
|
246
255
|
|
247
|
-
|
256
|
+
selector = field
|
257
|
+
if isinstance(field, fields.VirtualField):
|
258
|
+
selector = field.return_type
|
259
|
+
|
260
|
+
if isinstance(selector, ChoiceListField):
|
248
261
|
self.simple_values = getattr(meth, "simple_values", False)
|
249
262
|
self.instance_values = getattr(meth, "instance_values", True)
|
250
263
|
self.force_selection = getattr(
|
251
264
|
meth, "force_selection", self.force_selection
|
252
265
|
)
|
253
|
-
elif is_foreignkey(
|
254
|
-
pass
|
255
|
-
elif isinstance(field, fields.VirtualField) and isinstance(
|
256
|
-
field.return_type, models.ForeignKey
|
257
|
-
):
|
266
|
+
elif is_foreignkey(selector):
|
258
267
|
pass
|
268
|
+
# elif isinstance(field, fields.VirtualField) and isinstance(
|
269
|
+
# field.return_type, models.ForeignKey
|
270
|
+
# ):
|
271
|
+
# pass
|
259
272
|
else:
|
260
273
|
self.simple_values = getattr(meth, "simple_values", False)
|
261
274
|
self.instance_values = getattr(meth, "instance_values", False)
|
lino/utils/instantiator.py
CHANGED
@@ -210,6 +210,15 @@ def create_and_get(model, **kw):
|
|
210
210
|
o = create(model, **kw)
|
211
211
|
return model.objects.get(pk=o.pk)
|
212
212
|
|
213
|
+
def make_if_needed(model, **values):
|
214
|
+
qs = model.objects.filter(**values)
|
215
|
+
if qs.count() == 1:
|
216
|
+
pass # ok, nothing to do
|
217
|
+
elif qs.count() == 0:
|
218
|
+
return model(**values)
|
219
|
+
else:
|
220
|
+
raise Exception(f"Multiple {model._meta.verbose_name_plural} for {values}")
|
221
|
+
|
213
222
|
|
214
223
|
def _test():
|
215
224
|
import doctest
|