lino 25.8.3__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 (61) hide show
  1. lino/__init__.py +1 -1
  2. lino/api/dd.py +0 -1
  3. lino/core/__init__.py +0 -1
  4. lino/core/actions.py +1 -1
  5. lino/core/actors.py +8 -0
  6. lino/core/elems.py +1 -1
  7. lino/core/fields.py +4 -1
  8. lino/core/model.py +2 -11
  9. lino/core/requests.py +8 -7
  10. lino/core/site.py +0 -79
  11. lino/core/user_types.py +1 -10
  12. lino/help_texts.py +1 -5
  13. lino/management/commands/initdb.py +0 -3
  14. lino/modlib/__init__.py +0 -1
  15. lino/modlib/bootstrap5/README.txt +1 -1
  16. lino/modlib/bootstrap5/__init__.py +34 -38
  17. lino/modlib/bootstrap5/config/bootstrap5/base.html +4 -0
  18. lino/modlib/bootstrap5/models.py +23 -23
  19. lino/modlib/bootstrap5/views.py +2 -107
  20. lino/modlib/checkdata/choicelists.py +1 -1
  21. lino/modlib/comments/fixtures/demo2.py +1 -0
  22. lino/modlib/comments/ui.py +7 -7
  23. lino/modlib/extjs/__init__.py +2 -4
  24. lino/modlib/extjs/config/extjs/index.html +1 -1
  25. lino/modlib/extjs/ext_renderer.py +1 -7
  26. lino/modlib/extjs/views.py +2 -0
  27. lino/modlib/help/models.py +1 -12
  28. lino/modlib/linod/mixins.py +3 -2
  29. lino/modlib/memo/__init__.py +10 -9
  30. lino/modlib/memo/mixins.py +38 -21
  31. lino/modlib/memo/models.py +10 -7
  32. lino/modlib/memo/parser.py +3 -1
  33. lino/modlib/notify/models.py +6 -9
  34. lino/modlib/publisher/__init__.py +12 -7
  35. lino/modlib/publisher/choicelists.py +9 -64
  36. lino/modlib/publisher/config/publisher/page.pub.html +73 -7
  37. lino/modlib/publisher/fixtures/std.py +14 -1
  38. lino/modlib/publisher/mixins.py +41 -12
  39. lino/modlib/publisher/models.py +74 -75
  40. lino/modlib/publisher/renderer.py +28 -12
  41. lino/modlib/publisher/ui.py +35 -35
  42. lino/modlib/publisher/views.py +59 -24
  43. lino/modlib/system/models.py +3 -2
  44. lino/modlib/uploads/__init__.py +1 -0
  45. lino/modlib/uploads/mixins.py +2 -2
  46. lino/modlib/uploads/models.py +55 -21
  47. lino/modlib/uploads/ui.py +1 -0
  48. lino/modlib/uploads/utils.py +2 -2
  49. lino/modlib/users/__init__.py +2 -3
  50. lino/modlib/users/actions.py +12 -17
  51. lino/modlib/users/models.py +37 -36
  52. lino/modlib/weasyprint/choicelists.py +6 -0
  53. lino/utils/diag.py +5 -3
  54. lino/utils/html.py +103 -0
  55. lino/utils/mldbc/mixins.py +2 -2
  56. lino/utils/soup.py +16 -8
  57. {lino-25.8.3.dist-info → lino-25.9.0.dist-info}/METADATA +1 -1
  58. {lino-25.8.3.dist-info → lino-25.9.0.dist-info}/RECORD +61 -61
  59. {lino-25.8.3.dist-info → lino-25.9.0.dist-info}/WHEEL +0 -0
  60. {lino-25.8.3.dist-info → lino-25.9.0.dist-info}/licenses/AUTHORS.rst +0 -0
  61. {lino-25.8.3.dist-info → lino-25.9.0.dist-info}/licenses/COPYING +0 -0
@@ -9,30 +9,30 @@ from django.db import models
9
9
 
