lino 25.3.3__py3-none-any.whl → 25.4.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 (38) hide show
  1. lino/__init__.py +7 -1
  2. lino/api/dd.py +1 -1
  3. lino/config/admin_main_base.html +4 -3
  4. lino/help_texts.py +12 -14
  5. lino/locale/bn/LC_MESSAGES/django.po +1112 -907
  6. lino/locale/de/LC_MESSAGES/django.mo +0 -0
  7. lino/locale/de/LC_MESSAGES/django.po +1066 -918
  8. lino/locale/django.pot +1041 -900
  9. lino/locale/es/LC_MESSAGES/django.po +1103 -902
  10. lino/locale/et/LC_MESSAGES/django.po +1103 -915
  11. lino/locale/fr/LC_MESSAGES/django.po +1105 -916
  12. lino/locale/nl/LC_MESSAGES/django.po +1102 -917
  13. lino/locale/pt_BR/LC_MESSAGES/django.po +1099 -900
  14. lino/locale/zh_Hant/LC_MESSAGES/django.po +1099 -900
  15. lino/management/commands/buildcache.py +48 -3
  16. lino/management/commands/prep.py +1 -1
  17. lino/mixins/duplicable.py +1 -1
  18. lino/modlib/__init__.py +0 -1
  19. lino/modlib/checkdata/models.py +33 -15
  20. lino/modlib/comments/fixtures/demo2.py +1 -9
  21. lino/modlib/extjs/views.py +2 -3
  22. lino/modlib/linod/mixins.py +94 -41
  23. lino/modlib/linod/models.py +1 -1
  24. lino/modlib/memo/__init__.py +4 -0
  25. lino/modlib/memo/models.py +47 -1
  26. lino/modlib/users/ui.py +4 -2
  27. lino/modlib/weasyprint/__init__.py +12 -20
  28. lino/modlib/weasyprint/config/weasyprint/base.weasy.html +13 -2
  29. lino/sphinxcontrib/logo/__init__.py +2 -1
  30. lino/utils/__init__.py +1 -0
  31. lino/utils/dbhash.py +5 -3
  32. lino/utils/format_date.py +9 -4
  33. {lino-25.3.3.dist-info → lino-25.4.0.dist-info}/METADATA +1 -1
  34. {lino-25.3.3.dist-info → lino-25.4.0.dist-info}/RECORD +37 -38
  35. lino/management/commands/buildsite.py +0 -67
  36. {lino-25.3.3.dist-info → lino-25.4.0.dist-info}/WHEEL +0 -0
  37. {lino-25.3.3.dist-info → lino-25.4.0.dist-info}/licenses/AUTHORS.rst +0 -0
  38. {lino-25.3.3.dist-info → lino-25.4.0.dist-info}/licenses/COPYING +0 -0
@@ -1,13 +1,58 @@
1
1
  # -*- coding: UTF-8 -*-
2
- # Copyright 2023 Rumma & Ko Ltd.
2
+ # Copyright 2009-2023 Rumma & Ko Ltd.
3
3
  # License: GNU Affero General Public License v3 (see file COPYING for details)
4
4
 
5
- from django.core.management.base import BaseCommand
5
+ from click import confirm
6
+ from django.core.management.base import BaseCommand, CommandError
7
+ from django.core.management import call_command
6
8
  from django.conf import settings
9
+ from lino import logger
7
10
 
8
11
 
9
12
  class Command(BaseCommand):
13
+ """Build the site cache files and run collectstatic for this Lino site."""
14
+
15
+ def add_arguments(self, parser):
16
+ super().add_arguments(parser)
17
+ parser.add_argument(
18
+ "-b", "--batch", "--noinput",
19
+ action="store_false",
20
+ dest="interactive",
21
+ default=True,
22
+ help="Do not prompt for input of any kind.",
23
+ ),
24
+
10
25
  def handle(self, *args, **options):
26
+ interactive = options.get("interactive")
11
27
  verbosity = options.get("verbosity")
12
- # print("20231005", verbosity)
28
+ project_dir = settings.SITE.project_dir
29
+
30
+ options = dict(interactive=False, verbosity=verbosity)
31
+
32
+ if interactive:
33
+ msg = "Build everything for ({})".format(project_dir)
34
+ msg += ".\nAre you sure?"
35
+ if not confirm(msg, default=True):
36
+ raise CommandError("User abort.")
37
+
38
+ # the following log message was useful on Travis 20150104
39
+ if verbosity > 0:
40
+ logger.info("`buildcache` started on %s.", project_dir)
41
+
42
+ # pth = project_dir / "settings.py"
43
+ # if pth.exists():
44
+ # pth.touch()
45
+
46
+ call_command("collectstatic", **options)
47
+
13
48
  settings.SITE.build_site_cache(force=True, verbosity=verbosity)
