lino 25.7.1__py3-none-any.whl → 25.7.3__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 (55) hide show
  1. lino/__init__.py +1 -1
  2. lino/api/doctest.py +41 -12
  3. lino/core/__init__.py +0 -2
  4. lino/core/actions.py +15 -7
  5. lino/core/actors.py +2 -162
  6. lino/core/atomizer.py +9 -8
  7. lino/core/auth/utils.py +9 -1
  8. lino/core/callbacks.py +2 -2
  9. lino/core/elems.py +1 -1
  10. lino/core/fields.py +3 -1
  11. lino/core/kernel.py +14 -18
  12. lino/core/layouts.py +5 -7
  13. lino/core/model.py +12 -3
  14. lino/core/plugin.py +1 -1
  15. lino/core/renderer.py +1 -1
  16. lino/core/requests.py +3 -4
  17. lino/core/site.py +1 -1
  18. lino/core/store.py +3 -3
  19. lino/core/utils.py +20 -17
  20. lino/help_texts.py +10 -7
  21. lino/mixins/__init__.py +3 -2
  22. lino/mixins/{duplicable.py → clonable.py} +45 -50
  23. lino/mixins/dupable.py +2 -2
  24. lino/mixins/registrable.py +7 -5
  25. lino/mixins/sequenced.py +12 -14
  26. lino/modlib/dupable/models.py +2 -2
  27. lino/modlib/extjs/views.py +7 -0
  28. lino/modlib/linod/__init__.py +1 -1
  29. lino/modlib/memo/__init__.py +1 -2
  30. lino/modlib/notify/api.py +5 -0
  31. lino/modlib/office/roles.py +0 -1
  32. lino/modlib/printing/actions.py +2 -6
  33. lino/modlib/printing/choicelists.py +6 -6
  34. lino/modlib/printing/mixins.py +4 -4
  35. lino/modlib/publisher/__init__.py +21 -30
  36. lino/modlib/publisher/models.py +3 -1
  37. lino/modlib/publisher/views.py +4 -11
  38. lino/modlib/summaries/mixins.py +6 -4
  39. lino/modlib/users/actions.py +5 -0
  40. lino/modlib/weasyprint/__init__.py +9 -0
  41. lino/modlib/weasyprint/choicelists.py +14 -9
  42. lino/modlib/weasyprint/config/weasyprint/base.weasy.html +15 -13
  43. lino/sphinxcontrib/__init__.py +1 -1
  44. lino/sphinxcontrib/actordoc.py +1 -1
  45. lino/utils/diag.py +2 -2
  46. lino/utils/instantiator.py +21 -1
  47. {lino-25.7.1.dist-info → lino-25.7.3.dist-info}/METADATA +1 -1
  48. {lino-25.7.1.dist-info → lino-25.7.3.dist-info}/RECORD +51 -55
  49. lino/modlib/forms/__init__.py +0 -51
  50. lino/modlib/forms/models.py +0 -0
  51. lino/modlib/forms/renderer.py +0 -74
  52. lino/modlib/forms/views.py +0 -311
  53. {lino-25.7.1.dist-info → lino-25.7.3.dist-info}/WHEEL +0 -0
  54. {lino-25.7.1.dist-info → lino-25.7.3.dist-info}/licenses/AUTHORS.rst +0 -0
  55. {lino-25.7.1.dist-info → lino-25.7.3.dist-info}/licenses/COPYING +0 -0
@@ -294,19 +294,15 @@ class EditTemplate(BasePrintAction):
294
294
  ar.confirm(ok, msg, _("Are you sure?"))
295
295
 
296
296
 
297
- class ClearCacheAction(Action):
297
+ class ClearCache(Action):
298
298
  sort_index = 51
299
299
  url_action_name = "clear"
300
300
  label = _("Clear cache")
301
301
  icon_name = "printer_delete"
302
302
 
303
- # def disabled_for(self,obj,request):
304
- # if not obj.build_time:
305
- # return True
306
-
307
303
  def get_action_permission(self, ar, obj, state):
308
304
  # obj may be None when Lino asks whether this action
309
- # should be visible in the UI
305
+ # should be visible in the table toolbar
310
306
  if obj is not None and not obj.build_time:
