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.
Files changed (143) hide show
  1. lino/__init__.py +1 -1
  2. lino/api/dd.py +0 -1
  3. lino/config/unused/403.html +1 -1
  4. lino/config/unused/404.html +1 -1
  5. lino/config/unused/500.html +1 -1
  6. lino/core/__init__.py +0 -1
  7. lino/core/actions.py +2 -2
  8. lino/core/actors.py +10 -2
  9. lino/core/elems.py +1 -1
  10. lino/core/fields.py +4 -1
  11. lino/core/kernel.py +5 -1
  12. lino/core/model.py +2 -11
  13. lino/core/renderer.py +2 -2
  14. lino/core/requests.py +12 -12
  15. lino/core/site.py +5 -82
  16. lino/core/store.py +3 -1
  17. lino/core/urls.py +1 -1
  18. lino/core/user_types.py +1 -10
  19. lino/help_texts.py +6 -6
  20. lino/management/commands/initdb.py +0 -3
  21. lino/modlib/__init__.py +0 -1
  22. lino/modlib/bootstrap5/README.txt +2 -0
  23. lino/modlib/bootstrap5/__init__.py +69 -0
  24. lino/modlib/{bootstrap3/config/bootstrap3 → bootstrap5/config/bootstrap5}/base.html +9 -4
  25. lino/modlib/{bootstrap3/config/bootstrap3 → bootstrap5/config/bootstrap5}/detail.html +1 -1
  26. lino/modlib/{bootstrap3/config/bootstrap3 → bootstrap5/config/bootstrap5}/index.html +1 -1
  27. lino/modlib/{bootstrap3/config/bootstrap3 → bootstrap5/config/bootstrap5}/table.html +1 -1
  28. lino/modlib/bootstrap5/models.py +30 -0
  29. lino/modlib/bootstrap5/static/bootstrap-5.3.7/css/bootstrap-grid.css +4085 -0
  30. lino/modlib/bootstrap5/static/bootstrap-5.3.7/css/bootstrap-grid.css.map +1 -0
  31. lino/modlib/bootstrap5/static/bootstrap-5.3.7/css/bootstrap-grid.min.css +6 -0
  32. lino/modlib/bootstrap5/static/bootstrap-5.3.7/css/bootstrap-grid.min.css.map +1 -0
  33. lino/modlib/bootstrap5/static/bootstrap-5.3.7/css/bootstrap-grid.rtl.css +4084 -0
  34. lino/modlib/bootstrap5/static/bootstrap-5.3.7/css/bootstrap-grid.rtl.css.map +1 -0
  35. lino/modlib/bootstrap5/static/bootstrap-5.3.7/css/bootstrap-grid.rtl.min.css +6 -0
  36. lino/modlib/bootstrap5/static/bootstrap-5.3.7/css/bootstrap-grid.rtl.min.css.map +1 -0
  37. lino/modlib/bootstrap5/static/bootstrap-5.3.7/css/bootstrap-reboot.css +597 -0
  38. lino/modlib/bootstrap5/static/bootstrap-5.3.7/css/bootstrap-reboot.css.map +1 -0
  39. lino/modlib/bootstrap5/static/bootstrap-5.3.7/css/bootstrap-reboot.min.css +6 -0
  40. lino/modlib/bootstrap5/static/bootstrap-5.3.7/css/bootstrap-reboot.min.css.map +1 -0
  41. lino/modlib/bootstrap5/static/bootstrap-5.3.7/css/bootstrap-reboot.rtl.css +594 -0
  42. lino/modlib/bootstrap5/static/bootstrap-5.3.7/css/bootstrap-reboot.rtl.css.map +1 -0
  43. lino/modlib/bootstrap5/static/bootstrap-5.3.7/css/bootstrap-reboot.rtl.min.css +6 -0
  44. lino/modlib/bootstrap5/static/bootstrap-5.3.7/css/bootstrap-reboot.rtl.min.css.map +1 -0
  45. lino/modlib/bootstrap5/static/bootstrap-5.3.7/css/bootstrap-utilities.css +5406 -0
  46. lino/modlib/bootstrap5/static/bootstrap-5.3.7/css/bootstrap-utilities.css.map +1 -0
  47. lino/modlib/bootstrap5/static/bootstrap-5.3.7/css/bootstrap-utilities.min.css +6 -0
  48. lino/modlib/bootstrap5/static/bootstrap-5.3.7/css/bootstrap-utilities.min.css.map +1 -0
  49. lino/modlib/bootstrap5/static/bootstrap-5.3.7/css/bootstrap-utilities.rtl.css +5397 -0
  50. lino/modlib/bootstrap5/static/bootstrap-5.3.7/css/bootstrap-utilities.rtl.css.map +1 -0
  51. lino/modlib/bootstrap5/static/bootstrap-5.3.7/css/bootstrap-utilities.rtl.min.css +6 -0
  52. lino/modlib/bootstrap5/static/bootstrap-5.3.7/css/bootstrap-utilities.rtl.min.css.map +1 -0
  53. lino/modlib/bootstrap5/static/bootstrap-5.3.7/css/bootstrap.css +12043 -0
  54. lino/modlib/bootstrap5/static/bootstrap-5.3.7/css/bootstrap.css.map +1 -0
  55. lino/modlib/bootstrap5/static/bootstrap-5.3.7/css/bootstrap.min.css +6 -0
  56. lino/modlib/bootstrap5/static/bootstrap-5.3.7/css/bootstrap.min.css.map +1 -0
  57. lino/modlib/bootstrap5/static/bootstrap-5.3.7/css/bootstrap.rtl.css +12016 -0
  58. lino/modlib/bootstrap5/static/bootstrap-5.3.7/css/bootstrap.rtl.css.map +1 -0
  59. lino/modlib/bootstrap5/static/bootstrap-5.3.7/css/bootstrap.rtl.min.css +6 -0
  60. lino/modlib/bootstrap5/static/bootstrap-5.3.7/css/bootstrap.rtl.min.css.map +1 -0
  61. lino/modlib/bootstrap5/static/bootstrap-5.3.7/js/bootstrap.bundle.js +6315 -0
  62. lino/modlib/bootstrap5/static/bootstrap-5.3.7/js/bootstrap.bundle.js.map +1 -0
  63. lino/modlib/bootstrap5/static/bootstrap-5.3.7/js/bootstrap.bundle.min.js +7 -0
  64. lino/modlib/bootstrap5/static/bootstrap-5.3.7/js/bootstrap.bundle.min.js.map +1 -0
  65. lino/modlib/bootstrap5/static/bootstrap-5.3.7/js/bootstrap.esm.js +4450 -0
  66. lino/modlib/bootstrap5/static/bootstrap-5.3.7/js/bootstrap.esm.js.map +1 -0
  67. lino/modlib/bootstrap5/static/bootstrap-5.3.7/js/bootstrap.esm.min.js +7 -0
  68. lino/modlib/bootstrap5/static/bootstrap-5.3.7/js/bootstrap.esm.min.js.map +1 -0
  69. lino/modlib/bootstrap5/static/bootstrap-5.3.7/js/bootstrap.js +4497 -0
  70. lino/modlib/bootstrap5/static/bootstrap-5.3.7/js/bootstrap.js.map +1 -0
  71. lino/modlib/bootstrap5/static/bootstrap-5.3.7/js/bootstrap.min.js +7 -0
  72. lino/modlib/bootstrap5/static/bootstrap-5.3.7/js/bootstrap.min.js.map +1 -0
  73. lino/modlib/{bootstrap3 → bootstrap5}/views.py +12 -117
  74. lino/modlib/checkdata/choicelists.py +1 -1
  75. lino/modlib/comments/fixtures/demo2.py +1 -0
  76. lino/modlib/comments/mixins.py +1 -8
  77. lino/modlib/comments/models.py +2 -0
  78. lino/modlib/comments/ui.py +7 -7
  79. lino/modlib/extjs/__init__.py +2 -4
  80. lino/modlib/extjs/config/extjs/index.html +1 -1
  81. lino/modlib/extjs/ext_renderer.py +1 -7
  82. lino/modlib/extjs/views.py +2 -0
  83. lino/modlib/help/models.py +1 -12
  84. lino/modlib/jinja/renderer.py +1 -1
  85. lino/modlib/linod/mixins.py +3 -2
  86. lino/modlib/memo/__init__.py +11 -11
  87. lino/modlib/memo/mixins.py +38 -21
  88. lino/modlib/memo/models.py +10 -7
  89. lino/modlib/memo/parser.py +3 -1
  90. lino/modlib/notify/models.py +6 -9
  91. lino/modlib/odata/views.py +7 -7
  92. lino/modlib/publisher/__init__.py +15 -3
  93. lino/modlib/publisher/choicelists.py +8 -94
  94. lino/modlib/publisher/config/publisher/page.pub.html +82 -19
  95. lino/modlib/publisher/fixtures/std.py +14 -1
  96. lino/modlib/publisher/fixtures/synodalworld.py +3 -1
  97. lino/modlib/publisher/mixins.py +59 -77
  98. lino/modlib/publisher/models.py +109 -204
  99. lino/modlib/publisher/renderer.py +31 -11
  100. lino/modlib/publisher/ui.py +46 -98
  101. lino/modlib/publisher/views.py +61 -11
  102. lino/modlib/system/models.py +3 -2
  103. lino/modlib/uploads/__init__.py +1 -0
  104. lino/modlib/uploads/mixins.py +2 -2
  105. lino/modlib/uploads/models.py +55 -21
  106. lino/modlib/uploads/ui.py +1 -0
  107. lino/modlib/uploads/utils.py +2 -2
  108. lino/modlib/users/__init__.py +2 -3
  109. lino/modlib/users/actions.py +12 -17
  110. lino/modlib/users/fixtures/abc.py +20 -0
  111. lino/modlib/users/mixins.py +6 -6
  112. lino/modlib/users/models.py +37 -36
  113. lino/modlib/weasyprint/__init__.py +25 -14
  114. lino/modlib/weasyprint/choicelists.py +6 -0
  115. lino/modlib/weasyprint/config/weasyprint/base.weasy.html +43 -27
  116. lino/utils/diag.py +5 -3
  117. lino/utils/html.py +103 -0
  118. lino/utils/mldbc/mixins.py +2 -2
  119. lino/utils/soup.py +16 -8
  120. {lino-25.8.2.dist-info → lino-25.9.0.dist-info}/METADATA +1 -1
  121. {lino-25.8.2.dist-info → lino-25.9.0.dist-info}/RECORD +135 -95
  122. lino/modlib/bootstrap3/README.txt +0 -2
  123. lino/modlib/bootstrap3/__init__.py +0 -73
  124. lino/modlib/bootstrap3/models.py +0 -30
  125. lino/modlib/bootstrap3/static/bootstrap-3.3.4/css/bootstrap.css +0 -6584
  126. lino/modlib/bootstrap3/static/bootstrap-3.3.4/css/bootstrap.css.map +0 -1
  127. lino/modlib/bootstrap3/static/bootstrap-3.3.4/css/bootstrap.min.css +0 -5
  128. lino/modlib/bootstrap3/static/bootstrap-3.3.4/js/bootstrap.js +0 -2317
  129. lino/modlib/bootstrap3/static/bootstrap-3.3.4/js/bootstrap.min.js +0 -7
  130. /lino/modlib/{bootstrap3 → bootstrap5}/renderer.py +0 -0
  131. /lino/modlib/{bootstrap3/static/bootstrap-3.3.4 → bootstrap5/static/bootstrap-5.3.7}/css/bootstrap-theme.css +0 -0
  132. /lino/modlib/{bootstrap3/static/bootstrap-3.3.4 → bootstrap5/static/bootstrap-5.3.7}/css/bootstrap-theme.css.map +0 -0
  133. /lino/modlib/{bootstrap3/static/bootstrap-3.3.4 → bootstrap5/static/bootstrap-5.3.7}/css/bootstrap-theme.min.css +0 -0
  134. /lino/modlib/{bootstrap3/static/bootstrap-3.3.4 → bootstrap5/static/bootstrap-5.3.7}/fonts/glyphicons-halflings-regular.eot +0 -0
  135. /lino/modlib/{bootstrap3/static/bootstrap-3.3.4 → bootstrap5/static/bootstrap-5.3.7}/fonts/glyphicons-halflings-regular.svg +0 -0
  136. /lino/modlib/{bootstrap3/static/bootstrap-3.3.4 → bootstrap5/static/bootstrap-5.3.7}/fonts/glyphicons-halflings-regular.ttf +0 -0
  137. /lino/modlib/{bootstrap3/static/bootstrap-3.3.4 → bootstrap5/static/bootstrap-5.3.7}/fonts/glyphicons-halflings-regular.woff +0 -0
  138. /lino/modlib/{bootstrap3/static/bootstrap-3.3.4 → bootstrap5/static/bootstrap-5.3.7}/fonts/glyphicons-halflings-regular.woff2 +0 -0
  139. /lino/modlib/{bootstrap3/static/bootstrap-3.3.4 → bootstrap5/static/bootstrap-5.3.7}/js/bootstrap_lino.js +0 -0
  140. /lino/modlib/{bootstrap3/static/bootstrap-3.3.4 → bootstrap5/static/bootstrap-5.3.7}/js/npm.js +0 -0
  141. {lino-25.8.2.dist-info → lino-25.9.0.dist-info}/WHEEL +0 -0
  142. {lino-25.8.2.dist-info → lino-25.9.0.dist-info}/licenses/AUTHORS.rst +0 -0
  143. {lino-25.8.2.dist-info → lino-25.9.0.dist-info}/licenses/COPYING +0 -0