10
10
  from lino.api import dd, rt, _
11
11
  from lino.utils.html import E
12
+ from lino.utils.soup import MORE_MARKER
12
13
  from lino.core import constants
13
14
  # from lino.core.renderer import add_user_language
14
15
  from lino.modlib.office.roles import OfficeUser
15
16
 
16
- from .choicelists import PageFillers, SpecialPages
17
+ from .choicelists import SpecialPages
17
18
 
18
19
 
19
- if dd.is_installed("comments") and dd.is_installed("topics"):
20
- DISCUSSION_PANEL = """
21
- topics.TagsByOwner:20 comments.CommentsByRFC:60
22
- """
23
- else:
24
- DISCUSSION_PANEL = ""
20
+ VARTABS = ""
21
+
22
+ if dd.is_installed("topics"):
23
+ VARTABS += " topics.TagsByOwner:20"
24
+ if dd.is_installed("comments"):
25
+ VARTABS += " comments.CommentsByRFC:60"
25
26
 
26
27
 
27
28
  class PageDetail(dd.DetailLayout):
28
- main = "general first_panel more"
29
+ # main = "general first_panel more"
30
+ main = f"general first_panel {VARTABS} more"
29
31
 
30
32
  first_panel = dd.Panel(
31
33
  """
32
- treeview_panel:20 preview:60
33
- """,
34
- label=_("Preview"),
35
- )
34
+ treeview_panel:20 preview:60
35
+ """, label=_("Preview"))
36
36
 
37
37
  general = dd.Panel(
38
38
  """
@@ -42,16 +42,15 @@ class PageDetail(dd.DetailLayout):
42
42
  required_roles=dd.login_required(OfficeUser),
43
43
  )
44
44
 
45
- more = dd.Panel(
46
- DISCUSSION_PANEL,
47
- label=_("Discussion"),
48
- required_roles=dd.login_required(OfficeUser),
49
- )
45
+ # more = dd.Panel(
46
+ # VARTABS,
47
+ # label=_("Discussion"),
48
+ # required_roles=dd.login_required(OfficeUser),
49
+ # )
50
50
 
51
51
  content_panel = """
52
52
  title id
53
53
  body
54
- publisher.PagesByParent
55
54
  """
56
55
 
57
56
  # right_panel = """
@@ -62,23 +61,27 @@ class PageDetail(dd.DetailLayout):
62
61
  # """
63
62
 
