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
@@ -476,43 +476,44 @@ if dd.plugins.users.allow_online_registration:
476
476
  About.create_account = CreateAccount()
477
477
 
478
478
 
479
- @dd.receiver(dd.post_startup)
480
- def setup_memo_commands(sender=None, **kwargs):
481
- # See :doc:`/specs/memo`
482
-
483
- if not sender.is_installed("memo"):
484
- return
485
-
486
- mp = sender.plugins.memo.parser
487
- mp.add_suggester(
488
- "@",
489
- sender.models.users.User.objects.filter(username__isnull=False).order_by(
490
- "username"
491
- ),
492
- "username",
493
- )
479
+ if dd.is_installed("memo"):
494
480
 
481
+ @dd.receiver(dd.post_startup)
482
+ def setup_memo_commands(sender=None, **kwargs):
483
+ # See :doc:`/specs/memo`
495
484
 
496
- def welcome_messages(ar):
497
- me = ar.get_user()
498
- if not me.is_verified():
499
- # sar = rt.models.users.Me.create_request(parent=ar)
500
- sar = ar
501
- if me.email:
502
- # verify_me =
503
- msg = format_html(
504
- _("Your email address ({email}) is not verified, "
505
- "please check your mailbox and {verify} or {resend}."),
506
- email=me.email,
507
- verify=tostring(sar.instance_action_button(
508
- me.verify_me, _("verify now"))),
509
- resend=tostring(sar.instance_action_button(
510
- me.send_welcome_email, _("re-send our welcome email"))))
511
- else:
512
- msg = format_html(
513
- _("You have no email address, please {edit}."),
514
- edit=tostring(sar.obj2html(me, _("edit your user settings"))))
515
- yield mark_safe(msg)
485
+ # if not sender.is_installed("memo"):
486
+ # return
516
487
 
488
+ mp = sender.plugins.memo.parser
489
+ mp.add_suggester(
490
+ "@",
491
+ sender.models.users.User.objects.filter(
492
+ username__isnull=False).order_by("username"),
493
+ "username")
494
+
495
+
496
+ if dd.plugins.users.allow_online_registration:
517
497
 
518
- dd.add_welcome_handler(welcome_messages)
498
+ def welcome_messages(ar):
499
+ me = ar.get_user()
500
+ if not me.is_verified():
501
+ # sar = rt.models.users.Me.create_request(parent=ar)
502
+ sar = ar
503
+ if me.email:
504
+ # verify_me =
505
+ msg = format_html(
506
+ _("Your email address ({email}) is not verified, "
507
+ "please check your mailbox and {verify} or {resend}."),
508
+ email=me.email,
509
+ verify=tostring(sar.instance_action_button(
510
+ me.verify_me, _("verify now"))),
511
+ resend=tostring(sar.instance_action_button(
512
+ me.send_welcome_email, _("re-send our welcome email"))))
513
+ else:
514
+ msg = format_html(
515
+ _("You have no email address, please {edit}."),
516
+ edit=tostring(sar.obj2html(me, _("edit your user settings"))))
517
+ yield mark_safe(msg)
518
+
519
+ dd.add_welcome_handler(welcome_messages)
@@ -34,6 +34,8 @@ class Plugin(ad.Plugin):
34
34
  top_right_width = None
35
35
  page_background_image = None
36
36
  top_right_image = None
37
+ bottom_left_image = None
38
+ bottom_left_width = None
37
39
  header_image = None
38
40
  margin = 10
39
41
  margin_left = 17
@@ -53,25 +55,34 @@ class Plugin(ad.Plugin):
53
55
  yield 'django-bulma'
54
56
 
55
57
  def pre_site_startup(self, site):
58
+ fcf = site.confdirs.find_config_file
56
59
  for ext in ("jpg", "png"):
