lino 25.4.2__py3-none-any.whl → 25.4.4__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 (45) hide show
  1. lino/__init__.py +1 -1
  2. lino/core/kernel.py +33 -7
  3. lino/core/renderer.py +3 -3
  4. lino/core/site.py +10 -36
  5. lino/help_texts.py +3 -3
  6. lino/management/commands/demotest.py +16 -22
  7. lino/mixins/__init__.py +5 -5
  8. lino/mixins/dupable.py +2 -4
  9. lino/mixins/registrable.py +5 -2
  10. lino/modlib/about/models.py +2 -2
  11. lino/modlib/checkdata/choicelists.py +4 -4
  12. lino/modlib/checkdata/models.py +11 -3
  13. lino/modlib/comments/fixtures/demo2.py +4 -0
  14. lino/modlib/comments/models.py +1 -1
  15. lino/modlib/dupable/mixins.py +3 -5
  16. lino/modlib/extjs/ext_renderer.py +1 -1
  17. lino/modlib/extjs/views.py +1 -1
  18. lino/modlib/help/fixtures/demo2.py +3 -2
  19. lino/modlib/jinja/mixins.py +18 -5
  20. lino/modlib/linod/models.py +1 -1
  21. lino/modlib/linod/routing.py +49 -46
  22. lino/modlib/memo/mixins.py +3 -2
  23. lino/modlib/notify/api.py +33 -14
  24. lino/modlib/notify/mixins.py +4 -3
  25. lino/modlib/printing/mixins.py +1 -1
  26. lino/modlib/publisher/choicelists.py +41 -20
  27. lino/modlib/publisher/config/publisher/page.pub.html +24 -0
  28. lino/modlib/publisher/fixtures/demo2.py +12 -0
  29. lino/modlib/publisher/fixtures/std.py +2 -1
  30. lino/modlib/publisher/fixtures/synodalworld.py +17 -0
  31. lino/modlib/publisher/mixins.py +7 -0
  32. lino/modlib/publisher/models.py +77 -7
  33. lino/modlib/publisher/ui.py +5 -5
  34. lino/modlib/publisher/views.py +9 -2
  35. lino/modlib/system/models.py +1 -1
  36. lino/modlib/uploads/models.py +2 -2
  37. lino/static/bootstrap.css +1 -1
  38. lino/utils/dbfreader.py +64 -32
  39. lino/utils/dbhash.py +3 -2
  40. {lino-25.4.2.dist-info → lino-25.4.4.dist-info}/METADATA +1 -1
  41. {lino-25.4.2.dist-info → lino-25.4.4.dist-info}/RECORD +44 -43
  42. lino/management/commands/monitor.py +0 -160
  43. {lino-25.4.2.dist-info → lino-25.4.4.dist-info}/WHEEL +0 -0
  44. {lino-25.4.2.dist-info → lino-25.4.4.dist-info}/licenses/AUTHORS.rst +0 -0
  45. {lino-25.4.2.dist-info → lino-25.4.4.dist-info}/licenses/COPYING +0 -0
@@ -1,67 +1,70 @@
1
1
  # -*- coding: UTF-8 -*-
2
- # Copyright 2022 Rumma & Ko Ltd
2
+ # Copyright 2022-2025 Rumma & Ko Ltd
3
3
  # License: GNU Affero General Public License v3 (see file COPYING for details)
4
4
 
5
- from django.urls import re_path
5
+ from django.conf import settings
6
6
  from django.core.asgi import get_asgi_application
7
- from django.utils.functional import LazyObject
8
7
 
9
- from channels.middleware import BaseMiddleware
10
- from channels.db import database_sync_to_async
11
- from channels.sessions import SessionMiddlewareStack
12
- from channels.routing import ProtocolTypeRouter, URLRouter, ChannelNameRouter
8
+ if settings.SITE.plugins.linod.use_channels:
13
9
 