49
+
50
+ # if settings.SITE.is_installed("help"):
51
+ # call_command("makehelp", verbosity=verbosity)
52
+
53
+ # for p in settings.SITE.installed_plugins:
54
+ # p.on_buildsite(settings.SITE, verbosity=verbosity)
55
+
56
+ # settings.SITE.clear_site_config()
57
+
58
+ logger.info("`buildcache` finished on %s.", project_dir)
@@ -15,7 +15,7 @@ class Command(BaseCommand):
15
15
  super().add_arguments(parser)
16
16
  (
17
17
  parser.add_argument(
18
- "--noinput",
18
+ "-b", "--batch", "--noinput",
19
19
  action="store_false",
20
20
  dest="interactive",
21
21
  default=True,
lino/mixins/duplicable.py CHANGED
@@ -29,7 +29,7 @@ class Duplicate(actions.Action):
29
29
  sort_index = 11
30
30
  show_in_workflow = False
31
31
  # readonly = False # like ShowInsert. See docs/blog/2012/0726
32
- callable_from = "t"
32
+ callable_from = "td"
33
33
 
34
34
  # required_roles = set([Expert])
35
35
 
lino/modlib/__init__.py CHANGED
@@ -55,7 +55,6 @@ Communication
55
55
 
56
56
  comments
57
57
  notify
58
- linod
59
58
 
60
59
  Enterprise Resources
61
60
  ====================
@@ -1,5 +1,5 @@
1
1
  # -*- coding: UTF-8 -*-
2
- # Copyright 2015-2023 Rumma & Ko Ltd
2
+ # Copyright 2015-2025 Rumma & Ko Ltd
3
3
  # License: GNU Affero General Public License v3 (see file COPYING for details)
4
4
 
5
5
  from collections import OrderedDict
@@ -9,18 +9,17 @@ from django.conf import settings
9
9
  from django.utils import translation
10
10
  from django.template.defaultfilters import pluralize
11
11
 
12
+ from lino import logger
13
+ from lino.core import constants
12
14
  from lino.core.gfks import gfk2lookup
13
15
  from lino.modlib.gfks.mixins import Controllable
14
16
  from lino.modlib.users.mixins import UserAuthored
15
17
  from lino.modlib.linod.choicelists import background_task
16
18
  from lino.core.roles import SiteStaff
17
-
18
19
  from lino.api import dd, rt, _
19
20
 
20
21
  from .choicelists import Checker, Checkers
21
-
22
22
  from .roles import CheckdataUser
23
- from lino.core import constants
24
23
 
25
24
 
26
25
  class CheckerAction(dd.Action):
@@ -32,6 +31,8 @@ class CheckerAction(dd.Action):
32
31
  Message = rt.models.checkdata.Message
33
32
  gfk = Message.owner
34
33
  for obj in objects:
34
+ if checkers is None:
35
+ checkers = get_checkers_for(obj.__class__)
35
36
  qs = Message.objects.filter(**gfk2lookup(gfk, obj))
36
37
  qs.delete()
37
38
  for chk in checkers:
@@ -84,12 +85,12 @@ class UpdateMessagesByController(CheckerAction):
84
85
  combo_group = "checkdata"
85
86
  required_roles = dd.login_required()
86
87
 
87
- def __init__(self, model):
88
- self.model = model
89
- super().__init__()
88
+ # def __init__(self, model=None, **kwargs):
89
+ # self.model = model
90
+ # super().__init__(**kwargs)
90
91
 
91
92
  def run_from_ui(self, ar, fix=None):
92
- self.run_it(ar, fix, get_checkers_for(self.model), ar.selected_rows)
93
+ self.run_it(ar, fix, None, ar.selected_rows)
93
94
  # if fix is None:
94
95
  # fix = self.fix_them
95
96
  # Message = rt.models.checkdata.Message
@@ -109,6 +110,16 @@ class FixMessagesByController(UpdateMessagesByController):
109
110
  fix_them = True
110
111
 
111
112
 
113
+ class QuickFixMessagesByController(UpdateMessagesByController):
114
+ # label = _("Fix data problems")
115
+ fix_them = True
116
+ combo_group = None
117
+ # icon_name = "lightning"
118
+ icon_name = None
119
+ button_text = ' ⚡ ' # 26A1
120
+ # button_text = "✓" # u"\u2713"
121
+
122
+
112
123
  class FixAllProblems(CheckerAction):
113
124
  select_rows = False
114
125
  show_in_plain = True
@@ -119,7 +130,7 @@ class FixAllProblems(CheckerAction):
119
130
 
120
131
  def run_from_ui(self, ar, fix=None):
121
132
  mi = ar.master_instance
122
- print(f"20250307 {mi}")
133
+ # print(f"20250307 {mi}")
123
134
  self.run_it(ar, fix, get_checkers_for(mi.__class__), [mi])
124
135
  ar.set_response(refresh=True)
125
136
 
@@ -264,11 +275,14 @@ def set_checkdata_actions(sender, **kw):
264
275
  if m is None:
265
276
  continue
266
277
  assert m is not Message
267
- m.define_action(check_data=UpdateMessagesByController(m))
268
- m.define_action(fix_problems=FixMessagesByController(m))
269
- if True:
270
- # don't add it automatically because appdev might prefer
271
- # to show it in a detail_layout:
278
+ # if hasattr(m, 'check_data'):
279
+ if (label := getattr(m, 'quickfix_checkdata_label', None)):
280
+ # print(f"20250324 Customized quickfix_checkdata_label {label} for {m}")
281
+ m.define_action(quick_fix=QuickFixMessagesByController(label=label))
282
+ else:
283
+ # print(f"20250324 Default checkdata buttons for {m}")
284
+ m.define_action(check_data=UpdateMessagesByController())
285
+ m.define_action(fix_problems=FixMessagesByController())
272
286
  m.define_action(
273
287
  show_problems=dd.ShowSlaveTable(
274
288
  MessagesByOwner,
@@ -280,7 +294,11 @@ def set_checkdata_actions(sender, **kw):
280
294
 
281
295
 
282
296
  def get_checkers_for(model):
283
- return get_checkable_models()[model]
297
+ checkers = []
298
+ for m, lst in get_checkable_models().items():
299
+ if m is not None and issubclass(model, m):
300
+ checkers += lst
301
+ return checkers
284
302
 
285
303
 
286
304
  def check_instance(obj, **kwargs):
@@ -4,6 +4,7 @@
4
4
  Adds some demo comments.
5
5
 
6
6
  """
7
+
7
8
  import datetime
8
9
  from lino.utils import i2t
9
10
  from lino.utils import Cycler
@@ -32,12 +33,6 @@ plain1 = "Some plain text."
32
33
  plain2 = "Two paragraphs of plain text.\n\nThe second paragraph."
33
34
  # plain2 += " With an 👁 (U+1F441)." #5855 (Jane fails to store certain unicode characters)
34
35
 
35
- imageDataURL = """data:image/png;base64,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"""
36
- body_with_img = f"""\
37
- <p>Here is an image:</p>
38
- <p><img src="{imageDataURL}" class="bar"></p>\
39
- """
40
-
41
36
  BODIES = Cycler(
42
37
  [styled, table, lorem, short_lorem, breaking, cond_comment, plain1, plain2]
43
38
  )
@@ -45,9 +40,6 @@ BODIES = Cycler(
45
40
  BODIES.items.insert(0, "")
46
41
  BODIES.items.insert(0, "")
47
42
 
48
- if dd.is_installed('blogs'):
49
- BODIES.items.append(body_with_img)
50
-
51
43
 
52
44
  def objects():
53
45
  Comment = rt.models.comments.Comment
@@ -321,8 +321,7 @@ NOT_FOUND = "%s has no row with primary key %r"
321
321
 
322
322
  class ApiElement(View):
323
323
  """
324
- The view that responds to
325
- ``r'api/(?P<app_label>\w+)/(?P<actor>\w+)/(?P<pk>[^/]+)$'``.
324
+ The view that responds to ``api/app_label/actor/pk``.
326
325
  """
327
326
 
328
327
  @method_decorator(ensure_csrf_cookie)
@@ -354,7 +353,7 @@ class ApiElement(View):
354
353
  ar = ba.create_request(
355
354
  request=request, renderer=settings.SITE.kernel.default_renderer)
356
355
  if pk and pk != "-99999" and pk != "-99998":
357
- if issubclass(rpt.model, models.Model):
356
+ if rpt.model is not None and issubclass(rpt.model, models.Model):
358
357
  try:
359
358
  ar.set_selected_pks(pk)
360
359
  except rpt.model.DoesNotExist:
@@ -38,6 +38,11 @@ class RunNow(dd.Action):
38
38
  # icon_name = 'bell'
39
39
  # icon_name = 'lightning'
40
40
 
41
+ # def get_action_permission(self, ar, obj, state):
42
+ # if obj.requested_at or obj.is_running():
43
+ # return False
44
+ # return super().get_action_permission(ar, obj, state)
45
+
41
46
  def run_from_ui(self, ar, **kwargs):
42
47
  # print("20231102 RunNow", ar.selected_rows)
43
48
  for obj in ar.selected_rows:
@@ -45,10 +50,11 @@ class RunNow(dd.Action):
45
50
  if True: # dd.plugins.linod.use_channels:
46
51
  obj.last_start_time = None
47
52
  obj.last_end_time = None
53
+ obj.requested_at = timezone.now()
48
54
  obj.message = "{} requested to run this task at {}.".format(
49
55
  ar.get_user(), dd.ftl(timezone.now())
50
56
  )
51
- obj.disabled = False
57
+ # obj.disabled = False
52
58
  obj.full_clean()
53
59
  obj.save()
54
60
  else:
@@ -57,6 +63,30 @@ class RunNow(dd.Action):
57
63
  ar.set_response(refresh=True)
58
64
 
59
65
 
66
+ class CancelRun(dd.Action):
67
+ label = _("Cancel request")
68
+ help_text = _("Cancel the request to run this task asap.")
69
+ select_rows = True
70
+ button_text = "🗙" # ⛒
71
+ # icon_name = 'bell'
72
+ # icon_name = 'lightning'
73
+
74
+ # def get_action_permission(self, ar, obj, state):
75
+ # if obj.requested_at is None:
76
+ # return False
77
+ # return super().get_action_permission(ar, obj, state)
78
+
79
+ def run_from_ui(self, ar, **kwargs):
80
+ for obj in ar.selected_rows:
81
+ assert issubclass(obj.__class__, Runnable)
82
+ obj.requested_at = None
83
+ obj.message = "{} cancelled the request to run this task.".format(
84
+ ar.get_user())
85
+ obj.full_clean()
86
+ obj.save()
87
+ ar.set_response(refresh=True)
88
+
89
+
60
90
  class Runnable(Sequenced, RecurrenceSet):
61
91
  class Meta:
62
92
  abstract = True
@@ -66,6 +96,7 @@ class Runnable(Sequenced, RecurrenceSet):
66
96
  last_start_time = dd.DateTimeField(
67
97
  _("Started at"), null=True, editable=False)
68
98
  last_end_time = dd.DateTimeField(_("Ended at"), null=True, editable=False)
99
+ requested_at = dd.DateTimeField(_("Requested at"), null=True, editable=False)
69
100
  message = dd.RichTextField(
70
101
  _("Logged messages"), format="plain", editable=False)
71
102
 
@@ -74,12 +105,23 @@ class Runnable(Sequenced, RecurrenceSet):
74
105
  name = models.CharField(_("Name"), max_length=200, blank=True)
75
106
 
76
107
  run_now = RunNow()
108
+ cancel_run = CancelRun()
77
109
 
78
110
  def __str__(self):
111
+ # r = "{} ({} #{})".format(
112
+ # self.name, self._meta.verbose_name, self.seqno)
79
113
  r = "{} #{} ({})".format(
80
114
  self._meta.verbose_name, self.seqno, self.name)
81
115
  return r
82
116
 
117
+ def disabled_fields(self, ar):
118
+ rv = super().disabled_fields(ar)
119
+ if self.requested_at is None:
120
+ rv.add('cancel_run')
121
+ else:
122
+ rv.add('run_now')
123
+ return rv
124
+
83
125
  def full_clean(self, *args, **kwargs):
84
126
  super().full_clean(*args, **kwargs)
85
127
  # 20250213 The following caused 'Invalid procedure invoicing.Task for
@@ -114,6 +156,7 @@ class Runnable(Sequenced, RecurrenceSet):
114
156
  # ar.info("Start %s with logging level %s", astr(self), self.log_level)
115
157
  # forget about any previous run:
116
158
  self.last_start_time = timezone.now()
159
+ self.requested_at = None
117
160
  self.last_end_time = None
118
161
  self.message = ""
119
162
  # print("20231102 full_clean")
@@ -150,17 +193,20 @@ class Runnable(Sequenced, RecurrenceSet):
150
193
  @dd.displayfield("Status")
151
194
  def status(self, ar=None):
152
195
  if self.is_running():
153
- return _("Running since {}").format(dd.ftl(self.last_start_time))
196
+ return _("Running since {}").format(dd.ftf(self.last_start_time))
197
+ if self.requested_at is not None:
198
+ return _("Requested to run asap (since {})").format(
199
+ dd.ftf(self.requested_at))
154
200
  if self.disabled:
155
201
  return _("Disabled")
156
202
  if self.last_start_time is None or self.last_end_time is None:
157
- if self.every_unit in (Recurrences.never, None):
203
+ if self.every_unit in {Recurrences.never, None}:
158
204
  return _("Not scheduled")
159
205
  return _("Scheduled to run asap")
160
206
  next_time = self.get_next_suggested_date(self.last_end_time)
161
207
  if next_time is None:
162
208
  return _("Not scheduled")
163
- return _("Scheduled to run at {}").format(dd.ftl(next_time))
209
+ return _("Scheduled to run at {}").format(dd.ftf(next_time))
164
210
 
165
211
 
166
212
  async def start_task_runner(ar=None, max_count=None):
@@ -177,53 +223,60 @@ async def start_task_runner(ar=None, max_count=None):
177
223
  next_time = now + \
178
224
  timedelta(seconds=dd.plugins.linod.background_sleep_time)
179
225
 
226
+ tasks = []
180
227
  for cls in Procedures.task_classes():
181
228
  # asyncio.ensure_future(m.start_task_runner(ar.spawn_request()))
182
229
  # print("20240424b")
183
- tasks = cls.objects.filter(disabled=False).order_by("seqno")
230
+ async for obj in cls.objects.filter(
231
+ requested_at__isnull=False).order_by("requested_at"):
232
+ tasks.append(obj)
233
+ for cls in Procedures.task_classes():
234
+ async for obj in cls.objects.filter(
235
+ requested_at__isnull=True, disabled=False).order_by("seqno"):
236
+ tasks.append(obj)
184
237
  # print("20240424c")
185
238
  # async for self in tasks:
186
- async for self in tasks:
187
- # print("20240424d")
188
- # raise Warning("20231230")
189
- if self.last_end_time is None and self.last_start_time is not None:
190
- run_duration = now - self.last_start_time
191
- if run_duration > timedelta(hours=2):
192
- msg = "Kill {} because it has been running more than 2 hours".format(
193
- astr(self)
194
- )
195
- await ar.adebug(msg)
196
- self.last_end_time = now
197
- self.message = msg
198
- await sync_to_async(self.full_clean)()
199
- # self.full_clean()
200
- await self.asave()
201
- # self.disabled = True
202
- else:
203
- await ar.adebug("Skip running task %s", astr(self))
204
- continue
205
-
206
- if self.last_end_time is not None:
207
- nst = await sync_to_async(self.get_next_suggested_date)(
208
- self.last_end_time, ar.logger
239
+ for self in tasks:
240
+ # print("20240424d")
241
+ # raise Warning("20231230")
242
+ if self.last_end_time is None and self.last_start_time is not None:
243
+ run_duration = now - self.last_start_time
244
+ if run_duration > timedelta(hours=2):
245
+ msg = "Killed {} because running more than 2 hours".format(
246
+ astr(self)
209
247
  )
210
- if nst is None:
211
- await ar.adebug("No time suggested to start %s", astr(self))
212
- continue
213
- if nst > now:
214
- await ar.adebug("Too early to start %s", astr(self))
215
- next_time = min(next_time, nst)
216
- continue
217
-
218
- # await ar.adebug("Start %s", self)
219
- # print("20231021 1 gonna start", self)
220
- await self.start_task(ar)
221
- assert self.last_end_time is not None
248
+ await ar.adebug(msg)
249
+ self.last_end_time = now
250
+ self.message = msg
251
+ await sync_to_async(self.full_clean)()
252
+ # self.full_clean()
253
+ await self.asave()
254
+ # self.disabled = True
255
+ else:
256
+ await ar.adebug("Skip running task %s", astr(self))
257
+ continue
258
+
259
+ if self.requested_at is None and self.last_end_time is not None:
222
260
  nst = await sync_to_async(self.get_next_suggested_date)(
223
261
  self.last_end_time, ar.logger
224
262
  )
225
- if nst is not None:
263
+ if nst is None:
264
+ await ar.adebug("No time suggested to start %s", astr(self))
265
+ continue
266
+ if nst > now:
267
+ await ar.adebug("Too early to start %s", astr(self))
226
268
  next_time = min(next_time, nst)
269
+ continue
270
+
271
+ # await ar.adebug("Start %s", self)
272
+ # print("20231021 1 gonna start", self)
273
+ await self.start_task(ar)
274
+ assert self.last_end_time is not None
275
+ nst = await sync_to_async(self.get_next_suggested_date)(
276
+ self.last_end_time, ar.logger
277
+ )
278
+ if nst is not None:
279
+ next_time = min(next_time, nst)
227
280
 
228
281
  count += 1
229
282
  if max_count is not None and count >= max_count:
@@ -38,7 +38,7 @@ class SystemTasks(dd.Table):
38
38
  name
39
39
  every every_unit
40
40
  log_level disabled status
41
- last_start_time last_end_time
41
+ requested_at last_start_time last_end_time
42
42
  message
43
43
  """
44
44
  insert_layout = """
@@ -158,3 +158,7 @@ class Plugin(ad.Plugin):
158
158
  mg = site.plugins.office
159
159
  m = m.add_menu(mg.app_label, mg.verbose_name)
160
160
  m.add_action("memo.Mentions")
161
+ # m.add_action("about.About.insert_reference")
162
+
163
+ # def get_quicklinks(self):
164
+ # yield "about.About.insert_reference"
@@ -14,8 +14,9 @@ from lino.core.roles import SiteStaff
14
14
  from lino.core.gfks import gfk2lookup
15
15
  from lino.modlib.gfks.mixins import Controllable
16
16
  from lino.modlib.gfks.fields import GenericForeignKey, GenericForeignKeyIdField
17
+ from lino.modlib.about.models import About
17
18
  from .parser import split_name_rest
18
- # from .mixins import *
19
+ from .mixins import MemoReferrable
19
20
 
20
21
  # Translators: will also be concatenated with '(type)' and '(object)'
21
22
  target_label = _("Target")
@@ -71,8 +72,10 @@ class Mention(Controllable):
71
72
  obj = super()
72
73
  return obj.as_summary_item(ar, text, **kwargs)
73
74
 
75
+
74
76
  dd.update_field(Mention, 'owner', verbose_name=_("Referrer"))
75
77
 
78
+
76
79
  class Mentions(dd.Table):
77
80
  required_roles = dd.login_required(SiteStaff)
78
81
  editable = False
@@ -89,8 +92,51 @@ class Mentions(dd.Table):
89
92
  # column_names = "target *"
90
93
  # default_display_modes = {None: constants.DISPLAY_MODE_SUMMARY}
91
94
 
95
+
92
96
  class MentionsByTarget(Mentions):
93
97
  label = _("Mentioned by")
94
98
  master_key = "target"
95
99
  column_names = "owner *"
96
100
  default_display_modes = {None: constants.DISPLAY_MODE_SUMMARY}
101
+
102
+
103
+ CONTENT_TYPE_FIELD = dd.ForeignKey(ContentType, editable=True)
104
+
105
+
106
+ class InsertReference(dd.Action):
107
+
108
+ label = _("Insert reference")
109
+ select_rows = False
110
+ parameters = dict(
111
+ content_type=CONTENT_TYPE_FIELD,
112
+ primary_key=GenericForeignKeyIdField(CONTENT_TYPE_FIELD, editable=True)
113
+ )
114
+ params_layout = """
115
+ content_type
116
+ primary_key
117
+ """
118
+
119
+ @dd.chooser()
120
+ def content_type_choices(self):
121
+ for obj in rt.models.gfks.ContentType.objects.all():
122
+ if issubclass(obj.model_class(), MemoReferrable):
123
+ yield obj
124
+
125
+ @dd.chooser(instance_values=True)
126
+ def primary_key_choices(cls, content_type):
127
+ if content_type is None:
128
+ # print("You must select a content type")
129
+ # return []
130
+ return [("", _("You must select a content type"))]
131
+ m = content_type.model_class()
132
+ return m.objects.all()
133
+
134
+ def run_from_ui(self, ar, **kwargs):
135
+ pv = ar.action_param_values
136
+ ct = pv.content_type.model_class()
137
+ obj = ct.objects.get(pk=pv.primary_key)
138
+ txt = obj.obj2memo()
139
+ ar.success(message=txt)
140
+
141
+
142
+ About.insert_reference = InsertReference()