311
307
  return False
312
308
  return super().get_action_permission(ar, obj, state)
@@ -33,9 +33,9 @@ except ImportError:
33
33
 
34
34
 
35
35
  class BuildMethod(Choice):
36
- target_ext = None
37
- cache_name = "cache"
38
- use_webdav = False
36
+ target_ext: str = None
37
+ cache_name: str = "cache"
38
+ use_webdav: bool = False
39
39
 
40
40
  def __init__(self, names=None, **kwargs):
41
41
  # For build methods, `Choice.names` and `Choice.value` are the
@@ -65,9 +65,9 @@ class BuildMethod(Choice):
65
65
 
66
66
 
67
67
  class TemplatedBuildMethod(BuildMethod):
68
- template_ext = None
69
- templates_name = None
70
- default_template = "" # overridden by lino_xl.lib.appypod
68
+ template_ext: str = None
69
+ templates_name: str = None
70
+ default_template: str = "" # overridden by lino_xl.lib.appypod
71
71
 
72
72
  def __init__(self, *args, **kwargs):
73
73
  super().__init__(*args, **kwargs)
@@ -16,13 +16,13 @@ from lino.modlib.checkdata.choicelists import Checker
16
16
 
17
17
  from lino.utils.choosers import chooser
18
18
  from lino.core.model import Model
19
- from lino.mixins.duplicable import Duplicable
19
+ from lino.mixins.clonable import Clonable
20
20
 
21
21
  from .choicelists import BuildMethods
22
22
  from .actions import (
23
23
  DirectPrintAction,
24
24
  CachedPrintAction,
25
- ClearCacheAction,
25
+ ClearCache,
26
26
  EditTemplate,
27
27
  )
28
28
 
@@ -148,12 +148,12 @@ class Printable(Model):
148
148
  return True
149
149
 
150
150
 
151
- class CachedPrintable(Duplicable, Printable):
151
+ class CachedPrintable(Clonable, Printable):
152
152
  class Meta(object):
153
153
  abstract = True
154
154
 
155
155
  do_print = CachedPrintAction()
156
- do_clear_cache = ClearCacheAction()
156
+ do_clear_cache = ClearCache()
157
157
  edit_template = EditTemplate()
158
158
 
159
159
  build_time = models.DateTimeField(
@@ -1,5 +1,5 @@
1
1
  # -*- coding: UTF-8 -*-
2
- # Copyright 2020-2024 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 lino.api.ad import Plugin
@@ -12,17 +12,7 @@ class Plugin(Plugin):
12
12
  "lino.modlib.jinja",
13
13
  "lino.modlib.bootstrap3",
14
14
  ]
15
- locations = []
16
-
17
- def setup_main_menu(self, site, user_type, m, ar=None):
18
- mg = self.get_menu_group()
19
- m = m.add_menu(mg.app_label, mg.verbose_name)
20
- m.add_action("publisher.Pages")
21
-
22
- def setup_config_menu(self, site, user_type, m, ar=None):
23
- mg = self.get_menu_group()
24
- m = m.add_menu(mg.app_label, mg.verbose_name)
25
- m.add_action("publisher.SpecialPages")
15
+ locations: list[tuple[str, str]] = []
26
16
 
27
17
  def get_requirements(self, site):
28
18
  yield "python-lorem"
@@ -41,34 +31,35 @@ class Plugin(Plugin):
41
31
  app = site.models.get(app_label)
42
32
  cls = getattr(app, model_name, None)
43
33
  if not isinstance(cls, type) or not issubclass(cls, Actor):
44
- raise Exception("location {}: {} is not an Actor".format(view, cls))
34
+ raise Exception(f"location {loc}: {cls} is not an Actor")
45
35
  if not issubclass(cls.model, Publishable):
46
36
  raise Exception(
47
- "location {}: model is a {}, which is not Publishable".format(
48
- view, type(cls.model)
49
- )
50
- )
51
-
37
+ f"location {loc},{view}: "
38
+ f"model {type(cls.model)} is not Publishable")
52
39
  cls.model._lino_publisher_location = loc
53
40
  locations.append((loc, cls))
54
41
  self.locations = tuple(locations)
55
42
 
56
43
  def get_patterns(self):
