lino 25.2.3__py3-none-any.whl → 25.3.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/dd.py +11 -48
- lino/api/doctest.py +34 -36
- lino/core/actions.py +25 -23
- lino/core/actors.py +37 -17
- lino/core/choicelists.py +10 -8
- lino/core/dbtables.py +1 -1
- lino/core/elems.py +46 -30
- lino/core/fields.py +19 -9
- lino/core/inject.py +7 -6
- lino/core/kernel.py +26 -66
- lino/core/model.py +44 -31
- lino/core/plugin.py +4 -4
- lino/core/requests.py +76 -55
- lino/core/site.py +84 -30
- lino/core/store.py +5 -2
- lino/core/utils.py +12 -7
- lino/help_texts.py +3 -8
- lino/management/commands/prep.py +1 -1
- lino/mixins/duplicable.py +6 -4
- lino/mixins/sequenced.py +17 -6
- lino/modlib/__init__.py +0 -2
- lino/modlib/changes/models.py +21 -10
- lino/modlib/checkdata/models.py +59 -24
- lino/modlib/comments/fixtures/demo2.py +12 -3
- lino/modlib/comments/models.py +7 -7
- lino/modlib/comments/ui.py +8 -5
- lino/modlib/export_excel/models.py +7 -5
- lino/modlib/extjs/views.py +39 -20
- lino/modlib/help/management/commands/makehelp.py +5 -2
- lino/modlib/jinja/mixins.py +25 -14
- lino/modlib/linod/__init__.py +1 -0
- lino/modlib/linod/choicelists.py +21 -0
- lino/modlib/linod/consumers.py +13 -4
- lino/modlib/linod/management/commands/linod.py +6 -2
- lino/modlib/linod/mixins.py +16 -11
- lino/modlib/linod/models.py +4 -2
- lino/modlib/notify/models.py +18 -10
- lino/modlib/printing/actions.py +41 -30
- lino/modlib/printing/choicelists.py +11 -9
- lino/modlib/printing/mixins.py +25 -20
- lino/modlib/publisher/models.py +5 -5
- lino/modlib/summaries/models.py +3 -2
- lino/modlib/system/models.py +28 -29
- lino/modlib/uploads/__init__.py +5 -5
- lino/modlib/uploads/actions.py +2 -8
- lino/modlib/uploads/choicelists.py +10 -10
- lino/modlib/uploads/fixtures/std.py +17 -0
- lino/modlib/uploads/mixins.py +20 -8
- lino/modlib/uploads/models.py +60 -35
- lino/modlib/uploads/ui.py +10 -7
- lino/utils/media.py +45 -23
- lino/utils/report.py +5 -4
- lino/utils/soup.py +22 -4
- {lino-25.2.3.dist-info → lino-25.3.1.dist-info}/METADATA +1 -1
- {lino-25.2.3.dist-info → lino-25.3.1.dist-info}/RECORD +59 -80
- lino/mixins/uploadable.py +0 -3
- lino/sandbox/bcss/PerformInvestigation.py +0 -2260
- lino/sandbox/bcss/SSDNReply.py +0 -3924
- lino/sandbox/bcss/SSDNRequest.py +0 -3723
- lino/sandbox/bcss/__init__.py +0 -0
- lino/sandbox/bcss/readme.txt +0 -1
- lino/sandbox/bcss/test.py +0 -92
- lino/sandbox/bcss/test2.py +0 -128
- lino/sandbox/bcss/test3.py +0 -161
- lino/sandbox/bcss/test4.py +0 -167
- lino/sandbox/contacts/__init__.py +0 -0
- lino/sandbox/contacts/fixtures/__init__.py +0 -0
- lino/sandbox/contacts/fixtures/demo.py +0 -365
- lino/sandbox/contacts/manage.py +0 -10
- lino/sandbox/contacts/models.py +0 -395
- lino/sandbox/contacts/settings.py +0 -67
- lino/sandbox/tx25/XSD/RetrieveTIGroupsV3.wsdl +0 -65
- lino/sandbox/tx25/XSD/RetrieveTIGroupsV3.xsd +0 -286
- lino/sandbox/tx25/XSD/rn25_Release201104.xsd +0 -2855
- lino/sandbox/tx25/xsd2py1.py +0 -68
- lino/sandbox/tx25/xsd2py2.py +0 -62
- lino/sandbox/tx25/xsd2py3.py +0 -56
- {lino-25.2.3.dist-info → lino-25.3.1.dist-info}/WHEEL +0 -0
- {lino-25.2.3.dist-info → lino-25.3.1.dist-info}/licenses/AUTHORS.rst +0 -0
- {lino-25.2.3.dist-info → lino-25.3.1.dist-info}/licenses/COPYING +0 -0
lino/modlib/publisher/models.py
CHANGED
@@ -2,6 +2,7 @@
|
|
2
2
|
# Copyright 2012-2024 Rumma & Ko Ltd
|
3
3
|
# License: GNU Affero General Public License v3 (see file COPYING for details)
|
4
4
|
|
5
|
+
from .ui import *
|
5
6
|
from lino.api import rt, dd
|
6
7
|
|
7
8
|
from .choicelists import PublishingStates, PageFillers, SpecialPages
|
@@ -31,6 +32,7 @@ from lino.mixins import Hierarchical, Sequenced, Referrable
|
|
31
32
|
from lino.modlib.office.roles import OfficeUser
|
32
33
|
from lino.modlib.publisher.mixins import Publishable, PublishableContent
|
33
34
|
from lino.modlib.comments.mixins import Commentable
|
35
|
+
from lino.modlib.linod.choicelists import schedule_daily
|
34
36
|
from lino.modlib.memo.mixins import Previewable
|
35
37
|
from lino.mixins.polymorphic import Polymorphic
|
36
38
|
from lino_xl.lib.topics.mixins import Taggable
|
@@ -50,7 +52,8 @@ class Page(
|
|
50
52
|
|
51
53
|
memo_command = "page"
|
52
54
|
|
53
|
-
ref = models.CharField(
|
55
|
+
ref = models.CharField(
|
56
|
+
_("Reference"), max_length=200, blank=True, null=True)
|
54
57
|
|
55
58
|
title = dd.CharField(_("Title"), max_length=250, blank=True)
|
56
59
|
child_node_depth = models.IntegerField(default=1)
|
@@ -382,7 +385,7 @@ if dd.plugins.memo.use_markup:
|
|
382
385
|
# language = dd.LanguageField()
|
383
386
|
|
384
387
|
|
385
|
-
@
|
388
|
+
@schedule_daily()
|
386
389
|
def update_publisher_pages(ar):
|
387
390
|
# BaseRequest(parent=ar).run(settings.SITE.site_config.check_all_summaries)
|
388
391
|
# rt.login().run(settings.SITE.site_config.check_all_summaries)
|
@@ -398,6 +401,3 @@ def update_publisher_pages(ar):
|
|
398
401
|
prev = obj
|
399
402
|
count += 1
|
400
403
|
ar.logger.info("%d pages have been updated.", count)
|
401
|
-
|
402
|
-
|
403
|
-
from .ui import *
|
lino/modlib/summaries/models.py
CHANGED
@@ -5,7 +5,8 @@ from django.conf import settings
|
|
5
5
|
|
6
6
|
# from django.db import models
|
7
7
|
from lino.api import dd, rt, _
|
8
|
-
from lino.core.requests import BaseRequest
|
8
|
+
# from lino.core.requests import BaseRequest
|
9
|
+
from lino.modlib.linod.choicelists import schedule_daily
|
9
10
|
|
10
11
|
from .mixins import UpdateSummariesByMaster, SlaveSummarized, Summarized
|
11
12
|
|
@@ -54,7 +55,7 @@ def masters_with_summaries():
|
|
54
55
|
return summary_masters
|
55
56
|
|
56
57
|
|
57
|
-
@
|
58
|
+
@schedule_daily()
|
58
59
|
def checksummaries(ar):
|
59
60
|
# BaseRequest(parent=ar).run(settings.SITE.site_config.check_all_summaries)
|
60
61
|
# rt.login().run(settings.SITE.site_config.check_all_summaries)
|
lino/modlib/system/models.py
CHANGED
@@ -2,6 +2,27 @@
|
|
2
2
|
# Copyright 2009-2023 Rumma & Ko Ltd
|
3
3
|
# License: GNU Affero General Public License v3 (see file COPYING for details)
|
4
4
|
|
5
|
+
from lino.utils.report import EmptyTable
|
6
|
+
from lino.core.signals import testcase_setup
|
7
|
+
from django.test.signals import setting_changed
|
8
|
+
from .mixins import Lockable
|
9
|
+
from .choicelists import (
|
10
|
+
YesNo,
|
11
|
+
Genders,
|
12
|
+
PeriodEvents,
|
13
|
+
DurationUnits,
|
14
|
+
Recurrences,
|
15
|
+
Weekdays,
|
16
|
+
DisplayColors
|
17
|
+
)
|
18
|
+
from lino.modlib.checkdata.choicelists import Checker
|
19
|
+
from lino.modlib.printing.choicelists import BuildMethods
|
20
|
+
from lino.core.actors import resolve_action
|
21
|
+
from lino.core.roles import SiteStaff
|
22
|
+
from lino.core.utils import is_devserver
|
23
|
+
from lino.core.utils import full_model_name
|
24
|
+
from lino.core import actions
|
25
|
+
from lino.api import dd, rt
|
5
26
|
from lino.utils.html import E
|
6
27
|
from django.conf import settings
|
7
28
|
from django.utils.encoding import force_str
|
@@ -14,27 +35,8 @@ from django.apps import apps
|
|
14
35
|
|
15
36
|
get_models = apps.get_models
|
16
37
|
|
17
|
-
from lino.api import dd, rt
|
18
|
-
from lino.core import actions
|
19
|
-
from lino.core.utils import full_model_name
|
20
|
-
from lino.core.utils import is_devserver
|
21
|
-
from lino.core.roles import SiteStaff
|
22
|
-
from lino.core.actors import resolve_action
|
23
|
-
|
24
|
-
from lino.modlib.printing.choicelists import BuildMethods
|
25
|
-
from lino.modlib.checkdata.choicelists import Checker
|
26
38
|
|
27
39
|
# import them here to have them on rt.models.system:
|
28
|
-
from .choicelists import (
|
29
|
-
YesNo,
|
30
|
-
Genders,
|
31
|
-
PeriodEvents,
|
32
|
-
DurationUnits,
|
33
|
-
Recurrences,
|
34
|
-
Weekdays,
|
35
|
-
DisplayColors
|
36
|
-
)
|
37
|
-
from .mixins import Lockable
|
38
40
|
|
39
41
|
|
40
42
|
class BuildSiteCache(dd.Action):
|
@@ -89,7 +91,8 @@ class SiteConfig(dd.Model):
|
|
89
91
|
verbose_name=_("Default build method"), blank=True, null=True
|
90
92
|
)
|
91
93
|
|
92
|
-
simulate_today = models.DateField(
|
94
|
+
simulate_today = models.DateField(
|
95
|
+
_("Simulated date"), blank=True, null=True)
|
93
96
|
|
94
97
|
site_company = dd.ForeignKey(
|
95
98
|
"contacts.Company",
|
@@ -190,10 +193,12 @@ class SiteConfig(dd.Model):
|
|
190
193
|
oldval = getattr(cls._site_config, fld.attname)
|
191
194
|
newval = getattr(self, fld.attname)
|
192
195
|
if oldval != newval:
|
193
|
-
diffs.append(
|
196
|
+
diffs.append(
|
197
|
+
"{}: {} -> {}".format(fld.attname, oldval, newval))
|
194
198
|
if len(diffs):
|
195
199
|
print(
|
196
|
-
"20220824 Overriding SiteConfig instance (diffs={})".format(
|
200
|
+
"20220824 Overriding SiteConfig instance (diffs={})".format(
|
201
|
+
diffs)
|
197
202
|
)
|
198
203
|
cls._site_config = self
|
199
204
|
# cls._site_config.update_from(self)
|
@@ -212,9 +217,6 @@ def my_handler(sender, **kw):
|
|
212
217
|
# ~ dd.database_connected.send(sender,**kw)
|
213
218
|
|
214
219
|
|
215
|
-
from django.test.signals import setting_changed
|
216
|
-
from lino.core.signals import testcase_setup
|
217
|
-
|
218
220
|
setting_changed.connect(my_handler)
|
219
221
|
testcase_setup.connect(my_handler)
|
220
222
|
dd.connection_created.connect(my_handler)
|
@@ -244,9 +246,6 @@ class SiteConfigs(dd.Table):
|
|
244
246
|
do_build = BuildSiteCache()
|
245
247
|
|
246
248
|
|
247
|
-
from lino.utils.report import EmptyTable
|
248
|
-
|
249
|
-
|
250
249
|
class Dashboard(EmptyTable):
|
251
250
|
# label = _("D")
|
252
251
|
hide_navigator = True
|
@@ -328,7 +327,7 @@ class BleachChecker(Checker):
|
|
328
327
|
yield m
|
329
328
|
|
330
329
|
def get_checkdata_problems(self, obj, fix=False):
|
331
|
-
t = tuple(obj.fields_to_bleach())
|
330
|
+
t = tuple(obj.fields_to_bleach(save=False))
|
332
331
|
if len(t):
|
333
332
|
fldnames = ", ".join([f.name for f, old, new in t])
|
334
333
|
yield (True, _("Fields {} have unbleached content.").format(fldnames))
|
lino/modlib/uploads/__init__.py
CHANGED
@@ -1,9 +1,6 @@
|
|
1
1
|
# Copyright 2010-2024 Rumma & Ko Ltd
|
2
2
|
# License: GNU Affero General Public License v3 (see file COPYING for details)
|
3
|
-
"""See :doc:`/specs/uploads`.
|
4
3
|
|
5
|
-
|
6
|
-
"""
|
7
4
|
from os import symlink
|
8
5
|
from os.path import join
|
9
6
|
from lino import ad, _
|
@@ -12,6 +9,7 @@ from lino.modlib.memo.parser import split_name_rest
|
|
12
9
|
UPLOADS_ROOT = 'uploads'
|
13
10
|
VOLUMES_ROOT = 'volumes'
|
14
11
|
|
12
|
+
|
15
13
|
class Plugin(ad.Plugin):
|
16
14
|
"See :doc:`/dev/plugins`."
|
17
15
|
|
@@ -68,10 +66,12 @@ class Plugin(ad.Plugin):
|
|
68
66
|
|
69
67
|
def gallery(ar, text, cmdname, mentions, context):
|
70
68
|
Upload = site.models.uploads.Upload
|
71
|
-
photos = [Upload.objects.get(pk=int(pk))
|
69
|
+
photos = [Upload.objects.get(pk=int(pk))
|
70
|
+
for pk in text.split()]
|
72
71
|
# ctx = dict(width="{}%".format(int(100/len(photos))))
|
73
72
|
mentions.update(photos)
|
74
|
-
html = "".join([obj.memo2html(ar, obj.description)
|
73
|
+
html = "".join([obj.memo2html(ar, obj.description)
|
74
|
+
for obj in photos])
|
75
75
|
return '<p align="center">{}</p>'.format(html)
|
76
76
|
|
77
77
|
site.plugins.memo.parser.register_command("gallery", gallery)
|
lino/modlib/uploads/actions.py
CHANGED
@@ -2,10 +2,8 @@
|
|
2
2
|
# Copyright 2023 Rumma & Ko Ltd
|
3
3
|
# License: GNU Affero General Public License v3 (see file COPYING for details)
|
4
4
|
|
5
|
-
import base64
|
6
|
-
from django.utils import timezone
|
7
5
|
from lino.api import rt, dd, _
|
8
|
-
from .mixins import
|
6
|
+
from .mixins import base64_to_image
|
9
7
|
|
10
8
|
|
11
9
|
class CameraStream(dd.Action): # TODO: rename this to CaptureImage
|
@@ -29,12 +27,8 @@ class CameraStream(dd.Action): # TODO: rename this to CaptureImage
|
|
29
27
|
description
|
30
28
|
"""
|
31
29
|
|
32
|
-
def base64_to_image(self, imgstring):
|
33
|
-
imgdata = base64.b64decode(imgstring.split("base64,")[1])
|
34
|
-
return make_captured_image(imgdata, timezone.now())
|
35
|
-
|
36
30
|
def handle_uploaded_file(self, ar, **kwargs):
|
37
|
-
file =
|
31
|
+
file = base64_to_image(ar.request.POST["image"])
|
38
32
|
upload = rt.models.uploads.Upload(file=file, user=ar.get_user(), **kwargs)
|
39
33
|
upload.save_new_instance(ar)
|
40
34
|
return upload
|
@@ -6,6 +6,16 @@ from lino.api import dd, rt, _
|
|
6
6
|
from lino.modlib.office.roles import OfficeStaff
|
7
7
|
|
8
8
|
|
9
|
+
class UploadAreas(dd.ChoiceList):
|
10
|
+
required_roles = dd.login_required(OfficeStaff)
|
11
|
+
verbose_name = _("Upload area")
|
12
|
+
verbose_name_plural = _("Upload areas")
|
13
|
+
|
14
|
+
|
15
|
+
add = UploadAreas.add_item
|
16
|
+
add("90", _("Uploads"), "general")
|
17
|
+
|
18
|
+
|
9
19
|
class Shortcut(dd.Choice):
|
10
20
|
"""Represents a shortcut field."""
|
11
21
|
|
@@ -31,15 +41,5 @@ class Shortcuts(dd.ChoiceList):
|
|
31
41
|
max_length = 50 # fields get created before the values are known
|
32
42
|
|
33
43
|
|
34
|
-
class UploadAreas(dd.ChoiceList):
|
35
|
-
required_roles = dd.login_required(OfficeStaff)
|
36
|
-
verbose_name = _("Upload area")
|
37
|
-
verbose_name_plural = _("Upload areas")
|
38
|
-
|
39
|
-
|
40
|
-
add = UploadAreas.add_item
|
41
|
-
add("90", _("Uploads"), "general")
|
42
|
-
|
43
|
-
|
44
44
|
def add_shortcut(*args, **kw):
|
45
45
|
return Shortcuts.add_item(*args, **kw)
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# -*- coding: UTF-8 -*-
|
2
|
+
# Copyright 2015-2025 Rumma & Ko Ltd
|
3
|
+
# License: GNU Affero General Public License v3 (see file COPYING for details)
|
4
|
+
|
5
|
+
from lino.api import dd, rt
|
6
|
+
from lino.modlib.system.choicelists import Recurrences
|
7
|
+
from lino.modlib.uploads.choicelists import Shortcuts
|
8
|
+
|
9
|
+
|
10
|
+
def objects():
|
11
|
+
UploadType = rt.models.uploads.UploadType
|
12
|
+
|
13
|
+
kw = dict(max_number=1, wanted=True)
|
14
|
+
|
15
|
+
for us in Shortcuts.get_list_items():
|
16
|
+
kw.update(dd.str2kw('name', us.text))
|
17
|
+
yield UploadType(shortcut=us, **kw)
|
lino/modlib/uploads/mixins.py
CHANGED
@@ -2,6 +2,7 @@
|
|
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 base64
|
5
6
|
import os
|
6
7
|
import shutil
|
7
8
|
import uuid
|
@@ -17,6 +18,7 @@ from django.core.files.storage import default_storage
|
|
17
18
|
from django.core.exceptions import ValidationError, FieldError
|
18
19
|
from django.template.defaultfilters import filesizeformat
|
19
20
|
from django.utils.html import format_html
|
21
|
+
from django.utils import timezone
|
20
22
|
|
21
23
|
from lino.core import constants
|
22
24
|
from lino.utils import DATE_TO_DIR_TPL
|
@@ -57,7 +59,8 @@ def make_uploaded_file(filename, src=None, upload_date=None):
|
|
57
59
|
raise Exception(f"Source {src} does not exist")
|
58
60
|
filename = default_storage.generate_filename(safe_filename(filename))
|
59
61
|
upload_to = Path(upload_date.strftime(upload_to_tpl))
|
60
|
-
upload_filename = default_storage.generate_filename(
|
62
|
+
upload_filename = default_storage.generate_filename(
|
63
|
+
str(upload_to / filename))
|
61
64
|
dest = settings.SITE.media_root / upload_filename
|
62
65
|
if needs_update(src, dest):
|
63
66
|
print("cp {} {}".format(src, dest))
|
@@ -66,17 +69,26 @@ def make_uploaded_file(filename, src=None, upload_date=None):
|
|
66
69
|
return upload_filename
|
67
70
|
|
68
71
|
|
69
|
-
def
|
72
|
+
def base64_to_image(imgstring):
|
73
|
+
type, file = imgstring.split(";base64,")
|
74
|
+
imgdata = base64.b64decode(file)
|
75
|
+
return make_captured_image(imgdata, timezone.now(), ext=f".{type.split('/')[1]}")
|
76
|
+
|
77
|
+
|
78
|
+
def make_captured_image(imgdata, upload_date=None, filename=None, ext='.jpg'):
|
70
79
|
if upload_date is None:
|
71
|
-
upload_date = dd.
|
72
|
-
|
73
|
-
|
80
|
+
upload_date = dd.today()
|
81
|
+
if not filename:
|
82
|
+
filename = str(uuid.uuid4()) + ext
|
83
|
+
filename = default_storage.generate_filename(safe_filename(filename))
|
74
84
|
upload_to = Path(upload_date.strftime(upload_to_tpl))
|
75
|
-
upload_filename = default_storage.generate_filename(
|
85
|
+
upload_filename = default_storage.generate_filename(
|
86
|
+
str(upload_to / filename))
|
76
87
|
dest = Path(settings.MEDIA_ROOT) / upload_filename
|
77
88
|
dest.parent.mkdir(parents=True, exist_ok=True)
|
78
|
-
|
79
|
-
|
89
|
+
dest.write_bytes(imgdata)
|
90
|
+
# with dest.open("wb") as f:
|
91
|
+
# f.write(imgdata)
|
80
92
|
return upload_filename
|
81
93
|
|
82
94
|
|
lino/modlib/uploads/models.py
CHANGED
@@ -2,6 +2,7 @@
|
|
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
|
+
from .ui import *
|
5
6
|
import os
|
6
7
|
from os.path import join, exists
|
7
8
|
from pathlib import Path
|
@@ -26,13 +27,14 @@ from lino.modlib.users.mixins import UserAuthored, My
|
|
26
27
|
# from lino.modlib.office.roles import OfficeUser, OfficeStaff, OfficeOperator
|
27
28
|
from lino.modlib.office.roles import OfficeStaff
|
28
29
|
from lino.mixins import Referrable
|
30
|
+
from lino.utils.soup import register_sanitizer
|
29
31
|
from lino.utils.mldbc.mixins import BabelNamed
|
30
32
|
from lino.modlib.checkdata.choicelists import Checker
|
31
33
|
from lino.modlib.publisher.mixins import Publishable
|
32
34
|
|
33
35
|
from .actions import CameraStream
|
34
|
-
from .choicelists import Shortcuts, UploadAreas
|
35
|
-
from .mixins import UploadBase
|
36
|
+
from .choicelists import Shortcuts, UploadAreas, add_shortcut
|
37
|
+
from .mixins import UploadBase, base64_to_image
|
36
38
|
from .utils import previewer, UploadMediaFile
|
37
39
|
|
38
40
|
from . import VOLUMES_ROOT
|
@@ -121,8 +123,10 @@ class Upload(UploadBase, UserAuthored, Controllable, Publishable):
|
|
121
123
|
upload_area = UploadAreas.field(default="general")
|
122
124
|
type = dd.ForeignKey("uploads.UploadType", blank=True, null=True)
|
123
125
|
volume = dd.ForeignKey("uploads.Volume", blank=True, null=True)
|
124
|
-
library_file = models.CharField(
|
125
|
-
|
126
|
+
library_file = models.CharField(
|
127
|
+
_("Library file"), max_length=255, blank=True)
|
128
|
+
description = models.CharField(
|
129
|
+
_("Description"), max_length=200, blank=True)
|
126
130
|
source = dd.ForeignKey("sources.Source", blank=True, null=True)
|
127
131
|
|
128
132
|
camera_stream = CameraStream()
|
@@ -286,7 +290,8 @@ class UploadChecker(Checker):
|
|
286
290
|
if not exists(join(settings.MEDIA_ROOT, obj.file.name)):
|
287
291
|
yield (
|
288
292
|
False,
|
289
|
-
format_lazy(_("Upload entry {} has no file"),
|
293
|
+
format_lazy(_("Upload entry {} has no file"),
|
294
|
+
obj.file.name),
|
290
295
|
)
|
291
296
|
return
|
292
297
|
|
@@ -299,6 +304,7 @@ class UploadChecker(Checker):
|
|
299
304
|
for i in obj.check_previews(fix):
|
300
305
|
yield i
|
301
306
|
|
307
|
+
|
302
308
|
UploadChecker.activate()
|
303
309
|
|
304
310
|
|
@@ -319,7 +325,8 @@ class UploadsFolderChecker(Checker):
|
|
319
325
|
qs = Upload.objects.filter(file=rel_filename)
|
320
326
|
n = qs.count()
|
321
327
|
if n == 0:
|
322
|
-
msg = format_lazy(
|
328
|
+
msg = format_lazy(
|
329
|
+
_("File {} has no upload entry."), rel_filename)
|
323
330
|
# print(msg)
|
324
331
|
yield (dd.plugins.uploads.remove_orphaned_files, msg)
|
325
332
|
if fix and dd.plugins.uploads.remove_orphaned_files:
|
@@ -357,31 +364,33 @@ def before_analyze(sender, **kwargs):
|
|
357
364
|
items = []
|
358
365
|
target = sender.modules.resolve(i.target)
|
359
366
|
sar = ar.spawn_request(
|
360
|
-
actor=target, master_instance=obj, known_values=dict(
|
361
|
-
|
367
|
+
actor=target, master_instance=obj, known_values=dict(
|
368
|
+
type=utype))
|
362
369
|
# param_values=dict(pupload_type=et))
|
363
370
|
n = sar.get_total_count()
|
364
371
|
if n == 0:
|
365
|
-
iar = target.insert_action.request_from(
|
372
|
+
iar = target.insert_action.request_from(
|
373
|
+
sar, master_instance=obj)
|
366
374
|
btn = iar.ar2button(
|
367
375
|
None,
|
368
|
-
_("Upload"),
|
369
|
-
icon_name="page_add",
|
370
|
-
title=_("Upload a file from your PC to the server.")
|
371
|
-
)
|
376
|
+
"⊕", # _("Upload"),
|
377
|
+
# icon_name="page_add",
|
378
|
+
title=_("Upload a file from your PC to the server."))
|
372
379
|
items.append(btn)
|
373
|
-
elif n == 1:
|
380
|
+
# elif n == 1:
|
381
|
+
else:
|
374
382
|
after_show = ar.get_status()
|
375
383
|
obj = sar.data_iterator[0]
|
376
384
|
if (mf := obj.get_media_file()) is not None:
|
377
385
|
items.append(
|
378
386
|
sar.renderer.href_button(
|
379
387
|
mf.get_download_url(),
|
380
|
-
|
388
|
+
"⎙", # Unicode symbol Print Screen
|
381
389
|
target="_blank",
|
382
|
-
icon_name="page_go",
|
383
|
-
style="vertical-align:-30%;",
|
384
|
-
title=_(
|
390
|
+
# icon_name="page_go",
|
391
|
+
# style="vertical-align:-30%;",
|
392
|
+
title=_(
|
393
|
+
"Open the uploaded file in a new browser window"),
|
385
394
|
)
|
386
395
|
)
|
387
396
|
after_show.update(record_id=obj.pk)
|
@@ -389,23 +398,25 @@ def before_analyze(sender, **kwargs):
|
|
389
398
|
sar.window_action_button(
|
390
399
|
sar.ah.actor.detail_action,
|
391
400
|
after_show,
|
392
|
-
|
393
|
-
icon_name="application_form",
|
394
|
-
title=_("Edit
|
401
|
+
"⎆", # Unicode symbol Enter
|
402
|
+
# icon_name="application_form",
|
403
|
+
title=_("Edit the information about the uploaded file."),
|
395
404
|
)
|
396
405
|
)
|
397
|
-
else:
|
398
|
-
|
399
|
-
|
400
|
-
|
401
|
-
|
402
|
-
|
403
|
-
|
404
|
-
|
405
|
-
|
406
|
-
|
407
|
-
|
408
|
-
|
406
|
+
# else:
|
407
|
+
# obj = sar.sliced_data_iterator[0]
|
408
|
+
# items.append(ar.obj2html(
|
409
|
+
# obj, pgettext("uploaded file", "Last")))
|
410
|
+
|
411
|
+
btn = sar.renderer.action_button(
|
412
|
+
obj,
|
413
|
+
sar,
|
414
|
+
sar.bound_action,
|
415
|
+
"⏏", # _("All {0} files").format(n),
|
416
|
+
icon_name=None,
|
417
|
+
title=_("Manage the list of uploaded files.")
|
418
|
+
)
|
419
|
+
items.append(btn)
|
409
420
|
|
410
421
|
return E.div(*join_elems(items, ", "))
|
411
422
|
|
@@ -415,8 +426,6 @@ def before_analyze(sender, **kwargs):
|
|
415
426
|
# i.model_spec, i.name)
|
416
427
|
|
417
428
|
|
418
|
-
from .ui import *
|
419
|
-
|
420
429
|
# raise Exception("20241112")
|
421
430
|
|
422
431
|
|
@@ -466,3 +475,19 @@ def setup_memo_commands(sender=None, **kwargs):
|
|
466
475
|
|
467
476
|
mp = sender.plugins.memo.parser
|
468
477
|
mp.register_django_model('file', rt.models.uploads.Upload, rnd=file2html)
|
478
|
+
|
479
|
+
|
480
|
+
def on_sanitize(soup, save=False, ar=None):
|
481
|
+
# raise Exception(f"20250301")
|
482
|
+
for tag in soup.find_all():
|
483
|
+
tag_name = tag.name.lower()
|
484
|
+
if tag_name == "img" and ar is not None and save:
|
485
|
+
if (src := tag.get('src')) and src.startswith("data:image"):
|
486
|
+
file = base64_to_image(src)
|
487
|
+
upload = rt.models.uploads.Upload(file=file, user=ar.get_user())
|
488
|
+
sar = upload.get_default_table().request(parent=ar)
|
489
|
+
upload.save_new_instance(sar)
|
490
|
+
tag.replace_with(f'[file {upload.pk}]')
|
491
|
+
|
492
|
+
|
493
|
+
register_sanitizer(on_sanitize)
|
lino/modlib/uploads/ui.py
CHANGED
@@ -21,7 +21,7 @@ from lino.core import constants
|
|
21
21
|
def filename_leaf(name):
|
22
22
|
i = name.rfind("/")
|
23
23
|
if i != -1:
|
24
|
-
return name[i + 1
|
24
|
+
return name[i + 1:]
|
25
25
|
return name
|
26
26
|
|
27
27
|
|
@@ -187,12 +187,14 @@ class AreaUploads(Uploads):
|
|
187
187
|
qs = qs.filter(upload_area=area)
|
188
188
|
else:
|
189
189
|
return E.div(
|
190
|
-
"{} is not an UploadController!".format(
|
190
|
+
"{} is not an UploadController!".format(
|
191
|
+
model_class_path(obj.__class__))
|
191
192
|
)
|
192
193
|
volume = obj.get_uploads_volume()
|
193
194
|
# print(20190208, volume)
|
194
195
|
for ut in qs:
|
195
|
-
sar = ar.spawn(self, master_instance=obj,
|
196
|
+
sar = ar.spawn(self, master_instance=obj,
|
197
|
+
known_values=dict(type_id=ut.id))
|
196
198
|
# logger.info("20140430 %s", sar.data_iterator.query)
|
197
199
|
files = []
|
198
200
|
for m in sar:
|
@@ -211,7 +213,8 @@ class AreaUploads(Uploads):
|
|
211
213
|
mf.get_download_url(),
|
212
214
|
# u"\u21A7", # DOWNWARDS ARROW FROM BAR (↧)
|
213
215
|
# u"\u21E8",
|
214
|
-
"\u21f2", # SOUTH EAST ARROW TO CORNER (⇲)
|
216
|
+
# "\u21f2", # SOUTH EAST ARROW TO CORNER (⇲)
|
217
|
+
"⎙", # Unicode symbol Print Screen
|
215
218
|
style="text-decoration:none;",
|
216
219
|
# _(" [show]"), # fmt(m),
|
217
220
|
target="_blank",
|
@@ -248,12 +251,12 @@ class AreaUploads(Uploads):
|
|
248
251
|
else:
|
249
252
|
if len(types) == 0:
|
250
253
|
elems.append(str(ar.no_data_text))
|
251
|
-
elems.append(" / ")
|
254
|
+
# elems.append(" / ")
|
252
255
|
else:
|
253
256
|
for chunks in types:
|
254
257
|
elems.extend(chunks)
|
255
|
-
elems.append(" / ")
|
256
|
-
elems.append(obj.show_uploads.as_button_elem(ar))
|
258
|
+
# elems.append(" / ")
|
259
|
+
# elems.append(obj.show_uploads.as_button_elem(ar))
|
257
260
|
# ba = self.find_action_by_name("show_uploads")
|
258
261
|
return E.div(*elems)
|
259
262
|
|
lino/utils/media.py
CHANGED
@@ -5,6 +5,7 @@
|
|
5
5
|
"""
|
6
6
|
|
7
7
|
from os.path import join
|
8
|
+
from pathlib import Path
|
8
9
|
|
9
10
|
from django.conf import settings
|
10
11
|
from lino.core.utils import is_devserver
|
@@ -17,10 +18,10 @@ has_davlink = False
|
|
17
18
|
class MediaFile(object):
|
18
19
|
"""
|
19
20
|
Represents a file on the server below :setting:`MEDIA_ROOT` with
|
20
|
-
two properties :attr:`
|
21
|
+
two properties :attr:`path` and :attr:`url`.
|
21
22
|
|
22
|
-
It
|
23
|
-
:attr:`webdav_root <lino.core.site.Site.webdav_root
|
23
|
+
It takes into consideration the settings
|
24
|
+
:attr:`webdav_root <lino.core.site.Site.webdav_root>`,
|
24
25
|
:attr:`webdav_protocol <lino.core.site.Site.webdav_protocol>`
|
25
26
|
and
|
26
27
|
:attr:`webdav_url <lino.core.site.Site.webdav_url>`
|
@@ -28,28 +29,49 @@ class MediaFile(object):
|
|
28
29
|
|
29
30
|
def __init__(self, editable, *parts):
|
30
31
|
self.editable = editable
|
31
|
-
self.parts = parts
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
def get_url(self, request):
|
41
|
-
"return the url that points to file on the server"
|
42
|
-
if self.editable and request is not None:
|
43
|
-
if is_devserver():
|
44
|
-
url = "file://" + join(settings.SITE.webdav_root, *self.parts)
|
32
|
+
# self.parts = parts
|
33
|
+
if editable and settings.SITE.webdav_protocol:
|
34
|
+
path = Path(settings.SITE.webdav_root, *parts)
|
35
|
+
# 20250302 Removed the file:// trick on a devserver because anyway
|
36
|
+
# it doesn't work anymore. For editable media files we need a a
|
37
|
+
# webdav server and a protocol handler.
|
38
|
+
# if is_devserver():
|
39
|
+
if False:
|
40
|
+
url = "file://" + join(settings.SITE.webdav_root, *parts)
|
45
41
|
else:
|
46
|
-
url = settings.SITE.webdav_url + "/".join(
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
42
|
+
url = settings.SITE.webdav_url + "/".join(parts)
|
43
|
+
if settings.SITE.webdav_protocol:
|
44
|
+
url = settings.SITE.webdav_protocol + "://" + url
|
45
|
+
else:
|
46
|
+
path = Path(settings.MEDIA_ROOT, *parts)
|
47
|
+
url = settings.SITE.build_media_url(*parts)
|
48
|
+
self.url = url
|
49
|
+
self.path = path
|
51
50
|
|
52
|
-
|
51
|
+
# @property
|
52
|
+
# def path(self):
|
53
|
+
# "Return the full filename on the server as a Path object."
|
54
|
+
|
55
|
+
# @property
|
56
|
+
# def name(self):
|
57
|
+
# "return the filename on the server"
|
58
|
+
# if self.editable and (has_davlink or settings.SITE.webdav_protocol):
|
59
|
+
# return join(settings.SITE.webdav_root, *self.parts)
|
60
|
+
# return join(settings.MEDIA_ROOT, *self.parts)
|
61
|
+
|
62
|
+
# def get_url(self, request):
|
63
|
+
# "return the url that points to file on the server"
|
64
|
+
# if self.editable and request is not None:
|
65
|
+
# if is_devserver():
|
66
|
+
# url = "file://" + join(settings.SITE.webdav_root, *self.parts)
|
67
|
+
# else:
|
68
|
+
# url = settings.SITE.webdav_url + "/".join(self.parts)
|
69
|
+
# url = request.build_absolute_uri(url)
|
70
|
+
# if settings.SITE.webdav_protocol:
|
71
|
+
# url = settings.SITE.webdav_protocol + "://" + url
|
72
|
+
# return url
|
73
|
+
#
|
74
|
+
# return settings.SITE.build_media_url(*self.parts)
|
53
75
|
|
54
76
|
|
55
77
|
class TmpMediaFile(MediaFile):
|