@@ -1,33 +1,29 @@
1
1
  # -*- coding: UTF-8 -*-
2
2
  # Copyright 2009-2020 Rumma & Ko Ltd
3
3
  # License: GNU Affero General Public License v3 (see file COPYING for details)
4
- """Views for `lino.modlib.bootstrap3`.
4
+ """Views for `lino.modlib.bootstrap5`.
5
5
 
6
6
  """
7
- from past.utils import old_div
8
7
 
9
8
  from django import http
10
9
  from django.conf import settings
11
10
  from django.views.generic import View
12
- from django.core import exceptions
13
11
  from django.utils.translation import gettext as _
14
12
  from django.utils.translation import get_language
15
13
  from django.utils.decorators import method_decorator
16
14
  from django.views.decorators.csrf import ensure_csrf_cookie
17
-
18
15
  # from django.contrib import auth
19
16
  from lino.core import auth
20
-
21
17
  # from lino.api import dd
22
18
  from lino.core import constants
23
-
24
19
  # from lino.core import auth
25
20
  from lino.core.requests import BaseRequest
26
21
  from lino.core.tables import AbstractTable
27
22
  from lino.core.views import action_request
28
23
  from lino.core.utils import navinfo
29
24
  from lino.utils.html import E, tostring
30
- from etgen import html as xghtml
25
+
26
+ raise Exception("No longer used since 20250826")
31
27
 
32
28
  PLAIN_PAGE_LENGTH = 15
33
29
 
@@ -42,12 +38,12 @@ def http_response(ar, tplname, context):
42
38
  menu = MENUS.get(k, None)
43
39
  if menu is None:
44
40
  menu = settings.SITE.get_site_menu(u.user_type)
45
- bs3 = settings.SITE.plugins.bootstrap3
41
+ bs5 = settings.SITE.plugins.bootstrap5
46
42
  if False: # 20150803 home button now in base.html
47
- assert bs3.renderer is not None
48
- url = bs3.build_plain_url()
43
+ assert bs5.renderer is not None
44
+ url = bs5.build_plain_url()
49
45
  menu.add_url_button(url, label=_("Home"))
50
- e = bs3.renderer.show_menu(ar, menu)
46
+ e = bs5.renderer.show_menu(ar, menu)
51
47
  menu = tostring(e)
52
48
  MENUS[k] = menu
53
49
  context.update(menu=menu)
@@ -78,113 +74,12 @@ def buttons2pager(buttons, title=None):
78
74
  return E.ul(*items, **{"class": "pagination pagination-sm"})
79
75
 
80
76
 
81
- def table2html(ar, as_main=True):
82
- """Represent the given table request as an HTML table.
83
-
84
- `ar` is the request to be rendered, an instance of
85
- :class:`lino.core.requests.ActionRequest`.
86
-
87
- The returned HTML enclosed in a ``<div>`` tag and generated using
88
- :mod:`etgen.html`.
89
-
90
- If `as_main` is True, include additional elements such as a paging
91
- toolbar. (This argument is currently being ignored.)
92
-
93
- """
94
- # as_main = True
95
- t = xghtml.Table()
96
- t.attrib.update(**{"class": "table table-striped table-hover"})
97
- if ar.limit is None:
98
- ar.limit = PLAIN_PAGE_LENGTH
99
- pglen = ar.limit
100
- if ar.offset is None:
101
- page = 1
102
- else:
103
- """
104
- (assuming pglen is 5)
105
- offset page
106
- 0 1
107
- 5 2
108
- """
109
- page = int(old_div(ar.offset, pglen)) + 1
110
-
111
- ar.dump2html(t, ar.sliced_data_iterator, header_links=as_main)
112
- if not as_main:
113
- url = ar.get_request_url() or "#" # open in own window
114
- return E.div(
115
- E.div(
116
- E.div(
117
- E.a(
118
- E.span(**{"class": "glyphicon glyphicon-folder-open"}),
119
- href=url,
120
- style="margin-left: 4px;",
121
- **{"class": "btn btn-default pull-right"},
122
- ),
123
- E.h5(str(ar.get_title()), style="display: inline-block;"),
124
- **{"class": "panel-title"},
125
- ),
126
- **{"class": "panel-heading"},
127
- ),
128
- t.as_element(),
129
- style="display: inline-block;",
130
- **{"class": "panel panel-default"},
131
- )
132
-
133
- buttons = []
134
- kw = dict()
135
- kw = {}
136
- if pglen != PLAIN_PAGE_LENGTH:
137
- kw[constants.URL_PARAM_LIMIT] = pglen
138
-
139
- if page > 1:
140
- kw[constants.URL_PARAM_START] = pglen * (page - 2)
141
- prev_url = ar.get_request_url(**kw)
142
- kw[constants.URL_PARAM_START] = 0
143
- first_url = ar.get_request_url(**kw)
144
- else:
145
- prev_url = None
146
- first_url = None
147
- buttons.append(("<<", _("First page"), first_url))
148
- buttons.append(("<", _("Previous page"), prev_url))
149
-
150
- next_start = pglen * page
151
- if next_start < ar.get_total_count():
152
- kw[constants.URL_PARAM_START] = next_start
153
- next_url = ar.get_request_url(**kw)
154
- last_page = int(old_div((ar.get_total_count() - 1), pglen))
155
- kw[constants.URL_PARAM_START] = pglen * last_page
156
- last_url = ar.get_request_url(**kw)
157
- else:
158
- next_url = None
159
- last_url = None
160
- buttons.append((">", _("Next page"), next_url))
161
- buttons.append((">>", _("Last page"), last_url))
162
-
163
- return E.div(buttons2pager(buttons), t.as_element())
164
-
165
-
166
- def layout2html(ar, elem):
167
- wl = ar.bound_action.get_window_layout()
168
- if wl is None:
169
- raise Exception("{!r} has no window layout".format(ar.bound_action))
170
- # ~ print 20120901, wl.main
171
- lh = wl.get_layout_handle()
172
-
173
- items = list(lh.main.as_plain_html(ar, elem))
174
- # if navigator:
175
- # items.insert(0, navigator)
176
- # ~ print tostring(E.div())
177
- # ~ if len(items) == 0: return ""
178
- return E.form(*items)
179
- # ~ print 20120901, lh.main.__html__(ar)
180
-
181
-
182
77
  class List(View):
183
78
  """Render a list of records."""
184
79
 
185
80
  def get(self, request, app_label=None, actor=None):
186
81
  ar = action_request(app_label, actor, request, request.GET, True)
187
- ar.renderer = settings.SITE.plugins.bootstrap3.renderer
82
+ ar.renderer = settings.SITE.plugins.bootstrap5.renderer
188
83
 
189
84
  context = dict(
190
85
  title=ar.get_title(),
@@ -207,7 +102,7 @@ class Element(View):
207
102
  def get(self, request, app_label=None, actor=None, pk=None):
208
103
  # print(request, app_label, actor, pk)
209
104
  ar = action_request(app_label, actor, request, request.GET, False)
210
- ar.renderer = settings.SITE.plugins.bootstrap3.renderer
105
+ ar.renderer = settings.SITE.plugins.bootstrap5.renderer
211
106
 
212
107
  navigator = None
213
108
  if pk and pk != "-99999" and pk != "-99998":
@@ -317,7 +212,7 @@ class Index(View):
317
212
  def get(self, request, *args, **kw):
318
213
  # raise Exception("20171122 {} {}".format(
319
214
  # get_language(), settings.MIDDLEWARE_CLASSES))
320
- ui = settings.SITE.plugins.bootstrap3
215
+ ui = settings.SITE.plugins.bootstrap5
321
216
  # print("20170607", request.user)
322
217
  # assert ui.renderer is not None
323
218
  ar = BaseRequest(
@@ -329,7 +224,7 @@ class Index(View):
329
224
 
330
225
 
331
226
  def index_response(ar):
332
- ui = settings.SITE.plugins.bootstrap3
227
+ ui = settings.SITE.plugins.bootstrap5
333
228
  main = settings.SITE.get_main_html(ar, front_end=ui)
334
229
  main = ui.renderer.html_text(main)
335
230
  context = dict(
@@ -341,4 +236,4 @@ def index_response(ar):
341
236
  # else:
342
237
  # user = request.subst_user or request.user
343
238
  # context.update(ar=ar)
344
- return http_response(ar, "bootstrap3/index.html", context)
239
+ return http_response(ar, "bootstrap5/index.html", context)
@@ -138,7 +138,7 @@ class Checker(dd.Choice):
138
138
  return []
139
139
 
140
140
  def get_responsible_user(self, obj):
141
- return dd.plugins.users.get_demo_user(self, obj)
141
+ return dd.plugins.users.get_demo_user()
142
142
 
143
143
 
144
144
  class Checkers(dd.ChoiceList):
@@ -94,6 +94,7 @@ def objects():
94
94
  # else:
95
95
  # txt = TXT.pop() # txt = "Hackerish comment"
96
96
  body = BODIES.pop()
97
+ # if not body and len(MENTIONED) or SCREENSHOTS:
97
98
  if not body:
98
99
  if SCREENSHOTS is None or i % 2:
99
100
  cmd1 = MENTIONED.pop().obj2memo()
@@ -102,9 +102,6 @@ class Commentable(MemoReferrable):
102
102
  def get_rfc_description(self, ar):
103
103
  return ""
104
104
 
105
- # def get_comment_group(self):
106
- # return None
107
-
108
105
  if dd.is_installed("comments"):
109
106
 
110
107
  def save_new_instance(self, ar):
@@ -123,12 +120,8 @@ class Commentable(MemoReferrable):
123
120
  return _("Created new {model} {obj}.").format(
124
121
  model=self.__class__._meta.verbose_name, obj=self)
125
122
 
126
- # @classmethod
127
- # def add_comments_filter(cls, qs, ar):
128
- # return qs
129
-
130
123
  def get_comment_group(self):
131
- pass
124
+ return None # dd.plugins.groups.get_default_group()
132
125
 
133
126
  def on_create_comment(self, comment, ar):
134
127
  pass
@@ -285,8 +285,10 @@ class Comment(
285
285
  self.owner.on_create_comment(self, ar)
286
286
 
287
287
  def get_default_group(self):
288
+ # implements PrivacyRelevant
288
289
  if self.owner:
289
290
  return self.owner.get_comment_group()
291
+ return super().get_default_group()
290
292
 
291
293
  def after_ui_save(self, ar, cw):
292
294
  super().after_ui_save(ar, cw)
@@ -2,7 +2,6 @@
2
2
  # Copyright 2013-2023 Rumma & Ko Ltd
3
3
  # License: GNU Affero General Public License v3 (see file COPYING for details)
4
4
 
5
- from lino.modlib.publisher.choicelists import PageFillers
6
5
  from django.contrib.humanize.templatetags.humanize import naturaltime
7
6
  from django.contrib.contenttypes.models import ContentType
8
7
  from django.db import models
@@ -66,18 +65,22 @@ class CommentDetail(dd.DetailLayout):
66
65
 
67
66
  more = dd.Panel(
68
67
  """
