lino 25.3.2__py3-none-any.whl → 25.3.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.
@@ -1,13 +1,58 @@
1
1
  # -*- coding: UTF-8 -*-
2
- # Copyright 2023 Rumma & Ko Ltd.
2
+ # Copyright 2009-2023 Rumma & Ko Ltd.
3
3
  # License: GNU Affero General Public License v3 (see file COPYING for details)
4
4
 
5
- from django.core.management.base import BaseCommand
5
+ from click import confirm
6
+ from django.core.management.base import BaseCommand, CommandError
7
+ from django.core.management import call_command
6
8
  from django.conf import settings
9
+ from lino import logger
7
10
 
8
11
 
9
12
  class Command(BaseCommand):
13
+ """Build the site cache files and run collectstatic for this Lino site."""
14
+
15
+ def add_arguments(self, parser):
16
+ super().add_arguments(parser)
17
+ parser.add_argument(
18
+ "-b", "--batch", "--noinput",
19
+ action="store_false",
20
+ dest="interactive",
21
+ default=True,
22
+ help="Do not prompt for input of any kind.",
23
+ ),
24
+
10
25
  def handle(self, *args, **options):
26
+ interactive = options.get("interactive")
11
27
  verbosity = options.get("verbosity")
12
- # print("20231005", verbosity)
28
+ project_dir = settings.SITE.project_dir
29
+
30
+ options = dict(interactive=False, verbosity=verbosity)
31
+
32
+ if interactive:
33
+ msg = "Build everything for ({})".format(project_dir)
34
+ msg += ".\nAre you sure?"
35
+ if not confirm(msg, default=True):
36
+ raise CommandError("User abort.")
37
+
38
+ # the following log message was useful on Travis 20150104
39
+ if verbosity > 0:
40
+ logger.info("`buildsite` started on %s.", project_dir)
41
+
42
+ # pth = project_dir / "settings.py"
43
+ # if pth.exists():
44
+ # pth.touch()
45
+
46
+ call_command("collectstatic", **options)
47
+
13
48
  settings.SITE.build_site_cache(force=True, verbosity=verbosity)
49
+
50
+ # if settings.SITE.is_installed("help"):
51
+ # call_command("makehelp", verbosity=verbosity)
52
+
53
+ # for p in settings.SITE.installed_plugins:
54
+ # p.on_buildsite(settings.SITE, verbosity=verbosity)
55
+
56
+ # settings.SITE.clear_site_config()
57
+
58
+ logger.info("`buildsite` finished on %s.", project_dir)
@@ -2,6 +2,10 @@
2
2
  # Copyright 2013-2023 by Rumma & Ko Ltd.
3
3
  # License: GNU Affero General Public License v3 (see file COPYING for details)
4
4
 
5
+ from lino.utils.mldbc.fields import BabelCharField, BabelTextField
6
+ from lino.core.choicelists import ChoiceListField
7
+ from lino.core.utils import sorted_models_list, full_model_name
8
+ from lino.utils import puts
5
9
  from io import open
6
10
  from lino import logger
7
11
 
@@ -20,12 +24,6 @@ from datetime import timezone
20
24
 
21
25
  utc = timezone.utc
22
26
 
23
- from lino.utils import puts
24
- from lino.core.utils import sorted_models_list, full_model_name
25
- from lino.core.choicelists import ChoiceListField
26
-
27
- from lino.utils.mldbc.fields import BabelCharField, BabelTextField
28
-
29
27
 
30
28
  def is_pointer_to_contenttype(f):
31
29
  if not settings.SITE.is_installed("contenttypes"):
@@ -402,7 +400,7 @@ def main(args):
402
400
  if __name__ == '__main__':
403
401
  import argparse
404
402
  parser = argparse.ArgumentParser(description='Restore the data.')