57
- if self.header_height:
58
- fn = site.confdirs.find_config_file("top-right." + ext, "weasyprint")
59
- if fn:
60
+ if self.bottom_left_image is None:
61
+ if fn := fcf("bottom-left." + ext, "weasyprint"):
62
+ self.bottom_left_image = fn
63
+ if self.top_right_image is None:
64
+ if fn := fcf("top-right." + ext, "weasyprint"):
60
65
  self.top_right_image = fn
61
- if self.top_right_width is None:
62
- if imagesize is None:
63
- site.logger.warning("imagesize is not installed")
64
- continue
65
- w, h = imagesize.get(fn)
66
- self.top_right_width = self.header_height * w / h
67
- fn = site.confdirs.find_config_file("header." + ext, "weasyprint")
68
- if fn:
66
+ if self.header_image is None:
67
+ if fn := fcf("header." + ext, "weasyprint"):
69
68
  # site.logger.info("Found header_image %s", fn)
70
69
  self.header_image = fn
71
70
  if self.page_background_image is None:
72
- fn = site.confdirs.find_config_file(
73
- "page-background." + ext, "weasyprint")
74
- if fn:
71
+ if fn := fcf("page-background." + ext, "weasyprint"):
75
72
  # site.logger.info("Found page_background_image %s", fn)
76
73
  self.page_background_image = fn
74
+ if self.header_height:
75
+ if self.top_right_image and not self.top_right_width:
76
+ # if imagesize is None:
77
+ # site.logger.warning("imagesize is not installed")
78
+ # continue
79
+ w, h = imagesize.get(self.top_right_image)
80
+ self.top_right_width = self.header_height * w / h
81
+ if self.footer_height:
82
+ if self.bottom_left_image and not self.bottom_left_width:
83
+ # if imagesize is None:
84
+ # site.logger.warning("imagesize is not installed")
85
+ # continue
86
+ w, h = imagesize.get(self.bottom_left_image)
87
+ self.bottom_left_width = self.footer_height * w / h
77
88
  super().pre_site_startup(site)
@@ -14,6 +14,12 @@ except ImportError:
14
14
  BULMA_CSS = None
15
15
 
16
16
  if dd.plugins.weasyprint.with_bulma:
17
+
18
+ # Bulma causes weayprint to issue many warnings, more than 2000 during one
19
+ # tested doc. So we deactivate them:
20
+ from weasyprint.logger import LOGGER, logging
21
+ LOGGER.setLevel(logging.ERROR)
22
+
17
23
  try:
18
24
  from pathlib import Path
19
25
  import bulma
@@ -1,6 +1,7 @@
1
1
  <!DOCTYPE html>
2
2
  <html>
3
3
  <head>
4
+ {%- set WP = dd.plugins.weasyprint %}
4
5
  {%- block head %}
5
6
  <meta charset="UTF-8">
6
7
  <style type="text/css">
@@ -58,7 +59,7 @@ p {
58
59
  }
59
60
 