64
63
  right_panel = """
65
- group language ref
66
- parent seqno root_page
64
+ parent seqno
65
+ publisher.PagesByParent
66
+ """
67
+
68
+ more = dd.Panel("""
69
+ publisher_tree language
67
70
  child_node_depth main_image
68
- special_page filler
69
- publishing_state private
71
+ special_page #filler
72
+ publishing_state
70
73
  publisher.TranslationsByPage
71
- """
74
+ """, label=_("More"))
72
75
 
73
76
 
74
77
  class Pages(dd.Table):
75
78
  model = "publisher.Page"
76
- column_names = "ref title #page_type id *"
79
+ column_names = "title publisher_tree id *"
77
80
  detail_layout = "publisher.PageDetail"
78
81
  insert_layout = """
79
82
  title
80
- ref
81
- #page_type filler
83
+ publisher_tree
84
+ language #filler
82
85
  """
83
86
  default_display_modes = {None: constants.DISPLAY_MODE_LIST}
84
87
 
@@ -101,7 +104,7 @@ class PagesByParent(Pages):
101
104
  class TranslationsByPage(Pages):
102
105
  master_key = "translated_from"
103
106
  label = _("Translations")
104
- column_names = "ref title language id *"
107
+ column_names = "title language id *"
105
108
  default_display_modes = {None: constants.DISPLAY_MODE_SUMMARY}
106
109
 
107
110
  @classmethod
@@ -110,16 +113,13 @@ class TranslationsByPage(Pages):
110
113
  return E.span("({}) ".format(obj.language), obj.as_summary_item(ar, text, **kwargs))
111
114
 
112
115
 
113
- class RootPages(Pages):
114
- label = _("Root pages")
115
- filter = models.Q(parent__isnull=True, special_page='')
116
- column_names = "ref title language"
117
- # default_display_modes = {None: constants.DISPLAY_MODE_LIST}
118
-
116
+ class Trees(dd.Table):
117
+ model = "publisher.Tree"
118
+ column_names = "ref root_page group private id *"
119
119
 
120
- filler = PageFillers.add_item(RootPages)
121
120
 
122
121
  SpecialPages.add_item(
123
- "pages", filler=filler, body=_("List of root pages."),
122
+ "pages", # filler=filler,
123
+ body=_("List of root pages.") + MORE_MARKER + " [show publisher.Trees]",
124
124
  title=_("Root pages"),
125
125
  parent='home')
@@ -1,13 +1,15 @@
1
1
  # -*- coding: UTF-8 -*-
2
2
  # Copyright 2020-2025 Rumma & Ko Ltd
3
3
  # License: GNU Affero General Public License v3 (see file COPYING for details)
4
-
5
4
  from django import http
6
5
  from django.conf import settings
7
6
  from django.core.exceptions import ObjectDoesNotExist
7
+ from django.shortcuts import redirect
8
8
  from django.utils import translation
9
9
  from django.views.generic import View
10
+ from lino.core import auth
10
11
  from lino.core.requests import BaseRequest
12
+ from lino.core.views import json_response
11
13
 
12
14
 
13
15
  class Element(View):
@@ -24,50 +26,83 @@ class Element(View):
24
26
 
25
27
  # kw = dict(actor=self.publisher_model.get_default_table(),
26
28
  # request=request, renderer=rnd, permalink_uris=True)
27
- # kw = dict(renderer=rnd)
29
+ kw = dict(renderer=rnd, request=request)
28
30
  # kw = dict(renderer=rnd, permalink_uris=True)
29
31
  # if rnd.front_end.media_name == 'react':
30
32
  # kw.update(hash_router=True)
31
33
 
32
- # kw.update(selected_pks=[pk])
34
+ kw.update(selected_pks=[pk])
33
35
  #
34
- # try:
35
- # ar = self.table_class.create_request(request=request, **kw)
36
- # except ObjectDoesNotExist as e:
37
- # # print("20240911", e)
38
- # return http.HttpResponseNotFound(f"No row #{pk} in {self.table_class} ({e})")
39
- # if len(ar.selected_rows) == 0:
40
- # # print(f"20241003 Oops {ar} has no rows")
41
- # return http.HttpResponseNotFound(f"20241003 No row #{pk} in {self.table_class}")
42
- # obj = ar.selected_rows[0]
43
-
44
- m = self.table_class.model
45
36
  try:
46
- obj = m.objects.get(pk=pk)
47
- except m.DoesNotExist as e:
48
- return http.HttpResponseNotFound(f"No row #{pk} in {m} ({e})")
49
- ar = BaseRequest(renderer=rnd, request=request, selected_rows=[obj])
37
+ ar = self.table_class.create_request(**kw)
38
+ except ObjectDoesNotExist as e:
39
+ # print("20240911", e)
40
+ return http.HttpResponseNotFound(
41
+ f"No row #{pk} in {self.table_class} ({e})")
42
+ if len(ar.selected_rows) == 0:
43
+ # print(f"20241003 Oops {ar} has no rows")
44
+ return http.HttpResponseNotFound(
45
+ f"20241003 No row #{pk} in {self.table_class}")
46
+ obj = ar.selected_rows[0]
47
+
48
+ # m = self.table_class.model
49
+ # try:
50
+ # obj = m.objects.get(pk=pk)
51
+ # except m.DoesNotExist as e:
52
+ # return http.HttpResponseNotFound(f"No row #{pk} in {m} ({e})")
53
+ # ar = BaseRequest(renderer=rnd, request=request, selected_rows=[obj])
50
54
  # ar = BaseRequest(renderer=rnd, request=request)
51
55
  return obj.get_publisher_response(ar)
52
56
 
53
57
 
54
58
  class Index(View):
55
- def get(self, request, ref=''):
59
+
60
+ ref = 'index'
61
+
62
+ def get(self, request):
63
+ Tree = settings.SITE.models.publisher.Tree
56
64
  dv = settings.SITE.models.publisher.Pages
57
65
  if len(settings.SITE.languages) == 1:
58
66
  # language = settings.SITE.languages[0].django_code
59
67
  language = translation.get_language()
60
68
  else:
61
69
  language = request.LANGUAGE_CODE
62
- if ref == '':
63
- ref = 'index'
64
70
  try:
65
- obj = dv.model.objects.get(ref=ref, language=language)
66
- except dv.model.DoesNotExist:
67
- return http.HttpResponseNotFound(f"No row {ref} in {dv.model}")
71
+ tree = Tree.objects.get(ref=self.ref)
72
+ except Tree.DoesNotExist:
73
+ return http.HttpResponseNotFound(f"No tree for {self.ref}")
74
+ obj = tree.get_root_page(language)
75
+ # print(20250829, obj)
76
+ if obj is None:
77
+ return http.HttpResponseNotFound(
78
+ f"No root page for {self.ref} in {language}")
79
+ # try:
80
+ # obj = dv.model.objects.get(
81
+ # parent=None, publisher_tree=tree)
82
+ # except dv.model.DoesNotExist:
83
+ # return http.HttpResponseNotFound(f"No row {ref} in {dv.model}")
68
84
 
69
85
  # print("20231025", index_node)
70
86
  rnd = settings.SITE.plugins.publisher.renderer
71
87
  ar = dv.create_request(request=request, renderer=rnd,
72
88
  selected_rows=[obj])
73
89
  return obj.get_publisher_response(ar)
90
+
91
+
92
+ class Login(View):
93
+ def post(self, request):
94
+ username = request.POST.get("username")
95
+ password = request.POST.get("password")
96
+
97
+ user = auth.authenticate(request, username=username, password=password)
98
+ if user is None:
99
+ return json_response({"success": False})
100
+ else:
101
+ auth.login(request, user)
102
+ return json_response({"success": True})
103
+
104
+
105
+ class Logout(View):
106
+ def get(self, request):
107
+ auth.logout(request)
108
+ return redirect(request.META.get('HTTP_REFERER', '/'))
@@ -321,16 +321,17 @@ class BleachChecker(Checker):
321
321
  model = dd.Model
322
322
 
323
323
  def get_checkable_models(self):
324
- for m in super(BleachChecker, self).get_checkable_models():
324
+ for m in super().get_checkable_models():
325
325
  if len(m._bleached_fields):
326
326
  yield m
327
327
 
328
328
  def get_checkdata_problems(self, ar, obj, fix=False):
329
329
  t = tuple(obj.fields_to_bleach(save=False))
330
330
  if len(t):
331
- fldnames = ", ".join([f.name for f, old, new in t])
331
+ fldnames = tuple([f.name for f, old, new in t])
332
332
  yield (True, _("Fields {} have unbleached content.").format(fldnames))
333
333
  if fix:
334
+ # obj.before_ui_save(ar, None)
334
335
  obj.before_ui_save(None, None)
335
336
  obj.full_clean()
336
337
  obj.save()
@@ -15,6 +15,7 @@ class Plugin(ad.Plugin):
15
15
 
16
16
  verbose_name = _("Uploads")
17
17
  menu_group = "office"
18
+ # needs_plugins = ['lino.modlib.checkdata']
18
19
 
19
20
  remove_orphaned_files = False
20
21
  """
@@ -134,7 +134,7 @@ class UploadController(dd.Model):
134
134
 
135
135
 
136
136
  class GalleryViewable(dd.Model):
137
- class Meta(object):
137
+ class Meta:
138
138
  abstract = True
139
139
 
140
140
  def get_gallery_item(self, ar):
@@ -155,7 +155,7 @@ class UploadBase(Commentable, GalleryViewable):
155
155
 
156
156
  def handle_uploaded_files(self, request, file=None):
157
157
  # ~ from django.core.files.base import ContentFile
158
- if not file and not "file" in request.FILES:
158
+ if not file and "file" not in request.FILES:
159
159
  dd.logger.debug("No 'file' has been submitted.")
160
160
  return
161
161
  uf = file or request.FILES["file"] # an UploadedFile instance
@@ -24,7 +24,7 @@ from lino.modlib.gfks.mixins import Controllable
24
24
  from lino.modlib.users.mixins import UserAuthored
25
25
 
26
26
  from lino.mixins import Referrable
27
- from lino.utils.soup import register_sanitizer
27
+ from lino.utils.soup import register_sanitizer, DATA_UPLOAD_ID
28
28
  from lino.utils.mldbc.mixins import BabelNamed
29
29
  from lino.modlib.checkdata.choicelists import Checker
30
30
  from lino.modlib.publisher.mixins import Publishable
@@ -309,6 +309,9 @@ UploadChecker.activate()
309
309
  class UploadsFolderChecker(Checker):
310
310
  verbose_name = _("Find orphaned files in uploads folder")
311
311
 
312
+ # It is no problem to have multiple upload entries pointing to a same file
313
+ # on the file system
314
+
312
315
  def get_checkdata_problems(self, ar, obj, fix=False):
313
316
  assert obj is None # this is an unbound checker
314
317
  Upload = rt.models.uploads.Upload
@@ -321,8 +324,7 @@ class UploadsFolderChecker(Checker):
321
324
  continue
322
325
  rel_filename = str(filename)[start:]
323
326
  qs = Upload.objects.filter(file=rel_filename)
324
- n = qs.count()
325
- if n == 0:
327
+ if not qs.exists():
326
328
  msg = format_lazy(
327
329
  _("File {} has no upload entry."), rel_filename)
328
330
  # print(msg)
@@ -339,9 +341,6 @@ class UploadsFolderChecker(Checker):
339
341
  # else:
340
342
  # print("{} has {} entries.".format(filename, n))
341
343
  # elif n > 1:
342
- # msg = _("Multiple upload entries for {} ").format(filename)
343
- # yield (False, msg)
344
- # This is no problem. A same file should be linkable to diffeerent controlers.
345
344
 
346
345
 
347
346
  UploadsFolderChecker.activate()
@@ -454,46 +453,81 @@ def setup_memo_commands(sender=None, **kwargs):
454
453
  ctx.update(href=ar.obj2url(self))
455
454
  if not mf.is_image():
456
455
  if not text:
457
- text = str(self)
456
+ text = self.description or str(self)
458
457
  ctx.update(text=text)
459
458
  tpl = '(<a href="{src}" target="_blank">{text}</a>'
460
459
  tpl += '| <a href="{href}">Detail</a>)'
461
460
  return format_html(tpl, **ctx)
462
461
 
463
- fmt = parse_image_spec(text, **ctx)
462
+ # fmt = parse_image_spec(text, **ctx)
464
463
  # TODO: When an image is inserted with format "wide", we should not use
465
464
  # the thumbnail but the original file. But for a PDF file we must always
466
465
  # use the img_src because the download_url is not an image.
467
- fmt.context.update(src=mf.get_image_url())
466
+ ctx.update(src=mf.get_image_url())
467
+ # fmt.context.update(src=mf.get_image_url())
468
468
  # if isinstance(fmt, SMALL_FORMATS):
469
469
  # fmt.context.update(src=img_src)
470
470
  # else:
471
471
  # print(f"20241116 {fmt} {fmt.context}")
472
472
 
473
- if not fmt.context["caption"]:
474
- fmt.context["caption"] = self.description or str(self)
473
+ # if not fmt.context["caption"]:
474
+ # fmt.context["caption"] = self.description or str(self)
475
+
476
+ if not text:
477
+ text = ""
478
+
479
+ if 'title="' not in text:
480
+ text += f' title="{self.description or str(self)}"'
481
+
482
+ if 'style="' not in text:
483
+ # text += ' style="max-height: 20ex; width: auto;"'
484
+ text += ' style="max-width:100%;height:auto;"'
485
+
486
+ ctx.update(properties=mark_safe(text))
475
487
 
488
+ # rv = format_html(
489
+ # '<a href="{href}" target="_blank"><img src="{src}"'
490
+ # + ' style="{style}" title="{caption}"/></a>', **fmt.context)
476
491
  rv = format_html(
477
492
  '<a href="{href}" target="_blank"><img src="{src}"'
478
- + ' style="{style}" title="{caption}"/></a>', **fmt.context)
493
+ + ' {properties}/></a>', **ctx)
479
494
  return rv
