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
@@ -519,13 +519,10 @@ if remove_after:
519
519
  # "You do not have a working installation of the service_identity module: .* Many valid certificate/hostname mappings may be rejected.",
520
520
  # UserWarning)
521
521
 
522
+ if dd.get_plugin_setting("linod", "use_channels", False):
522
523
 
523
- @dd.receiver(dd.post_ui_save)
524
- def notify_panels(sender, instance, ar=None, **kwargs):
525
- if (
526
- not dd.get_plugin_setting("linod", "use_channels", False)
527
- or not instance.updatable_panels
528
- ):
529
- return
530
- data = get_updatables(instance, ar)
531
- send_panel_update(data)
524
+ @dd.receiver(dd.post_ui_save)
525
+ def notify_panels(sender, instance, ar=None, **kwargs):
526
+ if instance.updatable_panels:
527
+ data = get_updatables(instance, ar)
528
+ send_panel_update(data)
@@ -1,7 +1,7 @@
1
1
  # -*- coding: UTF-8 -*-
2
2
  # Copyright 2009-2017 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
7
  from __future__ import division
@@ -26,14 +26,14 @@ from lino.core.requests import BaseRequest
26
26
  from lino.core.tables import AbstractTable
27
27
  from lino.core.views import action_request
28
28
  from lino.core.utils import navinfo
29
- from lino.modlib.bootstrap3.views import http_response
29
+ from lino.modlib.bootstrap5.views import http_response
30
30
  from lino.utils.html import E
31
31
 
32
32
 
33
33
  class List(View):
34
34
  def get(self, request, app_label=None, actor=None):
35
35
  ar = action_request(app_label, actor, request, request.GET, True)
36
- ar.renderer = settings.SITE.plugins.bootstrap3.renderer
36
+ ar.renderer = settings.SITE.plugins.bootstrap5.renderer
37
37
 