69
- #body more2
68
+ more1 more2
70
69
  """,
71
70
  label=_("More"),
72
71
  )
73
72
 
74
- more2 = """
73
+ more1 = """
75
74
  id user group
76
75
  owner_type owner_id
77
- created modified
78
76
  comment_type
79
77
  ReactionsByComment
80
78
  """
79
+ more2 = """
80
+ created modified
81
+ memo.MentionsByTarget
82
+ memo.MentionsByOwner
83
+ """
81
84
 
82
85
 
83
86
  class Comments(dd.Table):
@@ -314,6 +317,3 @@ class Reactions(dd.Table):
314
317
  class ReactionsByComment(Reactions):
315
318
  master_key = "comment"
316
319
  default_display_modes = {None: constants.DISPLAY_MODE_SUMMARY}
317
-
318
-
319
- PageFillers.add_item(RecentComments)
@@ -28,7 +28,8 @@ from django.utils.translation import gettext_lazy as _
28
28
  class Plugin(Plugin):
29
29
  """Extends :class:`lino.core.plugin.Plugin`."""
30
30
 
31
- needs_plugins = ["lino.modlib.bootstrap3"]
31
+ # needs_plugins = ["lino.modlib.bootstrap5"]
32
+ needs_plugins = ["lino.modlib.jinja"]
32
33
 
