lino 24.11.0__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/core/actions.py +2 -4
- lino/core/actors.py +2 -2
- lino/core/dbtables.py +12 -12
- lino/core/fields.py +1 -0
- lino/core/inject.py +9 -2
- lino/core/kernel.py +7 -6
- lino/core/model.py +24 -27
- lino/core/renderer.py +2 -2
- lino/core/requests.py +21 -45
- lino/core/site.py +4 -45
- lino/core/store.py +4 -4
- lino/core/utils.py +5 -2
- lino/help_texts.py +4 -3
- 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/mixins/periods.py +6 -4
- 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/linod/mixins.py +3 -2
- lino/modlib/memo/mixins.py +10 -208
- lino/modlib/notify/mixins.py +33 -32
- lino/modlib/periods/mixins.py +0 -1
- lino/modlib/printing/choicelists.py +3 -3
- lino/modlib/search/models.py +17 -11
- lino/modlib/system/fixtures/__init__.py +0 -0
- lino/modlib/system/fixtures/std.py +5 -0
- lino/modlib/system/models.py +3 -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/utils/__init__.py +6 -0
- lino/utils/html.py +1 -1
- lino/utils/media.py +2 -3
- lino/utils/soup.py +311 -0
- {lino-24.11.0.dist-info → lino-24.11.1.dist-info}/METADATA +1 -3
- {lino-24.11.0.dist-info → lino-24.11.1.dist-info}/RECORD +54 -50
- {lino-24.11.0.dist-info → lino-24.11.1.dist-info}/WHEEL +1 -1
- {lino-24.11.0.dist-info → lino-24.11.1.dist-info}/licenses/AUTHORS.rst +0 -0
- {lino-24.11.0.dist-info → lino-24.11.1.dist-info}/licenses/COPYING +0 -0
@@ -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/search/models.py
CHANGED
@@ -1,9 +1,8 @@
|
|
1
1
|
# -*- coding: UTF-8 -*-
|
2
|
-
# Copyright 2021-
|
2
|
+
# Copyright 2021-2024 Rumma & Ko Ltd
|
3
3
|
# License: GNU Affero General Public License v3 (see file COPYING for details)
|
4
4
|
|
5
5
|
import re
|
6
|
-
from html import escape
|
7
6
|
from lxml import etree
|
8
7
|
from django.conf import settings
|
9
8
|
|
@@ -12,7 +11,7 @@ from lino.core import constants
|
|
12
11
|
from lino.core.utils import get_models
|
13
12
|
from lino.core.site import has_elasticsearch, has_haystack
|
14
13
|
|
15
|
-
from lino.utils.html import E
|
14
|
+
from lino.utils.html import E, escape, mark_safe, format_html
|
16
15
|
|
17
16
|
from .roles import SiteSearcher
|
18
17
|
|
@@ -23,15 +22,17 @@ class SiteSearchBase(dd.VirtualTable):
|
|
23
22
|
required_roles = dd.login_required(SiteSearcher)
|
24
23
|
label = _("Search")
|
25
24
|
column_names = "description matches"
|
25
|
+
# column_names = "search_overview matches"
|
26
26
|
private_apps = frozenset(["sessions", "contenttypes", "users"])
|
27
27
|
|
28
|
-
default_display_modes = {None: constants.
|
28
|
+
default_display_modes = {None: constants.DISPLAY_MODE_LIST}
|
29
|
+
# default_display_modes = {None: constants.DISPLAY_MODE_STORY}
|
29
30
|
|
30
31
|
card_layout = """description"""
|
31
|
-
list_layout = """
|
32
|
-
search_overview
|
33
|
-
matches
|
34
|
-
"""
|
32
|
+
# list_layout = """
|
33
|
+
# search_overview
|
34
|
+
# matches
|
35
|
+
# """
|
35
36
|
|
36
37
|
# _site_search_tables = []
|
37
38
|
# @classmethod
|
@@ -76,6 +77,11 @@ class SiteSearchBase(dd.VirtualTable):
|
|
76
77
|
return False
|
77
78
|
return True
|
78
79
|
|
80
|
+
@classmethod
|
81
|
+
def row_as_paragraph(cls, ar, obj):
|
82
|
+
text = "{} #{}".format(obj._meta.verbose_name, obj.pk)
|
83
|
+
return format_html("{} : {}", ar.obj2htmls(obj, text), obj.as_paragraph(ar))
|
84
|
+
|
79
85
|
@dd.displayfield(_("Description"))
|
80
86
|
def description(self, obj, ar):
|
81
87
|
elems = []
|
@@ -110,7 +116,8 @@ class SiteSearchBase(dd.VirtualTable):
|
|
110
116
|
s = matches.get(de, None)
|
111
117
|
if s is None:
|
112
118
|
s = str(de.value_from_object(obj))
|
113
|
-
s = escape(s, quote=False)
|
119
|
+
# s = escape(s, quote=False)
|
120
|
+
s = escape(s)
|
114
121
|
r, count = re.subn(w, bold, s, flags=re.IGNORECASE)
|
115
122
|
if count:
|
116
123
|
matches[de] = r
|
@@ -132,11 +139,10 @@ class SiteSearchBase(dd.VirtualTable):
|
|
132
139
|
raise Exception("{} : {}".format(e, s))
|
133
140
|
# return etree.fromstring(', '.join(chunks))
|
134
141
|
# return E.raw(', '.join(chunks))
|
135
|
-
return s
|
142
|
+
return mark_safe(s)
|
136
143
|
|
137
144
|
|
138
145
|
class SiteSearch(SiteSearchBase):
|
139
|
-
default_display_modes = {None: constants.DISPLAY_MODE_LIST}
|
140
146
|
|
141
147
|
@classmethod
|
142
148
|
def get_data_rows(cls, ar):
|
File without changes
|
lino/modlib/system/models.py
CHANGED
@@ -138,7 +138,7 @@ class SiteConfig(dd.Model):
|
|
138
138
|
# print("20180502 Created SiteConfig {}".format(
|
139
139
|
# obj2str(self._site_config, True)))
|
140
140
|
# 20120725
|
141
|
-
# polls_tutorial menu selection `Config --> Site
|
141
|
+
# polls_tutorial menu selection `Config --> Site configuration`
|
142
142
|
# said "SiteConfig 1 does not exist"
|
143
143
|
# cannot save the instance here because the db table possibly doesn't yet exit.
|
144
144
|
# ~ self._site_config.save()
|
@@ -146,7 +146,7 @@ class SiteConfig(dd.Model):
|
|
146
146
|
return cls._site_config
|
147
147
|
|
148
148
|
def __str__(self):
|
149
|
-
return force_str(_("Site
|
149
|
+
return force_str(_("Site configuration"))
|
150
150
|
|
151
151
|
def update(self, **kw):
|
152
152
|
"""
|
@@ -238,6 +238,7 @@ class SiteConfigs(dd.Table):
|
|
238
238
|
detail_layout = dd.DetailLayout(
|
239
239
|
"""
|
240
240
|
default_build_method
|
241
|
+
simulate_today
|
241
242
|
# lino.ModelsBySite
|
242
243
|
""",
|
243
244
|
window_size=(60, "auto"),
|
lino/modlib/uploads/__init__.py
CHANGED
@@ -9,7 +9,7 @@ from os.path import join
|
|
9
9
|
from lino import ad, _
|
10
10
|
from lino.modlib.memo.parser import split_name_rest
|
11
11
|
|
12
|
-
UPLOADS_ROOT =
|
12
|
+
UPLOADS_ROOT = 'uploads'
|
13
13
|
VOLUMES_ROOT = 'volumes'
|
14
14
|
|
15
15
|
class Plugin(ad.Plugin):
|
@@ -25,6 +25,10 @@ class Plugin(ad.Plugin):
|
|
25
25
|
|
26
26
|
"""
|
27
27
|
|
28
|
+
with_thumbnails = False
|
29
|
+
"""Whether to use PIL, the Python Imaging Library.
|
30
|
+
"""
|
31
|
+
|
28
32
|
# def on_ui_init(self, kernel):
|
29
33
|
# super().on_ui_init(kernel)
|
30
34
|
# self.site.makedirs_if_missing(self.get_uploads_root())
|
@@ -75,3 +79,8 @@ class Plugin(ad.Plugin):
|
|
75
79
|
|
76
80
|
# site.makedirs_if_missing(self.get_uploads_root())
|
77
81
|
# site.makedirs_if_missing(self.get_volumes_root())
|
82
|
+
|
83
|
+
def get_requirements(self, site):
|
84
|
+
if self.with_thumbnails:
|
85
|
+
yield "Pillow"
|
86
|
+
yield "PyMuPDF"
|
@@ -1,9 +1,9 @@
|
|
1
1
|
# -*- coding: UTF-8 -*-
|
2
|
-
# Copyright 2010-
|
2
|
+
# Copyright 2010-2024 Rumma & Ko Ltd
|
3
3
|
# License: GNU Affero General Public License v3 (see file COPYING for details)
|
4
4
|
|
5
|
-
from lino.modlib.office.roles import OfficeStaff
|
6
5
|
from lino.api import dd, rt, _
|
6
|
+
from lino.modlib.office.roles import OfficeStaff
|
7
7
|
|
8
8
|
|
9
9
|
class Shortcut(dd.Choice):
|
@@ -17,7 +17,7 @@ class Shortcut(dd.Choice):
|
|
17
17
|
self.target = target
|
18
18
|
self.model_spec = model_spec
|
19
19
|
value = model_spec + "." + name
|
20
|
-
super(
|
20
|
+
super().__init__(value, verbose_name, name)
|
21
21
|
|
22
22
|
def get_uploads(self, **kw):
|
23
23
|
"""Return a queryset with the uploads of this shortcut."""
|
lino/modlib/uploads/mixins.py
CHANGED
@@ -7,19 +7,24 @@ 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
14
|
from django.db import models
|
11
15
|
from django.conf import settings
|
12
16
|
from django.core.files.storage import default_storage
|
13
17
|
from django.core.exceptions import ValidationError, FieldError
|
14
18
|
from django.template.defaultfilters import filesizeformat
|
19
|
+
from django.utils.html import format_html
|
15
20
|
|
16
|
-
from rstgen.sphinxconf.sigal_image import parse_image_spec
|
17
21
|
from lino.core import constants
|
18
22
|
from lino.utils import DATE_TO_DIR_TPL
|
23
|
+
from lino.utils import needs_update
|
19
24
|
from lino.utils.html import E
|
20
25
|
from lino.api import dd, rt, _
|
21
26
|
from lino.modlib.uploads import UPLOADS_ROOT
|
22
|
-
from lino.modlib.
|
27
|
+
from lino.modlib.comments.mixins import Commentable
|
23
28
|
|
24
29
|
# from lino.mixins.sequenced import Sequenced
|
25
30
|
# from lino.modlib.gfks.mixins import Controllable
|
@@ -39,12 +44,6 @@ def safe_filename(name):
|
|
39
44
|
return name
|
40
45
|
|
41
46
|
|
42
|
-
def needs_update(src, dest):
|
43
|
-
if dest.exists() and dest.stat().st_mtime >= src.stat().st_mtime:
|
44
|
-
return False
|
45
|
-
return True
|
46
|
-
|
47
|
-
|
48
47
|
def make_uploaded_file(filename, src=None, upload_date=None):
|
49
48
|
"""
|
50
49
|
Create a dummy file that looks as if a file had really been uploaded.
|
@@ -130,8 +129,9 @@ class GalleryViewable(dd.Model):
|
|
130
129
|
return {}
|
131
130
|
|
132
131
|
|
133
|
-
class UploadBase(
|
134
|
-
|
132
|
+
class UploadBase(Commentable, GalleryViewable):
|
133
|
+
|
134
|
+
class Meta:
|
135
135
|
abstract = True
|
136
136
|
|
137
137
|
extra_display_modes = {constants.DISPLAY_MODE_GALLERY}
|
@@ -180,7 +180,8 @@ class UploadBase(MemoReferrable, GalleryViewable):
|
|
180
180
|
dd.logger.info("Wrote uploaded file %s", ff.path)
|
181
181
|
|
182
182
|
def get_gallery_item(self, ar):
|
183
|
-
|
183
|
+
mf = self.get_media_file()
|
184
|
+
return dict(image_src=mf.get_image_url())
|
184
185
|
|
185
186
|
def full_clean(self, *args, **kw):
|
186
187
|
super().full_clean(*args, **kw)
|
@@ -192,39 +193,36 @@ class UploadBase(MemoReferrable, GalleryViewable):
|
|
192
193
|
def get_file_button(self, text=None):
|
193
194
|
if text is None:
|
194
195
|
text = str(self)
|
195
|
-
|
196
|
-
if
|
196
|
+
mf = self.get_media_file()
|
197
|
+
if mf is None:
|
197
198
|
return text
|
198
|
-
return E.a(text, href=
|
199
|
+
return E.a(text, href=mf.get_download_url(), target="_blank")
|
199
200
|
|
200
201
|
def memo2html(self, ar, text, **ctx):
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
url = self.get_file_url()
|
206
|
-
if url is None:
|
207
|
-
return ""
|
208
|
-
ctx.update(src=url)
|
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())
|
209
206
|
ctx.update(href=ar.renderer.obj2url(ar, self))
|
210
|
-
|
207
|
+
small_url = mf.get_image_url()
|
208
|
+
if small_url is None or small_url == mf.url: # non-previewable mimetype
|
211
209
|
if not text:
|
212
210
|
text = str(self)
|
213
211
|
ctx.update(text=text)
|
214
|
-
tpl = '(<a href="{src}" target="_blank"
|
215
|
-
tpl += '| <a href="{href}"
|
216
|
-
return tpl
|
212
|
+
tpl = '(<a href="{src}" target="_blank">{text}</a>'
|
213
|
+
tpl += '| <a href="{href}">detail</a>)'
|
214
|
+
return format_html(tpl, **ctx)
|
217
215
|
|
218
216
|
fmt = parse_image_spec(text, **ctx)
|
217
|
+
if isinstance(fmt, SMALL_FORMATS):
|
218
|
+
fmt.context.update(src=small_url)
|
219
|
+
|
219
220
|
if not fmt.context["caption"]:
|
220
221
|
fmt.context["caption"] = self.description or str(self)
|
221
|
-
# if text:
|
222
|
-
# ctx.update(caption=text)
|
223
222
|
|
224
|
-
rv = (
|
225
|
-
'<a href="{href}" target="_blank"
|
226
|
-
+ ' style="{style}" title="{caption}"/></a>'
|
227
|
-
).format(**fmt.context)
|
223
|
+
rv = format_html(
|
224
|
+
'<a href="{href}" target="_blank"><img src="{src}"'
|
225
|
+
+ ' style="{style}" title="{caption}"/></a>', **fmt.context)
|
228
226
|
# if ar.renderer.front_end.media_name == 'react':
|
229
227
|
# return ('<figure class="lino-memo-image"><img src="{src}" ' +
|
230
228
|
# 'style="{style}" title="{caption}"/><figcaption' +
|
lino/modlib/uploads/models.py
CHANGED
@@ -11,7 +11,7 @@ from django.db import models
|
|
11
11
|
from django.conf import settings
|
12
12
|
from django.core.exceptions import ValidationError
|
13
13
|
from django.utils.text import format_lazy
|
14
|
-
from django.utils.html import format_html
|
14
|
+
from django.utils.html import format_html, mark_safe
|
15
15
|
from django.utils.translation import pgettext_lazy as pgettext
|
16
16
|
|
17
17
|
from lino.utils.html import E, join_elems
|
@@ -29,6 +29,7 @@ from lino.modlib.publisher.mixins import Publishable
|
|
29
29
|
from .actions import CameraStream
|
30
30
|
from .choicelists import Shortcuts, UploadAreas
|
31
31
|
from .mixins import UploadBase
|
32
|
+
from .utils import previewer, UploadMediaFile
|
32
33
|
|
33
34
|
from . import VOLUMES_ROOT
|
34
35
|
|
@@ -36,6 +37,7 @@ from . import VOLUMES_ROOT
|
|
36
37
|
class Volume(Referrable):
|
37
38
|
|
38
39
|
class Meta:
|
40
|
+
abstract = dd.is_abstract_model(__name__, "Volume")
|
39
41
|
app_label = "uploads"
|
40
42
|
verbose_name = _("Library volume")
|
41
43
|
verbose_name_plural = _("Library volumes")
|
@@ -50,10 +52,8 @@ class Volume(Referrable):
|
|
50
52
|
return self.ref or self.root_dir
|
51
53
|
|
52
54
|
def full_clean(self, *args, **kw):
|
53
|
-
# if self.ref == "uploads":
|
54
|
-
# raise ValidationError("Invalid reference for a volume.")
|
55
55
|
super().full_clean(*args, **kw)
|
56
|
-
pth =
|
56
|
+
pth = dd.plugins.uploads.get_volumes_root() / self.ref
|
57
57
|
if pth.exists():
|
58
58
|
if pth.resolve().absolute() != Path(self.root_dir).resolve().absolute():
|
59
59
|
raise ValidationError(
|
@@ -74,8 +74,9 @@ class Volume(Referrable):
|
|
74
74
|
|
75
75
|
|
76
76
|
class UploadType(BabelNamed):
|
77
|
-
class Meta
|
77
|
+
class Meta:
|
78
78
|
abstract = dd.is_abstract_model(__name__, "UploadType")
|
79
|
+
app_label = "uploads"
|
79
80
|
verbose_name = _("Upload type")
|
80
81
|
verbose_name_plural = _("Upload types")
|
81
82
|
|
@@ -105,12 +106,13 @@ class UploadType(BabelNamed):
|
|
105
106
|
|
106
107
|
class Upload(UploadBase, UserAuthored, Controllable, Publishable):
|
107
108
|
|
108
|
-
class Meta
|
109
|
+
class Meta:
|
110
|
+
app_label = "uploads"
|
109
111
|
abstract = dd.is_abstract_model(__name__, "Upload")
|
110
112
|
verbose_name = _("Upload file")
|
111
113
|
verbose_name_plural = _("Upload files")
|
112
114
|
|
113
|
-
memo_command = "
|
115
|
+
memo_command = "file"
|
114
116
|
|
115
117
|
upload_area = UploadAreas.field(default="general")
|
116
118
|
type = dd.ForeignKey("uploads.UploadType", blank=True, null=True)
|
@@ -134,9 +136,29 @@ class Upload(UploadBase, UserAuthored, Controllable, Publishable):
|
|
134
136
|
s = str(self.type) + " " + s
|
135
137
|
return s
|
136
138
|
|
139
|
+
def get_file_path(self):
|
140
|
+
if self.file and self.file.name:
|
141
|
+
return self.file.name
|
142
|
+
elif self.library_file and self.volume_id and self.volume.ref:
|
143
|
+
return VOLUMES_ROOT + "/" + self.volume.ref + "/" + self.library_file
|
144
|
+
return None
|
145
|
+
|
146
|
+
def get_media_file(self):
|
147
|
+
url = self.get_file_path()
|
148
|
+
if url is not None:
|
149
|
+
return UploadMediaFile(url)
|
150
|
+
|
151
|
+
def get_create_comment_text(self, ar):
|
152
|
+
mf = self.get_media_file()
|
153
|
+
if mf is None:
|
154
|
+
return super().get_create_comment_text(ar)
|
155
|
+
return _("Uploaded an {obj}.").format(obj=mf.get_mimetype_description())
|
156
|
+
# or mf.get_image_url() is None:
|
157
|
+
# return _("Uploaded {obj}. [{obj.memo_command} {obj.id}].").format(obj=self)
|
158
|
+
|
137
159
|
def get_memo_command(self, ar=None):
|
138
160
|
if dd.is_installed("memo"):
|
139
|
-
cmd = f"[
|
161
|
+
cmd = f"[{self.memo_command} {self.pk}"
|
140
162
|
if self.description:
|
141
163
|
cmd += " " + self.description + "]"
|
142
164
|
else:
|
@@ -144,24 +166,13 @@ class Upload(UploadBase, UserAuthored, Controllable, Publishable):
|
|
144
166
|
return cmd
|
145
167
|
return None
|
146
168
|
|
147
|
-
def get_file_url(self):
|
148
|
-
if self.file.name:
|
149
|
-
return settings.SITE.build_media_url(self.file.name)
|
150
|
-
if self.library_file and self.volume_id and self.volume.ref:
|
151
|
-
return settings.SITE.build_media_url(
|
152
|
-
VOLUMES_ROOT, self.volume.ref, self.library_file)
|
153
|
-
# return self.volume.base_url + self.library_file
|
154
|
-
return None
|
155
|
-
|
156
169
|
def get_real_file_size(self):
|
157
170
|
if self.file:
|
158
171
|
return self.file.size
|
159
172
|
if self.volume_id and self.library_file:
|
160
|
-
pth =
|
161
|
-
|
162
|
-
|
163
|
-
# pth = os.path.join(settings.MEDIA_ROOT, self.volume.ref, self.library_file)
|
164
|
-
return os.path.getsize(pth)
|
173
|
+
pth = dd.plugins.uploads.get_volumes_root() / self.volume.ref / self.library_file
|
174
|
+
return pth.stat().st_size
|
175
|
+
# return os.path.getsize(pth)
|
165
176
|
|
166
177
|
def disabled_fields(self, ar):
|
167
178
|
df = super().disabled_fields(ar)
|
@@ -169,6 +180,16 @@ class Upload(UploadBase, UserAuthored, Controllable, Publishable):
|
|
169
180
|
df.add("camera_stream")
|
170
181
|
return df
|
171
182
|
|
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
|
+
|
172
193
|
@dd.displayfield(_("Description"))
|
173
194
|
def description_link(self, ar):
|
174
195
|
s = str(self)
|
@@ -184,11 +205,10 @@ class Upload(UploadBase, UserAuthored, Controllable, Publishable):
|
|
184
205
|
|
185
206
|
@dd.chooser()
|
186
207
|
def type_choices(self, upload_area):
|
187
|
-
|
188
|
-
# logger.info("20140430 type_choices %s", upload_area)
|
208
|
+
UploadType = rt.models.uploads.UploadType
|
189
209
|
if upload_area is None:
|
190
|
-
return
|
191
|
-
return
|
210
|
+
return UploadType.objects.all()
|
211
|
+
return UploadType.objects.filter(upload_area=upload_area)
|
192
212
|
|
193
213
|
def full_clean(self, *args, **kw):
|
194
214
|
super().full_clean(*args, **kw)
|
@@ -205,40 +225,45 @@ class Upload(UploadBase, UserAuthored, Controllable, Publishable):
|
|
205
225
|
|
206
226
|
@dd.htmlbox()
|
207
227
|
def preview(self, ar):
|
208
|
-
|
209
|
-
if
|
228
|
+
mf = self.get_media_file()
|
229
|
+
if mf is None:
|
210
230
|
txt = _("No preview available")
|
211
231
|
return '<p style="text-align: center;padding: 2em;">({})</p>'.format(txt)
|
212
|
-
return '<img src="{}" style="max-width: 100%; max-height: 20em">'.format(
|
232
|
+
return '<img src="{}" style="max-width: 100%; max-height: 20em">'.format(mf.get_image_url())
|
213
233
|
|
214
234
|
@dd.htmlbox(_("Thumbnail"))
|
215
235
|
def thumbnail(self, ar):
|
216
236
|
# url = settings.SITE.build_media_url(self.file.name)
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
def thumbnail_medium(self, ar):
|
222
|
-
# url = settings.SITE.build_media_url(self.file.name)
|
223
|
-
url = self.get_file_url()
|
224
|
-
return '<img src="{}" style="width: 30ch;">'.format(url)
|
225
|
-
|
226
|
-
@dd.htmlbox(_("Thumbnail Large"))
|
227
|
-
def thumbnail_large(self, ar):
|
228
|
-
# url = settings.SITE.build_media_url(self.file.name)
|
229
|
-
url = self.get_file_url()
|
230
|
-
return '<img src="{}" style="width: 70ch;">'.format(url)
|
237
|
+
mf = self.get_media_file()
|
238
|
+
if mf is None:
|
239
|
+
return ""
|
240
|
+
return '<img src="{}" style="height: 15ch; max-width: 22.5ch">'.format(mf.get_image_url())
|
231
241
|
|
232
242
|
def as_page(self, ar, **kwargs):
|
233
243
|
yield format_html("<h1>{}</h1>", self)
|
234
|
-
|
235
|
-
|
244
|
+
mf = self.get_media_file()
|
245
|
+
if mf is not None:
|
246
|
+
yield format_html('<img src="{}" style="width: 100%;">', mf.get_image_url())
|
236
247
|
if self.description:
|
237
248
|
yield escape(self.description)
|
238
249
|
if self.source:
|
239
250
|
yield _("Source") + ": "
|
240
251
|
yield ar.obj2htmls(self.source)
|
241
252
|
|
253
|
+
def as_paragraph(self, ar, **kwargs):
|
254
|
+
rv = self.memo2html(ar, "")
|
255
|
+
# rv = ar.obj2htmls(self)
|
256
|
+
# mf = self.get_media_file()
|
257
|
+
# if mf is not None:
|
258
|
+
# src = mf.get_image_url()
|
259
|
+
# if src is not None:
|
260
|
+
# url = mf.get_download_url()
|
261
|
+
# rv += f'<a href="{url}"><img src="{src}" style="width: 30%;"></a>'
|
262
|
+
if self.source:
|
263
|
+
rv += format_html(
|
264
|
+
" ({}: {})", _("Source"), ar.obj2htmls(self.source))
|
265
|
+
return mark_safe(rv)
|
266
|
+
|
242
267
|
# def get_choices_text(self, ar, actor, field):
|
243
268
|
# if self.file:
|
244
269
|
# return str(obj) + " <span style=\"float: right;\">" + obj.thumbnail + "</span>"
|
@@ -246,11 +271,12 @@ class Upload(UploadBase, UserAuthored, Controllable, Publishable):
|
|
246
271
|
|
247
272
|
|
248
273
|
dd.update_field(Upload, "user", verbose_name=_("Uploaded by"))
|
274
|
+
dd.update_field(Upload, "owner", verbose_name=_("Attached to"))
|
249
275
|
|
250
276
|
|
251
277
|
class UploadChecker(Checker):
|
252
278
|
verbose_name = _("Check metadata of upload files")
|
253
|
-
model = Upload
|
279
|
+
model = "uploads.Upload"
|
254
280
|
|
255
281
|
def get_checkdata_problems(self, obj, fix=False):
|
256
282
|
if obj.file:
|
@@ -267,6 +293,8 @@ class UploadChecker(Checker):
|
|
267
293
|
tpl = "Stored file size {} differs from real file size {}"
|
268
294
|
yield (False, format_lazy(tpl, obj.file_size, file_size))
|
269
295
|
|
296
|
+
for i in obj.check_previews(fix):
|
297
|
+
yield i
|
270
298
|
|
271
299
|
UploadChecker.activate()
|
272
300
|
|
@@ -304,7 +332,6 @@ class UploadsFolderChecker(Checker):
|
|
304
332
|
UploadsFolderChecker.activate()
|
305
333
|
|
306
334
|
|
307
|
-
|
308
335
|
@dd.receiver(dd.pre_analyze)
|
309
336
|
def before_analyze(sender, **kwargs):
|
310
337
|
# This is the successor for `quick_upload_buttons`.
|
@@ -313,6 +340,8 @@ def before_analyze(sender, **kwargs):
|
|
313
340
|
UploadType = sender.models.uploads.UploadType
|
314
341
|
Shortcuts = sender.models.uploads.Shortcuts
|
315
342
|
|
343
|
+
# raise Exception(f"20241112 {UploadType}")
|
344
|
+
|
316
345
|
for i in Shortcuts.items():
|
317
346
|
|
318
347
|
def f(obj, ar):
|
@@ -341,16 +370,17 @@ def before_analyze(sender, **kwargs):
|
|
341
370
|
elif n == 1:
|
342
371
|
after_show = ar.get_status()
|
343
372
|
obj = sar.data_iterator[0]
|
344
|
-
|
345
|
-
|
346
|
-
|
347
|
-
|
348
|
-
|
349
|
-
|
350
|
-
|
351
|
-
|
373
|
+
if (mf := obj.get_media_file()) is not None:
|
374
|
+
items.append(
|
375
|
+
sar.renderer.href_button(
|
376
|
+
mf.get_download_url(),
|
377
|
+
_("show"),
|
378
|
+
target="_blank",
|
379
|
+
icon_name="page_go",
|
380
|
+
style="vertical-align:-30%;",
|
381
|
+
title=_("Open the uploaded file in a new browser window"),
|
382
|
+
)
|
352
383
|
)
|
353
|
-
)
|
354
384
|
after_show.update(record_id=obj.pk)
|
355
385
|
items.append(
|
356
386
|
sar.window_action_button(
|
@@ -381,4 +411,7 @@ def before_analyze(sender, **kwargs):
|
|
381
411
|
# logger.info("Installed upload shortcut field %s.%s",
|
382
412
|
# i.model_spec, i.name)
|
383
413
|
|
414
|
+
|
384
415
|
from .ui import *
|
416
|
+
|
417
|
+
# raise Exception("20241112")
|
lino/modlib/uploads/ui.py
CHANGED
@@ -66,10 +66,11 @@ class UploadDetail(dd.DetailLayout):
|
|
66
66
|
"""
|
67
67
|
|
68
68
|
left = """
|
69
|
-
file
|
69
|
+
file
|
70
70
|
volume:10 library_file:40
|
71
|
-
|
72
|
-
|
71
|
+
user owner
|
72
|
+
upload_area type
|
73
|
+
description
|
73
74
|
source
|
74
75
|
"""
|
75
76
|
|
@@ -82,6 +83,11 @@ class Uploads(dd.Table):
|
|
82
83
|
required_roles = dd.login_required(UploadsReader)
|
83
84
|
column_names = "file type user owner description id *"
|
84
85
|
order_by = ["-id"]
|
86
|
+
default_display_modes = {
|
87
|
+
70: constants.DISPLAY_MODE_LIST,
|
88
|
+
None: constants.DISPLAY_MODE_GALLERY
|
89
|
+
}
|
90
|
+
# extra_display_modes = {constants.DISPLAY_MODE_LIST, constants.DISPLAY_MODE_GALLERY}
|
85
91
|
|
86
92
|
detail_layout = "uploads.UploadDetail"
|
87
93
|
|
@@ -197,10 +203,10 @@ class AreaUploads(Uploads):
|
|
197
203
|
# icon_name='application_form',
|
198
204
|
title=_("Edit metadata of the uploaded file."),
|
199
205
|
)
|
200
|
-
|
201
|
-
if
|
206
|
+
mf = m.get_media_file()
|
207
|
+
if mf:
|
202
208
|
show = ar.renderer.href_button(
|
203
|
-
|
209
|
+
mf.get_download_url(),
|
204
210
|
# u"\u21A7", # DOWNWARDS ARROW FROM BAR (↧)
|
205
211
|
# u"\u21E8",
|
206
212
|
"\u21f2", # SOUTH EAST ARROW TO CORNER (⇲)
|