38
38
  context = dict(
39
39
  title=ar.get_title(),
@@ -53,7 +53,7 @@ class Element(View):
53
53
  def get(self, request, app_label=None, actor=None, pk=None):
54
54
  # print(request, app_label, actor, pk)
55
55
  ar = action_request(app_label, actor, request, request.GET, False)
56
- ar.renderer = settings.SITE.plugins.bootstrap3.renderer
56
+ ar.renderer = settings.SITE.plugins.bootstrap5.renderer
57
57
 
58
58
  navigator = None
59
59
  if pk and pk != "-99999" and pk != "-99998":
@@ -160,7 +160,7 @@ class Index(View):
160
160
  def get(self, request, *args, **kw):
161
161
  # raise Exception("20171122 {} {}".format(
162
162
  # get_language(), settings.MIDDLEWARE_CLASSES))
163
- ui = settings.SITE.plugins.bootstrap3
163
+ ui = settings.SITE.plugins.bootstrap5
164
164
  # print("20170607", request.user)
165
165
  # assert ui.renderer is not None
166
166
  ar = BaseRequest(
@@ -172,7 +172,7 @@ class Index(View):
172
172
 
173
173
 
174
174
  def index_response(ar):
175
- ui = settings.SITE.plugins.bootstrap3
175
+ ui = settings.SITE.plugins.bootstrap5
176
176
 
177
177
  main = settings.SITE.get_main_html(ar, front_end=ui)
178
178
  main = ui.renderer.html_text(main)
@@ -185,7 +185,7 @@ def index_response(ar):
185
185
  # else:
186
186
  # user = request.subst_user or request.user
187
187
  # context.update(ar=ar)
188
- return http_response(ar, "bootstrap3/index.html", context)
188
+ return http_response(ar, "bootstrap5/index.html", context)
189
189
 
190
190
 
191
191
  class Metadata(View):
@@ -10,7 +10,7 @@ class Plugin(Plugin):
10
10
  "lino.modlib.system", # 'lino.modlib.memo',
11
11
  "lino.modlib.linod",
12
12
  "lino.modlib.jinja",
13
- "lino.modlib.bootstrap3",
13
+ "lino.modlib.bootstrap5"
14
14
  ]
15
15
  locations: list[tuple[str, str]] = []
16
16
 
@@ -47,12 +47,23 @@ class Plugin(Plugin):
47
47
  for location, table_class in self.locations:
48
48
  yield url(
49
49
  f"^{location}/(?P<pk>.+)$",
50
+ # f"^{location}/<int:pk>$",
50
51
  views.Element.as_view(table_class=table_class))
51
52
 
52
53
  # Only if this is the primary front end:
53
- if self.site.kernel.web_front_ends[0] is self:
54
+ if self.site.kernel.primary_front_end is self:
55
+ # yield url("^(?P<ref>.*)$", views.Index.as_view())
56
+ Tree = self.site.models.publisher.Tree
57
+ from django.db.utils import OperationalError, ProgrammingError
58
+ # language=self.site.DEFAULT_LANGUAGE.django_code
59
+ try:
60
+ for t in Tree.objects.filter(ref__isnull=False):
61
+ yield url(f"^{t.ref}$", views.Index.as_view(ref=t.ref))
62
+ except (OperationalError, ProgrammingError):
63
+ pass
54
64
  yield url("^$", views.Index.as_view())
55
- # yield url('^login$',views.Login.as_view())
65
+ yield url('^login$',views.Login.as_view())
66
+ yield url('^logout$',views.Logout.as_view())
56
67
 
57
68
  def setup_main_menu(self, site, user_type, m, ar=None):
58
69
  mg = self.get_menu_group()
@@ -63,3 +74,4 @@ class Plugin(Plugin):
63
74
  mg = self.get_menu_group()
64
75
  m = m.add_menu(mg.app_label, mg.verbose_name)
65
76
  m.add_action("publisher.SpecialPages")
77
+ m.add_action("publisher.Trees")
@@ -57,42 +57,6 @@ class PublisherBuildMethod(JinjaBuildMethod):
57
57
 
58
58
  BuildMethods.add_item_instance(PublisherBuildMethod())
59
59
 
60
- # class PublisherView(dd.Choice):
61
- # table_class = None # TODO: rename this to data_view
62
- #
63
- # def __init__(self, location, table_class, text=None):
64
- # self.table_class = table_class
65
- # self.publisher_location = location
66
- # # model = dd.resolve_model(table_class.model)
67
- # # self.model = model
68
- # # value = dd.full_model_name(model)
69
- # value = str(table_class)
70
- # if text is None:
71
- # text = format_lazy("{} ({})", location, table_class)
72
- # # text = model._meta.verbose_name + ' (%s)' % dd.full_model_name(model)
73
- # # text = model._meta.verbose_name + ' (%s.%s)' % (
74
- # # text = format_lazy("{} ({})", model._meta.verbose_name, value)
75
- # # model.__module__, model.__name__)
76
- # name = None
77
- # super().__init__(value, text, name)
78
- #
79
- # def get_publisher_pages(self):
80
- # return self.table_class.model.objects.all()
81
-
82
- # class PublisherViews(dd.ChoiceList):
83
- # item_class = PublisherView
84
- # verbose_name = _("Publisher view")
85
- # verbose_name_plural = _("Publisher views")
86
- # column_names = "location value name text data_view *"
87
- #
88
- # @dd.virtualfield(models.CharField(_("Location")))
89
- # def location(cls, choice, ar):
90
- # return choice.publisher_location
91
- #
92
- # @dd.virtualfield(models.CharField(_("Data table")))
93
- # def data_view(cls, choice, ar):
94
- # return choice.table_class
95
-
96
60
 
97
61
  class PublishingState(RegistrableState):
98
62
  # is_published = False
@@ -123,60 +87,7 @@ add("20", _("Ready"), "ready", is_public=False)
123
87
  add("30", _("Public"), "published", is_public=True)
124
88
  add("40", _("Removed"), "removed", is_public=False)
125
89
 
126
- # class EntryStates(dd.Workflow):
127
- # # verbose_name_plural = _("Enrolment states")
128
- # required_roles = dd.login_required(dd.SiteAdmin)
129
- # is_public = models.BooleanField(_("Public"), default=False)
130
- #
131
- # @classmethod
132
- # def get_column_names(self, ar):
133
- # return "value name text button_text is_public"
134
- #
135
- # add = EntryStates.add_item
136
- # add('10', _("Draft"), 'draft', is_public=False)
137
- # add('20', _("Ready"), 'ready', is_public=False)
138
- # add('30', _("Public"), 'published', is_public=True)
139
- # add('40', _("Cancelled"), 'cancelled', is_public=False)
140
-
141
-
142
- class PageFiller(Choice):
143
- data_view = None
144
-
145
- def __init__(self, data_view, *args, **kwargs):
146
- self.data_view = data_view
147
- super().__init__(str(data_view), *args, **kwargs)
148
-
149
- def get_dynamic_story(self, ar, obj, **kwargs):
150
- txt = ""
151
- dv = self.data_view
152
- sar = dv.create_request(parent=ar, limit=dv.preview_limit)
153
- # print("20231028", dv, list(sar))
154
- # print("20230409", ar.renderer)
155
- # rv += "20230325 [show {}]".format(dv)
156
- for e in sar.renderer.table2story(sar, **kwargs):
157
- txt += tostring(e)
158
- return txt
159
-
160
- def get_dynamic_paragraph(self, ar, obj, **kwargs):
161
- dv = self.data_view
162
- # sar = dv.create_request(parent=ar, limit=dv.preview_limit)
163
- sar = dv.create_request(parent=ar)
164
- return " / ".join([sar.obj2htmls(row) for row in sar])
165
-
166
-
167
- class PageFillers(ChoiceList):
168
- verbose_name = _("Page filler")
169
- verbose_name_plural = _("Page fillers")
170
- item_class = PageFiller
171
- max_length = 50
172
- column_names = "value name text data_view *"
173
-
174
- @dd.virtualfield(models.CharField(_("Data table")))
175
- def data_view(cls, choice, ar):
176
- return choice.data_view
177
-
178
90
 
179
- # class SpecialPage(PointingChoice):
180
91
  class SpecialPage(dd.Choice):
181
92
  # pointing_field_name = 'publisher.Page.special_page'
182
93
  # show_values = True
@@ -184,7 +95,8 @@ class SpecialPage(dd.Choice):
184
95
  def __init__(self, name, text=None, parent=None, **kwargs):
185
96
  self.parent_value = parent
186
97
  self._default_values = dict()
187
- for k in ("ref", "title", "filler", "body"):
98
+ # for k in ("ref", "title", "filler", "body"):
99
+ for k in ("ref", "title", "body"):
188
100
  if k in kwargs:
189
101
  self._default_values[k] = kwargs.pop(k)
190
102
  super().__init__(name, text, name, **kwargs)
@@ -198,8 +110,8 @@ class SpecialPage(dd.Choice):
198
110
  def on_page_created(self, obj):
199
111
  for k, v in self._default_values.items():
200
112
  setattr(obj, k, v)
201
- if obj.filler and not obj.title:
202
- obj.title = obj.filler.data_view.get_actor_label()
113
+ # if obj.filler and not obj.title:
114
+ # obj.title = obj.filler.data_view.get_actor_label()
203
115
  if not obj.title:
204
116
  obj.title = self.text or "20250422"
205
117
  if self.parent_value:
@@ -214,7 +126,8 @@ class SpecialPage(dd.Choice):
214
126
  # else:
215
127
  # language = ar.request.LANGUAGE_CODE
216
128
  # 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)
129
+ return rt.models.publisher.Page.objects.get(
130
+ special_page=self, language=language)
218
131
 
219
132
 
220
133
  class SpecialPages(dd.ChoiceList):
@@ -237,7 +150,8 @@ class SpecialPages(dd.ChoiceList):
237
150
  Page = rt.models.publisher.Page
238
151
  for lng in settings.SITE.languages:
239
152
  try:
240
- page = Page.objects.get(special_page=choice, language=lng.django_code)
153
+ page = Page.objects.get(
154
+ special_page=choice, language=lng.django_code)
241
155
  lst.append(ar.obj2html(page, lng.name))
242
156
  except Page.DoesNotExist:
243
157
  page = _("(create)")
@@ -1,10 +1,10 @@
1
- {% extends "bootstrap3/base.html" %}
2
-
1
+ {% extends "bootstrap5/base.html" %}
2
+ {% block title %}{{site.title or site.verbose_name}}{% endblock %}
3
3
  {% block header %}
4
4
  <div class="container-fluid lino-bs-header">
5
5
  {% if ar -%}
6
6
  {% if isinstance(obj, rt.models.publisher.Page) -%}
7
- {{obj.get_prev_link(ar)}} | {{obj.get_next_link(ar)}} |
7
+ {{obj.get_prev_link(ar)}} {{obj.get_next_link(ar)}} |
8
8
  {% endif -%}
9
9
  {% if dd.is_installed('react') %}
10
10
  {% set ar_react = obj.get_default_table().request(parent=ar, permalink_uris=True, renderer=dd.plugins.react.renderer) %}
@@ -18,9 +18,10 @@
18
18
  {{_("Home")}}
19
19
  {% endif -%}
20
20
  {% if site.kernel.editing_front_end.url_prefix -%}
21
- <a href="/{{site.kernel.editing_front_end.url_prefix}}/">{{_("Admin")}}</a>
21
+ | <a href="/{{site.kernel.editing_front_end.url_prefix}}/">{{_("Admin")}}</a>
22
22
  {% endif -%}
23
23
  {% if len(site.languages) > 1 -%}
24
+ |
24
25
  {% for lang in site.languages -%}
25
26
  {% if lang.django_code == requested_language -%}
26
27
  {{lang.django_code}}
@@ -29,32 +30,94 @@
29
30
  {% endif -%}
30
31
  {% endfor -%}
31
32
  {% endif -%}
33
+ |
34
+ {% set user = ar.get_user() %}
35
+ {% if user.is_anonymous %}
36
+ <a href="" data-bs-toggle="modal" data-bs-target="#signinModal">
37
+ {{str(user)}}
38
+ </a>
39
+ {% else %}
40
+ {{str(user)}} <a href="/logout">{{_("Sign out")}}</a>
41
+ {% endif %}
32
42
  {%- if site.kernel.admin_ui -%}
33
43
  &mdash;
34
44
  <a href="{{site.kernel.admin_ui.build_plain_url()}}/">{{site.kernel.admin_ui.ui_label}}</a>
35
45
  {%- endif -%}
36
46
  {% endif -%}
37
47
 
48
+ <div class="modal fade" id="signinModal" tabindex="-1" aria-labelledby="exampleModalLabel" aria-hidden="true">
49
+ <div class="modal-dialog modal-dialog-centered">
50
+ <div class="modal-content">
51
+ <div class="modal-header">
52
+ <h1 class="modal-title fs-5" id="exampleModalLabel">{{_("Sign in form")}}</h1>
53
+ <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
54
+ </div>
55
+ <div class="modal-body">
56
+ <form id="signinForm" action="/login" method="post">
57
+ <div id="signinError" class="alert alert-danger d-none" role="alert">
58
+ {{_("Incorrect username or password.")}}
59
+ </div>
60
+ <div class="mb-3">
61
+ <label for="signinInputUsername" class="form-label">{{_("Username")}}</label>
62
+ <input type="text" name="username" class="form-control" id="signinInputUsername" aria-describedby="usernameHelp" required="true">
63
+ <!-- <div id="usernameHelp" class="form-text">Enter your username.</div>-->
64
+ </div>
65
+ <div class="mb-3">
66
+ <label for="signinInputPassword" class="form-label">{{_("Password")}}</label>
67
+ <input type="password" name="password" class="form-control" id="signinInputPassword" required="true">
68
+ </div>
69
+ </form>
70
+ </div>
71
+ <div class="modal-footer">
72
+ <button form="signinForm" type="submit" class="btn btn-primary">{{_("Sign in")}}</button>
73
+ </div>
74
+ </div>
75
+ </div>
76
+ </div>
77
+
78
+ <script>
79
+ const form = document.getElementById("signinForm");
80
+ form.addEventListener("submit", (event) => {
81
+ event.preventDefault();
82
+ const formData = new FormData(form);
83
+ fetch(form.action, {
84
+ method: form.method,
85
+ body: formData
86
+ }).then((resp) => {
87
+ if (resp.status >= 400) {
88
+ const eel = document.getElementById("signinError");
89
+ eel.innerText = "Some unknown error occured, please contact site administrator";
90
+ eel.classList.remove("d-none")
91
+ return {success: false}
92
+ }
93
+ return resp.json()
94
+ }).then((resp) => {
95
+ if (resp.success) window.location.reload()
96
+ else {
97
+ const eel = document.getElementById("signinError");
98
+ eel.classList.remove("d-none")
99
+ }
100
+ })
101
+ });
102
+ </script>
103
+
38
104
  </div>
39
105
  {% endblock %}
40
106
 
41
107
  {% block navbar %}
42
- <nav class="navbar navbar-default" role="navigation">
108
+ <nav class="navbar navbar-expand-lg bg-body-tertiary">
43
109
  <div class="container-fluid">
44
- {% set index_node, home_children = obj.home_and_children(ar) %}
45
- <div class="navbar-header">
46
- <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#bs-navbar-collapsible-content" aria-expanded="false">
47
- <span class="sr-only">Toggle navigation</span>
48
- <span class="icon-bar"></span>
49
- <span class="icon-bar"></span>
50
- <span class="icon-bar"></span>
51
- </button>
52
- <a class="navbar-brand" href="{{ar.get_home_url()}}">{{ index_node.title }}</a>
53
- </div>
110
+ {% set homepage, children = obj.home_and_children(ar) %}
111
+ <a class="navbar-brand" href="{{ar.obj2url(homepage)}}"><span class="pi pi-home"/></a>
112
+ <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#bs-navbar-collapsible-content" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
113
+ <span class="navbar-toggler-icon"></span>
114
+ </button>
54
115
  <div class="collapse navbar-collapse" id="bs-navbar-collapsible-content">
55
- <ul class="nav navbar-nav">
56
- {% for child in home_children %}
57
- <li>{{tostring(ar.obj2html(child))}}</li>
116
+ <ul class="navbar-nav me-auto mb-2 mb-lg-0">
117
+ {% for child in children %}
118
+ <li class="nav-item">
119
+ <a class="nav-link" href="{{ar.obj2url(child)}}">{{str(child)}}</a>
120
+ </li>
58
121
  {% endfor %}
59
122
  </ul>
60
123
  </div>
@@ -63,7 +126,7 @@
63
126
  {% endblock %}
64
127
 
65
128
  {% block content %}
66
- <div class="row-fluid">
129
+ <div class="row-fluid ql-editor">
67
130
  {% for chunk in obj.as_page(ar) %}
68
131
  {{ chunk }}
69
132
  {% endfor %}
@@ -11,13 +11,26 @@ from lino.api import rt
11
11
 
12
12
 
13
13
  def objects():
14
+ Tree = rt.models.publisher.Tree
14
15
  Page = rt.models.publisher.Page
16
+ index = Tree(ref='index')
17
+ yield index
18
+ # lng2tree = dict()
19
+ # for lng in settings.SITE.languages:
20
+ # with translation.override(lng.django_code):
21
+ # kwargs = dict(language=lng.django_code, ref='index')
22
+ # obj = Tree(**kwargs)
23
+ # yield obj
24
+ # lng2tree[lng.django_code] = obj
25
+
15
26
  for sp in SpecialPages.get_list_items():
16
27
  translated_from = None
17
28
  for lng in settings.SITE.languages:
18
29
  with translation.override(lng.django_code):
19
- kwargs = dict(language=lng.django_code, special_page=sp)
30
+ # tree = lng2tree[lng.django_code]
31
+ kwargs = dict(publisher_tree=index, special_page=sp)
20
32
  kwargs.update(publishing_state="published")
33
+ kwargs.update(language=lng.django_code)
21
34
  # kwargs.update(sp.default_values)
22
35
  if lng.suffix:
23
36
  kwargs.update(translated_from=translated_from)
@@ -1,4 +1,6 @@
1
1
  from lino.api import rt, _
2
+ raise Exception("20250809 content moved to lino_book/projects/noi2/fixtures/demo.py")
3
+
2
4
 
3
5
  home_children = [
4
6
  (_("Mission"), None, []),
@@ -15,6 +17,7 @@ home_children = [
15
17
 
16
18
  def objects():
17
19
  image = rt.models.uploads.Upload.objects.first()
20
+
18
21
  def iterate(iterable):
19
22
  try:
20
23
  for obj in iterable:
@@ -25,4 +28,3 @@ def objects():
25
28
  yield obj
26
29
  for obj in rt.models.publisher.make_demo_pages(home_children):
27
30
  yield iterate(obj)
28
-
@@ -1,22 +1,19 @@
1
1
  # -*- coding: UTF-8 -*-
2
- # Copyright 2020-2023 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
  # from inspect import isclass
6
- from lino.utils.html import tostring
7
- from lino.api import dd, rt, _
8
-
9
6
  from django import http
10
- from django.db import models
11
- from django.conf import settings
12
- from django.utils.translation import get_language
13
- from lino.core.renderer import add_user_language
14
- from lino.core.utils import full_model_name
15
- from lino.utils import buildurl
7
+ # from django.db import models
8
+ # from django.conf import settings
9
+ # from django.utils.translation import get_language
10
+ # from lino.core.renderer import add_user_language
11
+ # from lino.core.utils import full_model_name
12
+ # from lino.utils import buildurl
16
13
  from lino.utils.html import mark_safe
17
14
  from lino.modlib.printing.mixins import Printable
18
- from lino.modlib.printing.choicelists import BuildMethods
19
- from .choicelists import PublishingStates, PageFillers
15
+ from lino.api import dd, rt, _
16
+ from .choicelists import PublishingStates, SpecialPages
20
17
  # from .choicelists import PublisherViews, PublishingStates
21
18
 
22
19
 
@@ -48,6 +45,8 @@ class Publishable(Printable):
48
45
  publisher_template = "publisher/page.pub.html"
49
46
  _lino_publisher_location = None
50
47
 
48
+ publisher_tree = dd.ForeignKey("publisher.Tree", null=True, blank=True)
49
+
51
50
  @dd.htmlbox()
52
51
  def full_page(self, ar):
53
52
  if ar is None:
@@ -65,95 +64,50 @@ class Publishable(Printable):
65
64
 
66
65
  preview_publication = PreviewPublication()
67
66
 
68
- # @classmethod
69
- # def on_analyze(cls, site):
70
- # # Inject the previous_page field to all models that have a publisher
71
- # # view.
72
- # # print("20231103", cls, [pv.table_class.model for pv in PublisherViews.get_list_items()])
73
- # # for pv in PublisherViews.get_list_items():
74
- # for loc, table_class in site.plugins.publisher.locations:
75
- # if full_model_name(cls) == table_class.model:
76
- # # print("20231103", cls)
77
- # dd.inject_field(
78
- # cls, 'previous_page',
79
- # dd.ForeignKey("self", null=True, blank=True,
80
- # verbose_name=_("Previous page")))
81
- # return
82
-
83
- # @dd.action(select_rows=False)
84
- # def preview_publication(self, ar):
85
- # sr_selected = not isclass(self)
86
- # if sr_selected:
87
- # ar.success(open_url=self.publisher_url())
88
- # else:
89
- # ar.success(open_url=self.publisher_url(self, not sr_selected))
90
-
91
- # def publisher_url(self, ar, **kw):
92
- # for i in PublisherViews.get_list_items():
93
- # if isinstance(self, i.table_class.model):
94
- # # print("20230409", self.__class__, i)
95
- # # return "/{}/{}".format(i.publisher_location, self.pk)
96
- # add_user_language(kw, ar)
97
- # # return buildurl("/" + i.publisher_location, str(self.pk), **dd.urlkwargs())
98
- # return ar.renderer.front_end.buildurl(i.publisher_location, str(self.pk), **kw)
99
- # # return dd.plugins.publisher.buildurl("/"+i.publisher_location, str(self.pk), **kw)
100
- # # return buildurl("/", i.publisher_location, str(self.pk), **kw)
101
- # available = [i.table_class.model for i in PublisherViews.get_list_items()]
102
- # return "No publisher view for {} in {}".format(self, available)
103
-
104
67
  def is_public(self):
105
68
  return True
106
69
 
107
70
  def get_preview_context(self, ar):
108
71
  return ar.get_printable_context(obj=self)
109
72
 
110
- # def set_previous_page(self, ppv, ppi):
111
- # if self.previous_page_id != ppi or self.previous_page_view != ppv:
112
- # self.previous_page_id = ppi
113
- # self.previous_page_view = ppv
114
- # self.save()
115
-
116
- # @classmethod
117
- # def update_publisher_pages(cls):
118
- # pass
119
-
120
- # def render_from(self, tplname, ar):
121
- # env = settings.SITE.plugins.jinja.renderer.jinja_env
122
- # context = self.get_preview_context(ar)
123
- # template = env.get_template(tplname)
124
- # # print("20210112 publish {} {} using {}".format(cls, obj, template))
125
- # # context = dict(obj=self, request=request, language=get_language())
126
- # return template.render(**context)
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
-
133
73
  def get_publisher_response(self, ar):
134
74
  if not self.is_public():
135
75
  return http.HttpResponseNotFound(
136
- "{} {} is not public".format(self.__class__, self.pk)
137
- )
76
+ f"{self.__class__} {self.pk} is not public")
138
77
  context = self.get_preview_context(ar)
139
78
  # html = ''.join(self.as_page(ar))
140
79
  # # context.update(content=html, admin_site_prefix=dd.plugins.publisher.admin_location)
141
80
  # context.update(content=html)
142
81
  tpl = dd.plugins.jinja.renderer.jinja_env.get_template(self.publisher_template)
143
82
  return http.HttpResponse(
144
- tpl.render(**context), content_type='text/html;charset="utf-8"'
145
- )
83
+ tpl.render(**context), content_type='text/html;charset="utf-8"')
84
+
85
+ def home_and_children(self, ar):
86
+ # home = self.publisher_tree.root_page
87
+ Page = rt.models.publisher.Page
88
+ qs = Page.objects.filter(
89
+ publisher_tree=self.publisher_tree, parent__isnull=True)
90
+ home = qs.first()
91
+ if home is None:
92
+ home = SpecialPages.home.get_object()
93
+ return home, Page.objects.filter(parent=home)
94
+ # return dv.model.objects.filter(models.Q(parent=index_node) | models.Q(ref='index'), language=language)
146
95
 
147
96
 
148
97
  class PublishableContent(Publishable):
98
+
149
99
  class Meta:
150
100
  abstract = True
151
101
  app_label = "publisher"
152
102
 
153
103
  language = dd.LanguageField()
154
104
  publishing_state = PublishingStates.field(default="draft")
155
- filler = PageFillers.field(blank=True, null=True)
156
- main_image = dd.ForeignKey('uploads.Upload', blank=True, null=True, verbose_name=_("Main image"))
105
+ main_image = dd.ForeignKey('uploads.Upload', blank=True,
106
+ null=True, verbose_name=_("Main image"))
107
+ translated_from = dd.ForeignKey(
108
+ "self", verbose_name=_("Translated from"),
109
+ null=True, blank=True,
110
+ related_name="translated_to")
157
111
 
158
112
  def get_print_language(self):
159
113
  return self.language
@@ -167,4 +121,32 @@ class PublishableContent(Publishable):
167
121
  super().on_duplicate(ar, master)
168
122
 
169
123
  def is_public(self):
124
+ if self.publisher_tree.private:
125
+ return False
170
126
  return self.publishing_state.is_public
127
+
128
+ def get_publisher_response(self, ar):
129
+ if ar and ar.request and self.language != ar.request.LANGUAGE_CODE:
130
+ rqlang = ar.request.LANGUAGE_CODE
131
+ # tt = rt.models.pages.Translation.objects.filter(
132
+ # parent=self, language=ar.request.LANGUAGE_CODE).first()
133
+ obj = None
134
+ if self.translated_from_id and self.translated_from.language == rqlang:
135
+ obj = self.translated_from
136
+ else:
137
+ sources = set([self.id])
138
+ p = self.translated_from
139
+ while p is not None:
140
+ sources.add(p.id)
141
+ p = p.translated_from
142
+ qs = self.__class__.objects.filter(
143
+ language=rqlang, translated_from__in=sources)
144
+ obj = qs.first()
145
+ # obj = self.translated_to.filter(language=rqlang).first()
146
+ # print("20231027 redirect to translation", tt.language, ar.request.LANGUAGE_CODE)
147
+ if obj is not None:
148
+ # print("20231028", self.language, "!=", ar.request.LANGUAGE_CODE, tt)
149
+ ar.selected_rows = [obj]
150
+ url = ar.get_request_url()
151
+ return http.HttpResponseRedirect(url)
152
+ return super().get_publisher_response(ar)