33
34
  enter_submits_form = False
34
35
  """Whether the :kbd:`ENTER` key (or :kbd:`CTRL+ENTER` when in a
@@ -216,9 +217,6 @@ class Plugin(Plugin):
216
217
  from django.urls import re_path as url
217
218
  from . import views
218
219
 
219
- # if self.site.developer_site_cache:
220
- # self.renderer.build_site_cache()
221
-
222
220
  rx = "^"
223
221
 
224
222
  urlpatterns = [
@@ -130,7 +130,7 @@
130
130
  {{ ln }}{% endfor -%}
131
131
  {%- endfor -%}
132
132
  {# Main Lino js code #}
133
- {{ javascript(site.build_site_cache_url(*ext_renderer.lino_js_parts())) }}
133
+ {{ javascript(site.build_media_url(*ext_renderer.lino_js_parts())) }}
134
134
  {# javascript(site.buildurl('linolib.js')) #}
135
135
  {# ###### OnReady JS code ###### #}
136
136
  <script type="text/javascript">
@@ -420,10 +420,7 @@ class ExtRenderer(JsCacheRenderer):
420
420
  user = request.subst_user
421
421
 
422
422
  def getit():
423
- if (
424
- settings.SITE.developer_site_cache
425
- and not settings.SITE.never_build_site_cache
426
- ):
423
+ if settings.SITE.developer_site_cache:
427
424
  self.build_js_cache(False)
428
425
 
429
426
  # Render template
@@ -1478,9 +1475,6 @@ class ExtRenderer(JsCacheRenderer):
1478
1475
  )
1479
1476
  yield "LANGUAGE_CHOICES = %s;" % py2js(list(settings.SITE.LANGUAGE_CHOICES))
1480
1477
  yield "MEDIA_URL = %s;" % py2js(settings.SITE.build_media_url())
1481
- # if settings.SITE.never_build_site_cache:
1482
- # yield "GEN_TIMESTAMP = '%s';" % settings.SITE.kernel.lino_version
1483
- # else:
1484
1478
  yield "GEN_TIMESTAMP = %s;" % py2js(settings.SITE.kernel.lino_version)
1485
1479
 
1486
1480
  return "\n".join(fn())
@@ -597,11 +597,13 @@ class ApiList(View):
597
597
 
598
598
  if True:
599
599
  kw.update(title=str(ar.get_title()))
600
+ # kw.update(title=str(ar.get_breadcrumbs()))
600
601
  else:
601
602
  # 20190704 work in progress.
602
603
  # add open_in_own_window button after title of slave panel
603
604
  kw.update(
604
605
  title=str(ar.get_title())
606
+ # title=str(ar.get_breadcrumbs())
605
607
  + " "
606
608
  + tostring(ar.open_in_own_window_button())
607
609
  )
@@ -28,17 +28,6 @@ class OpenHelpWindow(dd.Action):
28
28
  help_text = _("Open Help Window")
29
29
  show_in_plain = True
30
30
 
31
- # def js_handler(self, actor):
32
- # parts = ['cache', 'help']
33
- # if get_language() != settings.SITE.DEFAULT_LANGUAGE.django_code:
34
- # parts.append(get_language())
35
- # parts.append(str(actor) + ".html")
36
- # url = settings.SITE.build_site_cache_url(*parts)
37
- # # return "let _ = window.open('%s');" % url
38
- # # return "() => window.open('%s')" % url
39
- # return "function (){window.open('%s')}" % url
40
- # # return "window.open('%s')" % url
41
-
42
31
  def run_from_ui(self, ar, **kwargs):
43
32
  # print("20210612")
44
33
  parts = ["cache", "help"]
@@ -46,7 +35,7 @@ class OpenHelpWindow(dd.Action):
46
35
  parts.append(get_language())
47
36
  parts.append(str(ar.actor) + ".html")
48
37
  # parts.append("index.html")
49
- url = settings.SITE.build_site_cache_url(*parts)
38
+ url = settings.SITE.build_media_url(*parts)
50
39
  ar.set_response(success=True)
51
40
  ar.success(open_url=url)
52
41
 
@@ -88,7 +88,7 @@ class JinjaRenderer(HtmlRenderer):
88
88
 
89
89
  def as_table2(ar):
90
90
  # 20150810
91
- # ar.renderer = settings.SITE.plugins.bootstrap3.renderer
91
+ # ar.renderer = settings.SITE.plugins.bootstrap5.renderer
92
92
  ar.renderer = self
93
93
 
94
94
  t = xghtml.Table()
@@ -179,10 +179,11 @@ class Runnable(Sequenced, RecurrenceSet):
179
179
  await ar.adebug("Terminated %s with warning %s", self, str(e))
180
180
  self.message = out.getvalue()
181
181
  except Exception as e:
182
+ tb = "".join(traceback.format_exception(e))
182
183
  self.message = out.getvalue()
183
- self.message += "\n" + "".join(traceback.format_exception(e))
184
+ self.message += "\n" + tb
184
185
  self.disabled = True
185
- await ar.awarning("Disabled %s after exception %s", self, e)
186
+ await ar.awarning("Disabled %s after exception:\n%s", self, tb)
186
187
  # ar.warning("Disabled %s after exception %s", astr(self), e)
187
188
  # now = await sync_to_async(dd.now)()
188
189
  self.last_end_time = timezone.now()
@@ -1,4 +1,4 @@
1
- # Copyright 2008-2023 Rumma & Ko Ltd
1
+ # Copyright 2008-2025 Rumma & Ko Ltd
2
2
  # License: GNU Affero General Public License v3 (see file COPYING for details)
3
3
  """See :doc:`/specs/memo`.
