lino 25.8.2__py3-none-any.whl → 25.9.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/api/dd.py +0 -1
- lino/config/unused/403.html +1 -1
- lino/config/unused/404.html +1 -1
- lino/config/unused/500.html +1 -1
- lino/core/__init__.py +0 -1
- lino/core/actions.py +2 -2
- lino/core/actors.py +10 -2
- lino/core/elems.py +1 -1
- lino/core/fields.py +4 -1
- lino/core/kernel.py +5 -1
- lino/core/model.py +2 -11
- lino/core/renderer.py +2 -2
- lino/core/requests.py +12 -12
- lino/core/site.py +5 -82
- lino/core/store.py +3 -1
- lino/core/urls.py +1 -1
- lino/core/user_types.py +1 -10
- lino/help_texts.py +6 -6
- lino/management/commands/initdb.py +0 -3
- lino/modlib/__init__.py +0 -1
- lino/modlib/bootstrap5/README.txt +2 -0
- lino/modlib/bootstrap5/__init__.py +69 -0
- lino/modlib/{bootstrap3/config/bootstrap3 → bootstrap5/config/bootstrap5}/base.html +9 -4
- lino/modlib/{bootstrap3/config/bootstrap3 → bootstrap5/config/bootstrap5}/detail.html +1 -1
- lino/modlib/{bootstrap3/config/bootstrap3 → bootstrap5/config/bootstrap5}/index.html +1 -1
- lino/modlib/{bootstrap3/config/bootstrap3 → bootstrap5/config/bootstrap5}/table.html +1 -1
- lino/modlib/bootstrap5/models.py +30 -0
- lino/modlib/bootstrap5/static/bootstrap-5.3.7/css/bootstrap-grid.css +4085 -0
- lino/modlib/bootstrap5/static/bootstrap-5.3.7/css/bootstrap-grid.css.map +1 -0
- lino/modlib/bootstrap5/static/bootstrap-5.3.7/css/bootstrap-grid.min.css +6 -0
- lino/modlib/bootstrap5/static/bootstrap-5.3.7/css/bootstrap-grid.min.css.map +1 -0
- lino/modlib/bootstrap5/static/bootstrap-5.3.7/css/bootstrap-grid.rtl.css +4084 -0
- lino/modlib/bootstrap5/static/bootstrap-5.3.7/css/bootstrap-grid.rtl.css.map +1 -0
- lino/modlib/bootstrap5/static/bootstrap-5.3.7/css/bootstrap-grid.rtl.min.css +6 -0
- lino/modlib/bootstrap5/static/bootstrap-5.3.7/css/bootstrap-grid.rtl.min.css.map +1 -0
- lino/modlib/bootstrap5/static/bootstrap-5.3.7/css/bootstrap-reboot.css +597 -0
- lino/modlib/bootstrap5/static/bootstrap-5.3.7/css/bootstrap-reboot.css.map +1 -0
- lino/modlib/bootstrap5/static/bootstrap-5.3.7/css/bootstrap-reboot.min.css +6 -0
- lino/modlib/bootstrap5/static/bootstrap-5.3.7/css/bootstrap-reboot.min.css.map +1 -0
- lino/modlib/bootstrap5/static/bootstrap-5.3.7/css/bootstrap-reboot.rtl.css +594 -0
- lino/modlib/bootstrap5/static/bootstrap-5.3.7/css/bootstrap-reboot.rtl.css.map +1 -0
- lino/modlib/bootstrap5/static/bootstrap-5.3.7/css/bootstrap-reboot.rtl.min.css +6 -0
- lino/modlib/bootstrap5/static/bootstrap-5.3.7/css/bootstrap-reboot.rtl.min.css.map +1 -0
- lino/modlib/bootstrap5/static/bootstrap-5.3.7/css/bootstrap-utilities.css +5406 -0
- lino/modlib/bootstrap5/static/bootstrap-5.3.7/css/bootstrap-utilities.css.map +1 -0
- lino/modlib/bootstrap5/static/bootstrap-5.3.7/css/bootstrap-utilities.min.css +6 -0
- lino/modlib/bootstrap5/static/bootstrap-5.3.7/css/bootstrap-utilities.min.css.map +1 -0
- lino/modlib/bootstrap5/static/bootstrap-5.3.7/css/bootstrap-utilities.rtl.css +5397 -0
- lino/modlib/bootstrap5/static/bootstrap-5.3.7/css/bootstrap-utilities.rtl.css.map +1 -0
- lino/modlib/bootstrap5/static/bootstrap-5.3.7/css/bootstrap-utilities.rtl.min.css +6 -0
- lino/modlib/bootstrap5/static/bootstrap-5.3.7/css/bootstrap-utilities.rtl.min.css.map +1 -0
- lino/modlib/bootstrap5/static/bootstrap-5.3.7/css/bootstrap.css +12043 -0
- lino/modlib/bootstrap5/static/bootstrap-5.3.7/css/bootstrap.css.map +1 -0
- lino/modlib/bootstrap5/static/bootstrap-5.3.7/css/bootstrap.min.css +6 -0
- lino/modlib/bootstrap5/static/bootstrap-5.3.7/css/bootstrap.min.css.map +1 -0
- lino/modlib/bootstrap5/static/bootstrap-5.3.7/css/bootstrap.rtl.css +12016 -0
- lino/modlib/bootstrap5/static/bootstrap-5.3.7/css/bootstrap.rtl.css.map +1 -0
- lino/modlib/bootstrap5/static/bootstrap-5.3.7/css/bootstrap.rtl.min.css +6 -0
- lino/modlib/bootstrap5/static/bootstrap-5.3.7/css/bootstrap.rtl.min.css.map +1 -0
- lino/modlib/bootstrap5/static/bootstrap-5.3.7/js/bootstrap.bundle.js +6315 -0
- lino/modlib/bootstrap5/static/bootstrap-5.3.7/js/bootstrap.bundle.js.map +1 -0
- lino/modlib/bootstrap5/static/bootstrap-5.3.7/js/bootstrap.bundle.min.js +7 -0
- lino/modlib/bootstrap5/static/bootstrap-5.3.7/js/bootstrap.bundle.min.js.map +1 -0
- lino/modlib/bootstrap5/static/bootstrap-5.3.7/js/bootstrap.esm.js +4450 -0
- lino/modlib/bootstrap5/static/bootstrap-5.3.7/js/bootstrap.esm.js.map +1 -0
- lino/modlib/bootstrap5/static/bootstrap-5.3.7/js/bootstrap.esm.min.js +7 -0
- lino/modlib/bootstrap5/static/bootstrap-5.3.7/js/bootstrap.esm.min.js.map +1 -0
- lino/modlib/bootstrap5/static/bootstrap-5.3.7/js/bootstrap.js +4497 -0
- lino/modlib/bootstrap5/static/bootstrap-5.3.7/js/bootstrap.js.map +1 -0
- lino/modlib/bootstrap5/static/bootstrap-5.3.7/js/bootstrap.min.js +7 -0
- lino/modlib/bootstrap5/static/bootstrap-5.3.7/js/bootstrap.min.js.map +1 -0
- lino/modlib/{bootstrap3 → bootstrap5}/views.py +12 -117
- lino/modlib/checkdata/choicelists.py +1 -1
- lino/modlib/comments/fixtures/demo2.py +1 -0
- lino/modlib/comments/mixins.py +1 -8
- lino/modlib/comments/models.py +2 -0
- lino/modlib/comments/ui.py +7 -7
- lino/modlib/extjs/__init__.py +2 -4
- lino/modlib/extjs/config/extjs/index.html +1 -1
- lino/modlib/extjs/ext_renderer.py +1 -7
- lino/modlib/extjs/views.py +2 -0
- lino/modlib/help/models.py +1 -12
- lino/modlib/jinja/renderer.py +1 -1
- lino/modlib/linod/mixins.py +3 -2
- lino/modlib/memo/__init__.py +11 -11
- lino/modlib/memo/mixins.py +38 -21
- lino/modlib/memo/models.py +10 -7
- lino/modlib/memo/parser.py +3 -1
- lino/modlib/notify/models.py +6 -9
- lino/modlib/odata/views.py +7 -7
- lino/modlib/publisher/__init__.py +15 -3
- lino/modlib/publisher/choicelists.py +8 -94
- lino/modlib/publisher/config/publisher/page.pub.html +82 -19
- lino/modlib/publisher/fixtures/std.py +14 -1
- lino/modlib/publisher/fixtures/synodalworld.py +3 -1
- lino/modlib/publisher/mixins.py +59 -77
- lino/modlib/publisher/models.py +109 -204
- lino/modlib/publisher/renderer.py +31 -11
- lino/modlib/publisher/ui.py +46 -98
- lino/modlib/publisher/views.py +61 -11
- lino/modlib/system/models.py +3 -2
- lino/modlib/uploads/__init__.py +1 -0
- lino/modlib/uploads/mixins.py +2 -2
- lino/modlib/uploads/models.py +55 -21
- lino/modlib/uploads/ui.py +1 -0
- lino/modlib/uploads/utils.py +2 -2
- lino/modlib/users/__init__.py +2 -3
- lino/modlib/users/actions.py +12 -17
- lino/modlib/users/fixtures/abc.py +20 -0
- lino/modlib/users/mixins.py +6 -6
- lino/modlib/users/models.py +37 -36
- lino/modlib/weasyprint/__init__.py +25 -14
- lino/modlib/weasyprint/choicelists.py +6 -0
- lino/modlib/weasyprint/config/weasyprint/base.weasy.html +43 -27
- lino/utils/diag.py +5 -3
- lino/utils/html.py +103 -0
- lino/utils/mldbc/mixins.py +2 -2
- lino/utils/soup.py +16 -8
- {lino-25.8.2.dist-info → lino-25.9.0.dist-info}/METADATA +1 -1
- {lino-25.8.2.dist-info → lino-25.9.0.dist-info}/RECORD +135 -95
- lino/modlib/bootstrap3/README.txt +0 -2
- lino/modlib/bootstrap3/__init__.py +0 -73
- lino/modlib/bootstrap3/models.py +0 -30
- lino/modlib/bootstrap3/static/bootstrap-3.3.4/css/bootstrap.css +0 -6584
- lino/modlib/bootstrap3/static/bootstrap-3.3.4/css/bootstrap.css.map +0 -1
- lino/modlib/bootstrap3/static/bootstrap-3.3.4/css/bootstrap.min.css +0 -5
- lino/modlib/bootstrap3/static/bootstrap-3.3.4/js/bootstrap.js +0 -2317
- lino/modlib/bootstrap3/static/bootstrap-3.3.4/js/bootstrap.min.js +0 -7
- /lino/modlib/{bootstrap3 → bootstrap5}/renderer.py +0 -0
- /lino/modlib/{bootstrap3/static/bootstrap-3.3.4 → bootstrap5/static/bootstrap-5.3.7}/css/bootstrap-theme.css +0 -0
- /lino/modlib/{bootstrap3/static/bootstrap-3.3.4 → bootstrap5/static/bootstrap-5.3.7}/css/bootstrap-theme.css.map +0 -0
- /lino/modlib/{bootstrap3/static/bootstrap-3.3.4 → bootstrap5/static/bootstrap-5.3.7}/css/bootstrap-theme.min.css +0 -0
- /lino/modlib/{bootstrap3/static/bootstrap-3.3.4 → bootstrap5/static/bootstrap-5.3.7}/fonts/glyphicons-halflings-regular.eot +0 -0
- /lino/modlib/{bootstrap3/static/bootstrap-3.3.4 → bootstrap5/static/bootstrap-5.3.7}/fonts/glyphicons-halflings-regular.svg +0 -0
- /lino/modlib/{bootstrap3/static/bootstrap-3.3.4 → bootstrap5/static/bootstrap-5.3.7}/fonts/glyphicons-halflings-regular.ttf +0 -0
- /lino/modlib/{bootstrap3/static/bootstrap-3.3.4 → bootstrap5/static/bootstrap-5.3.7}/fonts/glyphicons-halflings-regular.woff +0 -0
- /lino/modlib/{bootstrap3/static/bootstrap-3.3.4 → bootstrap5/static/bootstrap-5.3.7}/fonts/glyphicons-halflings-regular.woff2 +0 -0
- /lino/modlib/{bootstrap3/static/bootstrap-3.3.4 → bootstrap5/static/bootstrap-5.3.7}/js/bootstrap_lino.js +0 -0
- /lino/modlib/{bootstrap3/static/bootstrap-3.3.4 → bootstrap5/static/bootstrap-5.3.7}/js/npm.js +0 -0
- {lino-25.8.2.dist-info → lino-25.9.0.dist-info}/WHEEL +0 -0
- {lino-25.8.2.dist-info → lino-25.9.0.dist-info}/licenses/AUTHORS.rst +0 -0
- {lino-25.8.2.dist-info → lino-25.9.0.dist-info}/licenses/COPYING +0 -0
lino/modlib/publisher/ui.py
CHANGED
@@ -1,105 +1,38 @@
|
|
1
1
|
# -*- coding: UTF-8 -*-
|
2
|
-
# Copyright 2012-
|
2
|
+
# Copyright 2012-2025 Rumma & Ko Ltd
|
3
3
|
# License: GNU Affero General Public License v3 (see file COPYING for details)
|
4
4
|
|
5
|
-
from html import escape
|
6
5
|
from django.db import models
|
7
|
-
from django.http import HttpResponseRedirect
|
8
|
-
from django.conf import settings
|
9
|
-
from django.utils import translation
|
10
|
-
from django.utils.translation import pgettext_lazy
|
11
6
|
|
12
7
|
# from django.utils.translation import get_language
|
13
|
-
from django.utils.html import
|
14
|
-
from django.utils.html import format_html
|
8
|
+
# from django.utils.html import format_html
|
15
9
|
|
16
10
|
from lino.api import dd, rt, _
|
17
11
|
from lino.utils.html import E
|
12
|
+
from lino.utils.soup import MORE_MARKER
|
18
13
|
from lino.core import constants
|
19
14
|
# from lino.core.renderer import add_user_language
|
20
|
-
|
21
|
-
from lino.utils.mldbc.fields import LanguageField
|
22
|
-
from lino import mixins
|
23
|
-
from lino.mixins import Hierarchical, Sequenced, Referrable
|
24
15
|
from lino.modlib.office.roles import OfficeUser
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
# treeview_panel:20 preview:60
|
36
|
-
# """, label=_("Preview"))
|
37
|
-
#
|
38
|
-
# general = dd.Panel("""
|
39
|
-
# content_panel:60 right_panel:20
|
40
|
-
# """, label=_("General"), required_roles=dd.login_required(OfficeUser))
|
41
|
-
#
|
42
|
-
# more = dd.Panel("""
|
43
|
-
# # topics.TagsByOwner:20 add_interest
|
44
|
-
# comments.CommentsByRFC:20
|
45
|
-
# """, label=_("More"), required_roles=dd.login_required(OfficeUser))
|
46
|
-
#
|
47
|
-
# content_panel = """
|
48
|
-
# title id
|
49
|
-
# body
|
50
|
-
# publisher.PagesByParent
|
51
|
-
# """
|
52
|
-
#
|
53
|
-
# right_panel = """
|
54
|
-
# parent seqno
|
55
|
-
# child_node_depth
|
56
|
-
# page_type
|
57
|
-
# filler
|
58
|
-
# """
|
59
|
-
#
|
60
|
-
#
|
61
|
-
# class Nodes(dd.Table):
|
62
|
-
# model = 'pages.Node'
|
63
|
-
# column_names = "title page_type id *"
|
64
|
-
# order_by = ["id"]
|
65
|
-
# detail_layout = 'pages.NodeDetail'
|
66
|
-
# insert_layout = """
|
67
|
-
# title
|
68
|
-
# page_type filler
|
69
|
-
# """
|
70
|
-
# display_mode = ((None, constants.DISPLAY_MODE_STORY),)
|
71
|
-
#
|
72
|
-
#
|
73
|
-
|
74
|
-
# class Translations(dd.Table):
|
75
|
-
# model = 'pages.Translation'
|
76
|
-
#
|
77
|
-
# class TranslationsByParent(Translations):
|
78
|
-
# master_key = 'parent'
|
79
|
-
# label = _("Translated to...")
|
80
|
-
#
|
81
|
-
# class TranslationsByChild(Translations):
|
82
|
-
# master_key = 'child'
|
83
|
-
# label = _("Translated from...")
|
84
|
-
|
85
|
-
|
86
|
-
if dd.is_installed("comments") and dd.is_installed("topics"):
|
87
|
-
DISCUSSION_PANEL = """
|
88
|
-
topics.TagsByOwner:20 comments.CommentsByRFC:60
|
89
|
-
"""
|
90
|
-
else:
|
91
|
-
DISCUSSION_PANEL = ""
|
16
|
+
|
17
|
+
from .choicelists import SpecialPages
|
18
|
+
|
19
|
+
|
20
|
+
VARTABS = ""
|
21
|
+
|
22
|
+
if dd.is_installed("topics"):
|
23
|
+
VARTABS += " topics.TagsByOwner:20"
|
24
|
+
if dd.is_installed("comments"):
|
25
|
+
VARTABS += " comments.CommentsByRFC:60"
|
92
26
|
|
93
27
|
|
94
28
|
class PageDetail(dd.DetailLayout):
|
95
|
-
main = "general first_panel more"
|
29
|
+
# main = "general first_panel more"
|
30
|
+
main = f"general first_panel {VARTABS} more"
|
96
31
|
|
97
32
|
first_panel = dd.Panel(
|
98
33
|
"""
|
99
|
-
|
100
|
-
|
101
|
-
label=_("Preview"),
|
102
|
-
)
|
34
|
+
treeview_panel:20 preview:60
|
35
|
+
""", label=_("Preview"))
|
103
36
|
|
104
37
|
general = dd.Panel(
|
105
38
|
"""
|
@@ -109,16 +42,15 @@ class PageDetail(dd.DetailLayout):
|
|
109
42
|
required_roles=dd.login_required(OfficeUser),
|
110
43
|
)
|
111
44
|
|
112
|
-
more = dd.Panel(
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
)
|
45
|
+
# more = dd.Panel(
|
46
|
+
# VARTABS,
|
47
|
+
# label=_("Discussion"),
|
48
|
+
# required_roles=dd.login_required(OfficeUser),
|
49
|
+
# )
|
117
50
|
|
118
51
|
content_panel = """
|
119
52
|
title id
|
120
53
|
body
|
121
|
-
publisher.PagesByParent
|
122
54
|
"""
|
123
55
|
|
124
56
|
# right_panel = """
|
@@ -129,23 +61,27 @@ class PageDetail(dd.DetailLayout):
|
|
129
61
|
# """
|
130
62
|
|
131
63
|
right_panel = """
|
132
|
-
ref language
|
133
64
|
parent seqno
|
65
|
+
publisher.PagesByParent
|
66
|
+
"""
|
67
|
+
|
68
|
+
more = dd.Panel("""
|
69
|
+
publisher_tree language
|
134
70
|
child_node_depth main_image
|
135
|
-
#
|
136
|
-
publishing_state
|
71
|
+
special_page #filler
|
72
|
+
publishing_state
|
137
73
|
publisher.TranslationsByPage
|
138
|
-
"""
|
74
|
+
""", label=_("More"))
|
139
75
|
|
140
76
|
|
141
77
|
class Pages(dd.Table):
|
142
78
|
model = "publisher.Page"
|
143
|
-
column_names = "
|
79
|
+
column_names = "title publisher_tree id *"
|
144
80
|
detail_layout = "publisher.PageDetail"
|
145
81
|
insert_layout = """
|
146
82
|
title
|
147
|
-
|
148
|
-
#
|
83
|
+
publisher_tree
|
84
|
+
language #filler
|
149
85
|
"""
|
150
86
|
default_display_modes = {None: constants.DISPLAY_MODE_LIST}
|
151
87
|
|
@@ -168,10 +104,22 @@ class PagesByParent(Pages):
|
|
168
104
|
class TranslationsByPage(Pages):
|
169
105
|
master_key = "translated_from"
|
170
106
|
label = _("Translations")
|
171
|
-
column_names = "
|
107
|
+
column_names = "title language id *"
|
172
108
|
default_display_modes = {None: constants.DISPLAY_MODE_SUMMARY}
|
173
109
|
|
174
110
|
@classmethod
|
175
111
|
def row_as_summary(cls, ar, obj, text=None, **kwargs):
|
176
112
|
# return format_html("({}) {}", obj.language, obj.as_summary_row(ar, **kwargs))
|
177
113
|
return E.span("({}) ".format(obj.language), obj.as_summary_item(ar, text, **kwargs))
|
114
|
+
|
115
|
+
|
116
|
+
class Trees(dd.Table):
|
117
|
+
model = "publisher.Tree"
|
118
|
+
column_names = "ref root_page group private id *"
|
119
|
+
|
120
|
+
|
121
|
+
SpecialPages.add_item(
|
122
|
+
"pages", # filler=filler,
|
123
|
+
body=_("List of root pages.") + MORE_MARKER + " [show publisher.Trees]",
|
124
|
+
title=_("Root pages"),
|
125
|
+
parent='home')
|
lino/modlib/publisher/views.py
CHANGED
@@ -1,12 +1,15 @@
|
|
1
1
|
# -*- coding: UTF-8 -*-
|
2
2
|
# Copyright 2020-2025 Rumma & Ko Ltd
|
3
3
|
# License: GNU Affero General Public License v3 (see file COPYING for details)
|
4
|
-
|
5
4
|
from django import http
|
6
5
|
from django.conf import settings
|
7
6
|
from django.core.exceptions import ObjectDoesNotExist
|
7
|
+
from django.shortcuts import redirect
|
8
8
|
from django.utils import translation
|
9
9
|
from django.views.generic import View
|
10
|
+
from lino.core import auth
|
11
|
+
from lino.core.requests import BaseRequest
|
12
|
+
from lino.core.views import json_response
|
10
13
|
|
11
14
|
|
12
15
|
class Element(View):
|
@@ -23,36 +26,83 @@ class Element(View):
|
|
23
26
|
|
24
27
|
# kw = dict(actor=self.publisher_model.get_default_table(),
|
25
28
|
# request=request, renderer=rnd, permalink_uris=True)
|
26
|
-
kw = dict(renderer=rnd)
|
29
|
+
kw = dict(renderer=rnd, request=request)
|
27
30
|
# kw = dict(renderer=rnd, permalink_uris=True)
|
28
31
|
# if rnd.front_end.media_name == 'react':
|
29
32
|
# kw.update(hash_router=True)
|
30
33
|
|
31
34
|
kw.update(selected_pks=[pk])
|
32
|
-
|
35
|
+
#
|
33
36
|
try:
|
34
|
-
ar = self.table_class.create_request(
|
37
|
+
ar = self.table_class.create_request(**kw)
|
35
38
|
except ObjectDoesNotExist as e:
|
36
39
|
# print("20240911", e)
|
37
|
-
return http.HttpResponseNotFound(
|
40
|
+
return http.HttpResponseNotFound(
|
41
|
+
f"No row #{pk} in {self.table_class} ({e})")
|
38
42
|
if len(ar.selected_rows) == 0:
|
39
43
|
# print(f"20241003 Oops {ar} has no rows")
|
40
|
-
return http.HttpResponseNotFound(
|
44
|
+
return http.HttpResponseNotFound(
|
45
|
+
f"20241003 No row #{pk} in {self.table_class}")
|
41
46
|
obj = ar.selected_rows[0]
|
47
|
+
|
48
|
+
# m = self.table_class.model
|
49
|
+
# try:
|
50
|
+
# obj = m.objects.get(pk=pk)
|
51
|
+
# except m.DoesNotExist as e:
|
52
|
+
# return http.HttpResponseNotFound(f"No row #{pk} in {m} ({e})")
|
53
|
+
# ar = BaseRequest(renderer=rnd, request=request, selected_rows=[obj])
|
54
|
+
# ar = BaseRequest(renderer=rnd, request=request)
|
42
55
|
return obj.get_publisher_response(ar)
|
43
56
|
|
44
57
|
|
45
58
|
class Index(View):
|
46
|
-
|
47
|
-
|
59
|
+
|
60
|
+
ref = 'index'
|
61
|
+
|
62
|
+
def get(self, request):
|
63
|
+
Tree = settings.SITE.models.publisher.Tree
|
48
64
|
dv = settings.SITE.models.publisher.Pages
|
49
65
|
if len(settings.SITE.languages) == 1:
|
50
66
|
# language = settings.SITE.languages[0].django_code
|
51
67
|
language = translation.get_language()
|
52
68
|
else:
|
53
69
|
language = request.LANGUAGE_CODE
|
54
|
-
|
70
|
+
try:
|
71
|
+
tree = Tree.objects.get(ref=self.ref)
|
72
|
+
except Tree.DoesNotExist:
|
73
|
+
return http.HttpResponseNotFound(f"No tree for {self.ref}")
|
74
|
+
obj = tree.get_root_page(language)
|
75
|
+
# print(20250829, obj)
|
76
|
+
if obj is None:
|
77
|
+
return http.HttpResponseNotFound(
|
78
|
+
f"No root page for {self.ref} in {language}")
|
79
|
+
# try:
|
80
|
+
# obj = dv.model.objects.get(
|
81
|
+
# parent=None, publisher_tree=tree)
|
82
|
+
# except dv.model.DoesNotExist:
|
83
|
+
# return http.HttpResponseNotFound(f"No row {ref} in {dv.model}")
|
84
|
+
|
55
85
|
# print("20231025", index_node)
|
86
|
+
rnd = settings.SITE.plugins.publisher.renderer
|
56
87
|
ar = dv.create_request(request=request, renderer=rnd,
|
57
|
-
selected_rows=[
|
58
|
-
return
|
88
|
+
selected_rows=[obj])
|
89
|
+
return obj.get_publisher_response(ar)
|
90
|
+
|
91
|
+
|
92
|
+
class Login(View):
|
93
|
+
def post(self, request):
|
94
|
+
username = request.POST.get("username")
|
95
|
+
password = request.POST.get("password")
|
96
|
+
|
97
|
+
user = auth.authenticate(request, username=username, password=password)
|
98
|
+
if user is None:
|
99
|
+
return json_response({"success": False})
|
100
|
+
else:
|
101
|
+
auth.login(request, user)
|
102
|
+
return json_response({"success": True})
|
103
|
+
|
104
|
+
|
105
|
+
class Logout(View):
|
106
|
+
def get(self, request):
|
107
|
+
auth.logout(request)
|
108
|
+
return redirect(request.META.get('HTTP_REFERER', '/'))
|
lino/modlib/system/models.py
CHANGED
@@ -321,16 +321,17 @@ class BleachChecker(Checker):
|
|
321
321
|
model = dd.Model
|
322
322
|
|
323
323
|
def get_checkable_models(self):
|
324
|
-
for m in super(
|
324
|
+
for m in super().get_checkable_models():
|
325
325
|
if len(m._bleached_fields):
|
326
326
|
yield m
|
327
327
|
|
328
328
|
def get_checkdata_problems(self, ar, obj, fix=False):
|
329
329
|
t = tuple(obj.fields_to_bleach(save=False))
|
330
330
|
if len(t):
|
331
|
-
fldnames =
|
331
|
+
fldnames = tuple([f.name for f, old, new in t])
|
332
332
|
yield (True, _("Fields {} have unbleached content.").format(fldnames))
|
333
333
|
if fix:
|
334
|
+
# obj.before_ui_save(ar, None)
|
334
335
|
obj.before_ui_save(None, None)
|
335
336
|
obj.full_clean()
|
336
337
|
obj.save()
|
lino/modlib/uploads/__init__.py
CHANGED
lino/modlib/uploads/mixins.py
CHANGED
@@ -134,7 +134,7 @@ class UploadController(dd.Model):
|
|
134
134
|
|
135
135
|
|
136
136
|
class GalleryViewable(dd.Model):
|
137
|
-
class Meta
|
137
|
+
class Meta:
|
138
138
|
abstract = True
|
139
139
|
|
140
140
|
def get_gallery_item(self, ar):
|
@@ -155,7 +155,7 @@ class UploadBase(Commentable, GalleryViewable):
|
|
155
155
|
|
156
156
|
def handle_uploaded_files(self, request, file=None):
|
157
157
|
# ~ from django.core.files.base import ContentFile
|
158
|
-
if not file and
|
158
|
+
if not file and "file" not in request.FILES:
|
159
159
|
dd.logger.debug("No 'file' has been submitted.")
|
160
160
|
return
|
161
161
|
uf = file or request.FILES["file"] # an UploadedFile instance
|
lino/modlib/uploads/models.py
CHANGED
@@ -24,7 +24,7 @@ from lino.modlib.gfks.mixins import Controllable
|
|
24
24
|
from lino.modlib.users.mixins import UserAuthored
|
25
25
|
|
26
26
|
from lino.mixins import Referrable
|
27
|
-
from lino.utils.soup import register_sanitizer
|
27
|
+
from lino.utils.soup import register_sanitizer, DATA_UPLOAD_ID
|
28
28
|
from lino.utils.mldbc.mixins import BabelNamed
|
29
29
|
from lino.modlib.checkdata.choicelists import Checker
|
30
30
|
from lino.modlib.publisher.mixins import Publishable
|
@@ -309,6 +309,9 @@ UploadChecker.activate()
|
|
309
309
|
class UploadsFolderChecker(Checker):
|
310
310
|
verbose_name = _("Find orphaned files in uploads folder")
|
311
311
|
|
312
|
+
# It is no problem to have multiple upload entries pointing to a same file
|
313
|
+
# on the file system
|
314
|
+
|
312
315
|
def get_checkdata_problems(self, ar, obj, fix=False):
|
313
316
|
assert obj is None # this is an unbound checker
|
314
317
|
Upload = rt.models.uploads.Upload
|
@@ -321,8 +324,7 @@ class UploadsFolderChecker(Checker):
|
|
321
324
|
continue
|
322
325
|
rel_filename = str(filename)[start:]
|
323
326
|
qs = Upload.objects.filter(file=rel_filename)
|
324
|
-
|
325
|
-
if n == 0:
|
327
|
+
if not qs.exists():
|
326
328
|
msg = format_lazy(
|
327
329
|
_("File {} has no upload entry."), rel_filename)
|
328
330
|
# print(msg)
|
@@ -339,9 +341,6 @@ class UploadsFolderChecker(Checker):
|
|
339
341
|
# else:
|
340
342
|
# print("{} has {} entries.".format(filename, n))
|
341
343
|
# elif n > 1:
|
342
|
-
# msg = _("Multiple upload entries for {} ").format(filename)
|
343
|
-
# yield (False, msg)
|
344
|
-
# This is no problem. A same file should be linkable to diffeerent controlers.
|
345
344
|
|
346
345
|
|
347
346
|
UploadsFolderChecker.activate()
|
@@ -454,46 +453,81 @@ def setup_memo_commands(sender=None, **kwargs):
|
|
454
453
|
ctx.update(href=ar.obj2url(self))
|
455
454
|
if not mf.is_image():
|
456
455
|
if not text:
|
457
|
-
text = str(self)
|
456
|
+
text = self.description or str(self)
|
458
457
|
ctx.update(text=text)
|
459
458
|
tpl = '(<a href="{src}" target="_blank">{text}</a>'
|
460
459
|
tpl += '| <a href="{href}">Detail</a>)'
|
461
460
|
return format_html(tpl, **ctx)
|
462
461
|
|
463
|
-
fmt = parse_image_spec(text, **ctx)
|
462
|
+
# fmt = parse_image_spec(text, **ctx)
|
464
463
|
# TODO: When an image is inserted with format "wide", we should not use
|
465
464
|
# the thumbnail but the original file. But for a PDF file we must always
|
466
465
|
# use the img_src because the download_url is not an image.
|
467
|
-
|
466
|
+
ctx.update(src=mf.get_image_url())
|
467
|
+
# fmt.context.update(src=mf.get_image_url())
|
468
468
|
# if isinstance(fmt, SMALL_FORMATS):
|
469
469
|
# fmt.context.update(src=img_src)
|
470
470
|
# else:
|
471
471
|
# print(f"20241116 {fmt} {fmt.context}")
|
472
472
|
|
473
|
-
if not fmt.context["caption"]:
|
474
|
-
|
473
|
+
# if not fmt.context["caption"]:
|
474
|
+
# fmt.context["caption"] = self.description or str(self)
|
475
|
+
|
476
|
+
if not text:
|
477
|
+
text = ""
|
478
|
+
|
479
|
+
if 'title="' not in text:
|
480
|
+
text += f' title="{self.description or str(self)}"'
|
481
|
+
|
482
|
+
if 'style="' not in text:
|
483
|
+
# text += ' style="max-height: 20ex; width: auto;"'
|
484
|
+
text += ' style="max-width:100%;height:auto;"'
|
485
|
+
|
486
|
+
ctx.update(properties=mark_safe(text))
|
475
487
|
|
488
|
+
# rv = format_html(
|
489
|
+
# '<a href="{href}" target="_blank"><img src="{src}"'
|
490
|
+
# + ' style="{style}" title="{caption}"/></a>', **fmt.context)
|
476
491
|
rv = format_html(
|
477
492
|
'<a href="{href}" target="_blank"><img src="{src}"'
|
478
|
-
+ '
|
493
|
+
+ ' {properties}/></a>', **ctx)
|
479
494
|
return rv
|
480
495
|
|
481
496
|
mp = sender.plugins.memo.parser
|
482
497
|
mp.register_django_model('file', rt.models.uploads.Upload, rnd=file2html)
|
483
498
|
|
484
499
|
|
485
|
-
def on_sanitize(soup, save=False, ar=None):
|
500
|
+
def on_sanitize(soup, save=False, ar=None, mentions=None):
|
486
501
|
# raise Exception(f"20250301")
|
487
|
-
|
488
|
-
|
489
|
-
if tag_name == "img" and ar is not None and save:
|
502
|
+
if save:
|
503
|
+
for tag in soup.find_all('img'):
|
490
504
|
if (src := tag.get('src')) and src.startswith("data:image"):
|
491
505
|
file = base64_to_image(src)
|
492
|
-
|
493
|
-
|
494
|
-
|
495
|
-
|
496
|
-
|
506
|
+
user = ar.get_user() if ar else dd.plugins.users.get_demo_user()
|
507
|
+
obj = rt.models.uploads.Upload(file=file, user=user)
|
508
|
+
tag["src"] = obj.get_media_file().get_image_url()
|
509
|
+
if ar:
|
510
|
+
sar = obj.get_default_table().create_request(parent=ar)
|
511
|
+
obj.save_new_instance(sar) # create comment or notify message
|
512
|
+
else:
|
513
|
+
obj.save()
|
514
|
+
rt.models.checkdata.fix_instance(ar, obj) # create thumbnail
|
515
|
+
# style = ''
|
516
|
+
# if (s := tag.get("style")):
|
517
|
+
# # if not s.strip().endswith(";"):
|
518
|
+
# # s += ";"
|
519
|
+
# style += s
|
520
|
+
# if (w := tag.get("width")) is not None:
|
521
|
+
# style += f" width: {w}px;"
|
522
|
+
# else:
|
523
|
+
# # 20ex comes from the default value of Format.height in rstgen.sphinxconf.sigal_image
|
524
|
+
# style += " height: 20ex;"
|
525
|
+
# tag.replace_with(f'[file {upload.pk} style="{style}"]')
|
526
|
+
tag[DATA_UPLOAD_ID] = obj.id
|
527
|
+
if mentions is not None:
|
528
|
+
for tag in soup.find_all('img'):
|
529
|
+
if (upload_id := tag.get(DATA_UPLOAD_ID)):
|
530
|
+
mentions.add(rt.models.uploads.Upload(id=upload_id))
|
497
531
|
|
498
532
|
|
499
533
|
register_sanitizer(on_sanitize)
|
lino/modlib/uploads/ui.py
CHANGED
lino/modlib/uploads/utils.py
CHANGED
@@ -63,7 +63,7 @@ class Previewer:
|
|
63
63
|
# The bare media previewer. It doesn't do any real work.
|
64
64
|
base_dir = None
|
65
65
|
max_width = None
|
66
|
-
PREVIEW_SUFFIXES = {'.png', '.jpg'}
|
66
|
+
PREVIEW_SUFFIXES = {'.png', '.jpg', '.jpeg'}
|
67
67
|
|
68
68
|
def check_preview(self, obj, fix=False):
|
69
69
|
return []
|
@@ -71,7 +71,7 @@ class Previewer:
|
|
71
71
|
|
72
72
|
class FilePreviewer(Previewer):
|
73
73
|
# A media previewer that builds thumbnails in a separate directory tree
|
74
|
-
PREVIEW_SUFFIXES = {'.png', '.jpg', '.pdf'}
|
74
|
+
PREVIEW_SUFFIXES = {'.png', '.jpg', '.jpeg', '.pdf'}
|
75
75
|
|
76
76
|
def __init__(self, base_dir=None, max_width=None):
|
77
77
|
self.base_dir = base_dir
|
lino/modlib/users/__init__.py
CHANGED
@@ -57,14 +57,13 @@ class Plugin(ad.Plugin):
|
|
57
57
|
|
58
58
|
_demo_user = None # the cached User object
|
59
59
|
|
60
|
-
def get_demo_user(self
|
60
|
+
def get_demo_user(self):
|
61
61
|
if self.demo_username is None:
|
62
62
|
return None
|
63
63
|
if self._demo_user is None:
|
64
64
|
User = self.site.models.users.User
|
65
65
|
try:
|
66
|
-
self._demo_user = User.objects.get(
|
67
|
-
username=self.demo_username)
|
66
|
+
self._demo_user = User.objects.get(username=self.demo_username)
|
68
67
|
except User.DoesNotExist:
|
69
68
|
msg = "Invalid username '{0}' in `demo_username` "
|
70
69
|
msg = msg.format(self.demo_username)
|
lino/modlib/users/actions.py
CHANGED
@@ -18,6 +18,8 @@ from lino.core.roles import SiteAdmin
|
|
18
18
|
from lino.core import auth, layouts
|
19
19
|
from lino.core.actions import SubmitInsert
|
20
20
|
|
21
|
+
MSG_TAKEN = _("The username {} is taken. Please choose another one")
|
22
|
+
|
21
23
|
|
22
24
|
def send_welcome_email(ar, obj, recipients):
|
23
25
|
sender = settings.SERVER_EMAIL
|
@@ -36,14 +38,10 @@ class CheckedSubmitInsert(SubmitInsert):
|
|
36
38
|
|
37
39
|
def run_from_ui(self, ar, **kw):
|
38
40
|
obj = ar.create_instance_from_request()
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
)
|
44
|
-
|
45
|
-
ar.error(msg)
|
46
|
-
return
|
41
|
+
if obj.username:
|
42
|
+
if obj.__class__.objects.filter(username=obj.username).exist():
|
43
|
+
ar.error(MSG_TAKEN.format(obj.username))
|
44
|
+
return
|
47
45
|
|
48
46
|
def ok(ar2):
|
49
47
|
SubmitInsert.run_from_ui(self, ar, **kw)
|
@@ -96,16 +94,13 @@ class CreateAccount(dd.Action):
|
|
96
94
|
pv = ar.action_param_values
|
97
95
|
if not pv["username"]:
|
98
96
|
pv["username"] = pv["email"]
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
ar.error(msg)
|
105
|
-
# ar.set_response(close_window=False)
|
106
|
-
return
|
97
|
+
if pv["username"]:
|
98
|
+
if User.objects.filter(username=pv["username"]).exist():
|
99
|
+
ar.error(MSG_TAKEN.format(pv.username))
|
100
|
+
# ar.set_response(close_window=False)
|
101
|
+
return
|
107
102
|
|
108
|
-
|
103
|
+
validate_password(pv['password'])
|
109
104
|
|
110
105
|
ut = UserTypes.get_by_name(dd.plugins.users.user_type_new)
|
111
106
|
# pv.pop('social_auth_links')
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# -*- coding: UTF-8 -*-
|
2
|
+
# Copyright 2017-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
|
+
|
7
|
+
|
8
|
+
def objects():
|
9
|
+
User = rt.models.users.User
|
10
|
+
UserTypes = rt.models.users.UserTypes
|
11
|
+
|
12
|
+
def user(username, **kwargs):
|
13
|
+
kwargs.update(user_type=UserTypes.user, username=username)
|
14
|
+
if not dd.plugins.users.with_nickname:
|
15
|
+
kwargs.pop('nickname', None)
|
16
|
+
return User(**kwargs)
|
17
|
+
|
18
|
+
yield user("andy", first_name="Andreas", last_name="Anderson", nickname="Andy", email="andy@example.com")
|
19
|
+
yield user("bert", first_name="Albert", last_name="Bernstein", nickname="Bert", email="bert@example.com")
|
20
|
+
yield user("chloe", first_name="Chloe", last_name="Cleoment", email="chloe@example.com")
|
lino/modlib/users/mixins.py
CHANGED
@@ -490,18 +490,16 @@ class PrivacyRelevant(dd.Model):
|
|
490
490
|
qs = super().get_request_queryset(ar, **filter)
|
491
491
|
user = ar.get_user()
|
492
492
|
if user.is_anonymous:
|
493
|
-
if dd.is_installed('groups'):
|
494
|
-
|
493
|
+
# if dd.is_installed('groups'):
|
494
|
+
# qs = qs.filter(group__private=False)
|
495
495
|
return qs.filter(private=False)
|
496
|
-
# if user.current_group is not None:
|
497
|
-
# qs = qs.filter(group=user.current_group)
|
498
496
|
if user.user_type.has_required_roles([SiteAdmin]):
|
499
497
|
return qs
|
500
498
|
flt = Q(private=False)
|
501
499
|
if issubclass(cls, UserAuthored):
|
502
500
|
flt |= Q(user=user)
|
503
501
|
if dd.is_installed('groups'):
|
504
|
-
flt |= Q(group__private=False)
|
502
|
+
# flt |= Q(group__private=False)
|
505
503
|
flt |= Q(group__members__user=user)
|
506
504
|
qs = qs.filter(flt).distinct()
|
507
505
|
return qs
|
@@ -518,9 +516,11 @@ class PrivacyRelevant(dd.Model):
|
|
518
516
|
self.private = self.group.private
|
519
517
|
|
520
518
|
def get_default_group(self):
|
521
|
-
return None
|
519
|
+
return None # dd.plugins.groups.get_default_group()
|
522
520
|
|
523
521
|
def full_clean(self):
|
524
522
|
if not self.group_id:
|
525
523
|
self.group = self.get_default_group()
|
524
|
+
if self.group and self.group.private:
|
525
|
+
self.private = True
|
526
526
|
super().full_clean()
|