57
44
  from django.urls import re_path as url
58
- from lino.core.utils import models_by_base
59
45
  from . import views
60
- # from .choicelists import PublisherViews
61
- # raise Exception("20220927")
62
- # print("20220927", list(PublisherViews.get_list_items()))
63
46
 
64
- # for pv in PublisherViews.get_list_items():
65
- for publisher_location, table_class in self.locations:
66
- # print("20220927", pv.publisher_location)
67
- # if publisher_location is not None:
47
+ for location, table_class in self.locations:
68
48
  yield url(
69
- "^{}/(?P<pk>.+)$".format(publisher_location),
70
- views.Element.as_view(table_class=table_class),
71
- )
49
+ f"^{location}/(?P<pk>.+)$",
50
+ views.Element.as_view(table_class=table_class))
72
51
 
73
- yield url("^$", views.Index.as_view())
52
+ # Only if this is the primary front end:
53
+ if self.site.kernel.web_front_ends[0] is self:
54
+ yield url("^$", views.Index.as_view())
74
55
  # yield url('^login$',views.Login.as_view())
56
+
57
+ def setup_main_menu(self, site, user_type, m, ar=None):
58
+ mg = self.get_menu_group()
59
+ m = m.add_menu(mg.app_label, mg.verbose_name)
60
+ m.add_action("publisher.Pages")
61
+
62
+ def setup_config_menu(self, site, user_type, m, ar=None):
63
+ mg = self.get_menu_group()
64
+ m = m.add_menu(mg.app_label, mg.verbose_name)
65
+ m.add_action("publisher.SpecialPages")
@@ -47,6 +47,8 @@ from .ui import *
47
47
 
48
48
  # class Node(Referrable, Hierarchical, Sequenced, Previewable, Publishable, Commentable):
49
49
  # Polymorphic,
50
+
51
+
50
52
  class Page(
51
53
  Hierarchical, Sequenced, Previewable, Commentable, PublishableContent, Taggable
52
54
  ):
