lino 24.11.1__py3-none-any.whl → 25.1.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/core/actors.py +33 -12
- lino/core/dbtables.py +0 -4
- lino/core/fields.py +26 -18
- lino/core/kernel.py +4 -6
- lino/core/model.py +5 -16
- lino/core/renderer.py +1 -7
- lino/core/requests.py +70 -47
- lino/core/site.py +1 -1
- lino/core/tables.py +0 -16
- lino/help_texts.py +4 -4
- 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/mixins.py +45 -44
- lino/modlib/comments/models.py +2 -2
- lino/modlib/comments/ui.py +9 -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/system/__init__.py +0 -3
- lino/modlib/system/choicelists.py +0 -13
- lino/modlib/system/mixins.py +1 -0
- lino/modlib/system/models.py +0 -18
- 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/users/models.py +0 -2
- lino/modlib/users/ui.py +1 -1
- 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.1.dist-info}/METADATA +4 -2
- {lino-24.11.1.dist-info → lino-25.1.1.dist-info}/RECORD +60 -59
- {lino-24.11.1.dist-info → lino-25.1.1.dist-info}/WHEEL +1 -1
- {lino-24.11.1.dist-info → lino-25.1.1.dist-info}/licenses/AUTHORS.rst +0 -0
- {lino-24.11.1.dist-info → lino-25.1.1.dist-info}/licenses/COPYING +0 -0
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):
|
lino/modlib/users/models.py
CHANGED
@@ -27,7 +27,6 @@ from .actions import SendWelcomeMail, CreateAccount, ResetPassword, VerifyUser,
|
|
27
27
|
|
28
28
|
# from .actions import SignIn
|
29
29
|
from lino.modlib.about.choicelists import TimeZones, DateFormats
|
30
|
-
from lino.modlib.system.choicelists import DashboardLayouts
|
31
30
|
from lino.modlib.publisher.mixins import Publishable
|
32
31
|
|
33
32
|
from lino.core.roles import Supervisor
|
@@ -120,7 +119,6 @@ class User(AbstractBaseUser, Contactable, CreatedModified, Publishable, DateRang
|
|
120
119
|
time_zone = dd.DummyField()
|
121
120
|
|
122
121
|
date_format = DateFormats.field(default="default")
|
123
|
-
dashboard_layout = DashboardLayouts.field(blank=True, null=True)
|
124
122
|
|
125
123
|
ledger = dd.ForeignKey("ledgers.Ledger", null=True, blank=True)
|
126
124
|
|
lino/modlib/users/ui.py
CHANGED
@@ -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
|
lino/utils/soup.py
CHANGED
@@ -251,12 +251,12 @@ ALLOWED_ATTRIBUTES["span"] = {
|
|
251
251
|
"data-value",
|
252
252
|
"contenteditable",
|
253
253
|
}
|
254
|
-
ALLOWED_ATTRIBUTES["p"] = {"align"}
|
254
|
+
ALLOWED_ATTRIBUTES["p"] = {"align", "style"}
|
255
255
|
|
256
|
-
def safe_css(attr, css):
|
257
|
-
|
258
|
-
|
259
|
-
|
256
|
+
# def safe_css(attr, css):
|
257
|
+
# if attr == "style":
|
258
|
+
# return re.sub("(width|height):[^;]+;", "", css)
|
259
|
+
# return css
|
260
260
|
|
261
261
|
def sanitize(old):
|
262
262
|
|
@@ -1,6 +1,6 @@
|
|
1
|
-
Metadata-Version: 2.
|
1
|
+
Metadata-Version: 2.4
|
2
2
|
Name: lino
|
3
|
-
Version:
|
3
|
+
Version: 25.1.1
|
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
|
@@ -666,6 +666,8 @@ License: GNU AFFERO GENERAL PUBLIC LICENSE
|
|
666
666
|
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
667
667
|
For more information on this, and how to apply and follow the GNU AGPL, see
|
668
668
|
<https://www.gnu.org/licenses/>.
|
669
|
+
License-File: AUTHORS.rst
|
670
|
+
License-File: COPYING
|
669
671
|
Keywords: Django,React,customized,framework
|
670
672
|
Classifier: Development Status :: 5 - Production/Stable
|
671
673
|
Classifier: Environment :: Web Environment
|