4
4
 
@@ -15,6 +15,7 @@ Adds functionality for using memo commands in your text fields.
15
15
  # from importlib import import_module
16
16
  # from rstgen.utils import py2url_txt
17
17
  from lino.api import ad
18
+ from lino.core import constants
18
19
  from .parser import Parser, split_name_rest
19
20
  from lino.utils.html import tostring
20
21
 
@@ -46,7 +47,7 @@ class Plugin(ad.Plugin):
46
47
  front_end = None
47
48
  # front_end = 'extjs'
48
49
  # front_end = 'lino_react.react'
49
- # front_end = 'bootstrap3'
50
+ # front_end = 'bootstrap5'
50
51
  """The front end to use when writing previews.
51
52
 
52
53
  If this is `None`, Lino will use the default :term:`front end`
@@ -94,20 +95,20 @@ class Plugin(ad.Plugin):
94
95
  kwargs = dict() # , nosummary=True)
95
96
  dv = self.site.models.resolve(s)
96
97
  sar = dv.create_request(parent=ar, limit=dv.preview_limit)
97
- rv = ""
98
- # rv += "20230325 [show {}]".format(dv)
99
- for e in sar.renderer.table2story(sar, **kwargs):
100
- rv += tostring(e)
101
- return rv
98
+ return sar.show(**kwargs)
99
+ # kwargs.update(display_mode=constants.DISPLAY_MODE_STORY)
100
+ # return sar.show_story(**kwargs)
101
+ # rv = ""
102
+ # # rv += "20230325 [show {}]".format(dv)
103
+ # for e in sar.renderer.table2story(sar, **kwargs):
104
+ # rv += tostring(e)
105
+ # return rv
102
106
 
103
107
  self.parser.register_command("show", show2html)
104
108
 
105
109
  if False:
106
110
  # letting website users execute arbitrary code is a security risk
107
111
  def eval2html(ar, s, cmdname, mentions, context):
108
- from django.conf import settings # context of exec command
109
-
110
- sar = ar.spawn_request(renderer=settings.SITE.kernel.html_renderer)
111
112
  return eval(compile(s, cmdname, "eval"))
112
113
 
113
114
  self.parser.register_command("eval", eval2html)
@@ -147,7 +148,6 @@ class Plugin(ad.Plugin):
147
148
  # break
148
149
 
149
150
  def get_patterns(self):
150
- # from django.conf.urls import url
151
151
  from django.urls import re_path as url
152
152
  from . import views
153
153
 
@@ -1,7 +1,9 @@
1
1
  # -*- coding: UTF-8 -*-
2
- # Copyright 2016-2024 Rumma & Ko Ltd
2
+ # Copyright 2016-2025 Rumma & Ko Ltd
3
3
  # License: GNU Affero General Public License v3 (see file COPYING for details)
4
4
 
5
+ # import difflib
6
+ # import sys
5
7
  from lxml.html import fragments_fromstring
6
8
  import lxml
7
9
 
@@ -19,6 +21,7 @@ from lino.core.fields import RichTextField, PreviewTextField
19
21
  from lino.utils.html import E, tostring, mark_safe
20
22
  from lino.utils.restify import restify
21
23
  from lino.utils.soup import truncate_comment
24
+ from lino.utils.soup import MORE_MARKER
22
25
  from lino.utils.mldbc.fields import BabelTextField
23
26
  from lino.modlib.checkdata.choicelists import Checker
24
27
  from lino.api import rt, dd, _
@@ -130,6 +133,7 @@ class MemoReferrable(dd.Model):
130
133
 
131
134
  # class BasePreviewable(MentionGenerator):
132
135
  class BasePreviewable(dd.Model):
136
+
133
137
  class Meta:
134
138
  abstract = True
135
139
 
@@ -138,12 +142,13 @@ class BasePreviewable(dd.Model):
138
142
  def get_preview_length(self):
139
143
  return settings.SITE.plugins.memo.short_preview_length
140
144
 
145
+ # def after_ui_save(self, ar, watcher):
141
146
  def save(self, *args, **kwargs):
142
147
  """Updates the preview fields and the list of mentioned objects."""
143
- pf = self.previewable_field
144
148
  mentions = set()
149
+ pf = self.previewable_field
145
150
  txt = self.get_previewable_text(settings.SITE.DEFAULT_LANGUAGE)
146
- short, full = self.parse_previews(txt, None, mentions)
151
+ short, full = self.parse_previews(txt, None, mentions, True)
147
152
  # if "choose one or the other" in short:
148
153
  # raise Exception("20230928 {} {}".format(len(short), short))
149
154
  # print("20231023 b", short)
@@ -154,23 +159,30 @@ class BasePreviewable(dd.Model):
154
159
  src = self.get_previewable_text(lng)
155
160
  # src = getattr(self, pf + lng.suffix)
156
161
  with translation.override(lng.django_code):
157
- short, full = self.parse_previews(src, None, mentions)
162
+ short, full = self.parse_previews(src, None, mentions, True)
158
163
  setattr(self, pf + "_short_preview" + lng.suffix, short)
159
164
  setattr(self, pf + "_full_preview" + lng.suffix, full)
165
+
166
+ # self.save() # yes this causes a second save()
160
167
  super().save(*args, **kwargs)
168
+ # super().after_ui_save(ar, watcher)
161
169
  self.synchronize_mentions(mentions)
162
170
 
163
171
  def get_previewable_text(self, lng):
164
172
  return getattr(self, self.previewable_field + lng.suffix)
165
173
 
166
- def parse_previews(self, source, ar=None, mentions=None, **context):
174
+ def parse_previews(
175
+ self, source, ar=None, mentions=None, save=False, **context):
167
176
  context.update(self=self)
168
177
  full = settings.SITE.plugins.memo.parser.parse(
169
- source, ar=ar, context=context, mentions=mentions
170
- )
171
- short = truncate_comment(full, self.get_preview_length())
172
- if not full.startswith("<"):
173
- if settings.SITE.plugins.memo.use_markup:
178
+ source, ar=ar, mentions=mentions, context=context)
179
+ short = truncate_comment(
180
+ full, self.get_preview_length(),
181
+ ar=ar, save=save, mentions=mentions)
182
+ if len(chunks := full.split(MORE_MARKER, 1)) == 2:
183
+ full = " ".join(chunks)
184
+ if settings.SITE.plugins.memo.use_markup:
185
+ if not full.startswith("<"):
174
186
  full = markdown.markdown(full, **MARKDOWNCFG)
175
187
  return (short, full)
176
188
 
@@ -201,7 +213,7 @@ class BasePreviewable(dd.Model):
201
213
  for e in lxml.html.fragments_fromstring(self.body_short_preview):
202
214
  yield e
203
215
  except Exception as e:
204
- yield "{} [{}]".format(self.body_short_preview, e)
216
+ yield f"{self.body_short_preview} [{e}]"
205
217
 
206
218
 
207
219
  class Previewable(BasePreviewable):
@@ -218,20 +230,21 @@ class Previewable(BasePreviewable):
218
230
 
219
231
  def get_body_parsed(self, ar, short=False):
220
232
  if ar.renderer is settings.SITE.kernel.editing_front_end.renderer:
221
- return self.body_short_preview if short else self.body_full_preview
233
+ return mark_safe(
234
+ self.body_short_preview if short else self.body_full_preview)
222
235
  # raise Exception("{} is not {}".format(
223
236
  # ar.renderer, settings.SITE.kernel.editing_front_end.renderer))
224
237
  src = self.body
225
- s, f = self.parse_previews(src, ar, set())
226
- return s if short else f
238
+ s, f = self.parse_previews(src, ar, None, False)
239
+ return mark_safe(s if short else f)
227
240
 
228
241
  def as_paragraph(self, ar):
229
242
  s = super().as_paragraph(ar)
230
243
  # s = format_html("<b>{}</b> : {}", .format(ar.add_detail_link(self, str(self)))
231
244
  # s = ar.obj2htmls(self)
232
245
  s = format_html(
233
- "<b>{}</b> : {}", s, mark_safe(self.body_short_preview) or _("(no preview)")
234
- )
246
+ "<b>{}</b> : {}",
247
+ s, mark_safe(self.body_short_preview) or _("(no preview)"))
235
248
  return s
236
249
 
237
250
 
@@ -267,13 +280,16 @@ class PreviewableChecker(Checker):
267
280
  pf = obj.previewable_field
268
281
  # src = getattr(obj, pf+suffix)
269
282
  expected_mentions = set()
270
- short, full = obj.parse_previews(src, None, expected_mentions)
283
+ short, full = obj.parse_previews(src, None, expected_mentions, False)
271
284
  is_broken = False
272
- if (
273
- getattr(obj, pf + "_short_preview" + lng.suffix) != short
274
- or getattr(obj, pf + "_full_preview" + lng.suffix) != full
275
- ):
285
+ stored_short = getattr(obj, pf + "_short_preview" + lng.suffix)
286
+ stored_full = getattr(obj, pf + "_full_preview" + lng.suffix)
287
+ if stored_short != short or stored_full != full:
276
288
  yield (True, _("Preview differs from source."))
289
+ # print(f"20250908 found_full is {stored_full}, to expected: {full}")
290
+ # # print("20250908 found_full to expected_full:")
291
+ # sys.stdout.writelines(difflib.unified_diff(
292
+ # stored_full.splitlines(), full.splitlines()))
277
293
  is_broken = True
278
294
  found_mentions = set([obj.target for obj in obj.get_saved_mentions()])
279
295
  if expected_mentions != found_mentions:
@@ -284,6 +300,7 @@ class PreviewableChecker(Checker):
284
300
  # setattr(obj, pf+'_full_preview'+suffix, full)
285
301
  obj.full_clean()
286
302
  obj.save()
303
+ # obj.after_ui_save(ar, None)
287
304
  # self.synchronize_mentions(mentions)
288
305
 
289
306
  def get_checkdata_problems(self, ar, obj, fix=False):
@@ -23,7 +23,8 @@ target_label = _("Target")
23
23
 
24
24
 
25
25
  class Mention(Controllable):
26
- class Meta(object):
26
+
27
+ class Meta:
27
28
  app_label = "memo"
28
29
  abstract = dd.is_abstract_model(__name__, "Mention")
29
30
  verbose_name = _("Mention")
@@ -85,12 +86,14 @@ class Mentions(dd.Table):
85
86
  # id comment owner created
86
87
  # """