60
61
  div.recipient {
61
- position:relative; left:{{100-dd.plugins.weasyprint.margin_left}}mm;
62
+ position:relative; left:{{100-WP.margin_left}}mm;
62
63
  height:30mm;
63
64
  width:80mm;
64
65
  border: 1px solid lightgray;
@@ -80,25 +81,50 @@ div.recipient {
80
81
 
81
82
  @page {
82
83
  size: {%- block pagesize %}landscape{%- endblock %};
83
- margin: {{dd.plugins.weasyprint.margin}}mm;
84
- margin-bottom: {{dd.plugins.weasyprint.margin+dd.plugins.weasyprint.footer_height}}mm;
85
- {%- if True or dd.plugins.weasyprint.top_right_image -%}
86
- margin-top: {{dd.plugins.weasyprint.margin+dd.plugins.weasyprint.header_height}}mm;
84
+ margin: {{WP.margin}}mm;
85
+ margin-bottom: {{WP.margin+WP.footer_height}}mm;
86
+ {%- if True or WP.top_right_image -%}
87
+ margin-top: {{WP.margin+WP.header_height}}mm;
87
88
  {%- endif -%}
88
- margin-left: {{dd.plugins.weasyprint.margin_left}}mm;
89
- margin-right: {{dd.plugins.weasyprint.margin_right}}mm;
90
- {%- if dd.plugins.weasyprint.page_background_image -%}
91
- background-image: url(file://{{dd.plugins.weasyprint.page_background_image}});
89
+ margin-left: {{WP.margin_left}}mm;
90
+ margin-right: {{WP.margin_right}}mm;
91
+ {%- if WP.page_background_image -%}
92
+ background-image: url(file://{{WP.page_background_image}});
92
93
  background-repeat: no-repeat;
93
94
  background-attachment: fixed;
94
95
  background-size: contain;
95
96
  {%- endif -%}
96
97
  font-family: "Liberation sans", "arial";
97
98
  font-size: 10pt;
99
+
100
+ {%- block topright %}
101
+ {% if WP.top_right_image %}
102
+ @top-right {
103
+ height: {{WP.header_height}}mm;
104
+ width: {{WP.top_right_width}}mm;
105
+ padding: 0mm;
106
+ text-align: right;
107
+ background-image: url(file://{{WP.top_right_image}});
108
+ background-size: cover;
109
+ content: ""; // may be empty but must exist, otherwise bg is not rendered
110
+ // content: url(file://{{WP.top_right_image}});
111
+ }
112
+ {% endif %}
113
+ {%- endblock topright %}
98
114
  {%- block bottomleft %}
99
115
  @bottom-left {
116
+ {% if WP.bottom_left_image %}
117
+ height: {{WP.footer_height}}mm;
118
+ width: {{WP.bottom_left_width}}mm;
119
+ padding: 0mm;
120
+ text-align: right;
121
+ background-image: url(file://{{WP.bottom_left_image}});
122
+ background-size: cover;
123
+ content: ""; // may be empty but must exist, otherwise bg is not rendered
124
+ {% else %}
100
125
  vertical-align: top;
101
126
  content: '{{_("Printed")}} {{fdm(dd.today())}} {{_("at")}} {{now.time().strftime("%H:%M")}}';
127
+ {% endif %}
102
128
  }
103
129
  {%- endblock bottomleft %}
104
130
  {%- block bottomright %}
@@ -107,21 +133,6 @@ div.recipient {
107
133
  content: '{{_("Page")}} ' counter(page) ' {{_("of {0}").format("")}} ' counter(pages);
108
134
  }
109
135
  {%- endblock bottomright %}
110
-
111
- {%- block topright %}
112
- {% if dd.plugins.weasyprint.top_right_image %}
113
- @top-right {
114
- height: {{dd.plugins.weasyprint.header_height}}mm;
115
- width: {{dd.plugins.weasyprint.top_right_width}}mm;
116
- padding: 0mm;
117
- text-align: right;
118
- background-image: url(file://{{dd.plugins.weasyprint.top_right_image}});
119
- background-size: cover;
120
- content: ""; // may be empty but must exist, otherwise bg is not rendered
121
- // content: url(file://{{dd.plugins.weasyprint.top_right_image}});
122
- }
123
- {% endif %}
124
- {%- endblock topright %}
125
136
  }
126
137
 
127
138
  @media print {
@@ -142,7 +153,7 @@ div.recipient {
142
153
  div.footer_div {
143
154
  position: fixed;
144
155
  // bottom: -5mm;
145
- bottom: -{{dd.plugins.weasyprint.footer_height}}mm;
156
+ bottom: -{{WP.footer_height}}mm;
146
157
  width: 100%;
147
158
  // border-top: solid 1pt;
148
159
  padding: 0;
@@ -164,9 +175,14 @@ div.recipient {
164
175
  </head>
165
176
  <body>
166
177
  {%- block header %}
167
- {% if dd.plugins.weasyprint.header_image %}
178
+ {% if WP.header_image %}
168
179
  <div class="header_div">
169
- <img src="file://{{dd.plugins.weasyprint.header_image}}" style="height:{{dd.plugins.weasyprint.header_height}}mm">
180
+ <img src="file://{{WP.header_image}}" style="height:{{WP.header_height}}mm">
181
+ </div>
182
+ {% endif %}
183
+ {% if WP.footer_image %}
184
+ <div class="footer_div">
185
+ <img src="file://{{WP.footer_image}}" style="height:{{WP.footer_height}}mm">
170
186
  </div>
171
187
  {% endif %}
172
188
  {%- endblock header %}
lino/utils/diag.py CHANGED
@@ -144,14 +144,14 @@ class Analyzer(object):
144
144
 
145
145
  return rstgen.ul(items)
146
146
 
147
- def show_database_structure(self, sort_fields=False, sort_models=True, verbose_names=False):
147
+ def show_db_structure(self, sort_fields=False, sort_models=True, verbose_names=False):
148
148
  """Show a bullet list of all models and their fields.
149
149
 
150
150
  Both the list of models and the fields of each model can optionally be
151
151
  sorted alphabetically. The models are sorted by default, the fields not.
152
152
 
153
153
  """
154
- self.analyze()
154
+ # self.analyze()
155
155
  items = []
156
156
  for model in get_models():
157
157
  # names = []
@@ -170,7 +170,9 @@ class Analyzer(object):
170
170
 
171
171
  if sort_models:
172
172
  items = sorted(items)
173
- return rstgen.ul(items)
173
+ print(rstgen.ul(items))
174
+
175
+ show_database_structure = show_db_structure
174
176
 
175
177
  def show_fields(self, model, field_names=None, languages=None):
176
178
  model = dd.resolve_model(model)
lino/utils/html.py CHANGED
@@ -8,6 +8,7 @@ Some HTML utilities for Lino.
8
8
  import types
9
9
  from lxml import etree
10
10
  from etgen.html import E, to_rst, fromstring, iselement, join_elems, forcetext, lines2p
11
+ from etgen import html as xghtml
11
12
 
12
13
  # from etgen.html import tostring as et_tostring
13
14
  from html2text import HTML2Text
@@ -15,6 +16,7 @@ from django.utils.html import SafeString, mark_safe, escape, format_html
15
16
  # from lino.utils import tostring
16
17
 
17
18
  SAFE_EMPTY = mark_safe("")
19
+ PLAIN_PAGE_LENGTH = 15
18
20
 
19
21
 
20
22
  def html2text(html, **kwargs):
@@ -108,3 +110,104 @@ class Grouper:
108
110
  return SAFE_EMPTY
109
111
  self.last_values = self.current_values
110
112
  return self.ar.actor.after_group_change(self, obj)
113
+
114
+
115
+ def table2html(ar, as_main=True):
116
+ """Represent the given table request as an HTML table.
117
+
118
+ `ar` is the request to be rendered, an instance of
119
+ :class:`lino.core.requests.ActionRequest`.
120
+
121
+ The returned HTML enclosed in a ``<div>`` tag and generated using
122
+ :mod:`etgen.html`.
123
+
124
+ If `as_main` is True, include additional elements such as a paging
125
+ toolbar. (This argument is currently being ignored.)
126
+
127
+ """
128
+ # as_main = True
129
+ t = xghtml.Table()
130
+ t.attrib.update(**{"class": "table table-striped table-hover"})
131
+ if ar.limit is None:
132
+ ar.limit = PLAIN_PAGE_LENGTH
133
+ pglen = ar.limit
134
+ if ar.offset is None:
135
+ page = 1
136
+ else:
137
+ """
138
+ (assuming pglen is 5)
139
+ offset page
140
+ 0 1
141
+ 5 2
142
+ """
143
+ page = int(old_div(ar.offset, pglen)) + 1
144
+
145
+ ar.dump2html(t, ar.sliced_data_iterator, header_links=as_main)
146
+ if not as_main:
147
+ url = ar.get_request_url() or "#" # open in own window
148
+ return E.div(
149
+ E.div(
150
+ E.div(
151
+ E.a(
152
+ E.span(**{"class": "glyphicon glyphicon-folder-open"}),
153
+ href=url,
154
+ style="margin-left: 4px;",
155
+ **{"class": "btn btn-default pull-right"},
156
+ ),
157
+ E.h5(str(ar.get_title()), style="display: inline-block;"),
158
+ **{"class": "panel-title"},
159
+ ),
160
+ **{"class": "panel-heading"},
161
+ ),
162
+ t.as_element(),
163
+ style="display: inline-block;",
164
+ **{"class": "panel panel-default"},
165
+ )
166
+
167
+ buttons = []
168
+ kw = dict()
169
+ kw = {}
170
+ if pglen != PLAIN_PAGE_LENGTH:
171
+ kw[constants.URL_PARAM_LIMIT] = pglen
172
+
173
+ if page > 1:
174
+ kw[constants.URL_PARAM_START] = pglen * (page - 2)
175
+ prev_url = ar.get_request_url(**kw)
176
+ kw[constants.URL_PARAM_START] = 0
177
+ first_url = ar.get_request_url(**kw)
178
+ else:
179
+ prev_url = None
180
+ first_url = None
181
+ buttons.append(("<<", _("First page"), first_url))
182
+ buttons.append(("<", _("Previous page"), prev_url))
183
+
184
+ next_start = pglen * page
185
+ if next_start < ar.get_total_count():
186
+ kw[constants.URL_PARAM_START] = next_start
187
+ next_url = ar.get_request_url(**kw)
188
+ last_page = int(old_div((ar.get_total_count() - 1), pglen))
189
+ kw[constants.URL_PARAM_START] = pglen * last_page
190
+ last_url = ar.get_request_url(**kw)
191
+ else:
192
+ next_url = None
193
+ last_url = None
194
+ buttons.append((">", _("Next page"), next_url))
195
+ buttons.append((">>", _("Last page"), last_url))
196
+
197
+ return E.div(buttons2pager(buttons), t.as_element())
198
+
199
+
200
+ def layout2html(ar, elem):
201
+ wl = ar.bound_action.get_window_layout()
202
+ if wl is None:
203
+ raise Exception("{!r} has no window layout".format(ar.bound_action))
204
+ # ~ print 20120901, wl.main
205
+ lh = wl.get_layout_handle()
206
+
207
+ items = list(lh.main.as_plain_html(ar, elem))
208
+ # if navigator:
209
+ # items.insert(0, navigator)
210
+ # ~ print tostring(E.div())
211
+ # ~ if len(items) == 0: return ""
212
+ return E.form(*items)
213
+ # ~ print 20120901, lh.main.__html__(ar)
@@ -30,7 +30,7 @@ class BabelNamed(model.Model):
30
30
 
31
31
  """
32
32
 
33
- class Meta(object):
33
+ class Meta:
34
34
  abstract = True
35
35
 
36
36
  name = BabelCharField(max_length=200, verbose_name=_("Designation"))
@@ -58,7 +58,7 @@ class BabelDesignated(model.Model):
58
58
 
59
59
  """
60
60
 
61
- class Meta(object):
61
+ class Meta:
62
62
  abstract = True
63
63
 
64
64
  designation = BabelCharField(max_length=200, verbose_name=_("Designation"))
lino/utils/soup.py CHANGED
@@ -22,6 +22,8 @@ from bs4 import BeautifulSoup, NavigableString, Comment, Doctype
22
22
  from django.conf import settings
23
23
 
24
24
  MORE_INDICATOR = "..."
25
+ MORE_MARKER = "=MORE="
26
+ DATA_UPLOAD_ID = "data-upload_id"
25
27
 
26
28
  URL_REGEX = re.compile(
27
29
  r'([^"]|^)(https?:\/\/)((www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*))'
@@ -251,10 +253,11 @@ ALLOWED_TAGS = frozenset([
251
253
 
252
254
  GENERALLY_ALLOWED_ATTRS = {"title", "style", "class"}
253
255
 
256
+
254
257
  # Map of allowed attributes by tag. Originally copied from bleach.sanitizer.
255
258
  ALLOWED_ATTRIBUTES = {
256
259
  "a": {"href", "target"} | GENERALLY_ALLOWED_ATTRS,
257
- "img": {"src", "alt"} | GENERALLY_ALLOWED_ATTRS,
260
+ "img": {"src", "alt", "width", "height", DATA_UPLOAD_ID} | GENERALLY_ALLOWED_ATTRS,
258
261
  }
259
262
 
260
263
  ALLOWED_ATTRIBUTES["span"] = GENERALLY_ALLOWED_ATTRS | {
@@ -281,7 +284,7 @@ def register_sanitizer(func):
281
284
  SANITIZERS.append(func)
282
285
 
283
286
 
284
- def sanitized_soup(htmlstr):
287
+ def sanitized_soup(htmlstr, save=False, ar=None, mentions=None):
285
288
  if not htmlstr.startswith("<"):
286
289
  htmlstr = f"<p>{htmlstr}</p>"
287
290
  htmlstr = url2a(htmlstr)
@@ -313,6 +316,9 @@ def sanitized_soup(htmlstr):
313
316
  for comment in comments:
314
317
  comment.extract()
315
318
 
319
+ for func in SANITIZERS:
320
+ func(soup, save=save, ar=ar, mentions=mentions)
321
+
316
322
  # remove the wrapper tag if it is useless
317
323
  # if len(soup.contents) == 1:
318
324
  # main_tag = soup.contents[0]
@@ -322,22 +328,24 @@ def sanitized_soup(htmlstr):
322
328
  return soup
323
329
 
324
330
 
325
- def sanitize(htmlstr, save=False, ar=None):
331
+ def sanitize(htmlstr, **kwargs):
332
+ # if len(chunks := htmlstr.split(MORE_MARKER, 1)) == 2:
333
+ # htmlstr = " ".join(chunks)
326
334
  htmlstr = htmlstr.strip()
327
335
  if htmlstr == "":
328
336
  return htmlstr
329
- soup = sanitized_soup(htmlstr)
330
- for func in SANITIZERS:
331
- func(soup, save=save, ar=ar)
337
+ soup = sanitized_soup(htmlstr, **kwargs)
332
338
  return str(soup).strip()
333
339
 
334
340
 
335
- def truncate_comment(htmlstr, max_length=300):
341
+ def truncate_comment(htmlstr, max_length=300, **kwargs):
336
342
  # new implementation since 20230713
343
+ if len(chunks := htmlstr.split(MORE_MARKER, 1)) == 2:
344
+ htmlstr = chunks[0]
337
345
  htmlstr = htmlstr.strip() # remove leading or trailing newlines
338
346
  if htmlstr == '':
339
347
  return htmlstr
340
- soup = sanitized_soup(htmlstr)
348
+ soup = sanitized_soup(htmlstr, **kwargs)
341
349
  tc = TextCollector(max_length)
342
350
  tc.add_chunk(soup)
343
351
  return tc.text.strip()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: lino
3
- Version: 25.8.2
3
+ Version: 25.9.0
4
4
  Summary: A framework for writing desktop-like web applications using Django and ExtJS or React
5
5
  Project-URL: Homepage, https://www.lino-framework.org
6
6
  Project-URL: Repository, https://gitlab.com/lino-framework/lino