@@ -205,7 +207,7 @@ class Page(
205
207
  if not self.children.exists():
206
208
  return
207
209
 
208
- yield "<p><b>{}</b></p>".format(_("Children:"))
210
+ # yield "<p><b>{}</b></p>".format(_("Children:"))
209
211
 
210
212
  if hlevel > home.child_node_depth:
211
213
  yield " (...)"
@@ -1,19 +1,12 @@
1
1
  # -*- coding: UTF-8 -*-
2
- # Copyright 2020-2024 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
- from django.conf import settings
6
5
  from django import http
7
- from django.views.generic import View
8
- from django.utils import translation
9
-
10
- from lino.api import dd
11
- from lino.core import auth
12
- from lino.core.requests import BaseRequest, ActionRequest
6
+ from django.conf import settings
13
7
  from django.core.exceptions import ObjectDoesNotExist
14
-
15
- from django.shortcuts import redirect
16
- from django.views.decorators.csrf import ensure_csrf_cookie
8
+ from django.utils import translation
9
+ from django.views.generic import View
17
10
 
18
11
 
19
12
  class Element(View):
@@ -38,7 +38,8 @@ class UpdateSummariesByMaster(ComputeResults):
38
38
 
39
39
 
40
40
  class Summarized(dd.Model):
41
- class Meta(object):
41
+
42
+ class Meta:
42
43
  abstract = True
43
44
 
44
45
  compute_results = ComputeResults()
@@ -86,15 +87,17 @@ class Summarized(dd.Model):
86
87
 
87
88
  def get_summary_collectors(self):
88
89
  raise NotImplementedError(
89
- "{} must define get_summary_collectors()".format(self.__class__)
90
+ f"{self.__class__} must define get_summary_collectors()"
90
91
  )
91
92
 
92
93
 
93
94
  class SlaveSummarized(Summarized):
94
- class Meta(object):
95
+
96
+ class Meta:
95
97
  abstract = True
96
98
 
97
99
  allow_cascaded_delete = "master"
100
+ allow_cascaded_copy = set()
98
101
 
99
102
  @classmethod
100
103
  def check_all_summaries(cls):
@@ -181,7 +184,6 @@ class DateSummarized(Summarized):
181
184
  flt.update(month=period)
182
185
  if year is not None:
183
186
  flt.update(year=year)
184
- # obj = cls.get_for_period(**flt)
185
187
  for obj in cls.get_for_filter(**flt):
186
188
  obj.compute_summary_values()
187
189
 
@@ -495,6 +495,11 @@ def get_social_auth_links_func(content_header, flex_row, ar=None):
495
495
  else:
496
496
  el = E.span(anchor, style=style)
497
497
  links.append(el)
498
+ anchor = E.a(E.span(gettext("Smart ID")), href="/auth/smart_id")
499
+ if flex_row:
500
+ links.append(E.div(anchor, style=style))
501
+ else:
502
+ links.append(E.span(anchor, style=style))
498
503
  elems.append(E.div(*links, style="text-align: center;"))
499
504
  return tostring(E.div(*elems))
500
505
 
@@ -39,9 +39,18 @@ class Plugin(ad.Plugin):
39
39
  margin_left = 17
40
40
  margin_right = 10
41
41
  space_before_recipient = 15
42
+ with_bulma = False
43
+
44
+ def get_needed_plugins(self):
45
+ for p in super().get_needed_plugins():
46
+ yield p
47
+ if self.with_bulma:
48
+ yield 'bulma'
42
49
 
43
50
  def get_requirements(self, site):
44
51
  yield "imagesize"
52
+ if self.with_bulma:
53
+ yield 'django-bulma'
45
54
 
46
55
  def pre_site_startup(self, site):
47
56
  for ext in ("jpg", "png"):
@@ -2,22 +2,26 @@
2
2
  # Copyright 2016-2024 Rumma & Ko Ltd
3
3
  # License: GNU Affero General Public License v3 (see file COPYING for details)
4
4
 
5
- from pathlib import Path
6
5
  from lino.modlib.jinja.choicelists import JinjaBuildMethod
7
6
  from lino.modlib.printing.choicelists import BuildMethods
7
+ from lino.api import dd
8
8
 
9
9
  try:
10
10
  from weasyprint import HTML
11
11
  except ImportError:
12
12
  HTML = None
13
13
 
14
- try:
15
- import bulma
16
- from weasyprint import CSS
17
- BULMA_CSS = Path(bulma.__file__).parent / "static/bulma/css/style.min.css"
18
- assert BULMA_CSS.exists()
19
- except ImportError:
20
- BULMA_CSS = None
14
+ BULMA_CSS = None
15
+
16
+ if dd.plugins.weasyprint.with_bulma:
17
+ try:
18
+ from pathlib import Path
19
+ import bulma
20
+ from weasyprint import CSS
21
+ BULMA_CSS = Path(bulma.__file__).parent / "static/bulma/css/style.min.css"
22
+ assert BULMA_CSS.exists()
23
+ except ImportError:
24
+ pass
21
25
 
22
26
 
23
27
  class WeasyBuildMethod(JinjaBuildMethod):
@@ -38,7 +42,8 @@ class WeasyPdfBuildMethod(WeasyBuildMethod):
38
42
  def html2file(self, html, filename, context):
39
43
  pdf = HTML(string=html)
40
44
  if BULMA_CSS and context.get('use_bulma_css', False):
41
- pdf.write_pdf(filename, stylesheets=[CSS(filename=BULMA_CSS)])
45
+ pdf.write_pdf(
46
+ filename, stylesheets=[CSS(filename=BULMA_CSS)])
42
47
  else:
43
48
  pdf.write_pdf(filename)
44
49
 
@@ -31,13 +31,18 @@ table.footer td {
31
31
  border: none;
32
32
  padding: 6pt;
33
33
  }
34
- span.page_num_of::after {
35
- content: '{{_("Page")}} ' counter(page) ' {{_("of {0}").format("")}}' counter(pages);
36
- }
34
+ {#
35
+ Removed after #6179 (The footer of a multipage invoice shows the same
36
+ pagenumber on each page)
37
37
 
38
- span.printed_time::after {
39
- content: '{{_("Printed")}} {{fdm(dd.today())}} {{_("at")}} {{now.time().strftime("%H:%M")}}';
40
- }
38
+ span.page_num_of::after {
39
+ content: '{{_("Page")}} ' counter(page) ' {{_("of {0}").format("")}}' counter(pages);
40
+ }
41
+
42
+ span.printed_time::after {
43
+ content: '{{_("Printed")}} {{fdm(dd.today())}} {{_("at")}} {{now.time().strftime("%H:%M")}}';
44
+ }
45
+ #}
41
46
 
42
47
  body {
43
48
  font-family: "Liberation sans", "Arial", "Helvetica";
@@ -83,13 +88,10 @@ div.recipient {
83
88
  margin-left: {{dd.plugins.weasyprint.margin_left}}mm;
84
89
  margin-right: {{dd.plugins.weasyprint.margin_right}}mm;
85
90
  {%- if dd.plugins.weasyprint.page_background_image -%}
86
- {#
87
- background: url(file://{{dd.plugins.weasyprint.page_background_image}}) no-repeat center center fixed;
88
- #}
89
- background-image: url(file://{{dd.plugins.weasyprint.page_background_image}});
90
- background-repeat: no-repeat;
91
- background-attachment: fixed;
92
- background-size: contain;
91
+ background-image: url(file://{{dd.plugins.weasyprint.page_background_image}});
92
+ background-repeat: no-repeat;
93
+ background-attachment: fixed;
94
+ background-size: contain;
93
95
  {%- endif -%}
94
96
  font-family: "Liberation sans", "arial";
95
97
  font-size: 10pt;
@@ -102,7 +102,7 @@ def configure(globals_dict, django_settings_module=None):
102
102
  rstgen.sphinxconf.configure()
103
103
 
104
104
  You can specify an additional positional argument `django_settings_module`
105
- (the name of a Django settings module). If this argument is specified, call
105
+ (the name of a Django settings file). If this argument is specified, call
106
106
  :meth:`lino.startup` with it.
107
107
 
108
108
  """
@@ -11,7 +11,7 @@ for a Lino application.
11
11
  source code. This is like :rst:dir:`py2rst` but with the following
12
12
  names defined:
13
13
 
14
- :settings: The Django settings module which is active while building
14
+ :settings: The Django settings file which is active while building
15
15
  the docs.
16
16
 
17
17
  :dd: The :mod:`lino.api.dd` module.
lino/utils/diag.py CHANGED
@@ -341,9 +341,9 @@ class Analyzer(object):
341
341
  return "{0}.{1}".format(fmn(mfk[0]), mfk[1].name)
342
342
 
343
343
  items1 = []
344
- for target, dp in list(tdp.items()):
344
+ for target, dp in tdp.items():
345
345
  items2 = []
346
- for dh, pl in list(dp.items()):
346
+ for dh, pl in dp.items():
347
347
  items2.append(
348
348
  "{0} : {1}".format(
349
349
  dh.__name__, ", ".join([fk2str(mfk) for mfk in pl])
@@ -215,13 +215,33 @@ def create_and_get(model, **kw):
215
215
  def make_if_needed(model, **values):
216
216
  qs = model.objects.filter(**values)
217
217
  if qs.count() == 1:
218
- pass # ok, nothing to do
218
+ return qs.first()
219
+ # pass # ok, nothing to do
219
220
  elif qs.count() == 0:
220
221
  return model(**values)
221
222
  else:
222
223
  raise Exception(f"Multiple {model._meta.verbose_name_plural} for {values}")
223
224
 
224
225
 
226
+ def get_or_create(model, **kwargs):
227
+ # similar to Djanop's QuerySet.get_or_create() but calls full_clean
228
+ try:
229
+ obj = model.objects.get(**kwargs)
230
+ except model.DoesNotExist:
231
+ obj = model(**kwargs)
232
+ obj.full_clean()
233
+ obj.save()
234
+ return obj
235
+
236
+
237
+ def update_or_create(m, **kwargs):
238
+ if (obj := m.objects.filter(id=kwargs['id']).first()) is not None:
239
+ for k, v in kwargs.items():
240
+ setattr(obj, k, v)
241
+ return obj
242
+ return m(**kwargs)
243
+
244
+
225
245
  def _test():
226
246
  import doctest
227
247
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: lino
3
- Version: 25.7.1
3
+ Version: 25.7.3
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