lino 25.3.1__py3-none-any.whl → 25.3.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 +6 -7
- lino/api/dd.py +1 -0
- lino/api/doctest.py +4 -68
- lino/api/rt.py +2 -4
- lino/core/actors.py +22 -16
- lino/core/boundaction.py +17 -7
- lino/core/dashboard.py +5 -4
- lino/core/dbtables.py +15 -16
- lino/core/fields.py +3 -3
- lino/core/menus.py +1 -1
- lino/core/model.py +1 -1
- lino/core/renderer.py +10 -11
- lino/core/requests.py +11 -7
- lino/core/site.py +9 -76
- lino/core/tables.py +2 -2
- lino/core/utils.py +3 -3
- lino/core/views.py +3 -3
- lino/help_texts.py +1 -0
- lino/management/commands/buildsite.py +67 -0
- lino/management/commands/dump2py.py +5 -7
- lino/management/commands/initdb.py +12 -14
- lino/management/commands/install.py +2 -2
- lino/management/commands/prep.py +3 -7
- lino/mixins/sequenced.py +1 -1
- lino/modlib/comments/models.py +1 -1
- lino/modlib/comments/ui.py +2 -2
- lino/modlib/extjs/ext_renderer.py +2 -2
- lino/modlib/extjs/views.py +50 -48
- lino/modlib/help/config/makehelp/model.tpl.rst +1 -1
- lino/modlib/help/management/commands/makehelp.py +6 -5
- lino/modlib/jinja/renderer.py +2 -2
- lino/modlib/linod/management/commands/linod.py +5 -2
- lino/modlib/memo/__init__.py +1 -1
- lino/modlib/publisher/choicelists.py +3 -3
- lino/modlib/publisher/views.py +2 -2
- lino/modlib/search/models.py +5 -5
- lino/modlib/system/choicelists.py +6 -3
- lino/modlib/tinymce/views.py +1 -1
- lino/modlib/uploads/models.py +1 -1
- lino/modlib/users/ui.py +2 -3
- lino/utils/__init__.py +5 -1
- lino/utils/dbhash.py +112 -0
- lino/utils/diag.py +1 -1
- lino/utils/fieldutils.py +79 -0
- {lino-25.3.1.dist-info → lino-25.3.3.dist-info}/METADATA +1 -1
- {lino-25.3.1.dist-info → lino-25.3.3.dist-info}/RECORD +49 -46
- {lino-25.3.1.dist-info → lino-25.3.3.dist-info}/WHEEL +0 -0
- {lino-25.3.1.dist-info → lino-25.3.3.dist-info}/licenses/AUTHORS.rst +0 -0
- {lino-25.3.1.dist-info → lino-25.3.3.dist-info}/licenses/COPYING +0 -0
lino/modlib/extjs/views.py
CHANGED
@@ -24,14 +24,11 @@ Summary from <http://en.wikipedia.org/wiki/Restful>:
|
|
24
24
|
import json
|
25
25
|
import os
|
26
26
|
|
27
|
-
from pathlib import Path
|
28
|
-
|
29
27
|
from django import http
|
30
|
-
from django.contrib.messages import success
|
31
28
|
from django.db import models
|
32
29
|
from django.conf import settings
|
33
30
|
from django.core.cache import cache
|
34
|
-
from django.core.exceptions import PermissionDenied
|
31
|
+
from django.core.exceptions import PermissionDenied
|
35
32
|
from django.views.generic import View
|
36
33
|
from django.views.generic.base import TemplateView
|
37
34
|
from django.views.decorators.cache import never_cache
|
@@ -40,27 +37,24 @@ from django.views.decorators.csrf import ensure_csrf_cookie, csrf_protect
|
|
40
37
|
from django.utils.translation import gettext as _
|
41
38
|
from django.utils.encoding import force_str
|
42
39
|
from django.utils.html import mark_safe
|
43
|
-
from lino.core import auth
|
44
40
|
|
45
41
|
from lino.core.signals import pre_ui_delete
|
46
42
|
from lino.core.utils import obj2unicode
|
47
43
|
|
48
44
|
# from etgen import html as xghtml
|
49
|
-
from lino.utils.html import E, tostring
|
45
|
+
from lino.utils.html import E, tostring, escape
|
50
46
|
from etgen.html import Document
|
51
47
|
|
52
48
|
from lino.utils import ucsv
|
53
49
|
from lino import logger
|
54
50
|
# from lino.utils import dblogger
|
55
51
|
from lino.core import constants
|
56
|
-
from lino.core import actions
|
57
|
-
from lino.core import fields
|
58
52
|
from lino.core.fields import choices_for_field
|
59
53
|
from lino.core.views import requested_actor, action_request
|
60
|
-
from lino.core.views import json_response
|
54
|
+
from lino.core.views import json_response
|
61
55
|
from lino.core.views import choices_response
|
62
56
|
from lino.core.requests import BaseRequest
|
63
|
-
from lino.core.utils import
|
57
|
+
from lino.core.utils import is_devserver
|
64
58
|
|
65
59
|
MAX_ROW_COUNT = 300
|
66
60
|
|
@@ -207,7 +201,7 @@ class ActionParamChoices(View):
|
|
207
201
|
raise Exception("Unknown action %r for %s" % (an, actor))
|
208
202
|
field = ba.action.get_param_elem(field)
|
209
203
|
qs, row2dict = choices_for_field(
|
210
|
-
ba.
|
204
|
+
ba.create_request(request=request), ba.action, field)
|
211
205
|
if field.blank:
|
212
206
|
emptyValue = "<br/>"
|
213
207
|
else:
|
@@ -229,7 +223,7 @@ class Choices(View):
|
|
229
223
|
rpt = requested_actor(app_label, rptname)
|
230
224
|
emptyValue = None
|
231
225
|
if fldname is None:
|
232
|
-
ar = rpt.
|
226
|
+
ar = rpt.create_request(request=request)
|
233
227
|
qs = ar.data_iterator
|
234
228
|
|
235
229
|
def row2dict(obj, d):
|
@@ -247,7 +241,7 @@ class Choices(View):
|
|
247
241
|
if field.blank:
|
248
242
|
# logger.info("views.Choices: %r is blank",field)
|
249
243
|
emptyValue = "<br/>"
|
250
|
-
ar = rpt.
|
244
|
+
ar = rpt.create_request(request=request)
|
251
245
|
# if str(rpt) == 'comments.CommentsByRFC':
|
252
246
|
# print("20230426", ar.master_instance)
|
253
247
|
qs, row2dict = choices_for_field(ar, rpt, field)
|
@@ -263,7 +257,7 @@ class Restful(View):
|
|
263
257
|
@method_decorator(csrf_protect)
|
264
258
|
def post(self, request, app_label=None, actor=None, pk=None):
|
265
259
|
rpt = requested_actor(app_label, actor)
|
266
|
-
ar = rpt.
|
260
|
+
ar = rpt.create_request(request=request)
|
267
261
|
|
268
262
|
instance = ar.create_instance()
|
269
263
|
# store uploaded files.
|
@@ -284,14 +278,14 @@ class Restful(View):
|
|
284
278
|
@method_decorator(csrf_protect)
|
285
279
|
def delete(self, request, app_label=None, actor=None, pk=None):
|
286
280
|
rpt = requested_actor(app_label, actor)
|
287
|
-
ar = rpt.
|
281
|
+
ar = rpt.create_request(request=request)
|
288
282
|
ar.set_selected_pks(pk)
|
289
283
|
return delete_element(ar, ar.selected_rows[0])
|
290
284
|
|
291
285
|
def get(self, request, app_label=None, actor=None, pk=None):
|
292
286
|
rpt = requested_actor(app_label, actor)
|
293
287
|
assert pk is None, 20120814
|
294
|
-
ar = rpt.
|
288
|
+
ar = rpt.create_request(request=request)
|
295
289
|
rh = ar.ah
|
296
290
|
rows = [
|
297
291
|
rh.store.row2dict(ar, row, rh.store.grid_fields)
|
@@ -304,7 +298,7 @@ class Restful(View):
|
|
304
298
|
@method_decorator(csrf_protect)
|
305
299
|
def put(self, request, app_label=None, actor=None, pk=None):
|
306
300
|
rpt = requested_actor(app_label, actor)
|
307
|
-
ar = rpt.
|
301
|
+
ar = rpt.create_request(request=request)
|
308
302
|
ar.set_selected_pks(pk)
|
309
303
|
elem = ar.selected_rows[0]
|
310
304
|
rh = ar.ah
|
@@ -313,7 +307,7 @@ class Restful(View):
|
|
313
307
|
data = json.loads(data)
|
314
308
|
# 20240404 a = rpt.get_url_action(rpt.default_list_action_name)
|
315
309
|
a = rpt.default_action
|
316
|
-
ar = rpt.
|
310
|
+
ar = rpt.create_request(request=request, action=a)
|
317
311
|
ar.renderer = settings.SITE.kernel.extjs_renderer
|
318
312
|
ar.form2obj_and_save(data, elem, False)
|
319
313
|
# Ext.ensible needs grid_fields, not detail_fields
|
@@ -326,6 +320,10 @@ NOT_FOUND = "%s has no row with primary key %r"
|
|
326
320
|
|
327
321
|
|
328
322
|
class ApiElement(View):
|
323
|
+
"""
|
324
|
+
The view that responds to
|
325
|
+
``r'api/(?P<app_label>\w+)/(?P<actor>\w+)/(?P<pk>[^/]+)$'``.
|
326
|
+
"""
|
329
327
|
|
330
328
|
@method_decorator(ensure_csrf_cookie)
|
331
329
|
def get(self, request, app_label=None, actor=None, pk=None):
|
@@ -353,48 +351,53 @@ class ApiElement(View):
|
|
353
351
|
ba.action.default_format)
|
354
352
|
|
355
353
|
try:
|
354
|
+
ar = ba.create_request(
|
355
|
+
request=request, renderer=settings.SITE.kernel.default_renderer)
|
356
356
|
if pk and pk != "-99999" and pk != "-99998":
|
357
|
-
sr = [pk]
|
358
357
|
if issubclass(rpt.model, models.Model):
|
359
358
|
try:
|
360
|
-
ar
|
361
|
-
# except ObjectDoesNotExist as e: # 20250212
|
359
|
+
ar.set_selected_pks(pk)
|
362
360
|
except rpt.model.DoesNotExist:
|
363
361
|
if fmt == constants.URL_FORMAT_JSON:
|
364
|
-
# rescue_ar: without sr and even request, to render
|
365
|
-
|
362
|
+
# rescue_ar: without sr and even request, to render
|
363
|
+
# a table request (grid view action) on breadcrumb
|
364
|
+
safe_kw = dict(
|
365
|
+
user=request.user,
|
366
366
|
renderer=settings.SITE.kernel.default_renderer)
|
367
|
-
|
368
|
-
|
367
|
+
rescue_ar = rpt.create_request(**safe_kw)
|
369
368
|
title = tostring(rescue_ar.href_to_request(
|
370
369
|
rescue_ar, icon_name=None))
|
371
|
-
|
372
|
-
|
373
|
-
|
374
|
-
|
375
|
-
|
376
|
-
|
377
|
-
datarec
|
378
|
-
|
379
|
-
|
370
|
+
datarec = dict(alert=False, success=False,
|
371
|
+
title=title, **vm)
|
372
|
+
msg = _("Row {pk} is not visible here.").format(pk=pk)
|
373
|
+
default_table = rpt.model.get_default_table()
|
374
|
+
if default_table is rpt:
|
375
|
+
datarec.update(message=mark_safe(msg))
|
376
|
+
return json_response(datarec)
|
377
|
+
ar = default_table.detail_action.create_request(parent=ar)
|
380
378
|
try:
|
381
379
|
# take default table and try to show the row
|
382
|
-
ar
|
383
|
-
request=request, selected_pks=sr)
|
380
|
+
ar.set_selected_pks(pk)
|
384
381
|
except default_table.model.DoesNotExist:
|
385
|
-
|
386
|
-
|
387
|
-
|
388
|
-
|
389
|
-
|
390
|
-
|
382
|
+
msg += " " + _("Neither is it visible in {table}.").format(
|
383
|
+
table=ar.get_title())
|
384
|
+
datarec.update(message=mark_safe(msg))
|
385
|
+
return json_response(datarec)
|
386
|
+
|
387
|
+
lnk = ar.obj2htmls(ar.selected_rows[0], ar.get_title())
|
388
|
+
s = escape(_("But you can see it in {}.")).format(lnk)
|
389
|
+
msg += "<br>" + s
|
390
|
+
# msg = format_html("{}<br>{}", )
|
391
|
+
# msg = format_html("{}<br>{}", _("But you can see it in {link}"))
|
392
|
+
# url = ar.obj2url(dtar.selected_rows[0])
|
393
|
+
# msg += "<br>" + _('But you can see it in <a href="{url}">{table}</a>.').format(
|
394
|
+
# url=url, table=ar.get_title())
|
395
|
+
datarec.update(message=mark_safe(msg))
|
391
396
|
return json_response(datarec)
|
392
|
-
|
393
|
-
raise http.Http404(
|
394
|
-
f"Object {sr} does not exist on {rpt}")
|
397
|
+
raise http.Http404(f"Row {pk} does not exist on {rpt}")
|
395
398
|
else:
|
396
|
-
ar
|
397
|
-
# ar = ba.
|
399
|
+
ar.set_selected_pks(pk)
|
400
|
+
# ar = ba.create_request(request=request, selected_pks=sr)
|
398
401
|
elem = ar.selected_rows[0]
|
399
402
|
# print(
|
400
403
|
# "20170116 views.ApiElement.get", ba,
|
@@ -405,7 +408,6 @@ class ApiElement(View):
|
|
405
408
|
# raise http.Http404(
|
406
409
|
# "No permission to see {} {}.".format(rpt, action_name))
|
407
410
|
else:
|
408
|
-
ar = ba.request(request=request)
|
409
411
|
elem = None
|
410
412
|
except Exception as e:
|
411
413
|
|
@@ -41,7 +41,7 @@ from lino.core.model import Model
|
|
41
41
|
from lino.core.utils import model_class_path
|
42
42
|
from lino.modlib.help.utils import HelpTextsLoader, simplify_name
|
43
43
|
from lino.modlib.gfks.fields import GenericForeignKey
|
44
|
-
from lino.api
|
44
|
+
from lino.api import dd
|
45
45
|
|
46
46
|
# removed import doctest because it caused "pytest not installed" during
|
47
47
|
# makehelp on LF:
|
@@ -91,7 +91,7 @@ def runcmd(cmd, **kw):
|
|
91
91
|
|
92
92
|
def field_ref(f):
|
93
93
|
if isinstance(f.model, Model):
|
94
|
-
return ":ref:`{}.{}`".format(full_model_name(f.model), f.name)
|
94
|
+
return ":ref:`{}.{}`".format(dd.full_model_name(f.model), f.name)
|
95
95
|
# parameter field
|
96
96
|
return ":ref:`{}.{}`".format(str(f.model), f.name)
|
97
97
|
|
@@ -136,7 +136,7 @@ def model_referenced_from(model):
|
|
136
136
|
def ddhfmt(ddh):
|
137
137
|
return ", ".join(
|
138
138
|
[
|
139
|
-
"{}.{}".format(full_model_name(model), fk.name)
|
139
|
+
"{}.{}".format(dd.full_model_name(model), fk.name)
|
140
140
|
for model, fk in ddh.fklist
|
141
141
|
]
|
142
142
|
)
|
@@ -265,7 +265,8 @@ class Command(GeneratingCommand):
|
|
265
265
|
# ~ py2rst=rstgen.py2rst,
|
266
266
|
languages=[lng.django_code for lng in settings.SITE.languages],
|
267
267
|
get_models=apps.get_models,
|
268
|
-
full_model_name=full_model_name,
|
268
|
+
full_model_name=dd.full_model_name,
|
269
|
+
dd=dd,
|
269
270
|
model_overview=self.model_overview,
|
270
271
|
actor2par=self.actor2par,
|
271
272
|
actors2table=self.actors2table,
|
@@ -324,7 +325,7 @@ class Command(GeneratingCommand):
|
|
324
325
|
elif issubclass(x, models.Model):
|
325
326
|
if text is None:
|
326
327
|
text = x.__name__
|
327
|
-
return ":doc:`" + text + " <" + full_model_name(x) + ">`"
|
328
|
+
return ":doc:`" + text + " <" + dd.full_model_name(x) + ">`"
|
328
329
|
if isinstance(x, BoundAction):
|
329
330
|
if text is None:
|
330
331
|
text = x.action_name
|
lino/modlib/jinja/renderer.py
CHANGED
@@ -82,7 +82,7 @@ class JinjaRenderer(HtmlRenderer):
|
|
82
82
|
|
83
83
|
def as_table(action_spec):
|
84
84
|
a = settings.SITE.models.resolve(action_spec)
|
85
|
-
ar = a.
|
85
|
+
ar = a.create_request(user=settings.SITE.get_anonymous_user())
|
86
86
|
return self.as_table(ar)
|
87
87
|
|
88
88
|
def as_table2(ar):
|
@@ -99,7 +99,7 @@ class JinjaRenderer(HtmlRenderer):
|
|
99
99
|
|
100
100
|
def as_ul(action_spec):
|
101
101
|
a = settings.SITE.models.resolve(action_spec)
|
102
|
-
ar = a.
|
102
|
+
ar = a.create_request(user=settings.SITE.get_anonymous_user())
|
103
103
|
# 20150810
|
104
104
|
ar.renderer = self
|
105
105
|
# return tostring(E.ul(*[obj.as_paragraph(ar) for obj in ar]))
|
@@ -51,8 +51,11 @@ class Command(BaseCommand):
|
|
51
51
|
# print("20240424 Run Lino daemon without channels")
|
52
52
|
|
53
53
|
async def main():
|
54
|
-
|
55
|
-
|
54
|
+
try:
|
55
|
+
u = await settings.SITE.user_model.objects.aget(
|
56
|
+
username=settings.SITE.plugins.linod.daemon_user)
|
57
|
+
except settings.SITE.user_model.DoesNotExist:
|
58
|
+
u = None
|
56
59
|
ar = BaseRequest(user=u)
|
57
60
|
# ar = rt.login(dd.plugins.linod.daemon_user)
|
58
61
|
await asyncio.gather(start_log_server(), start_task_runner(ar))
|
lino/modlib/memo/__init__.py
CHANGED
@@ -93,7 +93,7 @@ class Plugin(ad.Plugin):
|
|
93
93
|
# kwargs = dict(header_level=3) #, nosummary=True)
|
94
94
|
kwargs = dict() # , nosummary=True)
|
95
95
|
dv = self.site.models.resolve(s)
|
96
|
-
sar = dv.
|
96
|
+
sar = dv.create_request(parent=ar, limit=dv.preview_limit)
|
97
97
|
rv = ""
|
98
98
|
# rv += "20230325 [show {}]".format(dv)
|
99
99
|
for e in sar.renderer.table2story(sar, **kwargs):
|
@@ -149,7 +149,7 @@ class PageFiller(Choice):
|
|
149
149
|
def get_dynamic_story(self, ar, obj, **kwargs):
|
150
150
|
txt = ""
|
151
151
|
dv = self.data_view
|
152
|
-
sar = dv.
|
152
|
+
sar = dv.create_request(parent=ar, limit=dv.preview_limit)
|
153
153
|
# print("20231028", dv, list(sar))
|
154
154
|
# print("20230409", ar.renderer)
|
155
155
|
# rv += "20230325 [show {}]".format(dv)
|
@@ -159,8 +159,8 @@ class PageFiller(Choice):
|
|
159
159
|
|
160
160
|
def get_dynamic_paragraph(self, ar, obj, **kwargs):
|
161
161
|
dv = self.data_view
|
162
|
-
# sar = dv.
|
163
|
-
sar = dv.
|
162
|
+
# sar = dv.create_request(parent=ar, limit=dv.preview_limit)
|
163
|
+
sar = dv.create_request(parent=ar)
|
164
164
|
return " / ".join([sar.obj2htmls(row) for row in sar])
|
165
165
|
|
166
166
|
|
lino/modlib/publisher/views.py
CHANGED
@@ -37,7 +37,7 @@ class Element(View):
|
|
37
37
|
kw.update(selected_pks=[pk])
|
38
38
|
|
39
39
|
try:
|
40
|
-
ar = self.table_class.
|
40
|
+
ar = self.table_class.create_request(request=request, **kw)
|
41
41
|
except ObjectDoesNotExist as e:
|
42
42
|
# print("20240911", e)
|
43
43
|
return http.HttpResponseNotFound(f"No row #{pk} in {self.table_class} ({e})")
|
@@ -54,5 +54,5 @@ class Index(View):
|
|
54
54
|
dv = settings.SITE.models.publisher.Pages
|
55
55
|
index_node = dv.model.objects.get(ref="index", language=request.LANGUAGE_CODE)
|
56
56
|
# print("20231025", index_node)
|
57
|
-
ar = dv.
|
57
|
+
ar = dv.create_request(request=request, renderer=rnd, selected_rows=[index_node])
|
58
58
|
return index_node.get_publisher_response(ar)
|
lino/modlib/search/models.py
CHANGED
@@ -52,7 +52,7 @@ class SiteSearchBase(dd.VirtualTable):
|
|
52
52
|
if t is None:
|
53
53
|
return
|
54
54
|
t = cls.get_table_for_role(t, ar.get_user().user_type.role)
|
55
|
-
sar = t.
|
55
|
+
sar = t.create_request(parent=ar)
|
56
56
|
return obj.as_search_item(sar)
|
57
57
|
|
58
58
|
@classmethod
|
@@ -68,7 +68,7 @@ class SiteSearchBase(dd.VirtualTable):
|
|
68
68
|
if t is None:
|
69
69
|
return
|
70
70
|
t = cls.get_table_for_role(t, ar.get_user().user_type.role)
|
71
|
-
sar = t.
|
71
|
+
sar = t.create_request(parent=ar)
|
72
72
|
return t.get_card_title(sar, obj)
|
73
73
|
|
74
74
|
@classmethod
|
@@ -158,7 +158,7 @@ class SiteSearch(SiteSearchBase):
|
|
158
158
|
t = cls.get_table_for_role(t, user_type.role)
|
159
159
|
if not t.get_view_permission(user_type):
|
160
160
|
continue
|
161
|
-
sar = t.
|
161
|
+
sar = t.create_request(parent=ar, quick_search=ar.quick_search)
|
162
162
|
try:
|
163
163
|
for obj in sar:
|
164
164
|
if obj.show_in_site_search: # don't show calview.HeaderRow
|
@@ -197,7 +197,7 @@ if settings.SITE.use_elasticsearch and has_elasticsearch:
|
|
197
197
|
user_type = ar.get_user().user_type
|
198
198
|
query = MultiMatch(query=ar.quick_search)
|
199
199
|
s = search.query(query)
|
200
|
-
s = s[ar.offset
|
200
|
+
s = s[ar.offset: ar.offset + ar.limit]
|
201
201
|
sq = execute_search(s, save=False)
|
202
202
|
return sq
|
203
203
|
return []
|
@@ -210,7 +210,7 @@ if settings.SITE.use_solr and has_haystack:
|
|
210
210
|
class SolrSiteSearch(SiteSearchBase):
|
211
211
|
@classmethod
|
212
212
|
def get_rows_from_search_query(cls, sqs, ar):
|
213
|
-
results = sqs[ar.offset
|
213
|
+
results = sqs[ar.offset: ar.offset + ar.limit]
|
214
214
|
for result in results:
|
215
215
|
yield result.model.objects.get(pk=result.pk)
|
216
216
|
|
@@ -222,9 +222,9 @@ add("P", _("per weekday"), "per_weekday") # deprecated
|
|
222
222
|
add("E", _("Relative to Easter"), "easter")
|
223
223
|
|
224
224
|
|
225
|
-
|
226
225
|
class DisplayColor(Choice):
|
227
226
|
font_color = None
|
227
|
+
|
228
228
|
def __init__(self, value, text, names, font_color="white"):
|
229
229
|
super().__init__(value, text, names)
|
230
230
|
self.font_color = font_color
|
@@ -247,12 +247,14 @@ class DisplayColors(ChoiceList):
|
|
247
247
|
# text = escape(bc.text)
|
248
248
|
# txt = f"""<span style="background-color:{bc.name};color:{bc.font_color}">{text}</span>"""
|
249
249
|
# txt = mark_safe(txt)
|
250
|
-
sample = f"""<span style="padding:3pt;background-color:{
|
250
|
+
sample = f"""<span style="padding:3pt;background-color:{
|
251
|
+
bc.name};color:{bc.font_color}">(sample)</span>"""
|
251
252
|
sample = mark_safe(sample)
|
252
253
|
txt = format_html("{} {}", bc.text, sample)
|
253
254
|
# raise Exception(f"20250118 {txt.__class__}")
|
254
255
|
return txt
|
255
256
|
|
257
|
+
|
256
258
|
add = DisplayColors.add_item
|
257
259
|
# cssColors = 'White Silver Gray Black Red Maroon Yellow Olive Lime Green Aqua Teal Blue Navy Fuchsia Purple'
|
258
260
|
# cssColors = 'white silver gray black red maroon yellow olive lime green aqua teal blue navy fuchsia purple'
|
@@ -275,7 +277,7 @@ add("220", _("Orange"), "orange", "white")
|
|
275
277
|
add("230", _("Yellow"), "yellow", "black")
|
276
278
|
add("240", _("Green"), "green", "white")
|
277
279
|
add("250", _("Blue"), "blue", "white")
|
278
|
-
add("260", _("Magenta"), "magenta","white")
|
280
|
+
add("260", _("Magenta"), "magenta", "white")
|
279
281
|
add("270", _("Violet"), "violet", "white")
|
280
282
|
|
281
283
|
# Other colors
|
@@ -291,6 +293,7 @@ add("342", _("DarkGreen"), "darkgreen", "white")
|
|
291
293
|
add("343", _("PaleGreen"), "palegreen", "black")
|
292
294
|
add("344", _("Chartreuse"), "chartreuse", "black")
|
293
295
|
add("345", _("Lime"), "lime", "black")
|
296
|
+
add("346", _("Teal"), "teal", "white")
|
294
297
|
add("350", _("Fuchsia"), "fuchsia", "white")
|
295
298
|
add("351", _("Cyan"), "cyan", "black")
|
296
299
|
add("361", _("Purple"), "purple", "white")
|
lino/modlib/tinymce/views.py
CHANGED
@@ -29,7 +29,7 @@ class Templates(View):
|
|
29
29
|
):
|
30
30
|
if request.method == "GET":
|
31
31
|
rpt = requested_actor(app_label, actor)
|
32
|
-
ar = rpt.
|
32
|
+
ar = rpt.create_request(request=request)
|
33
33
|
elem = rpt.get_row_by_pk(ar, pk)
|
34
34
|
if elem is None:
|
35
35
|
raise http.Http404("%s %s does not exist." % (rpt, pk))
|
lino/modlib/uploads/models.py
CHANGED
@@ -485,7 +485,7 @@ def on_sanitize(soup, save=False, ar=None):
|
|
485
485
|
if (src := tag.get('src')) and src.startswith("data:image"):
|
486
486
|
file = base64_to_image(src)
|
487
487
|
upload = rt.models.uploads.Upload(file=file, user=ar.get_user())
|
488
|
-
sar = upload.get_default_table().
|
488
|
+
sar = upload.get_default_table().create_request(parent=ar)
|
489
489
|
upload.save_new_instance(sar)
|
490
490
|
tag.replace_with(f'[file {upload.pk}]')
|
491
491
|
|
lino/modlib/users/ui.py
CHANGED
@@ -3,6 +3,7 @@
|
|
3
3
|
# License: GNU Affero General Public License v3 (see file COPYING for details)
|
4
4
|
# Documentation: :doc:`/specs/users` and :doc:`/dev/users`
|
5
5
|
|
6
|
+
from lino.core.site import has_socialauth
|
6
7
|
from datetime import datetime
|
7
8
|
from textwrap import wrap
|
8
9
|
from importlib import import_module
|
@@ -134,7 +135,7 @@ class UsersOverview(Users):
|
|
134
135
|
pv.update(password=dd.plugins.users.demo_password)
|
135
136
|
btn = rt.models.about.About.get_action_by_name("sign_in")
|
136
137
|
# print btn.get_row_permission(ar, None, None)
|
137
|
-
btn = btn.
|
138
|
+
btn = btn.create_request(
|
138
139
|
action_param_values=pv, renderer=settings.SITE.kernel.default_renderer
|
139
140
|
)
|
140
141
|
btn = btn.ar2button(label=self.username)
|
@@ -212,8 +213,6 @@ class AuthoritiesTaken(Authorities):
|
|
212
213
|
auto_fit_column_widths = True
|
213
214
|
|
214
215
|
|
215
|
-
from lino.core.site import has_socialauth
|
216
|
-
|
217
216
|
if has_socialauth and dd.get_plugin_setting("users", "third_party_authentication"):
|
218
217
|
import social_django
|
219
218
|
|
lino/utils/__init__.py
CHANGED
@@ -18,9 +18,11 @@ 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
|
25
|
+
fieldutils
|
24
26
|
html
|
25
27
|
html2odf
|
26
28
|
html2xhtml
|
@@ -78,6 +80,7 @@ from rstgen.utils import confirm, i2d, i2t
|
|
78
80
|
|
79
81
|
DATE_TO_DIR_TPL = "%Y/%m"
|
80
82
|
|
83
|
+
|
81
84
|
def read_exception(excinfo):
|
82
85
|
# TODO: why not use traceback.format_exc(excinfo) intead of this?
|
83
86
|
f = StringIO()
|
@@ -353,7 +356,7 @@ def hex2str(value):
|
|
353
356
|
raise Exception("hex2str got value %r" % value)
|
354
357
|
r = ""
|
355
358
|
for i in range(old_div(len(value), 2)):
|
356
|
-
s = value[i * 2
|
359
|
+
s = value[i * 2: i * 2 + 2]
|
357
360
|
h = int(s, 16)
|
358
361
|
r += chr(h)
|
359
362
|
return r
|
@@ -364,6 +367,7 @@ curry = lambda func, *args, **kw: lambda *p, **n: func(
|
|
364
367
|
*args + p, **dict(list(kw.items()) + list(n.items()))
|
365
368
|
)
|
366
369
|
|
370
|
+
|
367
371
|
def capture_output(func, *args, **kwargs):
|
368
372
|
s = StringIO()
|
369
373
|
with redirect_stdout(s):
|
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.")
|
lino/utils/diag.py
CHANGED
@@ -443,7 +443,7 @@ def py2rst(self, doctestfmt=False, fmt=None):
|
|
443
443
|
from lino.core.store import get_atomizer
|
444
444
|
|
445
445
|
if isinstance(self, models.Model) and fmt is None:
|
446
|
-
ar = self.get_default_table().
|
446
|
+
ar = self.get_default_table().create_request()
|
447
447
|
|
448
448
|
def fmt(e):
|
449
449
|
s = elem_label(e)
|