14
- from lino.core.auth import get_user
15
- from lino.modlib.notify.consumers import ClientConsumer
10
+ from django.urls import re_path
11
+ from django.utils.functional import LazyObject
12
+ from channels.middleware import BaseMiddleware
13
+ from channels.db import database_sync_to_async
14
+ from channels.sessions import SessionMiddlewareStack
15
+ from channels.routing import ProtocolTypeRouter, URLRouter, ChannelNameRouter
16
16
 
17
- from .utils import CHANNEL_NAME
18
- from .consumers import LinodConsumer
17
+ from lino.core.auth import get_user
18
+ from lino.modlib.notify.consumers import ClientConsumer
19
19
 
20
+ from .utils import CHANNEL_NAME
21
+ from .consumers import LinodConsumer
20
22
 
21
- class UserLazyObject(LazyObject):
22
- """
23
- Throw a more useful error message when scope['user'] is accessed before it's resolved
24
- """
23
+ class UserLazyObject(LazyObject):
24
+ """
25
+ Throw a more useful error message when scope['user'] is accessed before it's resolved
26
+ """
25
27
 
26
- def _setup(self):
27
- raise ValueError("Accessing scope user before it is ready.")
28
+ def _setup(self):
29
+ raise ValueError("Accessing scope user before it is ready.")
28
30
 
31
+ async def _get_user(scope):
32
+ class Wrapper:
33
+ def __init__(self, session):
34
+ self.session = session
29
35
 
30
- async def _get_user(scope):
31
- class Wrapper:
32
- def __init__(self, session):
33
- self.session = session
36
+ r = Wrapper(scope["session"])
37
+ return await database_sync_to_async(get_user)(r)
34
38
 
35
- r = Wrapper(scope["session"])
36
- return await database_sync_to_async(get_user)(r)
39
+ class AuthMiddleware(BaseMiddleware):
40
+ def populate_scope(self, scope):
41
+ # Make sure we have a session
42
+ if "session" not in scope:
43
+ raise ValueError("AuthMiddleware cannot find session in scope.")
44
+ # Add it to the scope if it's not there already
45
+ if "user" not in scope:
46
+ scope["user"] = UserLazyObject()
37
47
 
48
+ async def resolve_scope(self, scope):
49
+ scope["user"]._wrapped = await _get_user(scope)
38
50
 
39
- class AuthMiddleware(BaseMiddleware):
40
- def populate_scope(self, scope):
41
- # Make sure we have a session
42
- if "session" not in scope:
43
- raise ValueError("AuthMiddleware cannot find session in scope.")
44
- # Add it to the scope if it's not there already
45
- if "user" not in scope:
46
- scope["user"] = UserLazyObject()
51
+ async def __call__(self, scope, receive=None, send=None):
52
+ self.populate_scope(scope)
53
+ await self.resolve_scope(scope)
54
+ return await self.inner(scope, receive, send)
47
55
 
48
- async def resolve_scope(self, scope):
49
- scope["user"]._wrapped = await _get_user(scope)
56
+ routes = [re_path(r"^WS/$", ClientConsumer.as_asgi())]
50
57
 
51
- async def __call__(self, scope, receive=None, send=None):
52
- self.populate_scope(scope)
53
- await self.resolve_scope(scope)
54
- return await self.inner(scope, receive, send)
58
+ protocol_mapping = dict(
59
+ websocket=SessionMiddlewareStack(AuthMiddleware(URLRouter(routes))),
60
+ channel=ChannelNameRouter({CHANNEL_NAME: LinodConsumer.as_asgi()}),
61
+ http=get_asgi_application(),
62
+ )
55
63
 
64
+ application = ProtocolTypeRouter(protocol_mapping)
56
65
 
57
- routes = [re_path(r"^WS/$", ClientConsumer.as_asgi())]
66
+ # raise Exception("20240424")
58
67
 
59
- protocol_mapping = dict(
60
- websocket=SessionMiddlewareStack(AuthMiddleware(URLRouter(routes))),
61
- channel=ChannelNameRouter({CHANNEL_NAME: LinodConsumer.as_asgi()}),
62
- http=get_asgi_application(),
63
- )
68
+ else:
64
69
 
