lino 25.6.0__py3-none-any.whl → 25.7.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.
- lino/__init__.py +1 -1
- lino/api/doctest.py +21 -0
- lino/core/actions.py +59 -25
- lino/core/actors.py +38 -16
- lino/core/boundaction.py +16 -0
- lino/core/choicelists.py +7 -7
- lino/core/constants.py +3 -0
- lino/core/dashboard.py +1 -0
- lino/core/dbtables.py +1 -1
- lino/core/elems.py +38 -13
- lino/core/fields.py +20 -11
- lino/core/kernel.py +8 -0
- lino/core/layouts.py +6 -2
- lino/core/menus.py +3 -6
- lino/core/model.py +5 -4
- lino/core/renderer.py +14 -5
- lino/core/requests.py +8 -7
- lino/core/signals.py +1 -0
- lino/core/site.py +48 -28
- lino/core/store.py +4 -2
- lino/core/tables.py +23 -10
- lino/core/utils.py +4 -1
- lino/core/workflows.py +2 -1
- lino/help_texts.py +1 -2
- lino/management/commands/prep.py +2 -2
- lino/management/commands/show.py +8 -10
- lino/mixins/__init__.py +14 -13
- lino/mixins/periods.py +2 -0
- lino/mixins/sequenced.py +1 -1
- lino/modlib/about/models.py +4 -3
- lino/modlib/checkdata/__init__.py +42 -36
- lino/modlib/checkdata/choicelists.py +9 -1
- lino/modlib/checkdata/fixtures/checkdata.py +4 -2
- lino/modlib/checkdata/models.py +9 -2
- lino/modlib/comments/models.py +4 -3
- lino/modlib/extjs/ext_renderer.py +4 -4
- lino/modlib/extjs/views.py +8 -2
- lino/modlib/gfks/fields.py +1 -1
- lino/modlib/help/__init__.py +3 -3
- lino/modlib/help/config/makehelp/conf.tpl.py +2 -2
- lino/modlib/help/fixtures/demo2.py +6 -1
- lino/modlib/help/management/commands/makehelp.py +4 -1
- lino/modlib/help/models.py +2 -1
- lino/modlib/help/utils.py +12 -6
- lino/modlib/linod/choicelists.py +57 -4
- lino/modlib/linod/fixtures/{linod.py → checkdata.py} +3 -13
- lino/modlib/linod/management/commands/linod.py +0 -13
- lino/modlib/linod/mixins.py +8 -0
- lino/modlib/linod/models.py +29 -30
- lino/modlib/memo/__init__.py +7 -7
- lino/modlib/memo/management/__init__,py +0 -0
- lino/modlib/memo/management/commands/__init__.py +0 -0
- lino/modlib/memo/management/commands/removeurls.py +67 -0
- lino/modlib/memo/mixins.py +1 -9
- lino/modlib/memo/parser.py +1 -1
- lino/modlib/notify/config/notify/summary.eml +5 -2
- lino/modlib/notify/fixtures/demo2.py +5 -6
- lino/modlib/notify/models.py +9 -10
- lino/modlib/periods/__init__.py +11 -8
- lino/modlib/periods/choicelists.py +16 -10
- lino/modlib/periods/models.py +45 -45
- lino/modlib/summaries/fixtures/checksummaries.py +4 -2
- lino/modlib/system/models.py +17 -18
- lino/modlib/uploads/fixtures/demo.py +9 -3
- lino/modlib/uploads/mixins.py +5 -2
- lino/modlib/uploads/models.py +15 -9
- lino/modlib/uploads/utils.py +4 -1
- lino/modlib/users/__init__.py +59 -18
- lino/modlib/users/actions.py +24 -20
- lino/modlib/users/fixtures/demo_users.py +2 -35
- lino/modlib/users/mixins.py +3 -4
- lino/modlib/users/models.py +53 -13
- lino/modlib/users/ui.py +30 -16
- lino/modlib/users/utils.py +5 -6
- lino/projects/std/settings.py +1 -1
- lino/sphinxcontrib/logo/templates/footer.html +1 -0
- lino/utils/ajax.py +1 -1
- lino/utils/cycler.py +5 -0
- lino/utils/dbhash.py +4 -9
- lino/utils/dpy.py +2 -2
- lino/utils/format_date.py +4 -3
- lino/utils/html.py +13 -5
- lino/utils/jsgen.py +1 -1
- lino/utils/quantities.py +8 -0
- lino/utils/soup.py +75 -94
- {lino-25.6.0.dist-info → lino-25.7.0.dist-info}/METADATA +1 -1
- {lino-25.6.0.dist-info → lino-25.7.0.dist-info}/RECORD +90 -87
- {lino-25.6.0.dist-info → lino-25.7.0.dist-info}/WHEEL +0 -0
- {lino-25.6.0.dist-info → lino-25.7.0.dist-info}/licenses/AUTHORS.rst +0 -0
- {lino-25.6.0.dist-info → lino-25.7.0.dist-info}/licenses/COPYING +0 -0
lino/modlib/periods/models.py
CHANGED
@@ -4,13 +4,14 @@
|
|
4
4
|
|
5
5
|
import datetime
|
6
6
|
from django.db import models
|
7
|
+
from django.core.exceptions import ValidationError
|
7
8
|
from django.utils.translation import gettext_lazy as _
|
8
9
|
|
9
10
|
from lino.api import dd
|
10
|
-
from lino import mixins
|
11
11
|
from lino.utils import ONE_DAY
|
12
12
|
from lino.mixins.periods import DateRange
|
13
|
-
from lino.mixins import Referrable
|
13
|
+
from lino.mixins.ref import Referrable
|
14
|
+
from lino.mixins.sequenced import Sequenced
|
14
15
|
from lino.modlib.system.choicelists import DurationUnits
|
15
16
|
from lino.modlib.office.roles import OfficeStaff
|
16
17
|
from .choicelists import PeriodTypes, PeriodStates
|
@@ -97,7 +98,7 @@ class StoredYear(DateRange, Referrable):
|
|
97
98
|
# return self.__class__.get_or_create_from_date(nextyear)
|
98
99
|
|
99
100
|
|
100
|
-
class StoredPeriod(DateRange, Referrable):
|
101
|
+
class StoredPeriod(DateRange, Referrable, Sequenced):
|
101
102
|
|
102
103
|
class Meta:
|
103
104
|
ordering = ['ref']
|
@@ -112,6 +113,47 @@ class StoredPeriod(DateRange, Referrable):
|
|
112
113
|
null=True, related_name="periods")
|
113
114
|
remark = models.CharField(_("Remark"), max_length=250, blank=True)
|
114
115
|
|
116
|
+
@classmethod
|
117
|
+
# get_default_for_date until 20241020
|
118
|
+
def get_or_create_from_date(cls, date, save=True):
|
119
|
+
pt = dd.plugins.periods.period_type
|
120
|
+
month = date.month
|
121
|
+
month_offset = month - dd.plugins.periods.start_month
|
122
|
+
if month_offset < 0:
|
123
|
+
month_offset += 12
|
124
|
+
seqno = int(month_offset / pt.duration) + 1
|
125
|
+
ref = pt.ref_template.format(**locals())
|
126
|
+
ref = StoredYear.get_ref_for_date(date) + YEAR_PERIOD_SEP + ref
|
127
|
+
obj = cls.get_by_ref(ref, None)
|
128
|
+
if obj is None:
|
129
|
+
sd, ed = cls.get_range_for_date(date)
|
130
|
+
obj = cls(ref=ref, start_date=sd, end_date=ed, seqno=seqno)
|
131
|
+
if save:
|
132
|
+
obj.full_clean()
|
133
|
+
obj.save()
|
134
|
+
return obj
|
135
|
+
|
136
|
+
def full_clean(self, *args, **kwargs):
|
137
|
+
if self.start_date is None:
|
138
|
+
self.start_date = dd.today().replace(day=1)
|
139
|
+
if self.year_id is None:
|
140
|
+
self.year = StoredYear.get_or_create_from_date(self.start_date)
|
141
|
+
if not self.state:
|
142
|
+
self.state = self.year.state
|
143
|
+
super().full_clean(*args, **kwargs)
|
144
|
+
pt = dd.plugins.periods.period_type
|
145
|
+
if self.seqno not in pt.seqnos:
|
146
|
+
raise ValidationError(f"seqno must be in {pt.seqnos}")
|
147
|
+
|
148
|
+
def __str__(self):
|
149
|
+
if not self.ref:
|
150
|
+
return dd.obj2str(self)
|
151
|
+
# "{0} {1} (#{0})".format(self.pk, self.year)
|
152
|
+
return self.ref
|
153
|
+
|
154
|
+
def get_siblings(self):
|
155
|
+
return self.__class__.objects.filter(year=self.year)
|
156
|
+
|
115
157
|
@classmethod
|
116
158
|
def get_simple_parameters(cls):
|
117
159
|
yield super().get_simple_parameters()
|
@@ -170,18 +212,6 @@ class StoredPeriod(DateRange, Referrable):
|
|
170
212
|
kwargs[fieldname + '__in'] = periods
|
171
213
|
return kwargs
|
172
214
|
|
173
|
-
@classmethod
|
174
|
-
def get_ref_for_date(cls, date):
|
175
|
-
pt = dd.plugins.periods.period_type
|
176
|
-
month = date.month
|
177
|
-
month_offset = month - dd.plugins.periods.start_month
|
178
|
-
if month_offset < 0:
|
179
|
-
month_offset += 12
|
180
|
-
period = int(month_offset / pt.duration) + 1
|
181
|
-
# periods_per_year = int(12 / p.duration)
|
182
|
-
# period = (month_offset % (periods_per_year-1)) + 1
|
183
|
-
return pt.ref_template.format(**locals())
|
184
|
-
|
185
215
|
@classmethod
|
186
216
|
def get_range_for_date(cls, date):
|
187
217
|
"""
|
@@ -205,32 +235,6 @@ class StoredPeriod(DateRange, Referrable):
|
|
205
235
|
ed = DurationUnits.months.add_duration(sd, pt.duration) - ONE_DAY
|
206
236
|
return (sd, ed)
|
207
237
|
|
208
|
-
@classmethod
|
209
|
-
def get_or_create_from_date(cls, date): # get_default_for_date until 20241020
|
210
|
-
ref = date2ref(date)
|
211
|
-
obj = cls.get_by_ref(ref, None)
|
212
|
-
if obj is None:
|
213
|
-
sd, ed = cls.get_range_for_date(date)
|
214
|
-
obj = cls(ref=ref, start_date=sd, end_date=ed)
|
215
|
-
obj.full_clean()
|
216
|
-
obj.save()
|
217
|
-
return obj
|
218
|
-
|
219
|
-
def full_clean(self, *args, **kwargs):
|
220
|
-
if self.start_date is None:
|
221
|
-
self.start_date = dd.today().replace(day=1)
|
222
|
-
if not self.year_id:
|
223
|
-
self.year = StoredYear.get_or_create_from_date(self.start_date)
|
224
|
-
if not self.state:
|
225
|
-
self.state = self.year.state
|
226
|
-
super().full_clean(*args, **kwargs)
|
227
|
-
|
228
|
-
def __str__(self):
|
229
|
-
if not self.ref:
|
230
|
-
return dd.obj2str(self)
|
231
|
-
# "{0} {1} (#{0})".format(self.pk, self.year)
|
232
|
-
return self.ref
|
233
|
-
|
234
238
|
# def get_str_words(self, ar):
|
235
239
|
# # if ar.is_obvious_field("year"):
|
236
240
|
# if self.year.covers_date(dd.today()):
|
@@ -251,10 +255,6 @@ class StoredPeriod(DateRange, Referrable):
|
|
251
255
|
StoredPeriod.set_widget_options('ref', width=6)
|
252
256
|
|
253
257
|
|
254
|
-
def date2ref(d):
|
255
|
-
return StoredYear.get_ref_for_date(d) + YEAR_PERIOD_SEP + StoredPeriod.get_ref_for_date(d)
|
256
|
-
|
257
|
-
|
258
258
|
class StoredYears(dd.Table):
|
259
259
|
model = 'periods.StoredYear'
|
260
260
|
required_roles = dd.login_required(OfficeStaff)
|
@@ -1,13 +1,15 @@
|
|
1
1
|
# -*- coding: UTF-8 -*-
|
2
|
-
# Copyright 2017-
|
2
|
+
# Copyright 2017-2025 Rumma & Ko Ltd
|
3
3
|
# License: GNU Affero General Public License v3 (see file COPYING for details)
|
4
4
|
"""Runs the :manage:`checksummaries` management command.
|
5
5
|
|
6
6
|
"""
|
7
7
|
|
8
8
|
from django.core.management import call_command
|
9
|
+
from lino.api import dd
|
9
10
|
|
10
11
|
|
11
12
|
def objects():
|
12
|
-
|
13
|
+
if not dd.is_installed("linod"):
|
14
|
+
call_command("checksummaries")
|
13
15
|
return []
|
lino/modlib/system/models.py
CHANGED
@@ -78,7 +78,7 @@ class SiteConfig(dd.Model):
|
|
78
78
|
This is Lino's equivalent of Django's :setting:`SITE_ID` setting.
|
79
79
|
Lino applications don't need ``django.contrib.sites`` (`The
|
80
80
|
"sites" framework
|
81
|
-
<https://docs.djangoproject.com/en/5.
|
81
|
+
<https://docs.djangoproject.com/en/5.2/ref/contrib/sites/>`_)
|
82
82
|
because an analog functionality is provided by
|
83
83
|
:mod:`lino.modlib.system`.
|
84
84
|
"""
|
@@ -94,14 +94,6 @@ class SiteConfig(dd.Model):
|
|
94
94
|
simulate_today = models.DateField(
|
95
95
|
_("Simulated date"), blank=True, null=True)
|
96
96
|
|
97
|
-
site_company = dd.ForeignKey(
|
98
|
-
"contacts.Company",
|
99
|
-
blank=True,
|
100
|
-
null=True,
|
101
|
-
verbose_name=_("Site owner"),
|
102
|
-
related_name="site_company_sites",
|
103
|
-
)
|
104
|
-
|
105
97
|
_site_config = None
|
106
98
|
|
107
99
|
@classmethod
|
@@ -144,7 +136,7 @@ class SiteConfig(dd.Model):
|
|
144
136
|
# said "SiteConfig 1 does not exist"
|
145
137
|
# cannot save the instance here because the db table possibly doesn't yet exit.
|
146
138
|
# ~ self._site_config.save()
|
147
|
-
cls._site_config.on_startup()
|
139
|
+
# cls._site_config.on_startup()
|
148
140
|
return cls._site_config
|
149
141
|
|
150
142
|
def __str__(self):
|
@@ -171,14 +163,16 @@ class SiteConfig(dd.Model):
|
|
171
163
|
# kw[fld.attname] = getattr(other, fld.attname)
|
172
164
|
# self.update(**kw)
|
173
165
|
|
174
|
-
def on_startup(self):
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
166
|
+
# def on_startup(self):
|
167
|
+
# # if not self.site_company:
|
168
|
+
# # raise Exception("20230423")
|
169
|
+
# # if self.site_company:
|
170
|
+
# site = settings.SITE
|
171
|
+
# if (owner := site.get_plugin_setting('contacts', 'site_owner')) is not None:
|
172
|
+
# # print("20230423", self.site_company)
|
173
|
+
# site.copyright_name = str(owner)
|
174
|
+
# if owner.url:
|
175
|
+
# site.copyright_url = owner.url
|
182
176
|
|
183
177
|
# def full_clean(self, *args, **kw):
|
184
178
|
# super().full_clean(*args, **kw)
|
@@ -207,6 +201,11 @@ class SiteConfig(dd.Model):
|
|
207
201
|
super().save(*args, **kw)
|
208
202
|
# settings.SITE.clear_site_config()
|
209
203
|
|
204
|
+
@property
|
205
|
+
def site_company(self):
|
206
|
+
# Backwards compatibility after 20250617
|
207
|
+
return settings.SITE.plugins.contacts.site_owner
|
208
|
+
|
210
209
|
|
211
210
|
def my_handler(sender, **kw):
|
212
211
|
# print("20180502 {} my_handler calls clear_site_config()".format(
|
@@ -2,15 +2,14 @@
|
|
2
2
|
# Copyright 2015-2025 Rumma & Ko Ltd
|
3
3
|
# License: GNU Affero General Public License v3 (see file COPYING for details)
|
4
4
|
|
5
|
-
import os
|
6
5
|
from lino.api import dd, rt
|
7
|
-
from lino.modlib.uploads.mixins import make_uploaded_file
|
8
6
|
|
9
7
|
try:
|
10
8
|
from lino_book import DEMO_DATA
|
11
9
|
except ImportError:
|
12
10
|
DEMO_DATA = None
|
13
11
|
|
12
|
+
|
14
13
|
def walk(p):
|
15
14
|
# print("20230331", p)
|
16
15
|
for c in sorted(p.iterdir()):
|
@@ -20,8 +19,15 @@ def walk(p):
|
|
20
19
|
else:
|
21
20
|
yield c
|
22
21
|
|
22
|
+
|
23
23
|
def objects():
|
24
|
-
|
24
|
+
|
25
|
+
what = "removed" if dd.plugins.uploads.remove_orphaned_files else "collected"
|
26
|
+
orphan = dd.plugins.uploads.uploads_root / "orphan.txt"
|
27
|
+
orphan.parent.mkdir(parents=True, exist_ok=True)
|
28
|
+
orphan.write_text(f"This file will get {what} by uploads.UploadsFolderChecker")
|
29
|
+
dd.logger.info(f"Wrote {orphan}")
|
30
|
+
|
25
31
|
if DEMO_DATA is None:
|
26
32
|
# logger.info("No demo data because lino_book is not installed")
|
27
33
|
return
|
lino/modlib/uploads/mixins.py
CHANGED
@@ -192,8 +192,11 @@ class UploadBase(Commentable, GalleryViewable):
|
|
192
192
|
dd.logger.info("Wrote uploaded file %s", ff.path)
|
193
193
|
|
194
194
|
def get_gallery_item(self, ar):
|
195
|
-
mf
|
196
|
-
|
195
|
+
if (mf := self.get_media_file()) is not None:
|
196
|
+
url = mf.get_image_url()
|
197
|
+
else:
|
198
|
+
url = "20250703"
|
199
|
+
return dict(image_src=url)
|
197
200
|
|
198
201
|
def full_clean(self, *args, **kw):
|
199
202
|
super().full_clean(*args, **kw)
|
lino/modlib/uploads/models.py
CHANGED
@@ -2,7 +2,6 @@
|
|
2
2
|
# Copyright 2008-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
5
|
import os
|
7
6
|
from os.path import join, exists
|
8
7
|
from pathlib import Path
|
@@ -13,7 +12,7 @@ from django.conf import settings
|
|
13
12
|
from django.core.exceptions import ValidationError
|
14
13
|
from django.utils.text import format_lazy
|
15
14
|
from django.utils.html import format_html, mark_safe
|
16
|
-
from django.utils.translation import pgettext_lazy as pgettext
|
15
|
+
# from django.utils.translation import pgettext_lazy as pgettext
|
17
16
|
|
18
17
|
from rstgen.sphinxconf.sigal_image import parse_image_spec
|
19
18
|
# from rstgen.sphinxconf.sigal_image import Standard, Thumb, Tiny, Wide, Solo, Duo, Trio
|
@@ -22,10 +21,8 @@ from rstgen.sphinxconf.sigal_image import parse_image_spec
|
|
22
21
|
from lino.utils.html import E, join_elems
|
23
22
|
from lino.api import dd, rt, _
|
24
23
|
from lino.modlib.gfks.mixins import Controllable
|
25
|
-
from lino.modlib.users.mixins import UserAuthored
|
24
|
+
from lino.modlib.users.mixins import UserAuthored
|
26
25
|
|
27
|
-
# from lino.modlib.office.roles import OfficeUser, OfficeStaff, OfficeOperator
|
28
|
-
from lino.modlib.office.roles import OfficeStaff
|
29
26
|
from lino.mixins import Referrable
|
30
27
|
from lino.utils.soup import register_sanitizer
|
31
28
|
from lino.utils.mldbc.mixins import BabelNamed
|
@@ -33,11 +30,12 @@ from lino.modlib.checkdata.choicelists import Checker
|
|
33
30
|
from lino.modlib.publisher.mixins import Publishable
|
34
31
|
|
35
32
|
from .actions import CameraStream
|
36
|
-
from .choicelists import Shortcuts, UploadAreas
|
33
|
+
from .choicelists import Shortcuts, UploadAreas
|
37
34
|
from .mixins import UploadBase, base64_to_image
|
38
35
|
from .utils import previewer, UploadMediaFile
|
39
36
|
|
40
37
|
from . import VOLUMES_ROOT
|
38
|
+
from .ui import *
|
41
39
|
|
42
40
|
|
43
41
|
class Volume(Referrable):
|
@@ -328,9 +326,16 @@ class UploadsFolderChecker(Checker):
|
|
328
326
|
msg = format_lazy(
|
329
327
|
_("File {} has no upload entry."), rel_filename)
|
330
328
|
# print(msg)
|
331
|
-
yield (
|
332
|
-
if fix
|
333
|
-
|
329
|
+
yield (True, msg)
|
330
|
+
if fix:
|
331
|
+
if dd.plugins.uploads.remove_orphaned_files:
|
332
|
+
filename.unlink()
|
333
|
+
else:
|
334
|
+
obj = Upload(
|
335
|
+
file=rel_filename, user=ar.get_user(),
|
336
|
+
description=f"Found on {dd.today()} by {self}")
|
337
|
+
obj.full_clean()
|
338
|
+
obj.save()
|
334
339
|
# else:
|
335
340
|
# print("{} has {} entries.".format(filename, n))
|
336
341
|
# elif n > 1:
|
@@ -487,6 +492,7 @@ def on_sanitize(soup, save=False, ar=None):
|
|
487
492
|
upload = rt.models.uploads.Upload(file=file, user=ar.get_user())
|
488
493
|
sar = upload.get_default_table().create_request(parent=ar)
|
489
494
|
upload.save_new_instance(sar)
|
495
|
+
rt.models.checkdata.fix_instance(ar, upload)
|
490
496
|
tag.replace_with(f'[file {upload.pk}]')
|
491
497
|
|
492
498
|
|
lino/modlib/uploads/utils.py
CHANGED
@@ -93,6 +93,9 @@ class FilePreviewer(Previewer):
|
|
93
93
|
if needs_update(src, dst):
|
94
94
|
yield (True, format_lazy(_("Must build thumbnail for {}"), mf.url))
|
95
95
|
if fix:
|
96
|
+
dst.parent.mkdir(parents=True, exist_ok=True)
|
97
|
+
# Make parent dir also for pdf previews. See #6181 (Issues after
|
98
|
+
# uploading two PDFs to froinde)
|
96
99
|
if src.suffix.lower() == ".pdf":
|
97
100
|
doc = pymupdf.open(src)
|
98
101
|
page = doc.load_page(0)
|
@@ -101,9 +104,9 @@ class FilePreviewer(Previewer):
|
|
101
104
|
return
|
102
105
|
with Image.open(src) as im:
|
103
106
|
im.thumbnail((self.max_width, self.max_width))
|
104
|
-
dst.parent.mkdir(parents=True, exist_ok=True)
|
105
107
|
im.save(dst)
|
106
108
|
|
109
|
+
|
107
110
|
if with_thumbnails:
|
108
111
|
previewer = FilePreviewer("thumbs", 720)
|
109
112
|
else:
|
lino/modlib/users/__init__.py
CHANGED
@@ -5,6 +5,7 @@
|
|
5
5
|
|
6
6
|
|
7
7
|
from lino.api import ad, _
|
8
|
+
from lino import logger
|
8
9
|
|
9
10
|
|
10
11
|
class Plugin(ad.Plugin):
|
@@ -22,6 +23,15 @@ class Plugin(ad.Plugin):
|
|
22
23
|
# partner_model = 'contacts.Person'
|
23
24
|
partner_model = "contacts.Partner"
|
24
25
|
demo_password = "1234"
|
26
|
+
demo_username = None
|
27
|
+
|
28
|
+
def on_init(self):
|
29
|
+
super().on_init()
|
30
|
+
self.site.set_user_model("users.User")
|
31
|
+
from lino.core.site import has_socialauth
|
32
|
+
|
33
|
+
if has_socialauth and self.third_party_authentication:
|
34
|
+
self.needs_plugins.append("social_django")
|
25
35
|
|
26
36
|
def pre_site_startup(self, site):
|
27
37
|
super().pre_site_startup(site)
|
@@ -31,26 +41,27 @@ class Plugin(ad.Plugin):
|
|
31
41
|
# return
|
32
42
|
self.partner_model = site.models.resolve(self.partner_model)
|
33
43
|
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
# # your google accounts settings: https://myaccount.google.com/lesssecureapps
|
40
|
-
# site.update_settings(
|
41
|
-
# EMAIL_HOST='smtp.gmail.com',
|
42
|
-
# EMAIL_PORT=587, # For TLS | use 465 for SSL
|
43
|
-
# EMAIL_HOST_USER='username@gmail.com',
|
44
|
-
# EMAIL_HOST_PASSWORD='*********',
|
45
|
-
# EMAIL_USE_TLS=True)
|
44
|
+
def post_site_startup(self, site):
|
45
|
+
super().post_site_startup(site)
|
46
|
+
if self.demo_username is None:
|
47
|
+
if (kw := self.get_root_user_fields(site.DEFAULT_LANGUAGE)):
|
48
|
+
self.demo_username = kw['username']
|
46
49
|
|
47
|
-
|
48
|
-
super().on_init()
|
49
|
-
self.site.set_user_model("users.User")
|
50
|
-
from lino.core.site import has_socialauth
|
50
|
+
_demo_user = None # the cached User object
|
51
51
|
|
52
|
-
|
53
|
-
|
52
|
+
def get_demo_user(self, checker, obj):
|
53
|
+
if self.demo_username is None:
|
54
|
+
return None
|
55
|
+
if self._demo_user is None:
|
56
|
+
User = self.site.models.users.User
|
57
|
+
try:
|
58
|
+
self._demo_user = User.objects.get(
|
59
|
+
username=self.demo_username)
|
60
|
+
except User.DoesNotExist:
|
61
|
+
msg = "Invalid username '{0}' in `demo_username` "
|
62
|
+
msg = msg.format(self.demo_username)
|
63
|
+
raise Exception(msg)
|
64
|
+
return self._demo_user
|
54
65
|
|
55
66
|
def get_requirements(self, site):
|
56
67
|
yield "social-auth-app-django"
|
@@ -86,3 +97,33 @@ class Plugin(ad.Plugin):
|
|
86
97
|
|
87
98
|
def get_quicklinks(self):
|
88
99
|
yield "users.Me"
|
100
|
+
|
101
|
+
def get_root_user_fields(self, lang, **kw):
|
102
|
+
# ~ kw.update(user_type='900') # UserTypes.admin)
|
103
|
+
# ~ print 20130219, UserTypes.items()
|
104
|
+
kw.update(user_type=self.site.models.users.UserTypes.admin)
|
105
|
+
kw.update(email=self.site.demo_email) # 'root@example.com'
|
106
|
+
lang = lang.django_code
|
107
|
+
kw.update(language=lang)
|
108
|
+
lang = lang[:2]
|
109
|
+
if lang == "en":
|
110
|
+
kw.update(first_name="Robin", last_name="Rood")
|
111
|
+
elif lang == "de":
|
112
|
+
kw.update(first_name="Rolf", last_name="Rompen")
|
113
|
+
elif lang == "fr":
|
114
|
+
kw.update(first_name="Romain", last_name="Raffault")
|
115
|
+
elif lang == "et":
|
116
|
+
kw.update(first_name="Rando", last_name="Roosi")
|
117
|
+
elif lang == "pt":
|
118
|
+
kw.update(first_name="Ronaldo", last_name="Rosa")
|
119
|
+
elif lang == "es":
|
120
|
+
kw.update(first_name="Rodrigo", last_name="Rosalez")
|
121
|
+
elif lang == "nl":
|
122
|
+
kw.update(first_name="Rik", last_name="Rozenbos")
|
123
|
+
elif lang == "bn":
|
124
|
+
kw.update(first_name="Roby", last_name="Raza")
|
125
|
+
else:
|
126
|
+
logger.warning("No demo user for language %r.", lang)
|
127
|
+
return None
|
128
|
+
kw.update(username=kw.get("first_name").lower())
|
129
|
+
return kw
|
lino/modlib/users/actions.py
CHANGED
@@ -237,21 +237,21 @@ class VerifyMe(VerifyUser):
|
|
237
237
|
self.doit(ar, user, **kwargs)
|
238
238
|
|
239
239
|
|
240
|
-
if settings.SITE.default_ui == "lino_react.react":
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
240
|
+
# if settings.SITE.default_ui == "lino_react.react":
|
241
|
+
#
|
242
|
+
# class MySettings(dd.Action):
|
243
|
+
# label = _("My settings")
|
244
|
+
# select_rows = False
|
245
|
+
# show_in_toolbar = False
|
246
|
+
# # http_method = "POST"
|
247
|
+
# default_format = None
|
248
|
+
#
|
249
|
+
# def run_from_ui(self, ar, **kw):
|
250
|
+
# # assert len(ar.selected_rows) == 1
|
251
|
+
# # user = ar.selected_rows[0]
|
252
|
+
# # raise PermissionError("20210811")
|
253
|
+
# user = ar.get_user()
|
254
|
+
# ar.goto_instance(user)
|
255
255
|
|
256
256
|
|
257
257
|
class SendWelcomeMail(dd.Action):
|
@@ -263,16 +263,16 @@ class SendWelcomeMail(dd.Action):
|
|
263
263
|
show_in_toolbar = False
|
264
264
|
show_in_workflow = True
|
265
265
|
button_text = "\u2709" # ✉
|
266
|
+
select_rows = True
|
267
|
+
# required_roles = dd.login_required()
|
266
268
|
|
267
269
|
# required_roles = dd.login_required(SiteAdmin)
|
268
270
|
|
269
271
|
def get_action_permission(self, ar, obj, state):
|
270
|
-
if not obj.email:
|
271
|
-
return False
|
272
272
|
user = ar.get_user()
|
273
|
-
if user != obj:
|
274
|
-
|
275
|
-
|
273
|
+
if user != obj and not user.user_type.has_required_roles([SiteAdmin]):
|
274
|
+
# print(f"20250712 {user} != {obj}")
|
275
|
+
return False
|
276
276
|
return super().get_action_permission(ar, obj, state)
|
277
277
|
|
278
278
|
def run_from_ui(self, ar, **kw):
|
@@ -284,6 +284,10 @@ class SendWelcomeMail(dd.Action):
|
|
284
284
|
obj.full_clean()
|
285
285
|
obj.save()
|
286
286
|
|
287
|
+
if not obj.email:
|
288
|
+
ar.error(_("Cannot verify without email address"), alert=True)
|
289
|
+
return
|
290
|
+
|
287
291
|
recipients = ["{} <{}>".format(obj.get_full_name(), obj.email)]
|
288
292
|
|
289
293
|
def ok(ar):
|
@@ -2,41 +2,8 @@
|
|
2
2
|
# Copyright 2010-2020 Rumma & Ko Ltd
|
3
3
|
# License: GNU Affero General Public License v3 (see file COPYING for details)
|
4
4
|
|
5
|
-
from lino import logger
|
6
|
-
|
7
5
|
from django.conf import settings
|
8
|
-
from lino.
|
9
|
-
|
10
|
-
|
11
|
-
def root_user(lang, **kw):
|
12
|
-
# ~ kw.update(user_type='900') # UserTypes.admin)
|
13
|
-
# ~ print 20130219, UserTypes.items()
|
14
|
-
kw.update(user_type=UserTypes.admin)
|
15
|
-
kw.update(email=settings.SITE.demo_email) # 'root@example.com'
|
16
|
-
lang = lang.django_code
|
17
|
-
kw.update(language=lang)
|
18
|
-
lang = lang[:2]
|
19
|
-
if lang == "en":
|
20
|
-
kw.update(first_name="Robin", last_name="Rood")
|
21
|
-
elif lang == "de":
|
22
|
-
kw.update(first_name="Rolf", last_name="Rompen")
|
23
|
-
elif lang == "fr":
|
24
|
-
kw.update(first_name="Romain", last_name="Raffault")
|
25
|
-
elif lang == "et":
|
26
|
-
kw.update(first_name="Rando", last_name="Roosi")
|
27
|
-
elif lang == "pt":
|
28
|
-
kw.update(first_name="Ronaldo", last_name="Rosa")
|
29
|
-
elif lang == "es":
|
30
|
-
kw.update(first_name="Rodrigo", last_name="Rosalez")
|
31
|
-
elif lang == "nl":
|
32
|
-
kw.update(first_name="Rik", last_name="Rozenbos")
|
33
|
-
elif lang == "bn":
|
34
|
-
kw.update(first_name="Roby", last_name="Raza")
|
35
|
-
else:
|
36
|
-
logger.warning("No demo user for language %r.", lang)
|
37
|
-
return None
|
38
|
-
kw.update(username=kw.get("first_name").lower())
|
39
|
-
return kw
|
6
|
+
from lino.api import dd
|
40
7
|
|
41
8
|
|
42
9
|
def objects():
|
@@ -48,7 +15,7 @@ def objects():
|
|
48
15
|
for lang in SITE.languages:
|
49
16
|
if (SITE.hidden_languages is None
|
50
17
|
or lang.django_code not in SITE.hidden_languages):
|
51
|
-
kw =
|
18
|
+
kw = dd.plugins.users.get_root_user_fields(lang)
|
52
19
|
if kw:
|
53
20
|
u = User(**kw)
|
54
21
|
if SITE.is_demo_site:
|
lino/modlib/users/mixins.py
CHANGED
@@ -237,8 +237,8 @@ class UserPlan(UserAuthored):
|
|
237
237
|
abstract = True
|
238
238
|
|
239
239
|
today = models.DateField(_("Today"), default=dd.today)
|
240
|
-
# 20240621 today is no longer readonly because
|
241
|
-
#
|
240
|
+
# 20240621 today is no longer readonly because the user may want to continue
|
241
|
+
# a plan they started yesterday
|
242
242
|
|
243
243
|
update_plan = UpdatePlan()
|
244
244
|
start_plan = StartPlan()
|
@@ -263,8 +263,7 @@ class UserPlan(UserAuthored):
|
|
263
263
|
else:
|
264
264
|
if num > 1:
|
265
265
|
dd.logger.warning(
|
266
|
-
"Got {} {} for {}"
|
267
|
-
)
|
266
|
+
f"Got {num} {cls._meta.verbose_name_plural} for {user}")
|
268
267
|
qs.delete()
|
269
268
|
plan = cls(user=user, **options)
|
270
269
|
plan.full_clean()
|