87
88
 
88
- # Not used because when you are on the owner, you can see the mentions in the memo text
89
- # class MentionsByOwner(Mentions):
90
- # label = _("Mentions")
91
- # master_key = "owner"
92
- # column_names = "target *"
93
- # default_display_modes = {None: constants.DISPLAY_MODE_SUMMARY}
89
+
90
+ class MentionsByOwner(Mentions):
91
+ # Not much used because when you are on the owner, you can see the mentions
92
+ # in the memo text
93
+ label = _("Mentions")
94
+ master_key = "owner"
95
+ column_names = "target *"
96
+ default_display_modes = {None: constants.DISPLAY_MODE_SUMMARY}
94
97
 
95
98
 
96
99
  class MentionsByTarget(Mentions):
@@ -19,7 +19,7 @@ from django.conf import settings
19
19
  from lino import logger
20
20
 
21
21
  import re
22
- import inspect
22
+ import traceback
23
23
  from typing import Callable, Any
24
24
 
25
25
  from bs4 import BeautifulSoup, MarkupResemblesLocatorWarning
@@ -313,6 +313,8 @@ All remaining arguments are used as the text of the link.
313
313
  mo.end(),
314
314
  )
315
315
  # logger.debug(msg)
316
+ # print(msg, ":")
317
+ # traceback.print_exc()
316
318
  return msg
317
319
 
318
320
  def parse_suggestions(self, src):