65
- application = ProtocolTypeRouter(protocol_mapping)
66
-
67
- # raise Exception("20240424")
70
+ application = get_asgi_application()
@@ -3,7 +3,6 @@
3
3
  # License: GNU Affero General Public License v3 (see file COPYING for details)
4
4
 
5
5
  from lxml.html import fragments_fromstring
6
- from lino.utils.html import E, tostring, mark_safe
7
6
  import lxml
8
7
 
9
8
  try:
@@ -19,6 +18,7 @@ from django.utils.html import format_html
19
18
  from lino.core.gfks import gfk2lookup
20
19
  from lino.core.model import Model
21
20
  from lino.core.fields import fields_list, RichTextField, PreviewTextField
21
+ from lino.utils.html import E, tostring, mark_safe
22
22
  from lino.utils.restify import restify
23
23
  from lino.utils.soup import truncate_comment
24
24
  from lino.utils.mldbc.fields import BabelTextField
@@ -33,6 +33,7 @@ def django_truncate_comment(html_str):
33
33
  settings.SITE.plugins.memo.short_preview_length, html=True
34
34
  )
35
35
 
36
+
36
37
  MARKDOWNCFG = dict(
37
38
  extensions=["toc"], extension_configs=dict(toc=dict(toc_depth=3, permalink=True))
38
39
  )
@@ -293,7 +294,7 @@ class PreviewableChecker(Checker):
293
294
  obj.save()
294
295
  # self.synchronize_mentions(mentions)
295
296
 
296
- def get_checkdata_problems(self, obj, fix=False):
297
+ def get_checkdata_problems(self, ar, obj, fix=False):
297
298
  for x in self._get_checkdata_problems(settings.SITE.DEFAULT_LANGUAGE, obj, fix):
298
299
  yield x
299
300
  if isinstance(obj, BabelPreviewable):
lino/modlib/notify/api.py CHANGED
@@ -15,6 +15,8 @@ LIVE_PANEL_UPDATE = "PANEL_UPDATE"
15
15
 
16
16
  NOTIFICATION_TYPES = [NOTIFICATION, CHAT, LIVE_PANEL_UPDATE]
17
17
 
18
+ DONT_CATCH = True
19
+
18
20
 
19
21
  # def send_panel_update(actorIDs: list[str], pk: int):
20
22
  def send_panel_update(data):
@@ -27,16 +29,24 @@ def send_panel_update(data):
27
29
  from channels.layers import get_channel_layer
28
30
  from asgiref.sync import async_to_sync
29
31
 
32
+ if settings.SITE.loading_from_dump:
33
+ return
34
+
30
35
  channel_layer = get_channel_layer()
31
36
 
32
37
  data.update(type=LIVE_PANEL_UPDATE)
33
38
 
34
- try:
39
+ if DONT_CATCH:
35
40
  async_to_sync(channel_layer.send)(
36
41
  CHANNEL_NAME, {"type": "send.panel.update", "text": json.dumps(data)}
37
42
  )
38
- except Exception as e:
39
- logger.exception(e)
43
+ else:
44
+ try:
45
+ async_to_sync(channel_layer.send)(
46
+ CHANNEL_NAME, {"type": "send.panel.update", "text": json.dumps(data)}
47
+ )
48
+ except Exception as e:
49
+ logger.exception(e)
40
50
 
41
51
 