480
495
 
481
496
  mp = sender.plugins.memo.parser
482
497
  mp.register_django_model('file', rt.models.uploads.Upload, rnd=file2html)
483
498
 
484
499
 
485
- def on_sanitize(soup, save=False, ar=None):
500
+ def on_sanitize(soup, save=False, ar=None, mentions=None):
486
501
  # raise Exception(f"20250301")
487
- for tag in soup.find_all():
488
- tag_name = tag.name.lower()
489
- if tag_name == "img" and ar is not None and save:
502
+ if save:
503
+ for tag in soup.find_all('img'):
490
504
  if (src := tag.get('src')) and src.startswith("data:image"):
491
505
  file = base64_to_image(src)
492
- upload = rt.models.uploads.Upload(file=file, user=ar.get_user())
493
- sar = upload.get_default_table().create_request(parent=ar)
494
- upload.save_new_instance(sar)
495
- rt.models.checkdata.fix_instance(ar, upload)
496
- tag.replace_with(f'[file {upload.pk}]')
506
+ user = ar.get_user() if ar else dd.plugins.users.get_demo_user()
507
+ obj = rt.models.uploads.Upload(file=file, user=user)
508
+ tag["src"] = obj.get_media_file().get_image_url()
509
+ if ar:
510
+ sar = obj.get_default_table().create_request(parent=ar)
511
+ obj.save_new_instance(sar) # create comment or notify message
512
+ else:
513
+ obj.save()
514
+ rt.models.checkdata.fix_instance(ar, obj) # create thumbnail
515
+ # style = ''
516
+ # if (s := tag.get("style")):
517
+ # # if not s.strip().endswith(";"):
518
+ # # s += ";"
519
+ # style += s
520
+ # if (w := tag.get("width")) is not None:
521
+ # style += f" width: {w}px;"
522
+ # else:
523
+ # # 20ex comes from the default value of Format.height in rstgen.sphinxconf.sigal_image
524
+ # style += " height: 20ex;"
525
+ # tag.replace_with(f'[file {upload.pk} style="{style}"]')
526
+ tag[DATA_UPLOAD_ID] = obj.id
527
+ if mentions is not None:
528
+ for tag in soup.find_all('img'):
529
+ if (upload_id := tag.get(DATA_UPLOAD_ID)):
530
+ mentions.add(rt.models.uploads.Upload(id=upload_id))
497
531
 
