lino 25.4.1__py3-none-any.whl → 25.4.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.
- lino/__init__.py +1 -1
- lino/api/dd.py +8 -10
- lino/core/dbtables.py +2 -3
- lino/core/fields.py +30 -15
- lino/core/kernel.py +33 -7
- lino/core/renderer.py +3 -3
- lino/core/requests.py +24 -9
- lino/core/site.py +38 -29
- lino/core/tables.py +27 -29
- lino/help_texts.py +7 -3
- lino/management/commands/demotest.py +16 -22
- lino/mixins/__init__.py +32 -35
- lino/mixins/dupable.py +2 -4
- lino/mixins/registrable.py +5 -2
- lino/modlib/about/models.py +2 -2
- lino/modlib/changes/models.py +2 -2
- lino/modlib/checkdata/choicelists.py +4 -4
- lino/modlib/checkdata/models.py +2 -2
- lino/modlib/comments/fixtures/demo2.py +4 -0
- lino/modlib/comments/models.py +1 -1
- lino/modlib/dupable/mixins.py +3 -5
- lino/modlib/export_excel/__init__.py +6 -8
- lino/modlib/export_excel/models.py +1 -3
- lino/modlib/extjs/ext_renderer.py +1 -1
- lino/modlib/extjs/views.py +1 -1
- lino/modlib/help/fixtures/demo2.py +3 -2
- lino/modlib/jinja/mixins.py +20 -4
- lino/modlib/linod/mixins.py +17 -12
- lino/modlib/linod/models.py +1 -1
- lino/modlib/memo/mixins.py +3 -2
- lino/modlib/notify/api.py +33 -14
- lino/modlib/notify/mixins.py +16 -1
- lino/modlib/notify/models.py +10 -25
- lino/modlib/printing/mixins.py +1 -1
- lino/modlib/publisher/models.py +55 -6
- lino/modlib/publisher/ui.py +3 -3
- lino/modlib/publisher/views.py +9 -2
- lino/modlib/system/models.py +1 -1
- lino/modlib/uploads/mixins.py +1 -1
- lino/modlib/uploads/models.py +2 -2
- lino/modlib/users/actions.py +2 -4
- lino/modlib/users/models.py +13 -19
- lino/modlib/users/ui.py +1 -1
- lino/sphinxcontrib/logo/templates/part-of-synodalsoft.html +0 -1
- lino/utils/format_date.py +10 -5
- lino/utils/media.py +16 -31
- {lino-25.4.1.dist-info → lino-25.4.3.dist-info}/METADATA +1 -1
- {lino-25.4.1.dist-info → lino-25.4.3.dist-info}/RECORD +51 -52
- lino/management/commands/monitor.py +0 -160
- {lino-25.4.1.dist-info → lino-25.4.3.dist-info}/WHEEL +0 -0
- {lino-25.4.1.dist-info → lino-25.4.3.dist-info}/licenses/AUTHORS.rst +0 -0
- {lino-25.4.1.dist-info → lino-25.4.3.dist-info}/licenses/COPYING +0 -0
lino/modlib/notify/api.py
CHANGED
@@ -15,6 +15,8 @@ LIVE_PANEL_UPDATE = "PANEL_UPDATE"
|
|
15
15
|
|
16
16
|
NOTIFICATION_TYPES = [NOTIFICATION, CHAT, LIVE_PANEL_UPDATE]
|
17
17
|
|
18
|
+
DONT_CATCH = True
|
19
|
+
|
18
20
|
|
19
21
|
# def send_panel_update(actorIDs: list[str], pk: int):
|
20
22
|
def send_panel_update(data):
|
@@ -27,16 +29,24 @@ def send_panel_update(data):
|
|
27
29
|
from channels.layers import get_channel_layer
|
28
30
|
from asgiref.sync import async_to_sync
|
29
31
|
|
32
|
+
if settings.SITE.loading_from_dump:
|
33
|
+
return
|
34
|
+
|
30
35
|
channel_layer = get_channel_layer()
|
31
36
|
|
32
37
|
data.update(type=LIVE_PANEL_UPDATE)
|
33
38
|
|
34
|
-
|
39
|
+
if DONT_CATCH:
|
35
40
|
async_to_sync(channel_layer.send)(
|
36
41
|
CHANNEL_NAME, {"type": "send.panel.update", "text": json.dumps(data)}
|
37
42
|
)
|
38
|
-
|
39
|
-
|
43
|
+
else:
|
44
|
+
try:
|
45
|
+
async_to_sync(channel_layer.send)(
|
46
|
+
CHANNEL_NAME, {"type": "send.panel.update", "text": json.dumps(data)}
|
47
|
+
)
|
48
|
+
except Exception as e:
|
49
|
+
logger.exception(e)
|
40
50
|
|
41
51
|
|
42
52
|
def send_notification(
|
@@ -54,11 +64,9 @@ def send_notification(
|
|
54
64
|
OK button of their desktop notification.
|
55
65
|
|
56
66
|
"""
|
57
|
-
if user is None:
|
67
|
+
if user is None or settings.SITE.loading_from_dump:
|
58
68
|
return
|
59
69
|
|
60
|
-
created = created.strftime("%a %d %b %Y %H:%M")
|
61
|
-
|
62
70
|
if dd.get_plugin_setting("linod", "use_channels"):
|
63
71
|
# importing channels at module level would cause certain things to fail
|
64
72
|
# when channels isn't installed, e.g. `manage.py prep` in `lino_book.projects.workflows`.
|
@@ -73,7 +81,7 @@ def send_notification(
|
|
73
81
|
subject=subject,
|
74
82
|
id=primary_key,
|
75
83
|
body=body,
|
76
|
-
created=created,
|
84
|
+
created=created.strftime("%a %d %b %Y %H:%M"),
|
77
85
|
action_url=action_url,
|
78
86
|
)
|
79
87
|
|
@@ -86,6 +94,7 @@ def send_notification(
|
|
86
94
|
},
|
87
95
|
) # data
|
88
96
|
except Exception as e:
|
97
|
+
raise # 20250415
|
89
98
|
logger.exception(e)
|
90
99
|
|
91
100
|
if dd.plugins.notify.use_push_api:
|
@@ -95,7 +104,7 @@ def send_notification(
|
|
95
104
|
body=body,
|
96
105
|
action_title=action_title,
|
97
106
|
)
|
98
|
-
|
107
|
+
if DONT_CATCH:
|
99
108
|
async_to_sync(channel_layer.send)(
|
100
109
|
CHANNEL_NAME,
|
101
110
|
{
|
@@ -104,9 +113,19 @@ def send_notification(
|
|
104
113
|
"user_id": user.id if user is not None else None,
|
105
114
|
},
|
106
115
|
)
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
116
|
+
else:
|
117
|
+
try:
|
118
|
+
async_to_sync(channel_layer.send)(
|
119
|
+
CHANNEL_NAME,
|
120
|
+
{
|
121
|
+
"type": "send.push",
|
122
|
+
"data": data,
|
123
|
+
"user_id": user.id if user is not None else None,
|
124
|
+
},
|
125
|
+
)
|
126
|
+
except (
|
127
|
+
ChannelFull
|
128
|
+
) as e: # happens on older Pythons and can cause many tracebacks
|
129
|
+
# logger.exception(e)
|
130
|
+
# logger.warning(str(e))
|
131
|
+
pass
|
lino/modlib/notify/mixins.py
CHANGED
@@ -1,12 +1,27 @@
|
|
1
|
-
# Copyright 2016-
|
1
|
+
# Copyright 2016-2025 Rumma & Ko Ltd
|
2
2
|
# License: GNU Affero General Public License v3 (see file COPYING for details)
|
3
3
|
|
4
4
|
from lino.utils.html import E, tostring, format_html, mark_safe
|
5
|
+
from lino.core.utils import full_model_name as fmn
|
5
6
|
from lino.api import dd, rt, _
|
6
7
|
|
7
8
|
# PUBLIC_GROUP = "all_users_channel"
|
8
9
|
|
9
10
|
|
11
|
+
def get_updatables(instance, ar=None):
|
12
|
+
data = {
|
13
|
+
"actorIDs": instance.updatable_panels,
|
14
|
+
"pk": instance.pk,
|
15
|
+
"model": f"{fmn(instance)}",
|
16
|
+
"mk": None, "master_model": None
|
17
|
+
}
|
18
|
+
if ar is None:
|
19
|
+
return data
|
20
|
+
if mi := ar.master_instance:
|
21
|
+
data.update(mk=mi.pk, master_model=f"{fmn(mi)}")
|
22
|
+
return data
|
23
|
+
|
24
|
+
|
10
25
|
class ChangeNotifier(dd.Model):
|
11
26
|
|
12
27
|
class Meta:
|
lino/modlib/notify/models.py
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
# -*- coding: UTF-8 -*-
|
2
|
-
# Copyright 2011-
|
2
|
+
# Copyright 2011-2025 Rumma & Ko Ltd
|
3
3
|
# License: GNU Affero General Public License v3 (see file COPYING for details)
|
4
4
|
|
5
5
|
# import json
|
@@ -9,7 +9,6 @@ from datetime import timedelta
|
|
9
9
|
|
10
10
|
from django.db import models
|
11
11
|
from django.conf import settings
|
12
|
-
from django.utils import timezone
|
13
12
|
from django.utils import translation
|
14
13
|
|
15
14
|
from lino import logger
|
@@ -25,6 +24,7 @@ from lino.modlib.users.mixins import UserAuthored, My
|
|
25
24
|
from lino.modlib.linod.choicelists import schedule_daily, schedule_often
|
26
25
|
from lino.modlib.office.roles import OfficeUser
|
27
26
|
|
27
|
+
from .mixins import get_updatables
|
28
28
|
from .choicelists import MessageTypes, MailModes
|
29
29
|
from .api import send_notification, NOTIFICATION, send_panel_update
|
30
30
|
|
@@ -57,7 +57,7 @@ class MarkAllSeen(dd.Action):
|
|
57
57
|
user=ar.get_user(), seen__isnull=True
|
58
58
|
)
|
59
59
|
for obj in qs:
|
60
|
-
obj.seen =
|
60
|
+
obj.seen = dd.now()
|
61
61
|
obj.save()
|
62
62
|
ar.success(
|
63
63
|
eval_js='window.top.document.querySelectorAll(".'
|
@@ -81,7 +81,7 @@ class MarkSeen(dd.Action):
|
|
81
81
|
|
82
82
|
def run_from_ui(self, ar):
|
83
83
|
for obj in ar.selected_rows:
|
84
|
-
obj.seen =
|
84
|
+
obj.seen = dd.now()
|
85
85
|
obj.save()
|
86
86
|
ar.success(refresh_all=True)
|
87
87
|
|
@@ -297,9 +297,9 @@ class Message(UserAuthored, Controllable, Created):
|
|
297
297
|
# dd.logger.info("20240902 %s", sender)
|
298
298
|
ar.send_email(subject, sender, body, [user.email])
|
299
299
|
for msg in messages:
|
300
|
-
msg.sent =
|
300
|
+
msg.sent = dd.now()
|
301
301
|
if dd.plugins.notify.mark_seen_when_sent:
|
302
|
-
msg.seen =
|
302
|
+
msg.seen = dd.now()
|
303
303
|
msg.save()
|
304
304
|
|
305
305
|
# def send_browser_message_for_all_users(self, user):
|
@@ -500,7 +500,7 @@ if remove_after:
|
|
500
500
|
def clear_seen_messages(ar):
|
501
501
|
Message = rt.models.notify.Message
|
502
502
|
qs = Message.objects.filter(
|
503
|
-
created__lt=
|
503
|
+
created__lt=dd.now() - timedelta(days=remove_after)
|
504
504
|
)
|
505
505
|
what = "notification messages older than {} days".format(remove_after)
|
506
506
|
if dd.plugins.notify.keep_unseen:
|
@@ -522,26 +522,11 @@ if remove_after:
|
|
522
522
|
|
523
523
|
|
524
524
|
@dd.receiver(dd.post_ui_save)
|
525
|
-
def notify_panels(sender, instance, ar, **kwargs):
|
525
|
+
def notify_panels(sender, instance, ar=None, **kwargs):
|
526
526
|
if (
|
527
527
|
not dd.get_plugin_setting("linod", "use_channels", False)
|
528
|
-
or not
|
529
|
-
or not hasattr(ar, "rqdata")
|
528
|
+
or not instance.updatable_panels
|
530
529
|
):
|
531
530
|
return
|
532
|
-
data =
|
533
|
-
"actorIDs": ups,
|
534
|
-
"pk": instance.pk,
|
535
|
-
"model": f"{instance._meta.app_label}.{instance.__class__.__name__}",
|
536
|
-
}
|
537
|
-
data.update(
|
538
|
-
**(
|
539
|
-
{
|
540
|
-
"mk": (mi := ar.master_instance).pk,
|
541
|
-
"master_model": f"{mi._meta.app_label}.{mi.__class__.__name__}",
|
542
|
-
}
|
543
|
-
if ar.master_instance
|
544
|
-
else {"mk": None, "master_model": None}
|
545
|
-
)
|
546
|
-
)
|
531
|
+
data = get_updatables(instance, ar)
|
547
532
|
send_panel_update(data)
|
lino/modlib/printing/mixins.py
CHANGED
@@ -260,7 +260,7 @@ class CachedPrintableChecker(Checker):
|
|
260
260
|
model = CachedPrintable
|
261
261
|
verbose_name = _("Check for missing target files")
|
262
262
|
|
263
|
-
def get_checkdata_problems(self, obj, fix=False):
|
263
|
+
def get_checkdata_problems(self, ar, obj, fix=False):
|
264
264
|
if obj.build_time is not None:
|
265
265
|
t = obj.get_cache_mtime()
|
266
266
|
if t is None:
|
lino/modlib/publisher/models.py
CHANGED
@@ -2,12 +2,6 @@
|
|
2
2
|
# Copyright 2012-2024 Rumma & Ko Ltd
|
3
3
|
# License: GNU Affero General Public License v3 (see file COPYING for details)
|
4
4
|
|
5
|
-
from .ui import *
|
6
|
-
from lino.api import rt, dd
|
7
|
-
|
8
|
-
from .choicelists import PublishingStates, PageFillers, SpecialPages
|
9
|
-
from .mixins import Publishable
|
10
|
-
|
11
5
|
from html import escape
|
12
6
|
from django.db import models
|
13
7
|
from django.http import HttpResponseRedirect
|
@@ -15,6 +9,11 @@ from django.conf import settings
|
|
15
9
|
from django.utils import translation
|
16
10
|
from django.utils.translation import pgettext_lazy
|
17
11
|
|
12
|
+
from lorem import get_paragraph
|
13
|
+
from django.utils import translation
|
14
|
+
from django.conf import settings
|
15
|
+
|
16
|
+
|
18
17
|
# from django.utils.translation import get_language
|
19
18
|
from django.utils.html import mark_safe
|
20
19
|
|
@@ -38,6 +37,10 @@ from lino.mixins.polymorphic import Polymorphic
|
|
38
37
|
from lino_xl.lib.topics.mixins import Taggable
|
39
38
|
# from .utils import render_node
|
40
39
|
|
40
|
+
from lino.api import rt, dd
|
41
|
+
from .choicelists import PublishingStates, PageFillers, SpecialPages
|
42
|
+
from .mixins import Publishable
|
43
|
+
from .ui import *
|
41
44
|
|
42
45
|
# class Node(Referrable, Hierarchical, Sequenced, Previewable, Publishable, Commentable):
|
43
46
|
# Polymorphic,
|
@@ -401,3 +404,49 @@ def update_publisher_pages(ar):
|
|
401
404
|
prev = obj
|
402
405
|
count += 1
|
403
406
|
ar.logger.info("%d pages have been updated.", count)
|
407
|
+
|
408
|
+
|
409
|
+
def make_demo_pages(pages_desc):
|
410
|
+
# Translation = rt.models.pages.Translation
|
411
|
+
# for lc in settings.SITE.LANGUAGE_CHOICES:
|
412
|
+
# language = lc[0]
|
413
|
+
# kwargs = dict(language=language, ref='index')
|
414
|
+
# with translation.override(language):
|
415
|
+
|
416
|
+
parent_nodes = []
|
417
|
+
for lng in settings.SITE.languages:
|
418
|
+
counter = {None: 0}
|
419
|
+
# count = 0
|
420
|
+
home_page = Page.objects.get(
|
421
|
+
special_page=SpecialPages.home, language=lng.django_code)
|
422
|
+
|
423
|
+
with translation.override(lng.django_code):
|
424
|
+
|
425
|
+
def make_pages(pages, parent=None):
|
426
|
+
for page in pages:
|
427
|
+
if len(page) != 3:
|
428
|
+
raise Exception(f"Oops {page}")
|
429
|
+
title, body, children = page
|
430
|
+
kwargs = dict(title=title)
|
431
|
+
if body is None:
|
432
|
+
kwargs.update(body=get_paragraph())
|
433
|
+
else:
|
434
|
+
kwargs.update(body=body)
|
435
|
+
if parent is None:
|
436
|
+
# kwargs.update(ref='index')
|
437
|
+
continue # home page is created by SpecialPages
|
438
|
+
if lng.suffix:
|
439
|
+
kwargs.update(
|
440
|
+
translated_from=parent_nodes[counter[None]])
|
441
|
+
kwargs.update(language=lng.django_code)
|
442
|
+
if dd.is_installed("publisher"):
|
443
|
+
kwargs.update(publishing_state='published')
|
444
|
+
obj = Page(parent=parent, **kwargs)
|
445
|
+
yield obj
|
446
|
+
if not lng.suffix:
|
447
|
+
parent_nodes.append(obj)
|
448
|
+
counter[None] += 1
|
449
|
+
# print("20230324", title, kwargs)
|
450
|
+
yield make_pages(children, obj)
|
451
|
+
|
452
|
+
yield make_pages(pages_desc, parent=home_page)
|
lino/modlib/publisher/ui.py
CHANGED
@@ -92,7 +92,7 @@ else:
|
|
92
92
|
|
93
93
|
|
94
94
|
class PageDetail(dd.DetailLayout):
|
95
|
-
main = "first_panel
|
95
|
+
main = "general first_panel more"
|
96
96
|
|
97
97
|
first_panel = dd.Panel(
|
98
98
|
"""
|
@@ -129,11 +129,11 @@ class PageDetail(dd.DetailLayout):
|
|
129
129
|
# """
|
130
130
|
|
131
131
|
right_panel = """
|
132
|
-
ref
|
132
|
+
ref language
|
133
133
|
parent seqno
|
134
134
|
child_node_depth
|
135
135
|
#page_type filler
|
136
|
-
|
136
|
+
publishing_state special_page
|
137
137
|
publisher.TranslationsByPage
|
138
138
|
"""
|
139
139
|
|
lino/modlib/publisher/views.py
CHANGED
@@ -5,6 +5,7 @@
|
|
5
5
|
from django.conf import settings
|
6
6
|
from django import http
|
7
7
|
from django.views.generic import View
|
8
|
+
from django.utils import translation
|
8
9
|
|
9
10
|
from lino.api import dd
|
10
11
|
from lino.core import auth
|
@@ -52,7 +53,13 @@ class Index(View):
|
|
52
53
|
def get(self, request, pk=1):
|
53
54
|
rnd = settings.SITE.plugins.publisher.renderer
|
54
55
|
dv = settings.SITE.models.publisher.Pages
|
55
|
-
|
56
|
+
if len(settings.SITE.languages) == 1:
|
57
|
+
# language = settings.SITE.languages[0].django_code
|
58
|
+
language = translation.get_language()
|
59
|
+
else:
|
60
|
+
language = request.LANGUAGE_CODE
|
61
|
+
index_node = dv.model.objects.get(ref="index", language=language)
|
56
62
|
# print("20231025", index_node)
|
57
|
-
ar = dv.create_request(request=request, renderer=rnd,
|
63
|
+
ar = dv.create_request(request=request, renderer=rnd,
|
64
|
+
selected_rows=[index_node])
|
58
65
|
return index_node.get_publisher_response(ar)
|
lino/modlib/system/models.py
CHANGED
@@ -326,7 +326,7 @@ class BleachChecker(Checker):
|
|
326
326
|
if len(m._bleached_fields):
|
327
327
|
yield m
|
328
328
|
|
329
|
-
def get_checkdata_problems(self, obj, fix=False):
|
329
|
+
def get_checkdata_problems(self, ar, obj, fix=False):
|
330
330
|
t = tuple(obj.fields_to_bleach(save=False))
|
331
331
|
if len(t):
|
332
332
|
fldnames = ", ".join([f.name for f, old, new in t])
|
lino/modlib/uploads/mixins.py
CHANGED
@@ -72,7 +72,7 @@ def make_uploaded_file(filename, src=None, upload_date=None):
|
|
72
72
|
def base64_to_image(imgstring):
|
73
73
|
type, file = imgstring.split(";base64,")
|
74
74
|
imgdata = base64.b64decode(file)
|
75
|
-
return make_captured_image(imgdata,
|
75
|
+
return make_captured_image(imgdata, dd.now(), ext=f".{type.split('/')[1]}")
|
76
76
|
|
77
77
|
|
78
78
|
def make_captured_image(imgdata, upload_date=None, filename=None, ext='.jpg'):
|
lino/modlib/uploads/models.py
CHANGED
@@ -285,7 +285,7 @@ class UploadChecker(Checker):
|
|
285
285
|
verbose_name = _("Check metadata of upload files")
|
286
286
|
model = "uploads.Upload"
|
287
287
|
|
288
|
-
def get_checkdata_problems(self, obj, fix=False):
|
288
|
+
def get_checkdata_problems(self, ar, obj, fix=False):
|
289
289
|
if obj.file:
|
290
290
|
if not exists(join(settings.MEDIA_ROOT, obj.file.name)):
|
291
291
|
yield (
|
@@ -311,7 +311,7 @@ UploadChecker.activate()
|
|
311
311
|
class UploadsFolderChecker(Checker):
|
312
312
|
verbose_name = _("Find orphaned files in uploads folder")
|
313
313
|
|
314
|
-
def get_checkdata_problems(self, obj, fix=False):
|
314
|
+
def get_checkdata_problems(self, ar, obj, fix=False):
|
315
315
|
assert obj is None # this is an unbound checker
|
316
316
|
Upload = rt.models.uploads.Upload
|
317
317
|
pth = dd.plugins.uploads.uploads_root
|
lino/modlib/users/actions.py
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
# -*- coding: UTF-8 -*-
|
2
|
-
# Copyright 2011-
|
2
|
+
# Copyright 2011-2025 Rumma & Ko Ltd
|
3
3
|
# License: GNU Affero General Public License v3 (see file COPYING for details)
|
4
4
|
|
5
5
|
import datetime
|
@@ -9,7 +9,6 @@ from lino.utils.html import E, tostring
|
|
9
9
|
from django.db import models
|
10
10
|
from django.conf import settings
|
11
11
|
from django.http import HttpResponse
|
12
|
-
from django.utils import timezone
|
13
12
|
from django.utils.translation import gettext
|
14
13
|
from django.contrib.auth.password_validation import validate_password
|
15
14
|
from django.core.exceptions import ValidationError
|
@@ -115,7 +114,6 @@ class CreateAccount(dd.Action):
|
|
115
114
|
obj.full_clean()
|
116
115
|
obj.save()
|
117
116
|
|
118
|
-
|
119
117
|
ar.selected_rows = [obj]
|
120
118
|
recipients = ["{} <{}>".format(obj.get_full_name(), obj.email)]
|
121
119
|
send_welcome_email(ar, obj, recipients)
|
@@ -452,7 +450,7 @@ class SignOut(dd.Action):
|
|
452
450
|
def validate_sessions_limit(request):
|
453
451
|
if dd.plugins.users.active_sessions_limit == -1:
|
454
452
|
return
|
455
|
-
qs = rt.models.sessions.Session.objects.filter(expire_date__gt=
|
453
|
+
qs = rt.models.sessions.Session.objects.filter(expire_date__gt=dd.now())
|
456
454
|
if request.session.session_key:
|
457
455
|
qs = qs.exclude(session_key=request.session.session_key)
|
458
456
|
if qs.count() >= dd.plugins.users.active_sessions_limit:
|
lino/modlib/users/models.py
CHANGED
@@ -1,7 +1,9 @@
|
|
1
1
|
# -*- coding: UTF-8 -*-
|
2
|
-
# Copyright 2011-
|
2
|
+
# Copyright 2011-2025 Rumma & Ko Ltd
|
3
3
|
# License: GNU Affero General Public License v3 (see file COPYING for details)
|
4
4
|
|
5
|
+
import random
|
6
|
+
import string
|
5
7
|
from datetime import timedelta
|
6
8
|
from django.db import models
|
7
9
|
from django.db.models import Q
|
@@ -9,15 +11,17 @@ from django.conf import settings
|
|
9
11
|
from django.contrib.auth.base_user import AbstractBaseUser, BaseUserManager
|
10
12
|
from django.utils import timezone
|
11
13
|
|
12
|
-
from lino.utils.html import E
|
13
14
|
from lino.api import dd, rt, _
|
14
15
|
from lino.core import userprefs
|
15
|
-
|
16
|
-
# from lino.core.fields import NullCharField
|
16
|
+
from lino.core.roles import Supervisor
|
17
17
|
from lino.core.roles import SiteAdmin
|
18
|
-
|
18
|
+
# from lino.core.fields import NullCharField
|
19
19
|
from lino.mixins import CreatedModified, Contactable
|
20
20
|
from lino.mixins import DateRange
|
21
|
+
from lino.modlib.about.choicelists import TimeZones, DateFormats
|
22
|
+
from lino.modlib.publisher.mixins import Publishable
|
23
|
+
from lino.modlib.about.models import About
|
24
|
+
from lino.utils.html import E
|
21
25
|
|
22
26
|
from .choicelists import UserTypes
|
23
27
|
from .mixins import UserAuthored # , TimezoneHolder
|
@@ -25,14 +29,8 @@ from .actions import ChangePassword, SignOut, CheckedSubmitInsert
|
|
25
29
|
from .actions import SendWelcomeMail, SignIn, ConnectAccount
|
26
30
|
from .actions import SendWelcomeMail, CreateAccount, ResetPassword, VerifyUser, VerifyMe
|
27
31
|
|
28
|
-
|
29
|
-
from lino.modlib.about.choicelists import TimeZones, DateFormats
|
30
|
-
from lino.modlib.publisher.mixins import Publishable
|
32
|
+
from .ui import *
|
31
33
|
|
32
|
-
from lino.core.roles import Supervisor
|
33
|
-
|
34
|
-
import random
|
35
|
-
import string
|
36
34
|
|
37
35
|
if multi_ledger := dd.is_installed("ledgers"):
|
38
36
|
from lino_xl.lib.ledgers.actions import SubscribeToLedger
|
@@ -100,7 +98,7 @@ class User(AbstractBaseUser, Contactable, CreatedModified, Publishable, DateRang
|
|
100
98
|
if dd.plugins.users.with_nickname:
|
101
99
|
nickname = models.CharField(_("Nickname"), max_length=50, blank=True)
|
102
100
|
else:
|
103
|
-
nickname = dd.DummyField()
|
101
|
+
nickname = dd.DummyField("")
|
104
102
|
first_name = models.CharField(_("First name"), max_length=30, blank=True)
|
105
103
|
last_name = models.CharField(_("Last name"), max_length=30, blank=True)
|
106
104
|
remarks = models.TextField(_("Remarks"), blank=True) # ,null=True)
|
@@ -156,7 +154,7 @@ class User(AbstractBaseUser, Contactable, CreatedModified, Publishable, DateRang
|
|
156
154
|
|
157
155
|
def must_verify(self):
|
158
156
|
self.verification_code = id_generator(12)
|
159
|
-
self.verification_code_sent_on =
|
157
|
+
self.verification_code_sent_on = dd.now()
|
160
158
|
|
161
159
|
def is_verified(self):
|
162
160
|
return not self.verification_code
|
@@ -170,7 +168,7 @@ class User(AbstractBaseUser, Contactable, CreatedModified, Publishable, DateRang
|
|
170
168
|
return (
|
171
169
|
self.verification_code_sent_on
|
172
170
|
+ timedelta(minutes=dd.plugins.users.verification_code_expires)
|
173
|
-
<
|
171
|
+
< dd.now()
|
174
172
|
)
|
175
173
|
|
176
174
|
def get_as_user(self):
|
@@ -451,8 +449,6 @@ class Permission(dd.Model):
|
|
451
449
|
abstract = True
|
452
450
|
|
453
451
|
|
454
|
-
from lino.modlib.about.models import About
|
455
|
-
|
456
452
|
About.sign_in = SignIn()
|
457
453
|
About.reset_password = ResetPassword()
|
458
454
|
About.verify_user = VerifyUser()
|
@@ -478,7 +474,5 @@ def setup_memo_commands(sender=None, **kwargs):
|
|
478
474
|
)
|
479
475
|
|
480
476
|
|
481
|
-
from .ui import *
|
482
|
-
|
483
477
|
if dd.get_plugin_setting("users", "third_party_authentication"):
|
484
478
|
Me.connect_account = ConnectAccount()
|
lino/modlib/users/ui.py
CHANGED
@@ -118,7 +118,7 @@ class AllUsers(Users):
|
|
118
118
|
class UsersOverview(Users):
|
119
119
|
required_roles = set([])
|
120
120
|
column_names = "username user_type language"
|
121
|
-
exclude =
|
121
|
+
exclude = models.Q(user_type="")
|
122
122
|
# abstract = not settings.SITE.is_demo_site
|
123
123
|
detail_layout = None
|
124
124
|
|
@@ -5,7 +5,6 @@ This website is part of the Synodalsoft project:
|
|
5
5
|
<a class="reference external" href="https://using.lino-framework.org">User Guide</a> |
|
6
6
|
<a class="reference external" href="https://hosting.lino-framework.org">Hosting Guide</a> |
|
7
7
|
<a class="reference external" href="https://dev.lino-framework.org">Developer Guide</a> |
|
8
|
-
<a class="reference external" href="https://community.lino-framework.org">Community Guide</a> |
|
9
8
|
<a class="reference external" href="https://luc.lino-framework.org">Luc’s blog</a>
|
10
9
|
</p><p>
|
11
10
|
<a href="https://www.synodalsoft.net">
|
lino/utils/format_date.py
CHANGED
@@ -109,13 +109,18 @@ def day_and_weekday(d):
|
|
109
109
|
# return d.strftime("%a%d")
|
110
110
|
|
111
111
|
|
112
|
-
def
|
113
|
-
# "format time
|
112
|
+
def fts(t):
|
113
|
+
# "format time short"
|
114
|
+
return t.strftime(settings.SITE.time_format_strftime)
|
115
|
+
|
116
|
+
|
117
|
+
def fdtl(t):
|
118
|
+
# "format datetime long"
|
114
119
|
return "{} {}".format(
|
115
120
|
t.strftime(settings.SITE.date_format_strftime),
|
116
121
|
t.strftime(settings.SITE.time_format_strftime))
|
117
122
|
|
118
123
|
|
119
|
-
def
|
120
|
-
# "format
|
121
|
-
return "{} ({})".format(
|
124
|
+
def fdtf(t):
|
125
|
+
# "format datetime full"
|
126
|
+
return "{} ({})".format(fdtl(t), naturaltime(t))
|
lino/utils/media.py
CHANGED
@@ -1,14 +1,12 @@
|
|
1
1
|
# -*- coding: UTF-8 -*-
|
2
|
-
# Copyright 2013-
|
2
|
+
# Copyright 2013-2025 Rumma & Ko Ltd
|
3
3
|
# License: GNU Affero General Public License v3 (see file COPYING for details)
|
4
4
|
"""Defines the :class:`MediaFile` class.
|
5
5
|
"""
|
6
6
|
|
7
|
-
from os.path import join
|
8
7
|
from pathlib import Path
|
9
|
-
|
10
8
|
from django.conf import settings
|
11
|
-
from lino.core.utils import is_devserver
|
9
|
+
# from lino.core.utils import is_devserver
|
12
10
|
|
13
11
|
# davlink = settings.SITE.plugins.get('davlink', None)
|
14
12
|
# has_davlink = davlink is not None and settings.SITE.use_java
|
@@ -24,7 +22,19 @@ class MediaFile(object):
|
|
24
22
|
:attr:`webdav_root <lino.core.site.Site.webdav_root>`,
|
25
23
|
:attr:`webdav_protocol <lino.core.site.Site.webdav_protocol>`
|
26
24
|
and
|
27
|
-
:attr:`webdav_url <lino.core.site.Site.webdav_url
|
25
|
+
:attr:`webdav_url <lino.core.site.Site.webdav_url>`.
|
26
|
+
|
27
|
+
.. attribute:: path
|
28
|
+
|
29
|
+
A :class:`pathlib.Path` naming the file on the server's file system.
|
30
|
+
|
31
|
+
.. attribute:: url
|
32
|
+
|
33
|
+
The URL to use for getting this file from a web client.
|
34
|
+
|
35
|
+
Used by :meth:`lino.modlib.jinja.XMLMaker.get_xml_file`,
|
36
|
+
:attr:`lino.core.tables.AbstractTable.export_excel` and others.
|
37
|
+
|
28
38
|
"""
|
29
39
|
|
30
40
|
def __init__(self, editable, *parts):
|
@@ -37,7 +47,7 @@ class MediaFile(object):
|
|
37
47
|
# webdav server and a protocol handler.
|
38
48
|
# if is_devserver():
|
39
49
|
if False:
|
40
|
-
url = "file://" +
|
50
|
+
url = "file://" + settings.SITE.webdav_root + "/".join(parts)
|
41
51
|
else:
|
42
52
|
url = settings.SITE.webdav_url + "/".join(parts)
|
43
53
|
if settings.SITE.webdav_protocol:
|
@@ -48,31 +58,6 @@ class MediaFile(object):
|
|
48
58
|
self.url = url
|
49
59
|
self.path = path
|
50
60
|
|
51
|
-
# @property
|
52
|
-
# def path(self):
|
53
|
-
# "Return the full filename on the server as a Path object."
|
54
|
-
|
55
|
-
# @property
|
56
|
-
# def name(self):
|
57
|
-
# "return the filename on the server"
|
58
|
-
# if self.editable and (has_davlink or settings.SITE.webdav_protocol):
|
59
|
-
# return join(settings.SITE.webdav_root, *self.parts)
|
60
|
-
# return join(settings.MEDIA_ROOT, *self.parts)
|
61
|
-
|
62
|
-
# def get_url(self, request):
|
63
|
-
# "return the url that points to file on the server"
|
64
|
-
# if self.editable and request is not None:
|
65
|
-
# if is_devserver():
|
66
|
-
# url = "file://" + join(settings.SITE.webdav_root, *self.parts)
|
67
|
-
# else:
|
68
|
-
# url = settings.SITE.webdav_url + "/".join(self.parts)
|
69
|
-
# url = request.build_absolute_uri(url)
|
70
|
-
# if settings.SITE.webdav_protocol:
|
71
|
-
# url = settings.SITE.webdav_protocol + "://" + url
|
72
|
-
# return url
|
73
|
-
#
|
74
|
-
# return settings.SITE.build_media_url(*self.parts)
|
75
|
-
|
76
61
|
|
77
62
|
class TmpMediaFile(MediaFile):
|
78
63
|
def __init__(self, ar, fmt):
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: lino
|
3
|
-
Version: 25.4.
|
3
|
+
Version: 25.4.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
|