42
52
  def send_notification(
@@ -54,11 +64,9 @@ def send_notification(
54
64
  OK button of their desktop notification.
55
65
 
56
66
  """
57
- if user is None:
67
+ if user is None or settings.SITE.loading_from_dump:
58
68
  return
59
69
 
60
- created = created.strftime("%a %d %b %Y %H:%M")
61
-
62
70
  if dd.get_plugin_setting("linod", "use_channels"):
63
71
  # importing channels at module level would cause certain things to fail
64
72
  # when channels isn't installed, e.g. `manage.py prep` in `lino_book.projects.workflows`.
@@ -73,7 +81,7 @@ def send_notification(
73
81
  subject=subject,
74
82
  id=primary_key,
75
83
  body=body,
76
- created=created,
84
+ created=created.strftime("%a %d %b %Y %H:%M"),
77
85
  action_url=action_url,
78
86
  )
79
87
 
@@ -86,6 +94,7 @@ def send_notification(
86
94
  },
87
95
  ) # data
88
96
  except Exception as e:
97
+ raise # 20250415
89
98
  logger.exception(e)
90
99
 
91
100
  if dd.plugins.notify.use_push_api:
@@ -95,7 +104,7 @@ def send_notification(
95
104
  body=body,
96
105
  action_title=action_title,
97
106
  )
98
- try:
107
+ if DONT_CATCH:
99
108
  async_to_sync(channel_layer.send)(
100
109
  CHANNEL_NAME,
101
110
  {
@@ -104,9 +113,19 @@ def send_notification(
104
113
  "user_id": user.id if user is not None else None,
105
114
  },
106
115
  )
107
- except (
108
- ChannelFull
109
- ) as e: # happens on older Pythons and can cause many tracebacks
110
- # logger.exception(e)
111
- # logger.warning(str(e))
112
- pass
116
+ else:
117
+ try:
118
+ async_to_sync(channel_layer.send)(
119
+ CHANNEL_NAME,
120
+ {
121
+ "type": "send.push",
122
+ "data": data,
123
+ "user_id": user.id if user is not None else None,
124
+ },
125
+ )
126
+ except (
127
+ ChannelFull
128
+ ) as e: # happens on older Pythons and can cause many tracebacks
129
+ # logger.exception(e)
130
+ # logger.warning(str(e))
131
+ pass
@@ -1,7 +1,8 @@
1
- # Copyright 2016-2024 Rumma & Ko Ltd
1
+ # Copyright 2016-2025 Rumma & Ko Ltd
2
2
  # License: GNU Affero General Public License v3 (see file COPYING for details)
3
3
 
4
4
  from lino.utils.html import E, tostring, format_html, mark_safe
5
+ from lino.core.utils import full_model_name as fmn
5
6
  from lino.api import dd, rt, _
6
7
 
7
8
  # PUBLIC_GROUP = "all_users_channel"
@@ -11,13 +12,13 @@ def get_updatables(instance, ar=None):
11
12
  data = {
12
13
  "actorIDs": instance.updatable_panels,
13
14
  "pk": instance.pk,
14
- "model": f"{instance._meta.app_label}.{instance.__class__.__name__}",
15
+ "model": f"{fmn(instance)}",
15
16
  "mk": None, "master_model": None
16
17
  }
17
18
  if ar is None:
18
19
  return data
19
20
  if mi := ar.master_instance:
20
- data.update(mk=mi.pk, master_model=f"{mi._meta.app_label}.{mi.__class__.__name__}")
21
+ data.update(mk=mi.pk, master_model=f"{fmn(mi)}")
21
22
  return data
22
23
 
23
24
 
@@ -260,7 +260,7 @@ class CachedPrintableChecker(Checker):
260
260
  model = CachedPrintable
261
261
  verbose_name = _("Check for missing target files")
262
262
 
263
- def get_checkdata_problems(self, obj, fix=False):
263
+ def get_checkdata_problems(self, ar, obj, fix=False):
264
264
  if obj.build_time is not None:
265
265
  t = obj.get_cache_mtime()
266
266
  if t is None:
@@ -1,5 +1,5 @@
1
1
  # -*- coding: UTF-8 -*-
2
- # Copyright 2020-2022 Rumma & Ko Ltd
2
+ # Copyright 2020-2025 Rumma & Ko Ltd
3
3
  # License: GNU Affero General Public License v3 (see file COPYING for details)
4
4
 
5
5
  import os
@@ -8,7 +8,7 @@ from copy import copy
8
8
  from django.db import models
9
9
  from django.conf import settings
10
10
  from django.utils import translation
11
- from django.utils.text import format_lazy
11
+ # from django.utils.text import format_lazy
12
12
 
13
13
  from lino.api import dd, rt, _
14
14
  from lino.utils.html import E, tostring, join_elems
@@ -181,19 +181,40 @@ class SpecialPage(dd.Choice):
181
181
  # pointing_field_name = 'publisher.Page.special_page'
182
182
  # show_values = True
183
183
 
184
- def __init__(self, *args, **kwargs):
185
- self.default_values = dict()
186
- for k in ("ref", "title"):
184
+ def __init__(self, name, text=None, parent=None, **kwargs):
185
+ self.parent_value = parent
186
+ self._default_values = dict()
187
+ for k in ("ref", "title", "filler", "body"):
187
188
  if k in kwargs:
188
- self.default_values[k] = kwargs.pop(k)
189
- super().__init__(*args, **kwargs)
190
- if not "title" in self.default_values:
191
- self.default_values["title"] = self.text
192
-
193
- def create_object(self, **kwargs):
194
- kwargs.update(self.default_values)
195
- kwargs.update(special_page=self)
196
- return self.pointing_field.model(**kwargs)
189
+ self._default_values[k] = kwargs.pop(k)
190
+ super().__init__(name, text, name, **kwargs)
191
+ # if (filler := self.default_values.get('filler', None)):
192
+ # if "title" not in self.default_values:
193
+ # self.default_values["title"] = filler.data_view.get_actor_label()
194
+ # else:
195
+ # if "title" not in self.default_values:
196
+ # self.default_values["title"] = self.text
197
+
198
+ def on_page_created(self, obj):
199
+ for k, v in self._default_values.items():
200
+ setattr(obj, k, v)
201
+ if obj.filler and not obj.title:
202
+ obj.title = obj.filler.data_view.get_actor_label()
203
+ if not obj.title:
204
+ obj.title = self.text or "20250422"
205
+ if self.parent_value:
206
+ psp = self.choicelist.get_by_value(self.parent_value)
207
+ obj.parent = psp.get_object()
208
+
209
+ # def get_object(self, ar):
210
+ def get_object(self):
211
+ language = translation.get_language()
212
+ # if len(settings.SITE.languages) == 1:
213
+ # language = translation.get_language()
214
+ # else:
215
+ # language = ar.request.LANGUAGE_CODE
216
+ # return rt.models.publisher.Page.objects.get(ref=self.defaul_values['ref'], language=language)
217
+ return rt.models.publisher.Page.objects.get(special_page=self, language=language)
197
218
 
198
219
 
199
220
  class SpecialPages(dd.ChoiceList):
@@ -227,9 +248,9 @@ class SpecialPages(dd.ChoiceList):
227
248
 
228
249
  add = SpecialPages.add_item
229
250
 
230
- add("100", _("Home"), "home", ref="index", body=_("Welcome to our great website."))
231
- add("200", _("Terms and conditions"), "terms")
232
- add("300", _("Privacy policy"), "privacy")
233
- add("400", _("Cookie settings"), "cookies")
234
- add("500", _("Copyright"), "copyright")
235
- add("600", _("About us"), "about")
251
+ add("home", _("Home"), ref="index", body=_("Welcome to our great website."))
252
+ add("terms", _("Terms and conditions"))
253
+ add("privacy", _("Privacy policy"))
254
+ add("cookies", _("Cookie settings"))
255
+ add("copyright", _("Copyright"))
256
+ add("about", _("About us"))
@@ -32,6 +32,30 @@
32
32
  </div>
33
33
  {% endblock %}
34
34
 
35
+ {% block navbar %}
36
+ <nav class="navbar navbar-default" role="navigation">
37
+ <div class="container-fluid">
38
+ {% set index_node, home_children = obj.home_and_children(ar) %}
39
+ <div class="navbar-header">
40
+ <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#bs-navbar-collapsible-content" aria-expanded="false">
41
+ <span class="sr-only">Toggle navigation</span>
42
+ <span class="icon-bar"></span>
43
+ <span class="icon-bar"></span>
44
+ <span class="icon-bar"></span>
45
+ </button>
46
+ <a class="navbar-brand" href="{{ar.get_home_url()}}">{{ index_node.title }}</a>
47
+ </div>
48
+ <div class="collapse navbar-collapse" id="bs-navbar-collapsible-content">
49
+ <ul class="nav navbar-nav">
50
+ {% for child in home_children %}
51
+ <li>{{tostring(ar.obj2html(child))}}</li>
52
+ {% endfor %}
53
+ </ul>
54
+ </div>
55
+ </div>
56
+ </nav>
57
+ {% endblock %}
58
+
35
59
  {% block content %}
36
60
  <div class="row-fluid">
37
61
  {% for chunk in obj.as_page(ar) %}
@@ -0,0 +1,12 @@
1
+ from asgiref.sync import async_to_sync
2
+ from django.conf import settings
3
+ from lino.modlib.linod.choicelists import Procedures
4
+ from lino.api import rt
5
+
6
+
7
+ def objects():
8
+ yield None
9
+ ar = rt.login("robin")
10
+ if True: # settings.SITE.use_linod:
11
+ Procedures.update_publisher_pages.run(ar)
12
+ # async_to_sync(Procedures.update_publisher_pages.run)(ar)
@@ -18,10 +18,11 @@ def objects():
18
18
  with translation.override(lng.django_code):
19
19
  kwargs = dict(language=lng.django_code, special_page=sp)
20
20
  kwargs.update(publishing_state="published")
21
- kwargs.update(sp.default_values)
21
+ # kwargs.update(sp.default_values)
22
22
  if lng.suffix:
23
23
  kwargs.update(translated_from=translated_from)
24
24
  obj = Page(**kwargs)
25
+ sp.on_page_created(obj)
25
26
  yield obj
26
27
  if not lng.suffix:
27
28
  translated_from = obj
@@ -0,0 +1,17 @@
1
+ from lino.api import rt, _
2
+
3
+ home_children = [
4
+ (_("Mission"), None, []),
5
+ (_("Maxim"), None, []),
6
+ (_("Propaganda"), None, []),
7
+ (_("About us"), None, [
8
+ (_("Team"), None, []),
9
+ (_("History"), None, []),
10
+ (_("Contact"), None, []),
11
+ (_("Terms & conditions"), None, []),
12
+ ]),
13
+ ]
14
+
15
+
16
+ def objects():
17
+ return rt.models.publisher.make_demo_pages(home_children)
@@ -7,6 +7,7 @@ from lino.utils.html import tostring
7
7
  from lino.api import dd, rt, _
8
8
 
9
9
  from django import http
10
+ from django.db import models
10
11
  from django.conf import settings
11
12
  from django.utils.translation import get_language
12
13
  from lino.core.renderer import add_user_language
@@ -124,6 +125,11 @@ class Publishable(Printable):
124
125
  # # context = dict(obj=self, request=request, language=get_language())
125
126
  # return template.render(**context)
126
127
 
128
+ def home_and_children(self, ar):
129
+ home = rt.models.publisher.SpecialPages.home.get_object()
130
+ return home, rt.models.publisher.Page.objects.filter(parent=home)
131
+ # return dv.model.objects.filter(models.Q(parent=index_node) | models.Q(ref='index'), language=language)
132
+
127
133
  def get_publisher_response(self, ar):
128
134
  if not self.is_public():
129
135
  return http.HttpResponseNotFound(
@@ -147,6 +153,7 @@ class PublishableContent(Publishable):
147
153
  language = dd.LanguageField()
148
154
  publishing_state = PublishingStates.field(default="draft")
149
155
  filler = PageFillers.field(blank=True, null=True)
156
+ main_image = dd.ForeignKey('uploads.Upload', blank=True, null=True, verbose_name=_("Main image"))
150
157
 
151
158
  def get_print_language(self):
152
159
  return self.language
@@ -2,12 +2,6 @@
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 *
6
- from lino.api import rt, dd
7
-
8
- from .choicelists import PublishingStates, PageFillers, SpecialPages
9
- from .mixins import Publishable
10
-
11
5
  from html import escape
12
6
  from django.db import models
13
7
  from django.http import HttpResponseRedirect
@@ -15,6 +9,11 @@ from django.conf import settings
15
9
  from django.utils import translation
16
10
  from django.utils.translation import pgettext_lazy
17
11
 
12
+ from lorem import get_paragraph
13
+ from django.utils import translation
14
+ from django.conf import settings
15
+
16
+
18
17
  # from django.utils.translation import get_language
19
18
  from django.utils.html import mark_safe
20
19
 
@@ -38,6 +37,10 @@ from lino.mixins.polymorphic import Polymorphic
38
37
  from lino_xl.lib.topics.mixins import Taggable
39
38
  # from .utils import render_node
40
39
 
40
+ from lino.api import rt, dd
41
+ from .choicelists import PublishingStates, PageFillers, SpecialPages
42
+ from .mixins import Publishable
43
+ from .ui import *
41
44
 
42
45
  # class Node(Referrable, Hierarchical, Sequenced, Previewable, Publishable, Commentable):
43
46
  # Polymorphic,
@@ -105,6 +108,17 @@ class Page(
105
108
  # def as_story_item(self, ar, **kwargs):
106
109
  # return "".join(self.as_page(ar, **kwargs))
107
110
 
111
+ def as_paragraph(self, ar):
112
+ title = E.b(escape(self.title))
113
+ url = ar.obj2url(self)
114
+ if url is not None:
115
+ title = E.a(title, href=url, style="text-decoration: none; color: black;")
116
+ body = self.get_body_parsed(ar, short=True)
117
+ if body:
118
+ body = " - " + body
119
+ item = E.li(title, body)
120
+ return tostring(item)
121
+
108
122
  def toc_html(self, ar, max_depth=1):
109
123
  def li(obj):
110
124
  # return "<li>{}</li>".format(obj.memo2html(ar, str(obj)))
@@ -131,7 +145,7 @@ class Page(
131
145
  else:
132
146
  title = "<b>{}</b> — ".format(escape(self.title))
133
147
  title += self.get_body_parsed(ar, short=True)
134
- title = "<li>{}</i>".format(title)
148
+ title = "<li>{}</li>".format(title)
135
149
  # edit_url = ar.renderer.obj2url(ar, self)
136
150
  # url = self.publisher_url(ar)
137
151
  # print("20231029", ar.renderer)
@@ -146,6 +160,16 @@ class Page(
146
160
 
147
161
  # if not self.is_public():
148
162
  # return
163
+ if hlevel == 1 and self.main_image:
164
+ yield f"""
165
+ <div class="row">
166
+ <div class="center-block">
167
+ <a href="#" class="thumbnail">
168
+ <img src="{self.main_image.get_media_file().get_image_url()}">
169
+ </a>
170
+ </div>
171
+ </div>
172
+ """
149
173
 
150
174
  if display_mode in ("detail",):
151
175
  info = self.get_node_info(ar)
@@ -401,3 +425,49 @@ def update_publisher_pages(ar):
401
425
  prev = obj
402
426
  count += 1
403
427
  ar.logger.info("%d pages have been updated.", count)
428
+
429
+
430
+ def make_demo_pages(pages_desc):
431
+ # Translation = rt.models.pages.Translation
432
+ # for lc in settings.SITE.LANGUAGE_CHOICES:
433
+ # language = lc[0]
434
+ # kwargs = dict(language=language, ref='index')
435
+ # with translation.override(language):
436
+
437
+ parent_nodes = []
438
+ for lng in settings.SITE.languages:
439
+ counter = {None: 0}
440
+ # count = 0
441
+ home_page = Page.objects.get(
442
+ special_page=SpecialPages.home, language=lng.django_code)
443
+
444
+ with translation.override(lng.django_code):
445
+
446
+ def make_pages(pages, parent=None):
447
+ for page in pages:
448
+ if len(page) != 3:
449
+ raise Exception(f"Oops {page}")
450
+ title, body, children = page
451
+ kwargs = dict(title=title)
452
+ if body is None:
453
+ kwargs.update(body=get_paragraph())
454
+ else:
455
+ kwargs.update(body=body)
456
+ if parent is None:
457
+ # kwargs.update(ref='index')
458
+ continue # home page is created by SpecialPages
459
+ if lng.suffix:
460
+ kwargs.update(
461
+ translated_from=parent_nodes[counter[None]])
462
+ kwargs.update(language=lng.django_code)
463
+ if dd.is_installed("publisher"):
464
+ kwargs.update(publishing_state='published')
465
+ obj = Page(parent=parent, **kwargs)
466
+ yield obj
467
+ if not lng.suffix:
468
+ parent_nodes.append(obj)
469
+ counter[None] += 1
470
+ # print("20230324", title, kwargs)
471
+ yield make_pages(children, obj)
472
+
473
+ yield make_pages(pages_desc, parent=home_page)
@@ -92,7 +92,7 @@ else:
92
92
 
93
93
 
94
94
  class PageDetail(dd.DetailLayout):
95
- main = "first_panel general more"
95
+ main = "general first_panel more"
96
96
 
97
97
  first_panel = dd.Panel(
98
98
  """
@@ -129,11 +129,11 @@ class PageDetail(dd.DetailLayout):
129
129
  # """
130
130
 
131
131
  right_panel = """
132
- ref
132
+ ref language
133
133
  parent seqno
134
- child_node_depth
134
+ child_node_depth main_image
135
135
  #page_type filler
136
- language
136
+ publishing_state special_page
137
137
  publisher.TranslationsByPage
138
138
  """
139
139
 
@@ -147,7 +147,7 @@ class Pages(dd.Table):
147
147
  ref
148
148
  #page_type filler
149
149
  """
150
- default_display_modes = {None: constants.DISPLAY_MODE_STORY}
150
+ default_display_modes = {None: constants.DISPLAY_MODE_LIST}
151
151
 
152
152
 
153
153
  class PagesByParent(Pages):
@@ -5,6 +5,7 @@
5
5
  from django.conf import settings
6
6
  from django import http
7
7
  from django.views.generic import View
8
+ from django.utils import translation
8
9
 
9
10
  from lino.api import dd
10
11
  from lino.core import auth
@@ -52,7 +53,13 @@ class Index(View):
52
53
  def get(self, request, pk=1):
53
54
  rnd = settings.SITE.plugins.publisher.renderer
54
55
  dv = settings.SITE.models.publisher.Pages
55
- index_node = dv.model.objects.get(ref="index", language=request.LANGUAGE_CODE)
56
+ if len(settings.SITE.languages) == 1:
57
+ # language = settings.SITE.languages[0].django_code
58
+ language = translation.get_language()
59
+ else:
60
+ language = request.LANGUAGE_CODE
61
+ index_node = dv.model.objects.get(ref="index", language=language)
56
62
  # print("20231025", index_node)
57
- ar = dv.create_request(request=request, renderer=rnd, selected_rows=[index_node])
63
+ ar = dv.create_request(request=request, renderer=rnd,
64
+ selected_rows=[index_node])
58
65
  return index_node.get_publisher_response(ar)
@@ -326,7 +326,7 @@ class BleachChecker(Checker):
326
326
  if len(m._bleached_fields):
327
327
  yield m
328
328
 
329
- def get_checkdata_problems(self, obj, fix=False):
329
+ def get_checkdata_problems(self, ar, obj, fix=False):
330
330
  t = tuple(obj.fields_to_bleach(save=False))
331
331
  if len(t):
332
332
  fldnames = ", ".join([f.name for f, old, new in t])