498
532
 
499
533
  register_sanitizer(on_sanitize)
lino/modlib/uploads/ui.py CHANGED
@@ -72,6 +72,7 @@ class UploadDetail(dd.DetailLayout):
72
72
  upload_area type
73
73
  description
74
74
  source
75
+ memo.MentionsByTarget
75
76
  """
76
77
 
77
78
  window_size = (80, "auto")
@@ -63,7 +63,7 @@ class Previewer:
63
63
  # The bare media previewer. It doesn't do any real work.
64
64
  base_dir = None
65
65
  max_width = None
66
- PREVIEW_SUFFIXES = {'.png', '.jpg'}
66
+ PREVIEW_SUFFIXES = {'.png', '.jpg', '.jpeg'}
67
67
 
68
68
  def check_preview(self, obj, fix=False):
69
69
  return []
@@ -71,7 +71,7 @@ class Previewer:
71
71
 
72
72
  class FilePreviewer(Previewer):
73
73
  # A media previewer that builds thumbnails in a separate directory tree
74
- PREVIEW_SUFFIXES = {'.png', '.jpg', '.pdf'}
74
+ PREVIEW_SUFFIXES = {'.png', '.jpg', '.jpeg', '.pdf'}
75
75
 
76
76
  def __init__(self, base_dir=None, max_width=None):
77
77
  self.base_dir = base_dir
@@ -57,14 +57,13 @@ class Plugin(ad.Plugin):
57
57
 
58
58
  _demo_user = None # the cached User object
59
59
 
60
- def get_demo_user(self, checker, obj):
60
+ def get_demo_user(self):
61
61
  if self.demo_username is None:
62
62
  return None
63
63
  if self._demo_user is None:
64
64
  User = self.site.models.users.User
65
65
  try:
66
- self._demo_user = User.objects.get(
67
- username=self.demo_username)
66
+ self._demo_user = User.objects.get(username=self.demo_username)
68
67
  except User.DoesNotExist:
69
68
  msg = "Invalid username '{0}' in `demo_username` "
70
69
  msg = msg.format(self.demo_username)
@@ -18,6 +18,8 @@ from lino.core.roles import SiteAdmin
18
18
  from lino.core import auth, layouts
19
19
  from lino.core.actions import SubmitInsert
20
20
 
21
+ MSG_TAKEN = _("The username {} is taken. Please choose another one")
22
+
21
23
 
22
24
  def send_welcome_email(ar, obj, recipients):
23
25
  sender = settings.SERVER_EMAIL
@@ -36,14 +38,10 @@ class CheckedSubmitInsert(SubmitInsert):
36
38
 
37
39
  def run_from_ui(self, ar, **kw):
38
40
  obj = ar.create_instance_from_request()
39
- qs = obj.__class__.objects.filter(username=obj.username)
40
- if len(qs) > 0:
41
- msg = _("The username {} is taken. " "Please choose another one").format(
42
- obj.username
43
- )
44
-
45
- ar.error(msg)
46
- return
41
+ if obj.username:
42
+ if obj.__class__.objects.filter(username=obj.username).exist():
43
+ ar.error(MSG_TAKEN.format(obj.username))
44
+ return
47
45
 
48
46
  def ok(ar2):
49
47
  SubmitInsert.run_from_ui(self, ar, **kw)
@@ -96,16 +94,13 @@ class CreateAccount(dd.Action):
96
94
  pv = ar.action_param_values
97
95
  if not pv["username"]:
98
96
  pv["username"] = pv["email"]
99
- qs = User.objects.filter(username=pv["username"])
100
- if len(qs) > 0:
101
- msg = _("The username {} is taken. " "Please choose another one").format(
102
- pv.username
103
- )
104
- ar.error(msg)
105
- # ar.set_response(close_window=False)
106
- return
97
+ if pv["username"]:
98
+ if User.objects.filter(username=pv["username"]).exist():
99
+ ar.error(MSG_TAKEN.format(pv.username))
100
+ # ar.set_response(close_window=False)
101
+ return
107
102
 
108
- validate_password(pv['password'])
103
+ validate_password(pv['password'])
109
104
 
110
105
  ut = UserTypes.get_by_name(dd.plugins.users.user_type_new)
111
106
  # pv.pop('social_auth_links')
@@ -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)
@@ -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
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)