405
- parser.add_argument('--noinput', dest='interactive',
403
+ parser.add_argument('-b', '--noinput', '--batch', dest='interactive',
406
404
  action='store_false', default=True,
407
405
  help="Don't ask for confirmation before flushing the database.")
408
406
  parser.add_argument('--quick', dest='quick',
@@ -4,7 +4,7 @@
4
4
 
5
5
  import sys
6
6
  import subprocess
7
- from rstgen.utils import confirm
7
+ from click import confirm
8
8
  from django.core.management.base import BaseCommand
9
9
  from django.conf import settings
10
10
 
@@ -54,5 +54,5 @@ class Command(BaseCommand):
54
54
  return
55
55
  # cmd = "pip install --upgrade --trusted-host svn.forge.pallavi.be {}".format(' '.join(reqs))
56
56
  cmd = sys.executable + " -m pip install --upgrade pip {}".format(" ".join(reqs))
57
- if not options["interactive"] or confirm("{} (y/n) ?".format(cmd)):
57
+ if not options["interactive"] or confirm("{} ?".format(cmd), default=True):
58
58
  runcmd(cmd)
@@ -1,15 +1,11 @@
1
1
  # -*- coding: UTF-8 -*-
2
- # Copyright 2013-2023 Rumma & Ko Ltd
2
+ # Copyright 2013-2025 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 django.core.management import call_command
7
6
  from django.core.management.base import BaseCommand, CommandError
8
7
  from django.conf import settings
9
- from django.db import DEFAULT_DB_ALIAS
10
- from rstgen.utils import confirm
11
- # from lino.management.commands.initdb import Command as BaseCommand
12
- # from lino.management.commands.initdb import CommandError
8
+ from lino.utils import dbhash
13
9
 
14
10
 
15
11
  class Command(BaseCommand):
@@ -19,7 +15,7 @@ class Command(BaseCommand):
19
15
  super().add_arguments(parser)
20
16
  (
21
17
  parser.add_argument(
22
- "--noinput",
18
+ "-b", "--batch", "--noinput",
23
19
  action="store_false",
24
20
  dest="interactive",
25
21
  default=True,
@@ -66,4 +62,4 @@ class Command(BaseCommand):
66
62
  kwargs["removemedia"] = True
67
63
  call_command("initdb", *args, **kwargs)
68
64
 
69
- settings.SITE.mark_virgin()
65
+ dbhash.mark_virgin()
lino/mixins/duplicable.py CHANGED
@@ -29,7 +29,7 @@ class Duplicate(actions.Action):
29
29
  sort_index = 11
30
30
  show_in_workflow = False
31
31
  # readonly = False # like ShowInsert. See docs/blog/2012/0726
32
- callable_from = "t"
32
+ callable_from = "td"
33
33
 
34
34
  # required_roles = set([Expert])
35
35
 
@@ -1,5 +1,5 @@
1
1
  # -*- coding: UTF-8 -*-
2
- # Copyright 2015-2023 Rumma & Ko Ltd
2
+ # Copyright 2015-2025 Rumma & Ko Ltd
3
3
  # License: GNU Affero General Public License v3 (see file COPYING for details)
4
4
 
5
5
  from collections import OrderedDict
@@ -9,18 +9,17 @@ from django.conf import settings
9
9
  from django.utils import translation
10
10
  from django.template.defaultfilters import pluralize
11
11
 
12
+ from lino import logger
13
+ from lino.core import constants
12
14
  from lino.core.gfks import gfk2lookup
13
15
  from lino.modlib.gfks.mixins import Controllable
14
16
  from lino.modlib.users.mixins import UserAuthored
15
17
  from lino.modlib.linod.choicelists import background_task
16
18
  from lino.core.roles import SiteStaff
17
-
18
19
  from lino.api import dd, rt, _
19
20
 
20
21
  from .choicelists import Checker, Checkers
21
-
22
22
  from .roles import CheckdataUser
23
- from lino.core import constants
24
23
 
25
24
 
26
25
  class CheckerAction(dd.Action):
@@ -32,6 +31,8 @@ class CheckerAction(dd.Action):
32
31
  Message = rt.models.checkdata.Message
33
32
  gfk = Message.owner
34
33
  for obj in objects:
34
+ if checkers is None:
35
+ checkers = get_checkers_for(obj.__class__)
35
36
  qs = Message.objects.filter(**gfk2lookup(gfk, obj))
36
37
  qs.delete()
37
38
  for chk in checkers:
@@ -84,12 +85,12 @@ class UpdateMessagesByController(CheckerAction):
84
85
  combo_group = "checkdata"
85
86
  required_roles = dd.login_required()
86
87
 
87
- def __init__(self, model):
88
- self.model = model
89
- super().__init__()
88
+ # def __init__(self, model=None, **kwargs):
89
+ # self.model = model
90
+ # super().__init__(**kwargs)
90
91
 
91
92
  def run_from_ui(self, ar, fix=None):
92
- self.run_it(ar, fix, get_checkers_for(self.model), ar.selected_rows)
93
+ self.run_it(ar, fix, None, ar.selected_rows)
93
94
  # if fix is None:
94
95
  # fix = self.fix_them
95
96
  # Message = rt.models.checkdata.Message
@@ -109,6 +110,16 @@ class FixMessagesByController(UpdateMessagesByController):
109
110
  fix_them = True
110
111
 
111
112
 
113
+ class QuickFixMessagesByController(UpdateMessagesByController):
114
+ # label = _("Fix data problems")
115
+ fix_them = True
116
+ combo_group = None
117
+ # icon_name = "lightning"
118
+ icon_name = None
119
+ button_text = ' ⚡ ' # 26A1
120
+ # button_text = "✓" # u"\u2713"
121
+
122
+
112
123
  class FixAllProblems(CheckerAction):
113
124
  select_rows = False
114
125
  show_in_plain = True
@@ -119,7 +130,7 @@ class FixAllProblems(CheckerAction):
119
130
 
120
131
  def run_from_ui(self, ar, fix=None):
121
132
  mi = ar.master_instance
122
- print(f"20250307 {mi}")
133
+ # print(f"20250307 {mi}")
123
134
  self.run_it(ar, fix, get_checkers_for(mi.__class__), [mi])
124
135
  ar.set_response(refresh=True)
125
136
 
@@ -264,11 +275,14 @@ def set_checkdata_actions(sender, **kw):
264
275
  if m is None:
265
276
  continue
266
277
  assert m is not Message
267
- m.define_action(check_data=UpdateMessagesByController(m))
268
- m.define_action(fix_problems=FixMessagesByController(m))
269
- if True:
270
- # don't add it automatically because appdev might prefer
271
- # to show it in a detail_layout:
278
+ # if hasattr(m, 'check_data'):
279
+ if (label := getattr(m, 'quickfix_checkdata_label', None)):
280
+ # print(f"20250324 Customized quickfix_checkdata_label {label} for {m}")
281
+ m.define_action(quick_fix=QuickFixMessagesByController(label=label))
282
+ else:
283
+ # print(f"20250324 Default checkdata buttons for {m}")
284
+ m.define_action(check_data=UpdateMessagesByController())
285
+ m.define_action(fix_problems=FixMessagesByController())
272
286
  m.define_action(
273
287
  show_problems=dd.ShowSlaveTable(
274
288
  MessagesByOwner,
@@ -280,7 +294,11 @@ def set_checkdata_actions(sender, **kw):
280
294
 
281
295
 
282
296
  def get_checkers_for(model):
283
- return get_checkable_models()[model]
297
+ checkers = []
298
+ for m, lst in get_checkable_models().items():
299
+ if m is not None and issubclass(model, m):
300
+ checkers += lst
301
+ return checkers
284
302
 
285
303
 
286
304
  def check_instance(obj, **kwargs):
@@ -4,6 +4,7 @@
4
4
  Adds some demo comments.
5
5
 
6
6
  """
7
+
7
8
  import datetime
8
9
  from lino.utils import i2t
9
10
  from lino.utils import Cycler
@@ -32,12 +33,6 @@ plain1 = "Some plain text."
32
33
  plain2 = "Two paragraphs of plain text.\n\nThe second paragraph."
33
34
  # plain2 += " With an 👁 (U+1F441)." #5855 (Jane fails to store certain unicode characters)
34
35
 
35
- imageDataURL = """data:image/png;base64,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"""
36
- body_with_img = f"""\
37
- <p>Here is an image:</p>
38
- <p><img src="{imageDataURL}" class="bar"></p>\
39
- """
40
-
41
36
  BODIES = Cycler(
42
37
  [styled, table, lorem, short_lorem, breaking, cond_comment, plain1, plain2]
43
38
  )
@@ -45,9 +40,6 @@ BODIES = Cycler(
45
40
  BODIES.items.insert(0, "")
46
41
  BODIES.items.insert(0, "")
47
42
 
48
- if dd.is_installed('blogs'):
49
- BODIES.items.append(body_with_img)
50
-
51
43
 
52
44
  def objects():
53
45
  Comment = rt.models.comments.Comment
@@ -354,7 +354,7 @@ class ApiElement(View):
354
354
  ar = ba.create_request(
355
355
  request=request, renderer=settings.SITE.kernel.default_renderer)
356
356
  if pk and pk != "-99999" and pk != "-99998":
357
- if issubclass(rpt.model, models.Model):
357
+ if rpt.model is not None and issubclass(rpt.model, models.Model):
358
358
  try:
359
359
  ar.set_selected_pks(pk)
360
360
  except rpt.model.DoesNotExist:
lino/modlib/users/ui.py CHANGED
@@ -46,8 +46,8 @@ class UserDetail(dd.DetailLayout):
46
46
  """
47
47
 
48
48
  main = """
49
- box1 box2 #MembershipsByUser:20
50
- remarks:40 AuthoritiesGiven:20 SocialAuthsByUser:30
49
+ box1 box2 #MembershipsByUser:20 remarks
50
+ AuthoritiesGiven:20 AuthoritiesTaken:20 SocialAuthsByUser:30
51
51
  """
52
52
 
53
53
  # main_m = """
@@ -203,6 +203,7 @@ class AuthoritiesGiven(Authorities):
203
203
  label = _("Authorities given")
204
204
  column_names = "authorized"
205
205
  auto_fit_column_widths = True
206
+ details_of_master_template = _("%(details)s by %(master)s")
206
207
 
207
208
 
208
209
  class AuthoritiesTaken(Authorities):
@@ -211,6 +212,7 @@ class AuthoritiesTaken(Authorities):
211
212
  label = _("Authorities taken")
212
213
  column_names = "user"
213
214
  auto_fit_column_widths = True
215
+ details_of_master_template = _("%(details)s by %(master)s")
214
216
 
215
217
 
216
218
  if has_socialauth and dd.get_plugin_setting("users", "third_party_authentication"):
@@ -1,4 +1,4 @@
1
- # Copyright 2016-2021 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
  """This plugins installs two build methods for generating
4
4
  :term:`printable documents <printable document>` using `weasyprint
@@ -25,31 +25,17 @@ from lino.api import ad, _
25
25
 
26
26
 
27
27
  class Plugin(ad.Plugin):
28
- "See :doc:`/dev/plugins`."
29
28
 
30
29
  verbose_name = _("WeasyPrint")
31
-
32
30
  needs_plugins = ["lino.modlib.jinja"]
33
31
 
34
32
  header_height = 20
35
- """Height of header in mm. Set to `None` if you want no header."""
36
-
37
33
  footer_height = 20
38
- """Height of footer in mm. Set to `None` if you want no header."""
39
-
40
34
  top_right_width = None
41
- """Width of top-right.jpg in mm. If not given, Lino computes it based on height.
42
- """
43
-
35
+ page_background_image = None
44
36
  top_right_image = None
45
- """The first image file found in config named either top-right.jpg or top-right.png."""
46
-
47
37
  header_image = None
48
- """The first image file found in config named either header.jpg or header.png."""
49
-
50
38
  margin = 10
51
- """Top and bottom page margin in mm."""
52
-
53
39
  margin_left = 17
54
40
  margin_right = 10
55
41
 
@@ -57,8 +43,8 @@ class Plugin(ad.Plugin):
57
43
  yield "imagesize"
58
44
 
59
45
  def pre_site_startup(self, site):
60
- if self.header_height:
61
- for ext in ("jpg", "png"):
46
+ for ext in ("jpg", "png"):
47
+ if self.header_height:
62
48
  fn = site.confdirs.find_config_file("top-right." + ext, "weasyprint")
63
49
  if fn:
64
50
  self.top_right_image = fn
@@ -70,6 +56,12 @@ class Plugin(ad.Plugin):
70
56
  self.top_right_width = self.header_height * w / h
71
57
  fn = site.confdirs.find_config_file("header." + ext, "weasyprint")
72
58
  if fn:
59
+ # site.logger.info("Found header_image %s", fn)
73
60
  self.header_image = fn
74
-
75
- super(Plugin, self).pre_site_startup(site)
61
+ if self.page_background_image is None:
62
+ fn = site.confdirs.find_config_file(
63
+ "page-background." + ext, "weasyprint")
64
+ if fn:
65
+ # site.logger.info("Found page_background_image %s", fn)
66
+ self.page_background_image = fn
67
+ super().pre_site_startup(site)
@@ -53,7 +53,7 @@ p {
53
53
  }
54
54
 
55
55
  div.recipient {
56
- position:relative; left:80mm;
56
+ position:relative; left:{{100-dd.plugins.weasyprint.margin_left}}mm;
57
57
  height:30mm;
58
58
  width:80mm;
59
59
  border: 1px solid grey;
@@ -80,6 +80,15 @@ div.recipient {
80
80
  {%- endif -%}
81
81
  margin-left: {{dd.plugins.weasyprint.margin_left}}mm;
82
82
  margin-right: {{dd.plugins.weasyprint.margin_right}}mm;
83
+ {%- if dd.plugins.weasyprint.page_background_image -%}
84
+ {#
85
+ background: url(file://{{dd.plugins.weasyprint.page_background_image}}) no-repeat center center fixed;
86
+ #}
87
+ background-image: url(file://{{dd.plugins.weasyprint.page_background_image}});
88
+ background-repeat: no-repeat;
89
+ background-attachment: fixed;
90
+ background-size: contain;
91
+ {%- endif -%}
83
92
  font-family: "Liberation sans", "arial";
84
93
  font-size: 10pt;
85
94
  {%- block bottomleft %}
lino/utils/__init__.py CHANGED
@@ -18,6 +18,7 @@ function for general use. It has also many subpackages and submodules.
18
18
  dataserializer
19
19
  dbfreader
20
20
  dblogger
21
+ dbhash
21
22
  diag
22
23
  djangotest
23
24
  dpy
lino/utils/dbhash.py ADDED
@@ -0,0 +1,112 @@
1
+ # -*- coding: UTF-8 -*-
2
+ # Copyright 2009-2025 Rumma & Ko Ltd
3
+ # License: GNU Affero General Public License v3 (see file COPYING for details)
4
+
5
+ """
6
+ Utilities around a "database hash".
7
+ See :doc:`/utils/dbhash`.
8
+ """
9
+
10
+ import json
11
+ from importlib import import_module
12
+ from pathlib import Path
13
+ from django.conf import settings
14
+ from django.apps import apps
15
+ from django.db.models.deletion import ProtectedError
16
+
17
+ mod = import_module(settings.SETTINGS_MODULE)
18
+ HASH_FILE = Path(mod.__file__).parent / "dbhash.json"
19
+
20
+
21
+ def fmn(m):
22
+ return f"{m._meta.app_label}.{m._meta.object_name}"
23
+
24
+
25
+ def compute_dbhash():
26
+ """
27
+ Return a dictionary with a hash value of the current database content.
28
+ """
29
+ rv = dict()
30
+ for m in apps.get_models(include_auto_created=True):
31
+ k = fmn(m)
32
+ if k != "sessions.Session":
33
+ # rv[k] = m.objects.count()
34
+ rv[k] = list(m.objects.values_list('pk', flat=True))
35
+ return rv
36
+
37
+
38
+ def mark_virgin():
39
+ """
40
+ Mark the database as virgin. This is called by :manage:`prep`.
41
+ """
42
+ dbhash = compute_dbhash()
43
+ with HASH_FILE.open("w") as fp:
44
+ json.dump(dbhash, fp)
45
+
46
+
47
+ def load_dbhash():
48
+ """
49
+ Load the dbhash that was saved in :xfile:`dbhash.json`
50
+ """
51
+ if not HASH_FILE.exists():
52
+ raise Exception(
53
+ f"No file {HASH_FILE} (did you run `django-admin prep`?)")
54
+ with HASH_FILE.open("r") as fp:
55
+ return json.load(fp)
56
+
57
+
58
+ def check_virgin(restore=True, verbose=True):
59
+ """
60
+ Verify whether the database is virgin. Print the differences if there
61
+ are any.
62
+ """
63
+ new = compute_dbhash()
64
+ old = load_dbhash()
65
+
66
+ first_diff = True
67
+ can_restore = True
68
+ must_delete = {}
69
+ for k, v in new.items():
70
+ v = set(v)
71
+ oldv = set(old.get(k, None))
72
+ if oldv != v:
73
+ if first_diff:
74
+ if verbose:
75
+ print(f"Database {HASH_FILE.parent} isn't virgin:")
76
+ first_diff = False
77
+ diffs = []
78
+ added = v - oldv
79
+ if len(added):
80
+ diffs.append(f"{len(added)} rows added")
81
+ must_delete[apps.get_model(k)] = added
82
+ # for pk in added:
83
+ # must_delete.append(m.objects.get(pk=pk))
84
+ if (removed := len(oldv-v)):
85
+ diffs.append(f"{removed} rows deleted")
86
+ can_restore = False
87
+ if verbose:
88
+ print(f"- {k}: {', '.join(diffs)}")
89
+ if len(must_delete) == 0 or not restore:
90
+ return
91
+ if not can_restore:
92
+ raise Exception(
93
+ "Cannot restore database because some rows have been deleted")
94
+ # print(f"Tidy up {len(must_delete)} rows from database")
95
+ # It can happen that some rows refer to each other with a protected fk
96
+ # We call bulk delete() to avoid Lino deleting the items of an invoice
97
+ must_delete = list(must_delete.items())
98
+ while len(must_delete):
99
+ todo = []
100
+ hope = False
101
+ for m, added in must_delete:
102
+ try:
103
+ m.objects.filter(pk__in=added).delete()
104
+ # obj.delete()
105
+ hope = True
106
+ except Exception:
107
+ todo.append((m, added))
108
+ if not hope:
109
+ raise Exception(f"Failed to delete {todo}")
110
+ must_delete = todo
111
+ if verbose:
112
+ print("Database has been restored.")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: lino
3
- Version: 25.3.2
3
+ Version: 25.3.4
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