lino 24.10.0__py3-none-any.whl → 24.10.1__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/core/tablerequest.py DELETED
@@ -1,758 +0,0 @@
1
- # -*- coding: UTF-8 -*-
2
- # Copyright 2009-2023 Rumma & Ko Ltd
3
- # License: GNU Affero General Public License v3 (see file COPYING for details)
4
- """Defines the `TableRequest` class.
5
-
6
- """
7
- from past.utils import old_div
8
-
9
- from types import GeneratorType
10
- import sys
11
- from io import StringIO
12
- import json
13
-
14
- from django.db import models
15
- from django.conf import settings
16
- from django.db.models.query import QuerySet
17
- from django.core.exceptions import ObjectDoesNotExist, SuspiciousOperation
18
-
19
- if settings.SITE.is_installed("contenttypes"):
20
- from django.contrib.contenttypes.models import ContentType
21
- else:
22
- ContentType = None
23
-
24
- from lino.core.utils import obj2str
25
- from lino.core.utils import format_request
26
- from lino.core.store import get_atomizer
27
- from lino.core import constants
28
- from etgen import html as xghtml
29
- from lino.utils.html import E, tostring
30
- from lino.utils import jsgen
31
- from lino.utils import MissingRow
32
- from lino.core.utils import getrqdata
33
-
34
- from .fields import RemoteField, FakeField, TableRow
35
- from .requests import ActionRequest
36
-
37
-
38
- class SearchQuerySet:
39
- pass
40
-
41
-
42
- class SearchQuery:
43
- pass
44
-
45
-
46
- if settings.SITE.is_installed("search"):
47
- if settings.SITE.use_elasticsearch:
48
- try:
49
- from elasticsearch_django.models import SearchQuery
50
- except ImportError:
51
- pass
52
- elif settings.SITE.use_solr:
53
- try:
54
- from haystack.query import SearchQuerySet
55
- except ImportError:
56
- pass
57
-
58
- WARNINGS_LOGGED = dict()
59
-
60
-
61
- def column_header(col):
62
- # ~ if col.label:
63
- # ~ return join_elems(col.label.split('\n'),sep=E.br)
64
- # ~ return [unicode(col.name)]
65
- label = col.get_label()
66
- if label is None:
67
- return col.name
68
- return str(label)
69
-
70
-
71
- def sliced_data_iterator(qs, offset, limit):
72
- # qs is either a Django QuerySet or iterable
73
- # When limit is None or 0, offset cannot be -1
74
- if offset is not None:
75
- if isinstance(qs, QuerySet):
76
- num = qs.count()
77
- else:
78
- num = len(qs)
79
- if offset == -1:
80
- page_num = num // limit
81
- offset = limit * page_num
82
- qs = qs[offset:num]
83
- if limit is not None and limit != 0:
84
- qs = qs[:limit]
85
- return qs
86
-
87
-
88
- class TableRequest(ActionRequest):
89
- """A specialized :class:`ActionRequest
90
- <lino.core.requests.ActionRequest>` whose actor is a :class:`table
91
- <lino.core.tables.AbstractTable>`.
92
-
93
- """
94
-
95
- master = None
96
- extra = None
97
- title = None
98
- filter = None
99
- # known_values = None
100
- limit = None
101
- offset = None
102
-
103
- _data_iterator = None
104
- _sliced_data_iterator = None
105
- _insert_sar = None
106
-
107
- def gen_insert_button(
108
- self, target, button_attrs=dict(style="float: right;"), **values
109
- ):
110
- """
111
- Generate an insert button using a cached insertable object.
112
-
113
- This is functionally equivalent to saying::
114
-
115
- if self.insert_action is not None:
116
- ir = self.insert_action.request_from(ar)
117
- if ir.get_permission():
118
- return ir.ar2button()
119
-
120
- The difference is that gen_insert_button is more efficient when you do
121
- this more than once during a single request.
122
-
123
- `target` is the actor into which we want to insert an object.
124
- `button_label` is the optional button label.
125
- `values` is a dict of extra default values to apply to the insertable object.
126
-
127
- First usage example is in :mod:`lino_xl.lib.calview`.
128
-
129
- """
130
- if self._insert_sar is None:
131
- self._insert_sar = target.insert_action.request_from(self)
132
- else:
133
- assert self._insert_sar.actor is target
134
- if self._insert_sar.get_permission():
135
- st = self._insert_sar.get_status()
136
- st["data_record"]["data"].update(values)
137
- # obj = st['data_record']
138
- # for k, v in values.items():
139
- # setattr(obj, k, v)
140
- # print(20200302, st['data_record'])
141
- return self._insert_sar.ar2button(**button_attrs)
142
-
143
- def execute(self):
144
- """This will actually call the :meth:`get_data_iterator` and run the
145
- database query.
146
-
147
- Automatically called when either :attr:`data_iterator`
148
- or :attr:`sliced_data_iterator` is accesed.
149
-
150
- """
151
- # print("20181121 execute", self.actor)
152
- try:
153
- self._data_iterator = self.get_data_iterator()
154
- if self._data_iterator is None:
155
- raise Exception("No data iterator for {}".format(self))
156
- except Warning as e:
157
- # ~ logger.info("20130809 Warning %s",e)
158
- self.no_data_text = str(e)
159
- self._data_iterator = []
160
- except Exception as e:
161
- if not settings.SITE.catch_layout_exceptions:
162
- raise
163
- # Report this exception. But since such errors may occur
164
- # rather often and since exception loggers usually send an
165
- # email to the local system admin, make sure to log each
166
- # exception only once.
167
- self.no_data_text = (
168
- "{} (set catch_layout_exceptions to see details)".format(e)
169
- )
170
- w = WARNINGS_LOGGED.get(str(e))
171
- if w is None:
172
- WARNINGS_LOGGED[str(e)] = True
173
- raise
174
- # logger.exception(e)
175
- self._data_iterator = []
176
-
177
- if isinstance(self._data_iterator, GeneratorType):
178
- # print 20150718, self._data_iterator
179
- self._data_iterator = tuple(self._data_iterator)
180
- if isinstance(self._data_iterator, (SearchQuery, SearchQuerySet)):
181
- self._sliced_data_iterator = tuple(
182
- self.actor.get_rows_from_search_query(self._data_iterator, self)
183
- )
184
- else:
185
- self._sliced_data_iterator = sliced_data_iterator(
186
- self._data_iterator, self.offset, self.limit
187
- )
188
- # logger.info("20171116 executed : %s", self._sliced_data_iterator)
189
-
190
- def must_execute(self):
191
- return self._data_iterator is None
192
-
193
- def get_data_iterator_property(self):
194
- if self._data_iterator is None:
195
- self.execute()
196
- return self._data_iterator
197
-
198
- def get_sliced_data_iterator_property(self):
199
- if self._sliced_data_iterator is None:
200
- self.execute()
201
- return self._sliced_data_iterator
202
-
203
- data_iterator = property(get_data_iterator_property)
204
- sliced_data_iterator = property(get_sliced_data_iterator_property)
205
-
206
- def get_data_iterator(self):
207
- self.actor.check_params(self.param_values)
208
- if self.actor.get_data_rows is not None:
209
- l = []
210
- for row in self.actor.get_data_rows(self):
211
- group = self.actor.group_from_row(row)
212
- group.process_row(l, row)
213
- return l
214
- # ~ logger.info("20120914 tables.get_data_iterator %s",self)
215
- # ~ logger.info("20120914 tables.get_data_iterator %s",self.actor)
216
- # print("20181121 get_data_iterator", self.actor)
217
- return self.actor.get_request_queryset(self)
218
-
219
- def get_total_count(self):
220
- """
221
- Calling `len()` on a QuerySet would execute the whole SELECT.
222
- See `/blog/2012/0124`
223
- """
224
- di = self.data_iterator
225
- if isinstance(di, SearchQuery):
226
- return di.total_hits
227
- if isinstance(di, QuerySet):
228
- return di.count()
229
- # try:
230
- # return di.count()
231
- # except Exception as e:
232
- # raise e.__class__("{} : {}".format(self, e))
233
- # ~ if di is None:
234
- # ~ raise Exception("data_iterator is None: %s" % self)
235
- if True:
236
- return len(di)
237
- else:
238
- try:
239
- return len(di)
240
- except TypeError:
241
- raise TypeError("{0} has no length".format(di))
242
-
243
- def __iter__(self):
244
- return self.data_iterator.__iter__()
245
-
246
- def __getitem__(self, i):
247
- return self.data_iterator.__getitem__(i)
248
-
249
- def parse_req(self, request, rqdata, **kw):
250
- """Parse the incoming HttpRequest and translate it into keyword
251
- arguments to be used by :meth:`setup`.
252
-
253
- The `mt` url param is parsed only when needed. Usually it is
254
- not needed because the `master_class` is constant and known
255
- per actor. But there are exceptions:
256
-
257
- - `master` is `ContentType`
258
-
259
- - `master` is some abstract model
260
-
261
- - `master` is not a subclass of Model, e.g.
262
- :class:`lino_xl.lib.polls.AnswersByResponse`, a
263
- virtual table that defines :meth:`get_row_by_pk
264
- <lino.core.actors.Actor.get_row_by_pk>`.
265
-
266
- """
267
- # logger.info("20120723 %s.parse_req() %s", self.actor, rqdata)
268
- # ~ rh = self.ah
269
-
270
- if "master_instance" not in kw:
271
- kw.update(
272
- master_type=rqdata.get(constants.URL_PARAM_MASTER_TYPE, None),
273
- master_key=rqdata.get(constants.URL_PARAM_MASTER_PK, None),
274
- )
275
-
276
- # if settings.SITE.use_filterRow:
277
- # exclude = dict()
278
- # for f in self.ah.store.fields:
279
- # if f.field:
280
- # filterOption = rqdata.get(
281
- # 'filter[%s_filterOption]' % f.field.name)
282
- # if filterOption == 'empty':
283
- # kw[f.field.name + "__isnull"] = True
284
- # elif filterOption == 'notempty':
285
- # kw[f.field.name + "__isnull"] = False
286
- # else:
287
- # filterValue = rqdata.get('filter[%s]' % f.field.name)
288
- # if filterValue:
289
- # if not filterOption:
290
- # filterOption = 'contains'
291
- # if filterOption == 'contains':
292
- # kw[f.field.name + "__icontains"] = filterValue
293
- # elif filterOption == 'doesnotcontain':
294
- # exclude[f.field.name +
295
- # "__icontains"] = filterValue
296
- # else:
297
- # print("unknown filterOption %r" % filterOption)
298
- # if len(exclude):
299
- # kw.update(exclude=exclude)
300
-
301
- if settings.SITE.use_gridfilters:
302
- filter = rqdata.get(constants.URL_PARAM_GRIDFILTER, None)
303
- if filter is not None:
304
- filter = json.loads(filter)
305
- kw["gridfilters"] = [constants.dict2kw(flt) for flt in filter]
306
-
307
- kw = ActionRequest.parse_req(self, request, rqdata, **kw)
308
-
309
- # if str(self.actor) == 'comments.CommentsByRFC':
310
- # print("20230426", kw)
311
-
312
- # ~ kw.update(self.report.known_values)
313
- # ~ for fieldname, default in self.report.known_values.items():
314
- # ~ v = request.REQUEST.get(fieldname,None)
315
- # ~ if v is not None:
316
- # ~ kw[fieldname] = v
317
-
318
- quick_search = rqdata.get(constants.URL_PARAM_FILTER, None)
319
- if quick_search:
320
- kw.update(quick_search=quick_search)
321
-
322
- sort = rqdata.get(constants.URL_PARAM_SORT, None)
323
- if sort:
324
- sortfld = self.actor.get_data_elem(sort)
325
- if isinstance(sortfld, FakeField):
326
- sort = sortfld.sortable_by
327
- # sort might be None when user asked to sort a virtual
328
- # field without sortable_by.
329
- else:
330
- sort = [sort]
331
- # print("20231006 sort", sortfld, sort)
332
- if sort is not None:
333
-
334
- def si(k):
335
- if k[0] == "-":
336
- return k[1:]
337
- else:
338
- return "-" + k
339
-
340
- sort_dir = rqdata.get(constants.URL_PARAM_SORTDIR, "ASC")
341
- if sort_dir == "DESC":
342
- sort = [si(k) for k in sort]
343
- # sort = ['-' + k for k in sort]
344
- # print("20171123", sort)
345
- kw.update(order_by=sort)
346
-
347
- try:
348
- offset = rqdata.get(constants.URL_PARAM_START, None)
349
- if offset:
350
- kw.update(offset=int(offset))
351
- limit = rqdata.get(constants.URL_PARAM_LIMIT, self.actor.preview_limit)
352
- if limit:
353
- kw.update(limit=int(limit))
354
- except ValueError:
355
- # Example: invalid literal for int() with base 10:
356
- # 'fdpkvcnrfdybhur'
357
- raise SuspiciousOperation("Invalid value for limit or offset")
358
-
359
- kw = self.actor.parse_req(request, rqdata, **kw)
360
- # print("20171123 %s.parse_req() --> %s" % (self, kw))
361
- return kw
362
-
363
- def setup(
364
- self,
365
- quick_search=None,
366
- order_by=None,
367
- offset=None,
368
- limit=None,
369
- title=None,
370
- filter=None,
371
- gridfilters=None,
372
- exclude=None,
373
- extra=None,
374
- **kw,
375
- ):
376
- self.quick_search = quick_search
377
- self.order_by = order_by
378
- self.filter = filter
379
- self.gridfilters = gridfilters
380
- self.extra = extra
381
- self.exclude = exclude or self.actor.exclude
382
- self.page_length = self.actor.page_length
383
-
384
- if title is not None:
385
- self.title = title
386
- if offset is not None:
387
- self.offset = offset
388
- if limit is not None:
389
- self.limit = limit
390
-
391
- super().setup(**kw)
392
- self.actor.setup_request(self)
393
-
394
- # if str(self.actor) == 'outbox.MyOutbox':
395
- # if self.master_instance is None:
396
- # raise Exception("20230426 b {}".format(self.master))
397
-
398
- if isinstance(self.actor.master_field, RemoteField):
399
- # cannot insert rows in a slave table with a remote master
400
- # field
401
- self.create_kw = None
402
- elif isinstance(self.master_instance, MissingRow):
403
- self.create_kw = None # 20230426
404
- else:
405
- self.create_kw = self.actor.get_filter_kw(self)
406
-
407
- def to_rst(self, *args, **kw):
408
- """Returns a string representing this table request in
409
- reStructuredText markup.
410
-
411
- """
412
- stdout = sys.stdout
413
- sys.stdout = StringIO()
414
- self.table2rst(*args, **kw)
415
- rv = sys.stdout.getvalue()
416
- sys.stdout = stdout
417
- return rv
418
-
419
- def table2rst(self, *args, **kwargs):
420
- """
421
- Print a reStructuredText representation of this table request to
422
- stdout.
423
- """
424
- settings.SITE.kernel.text_renderer.show_table(self, *args, **kwargs)
425
-
426
- def table2xhtml(self, **kwargs):
427
- """
428
- Return an HTML representation of this table request.
429
- """
430
- t = xghtml.Table()
431
- self.dump2html(t, self.sliced_data_iterator, **kwargs)
432
- e = t.as_element()
433
- # print "20150822 table2xhtml", tostring(e)
434
- # if header_level is not None:
435
- # return E.div(E.h2(str(self.actor.label)), e)
436
- return e
437
-
438
- def dump2html(
439
- self,
440
- tble,
441
- data_iterator,
442
- column_names=None,
443
- header_links=False,
444
- max_width=None, # ignored
445
- hide_sums=None,
446
- ):
447
- """
448
- Render this table into an existing :class:`etgen.html.Table`
449
- instance. This central method is used by all Lino
450
- renderers.
451
-
452
- Arguments:
453
-
454
- `tble` An instance of :class:`etgen.html.Table`.
455
-
456
- `data_iterator` the iterable provider of table rows. This can
457
- be a queryset or a list.
458
-
459
- `column_names` is an optional string with space-separated
460
- column names. If this is None, the table's
461
- :attr:`column_names <lino.core.tables.Table.column_names>` is
462
- used.
463
-
464
- `header_links` says whether to render column headers clickable
465
- with a link that sorts the table.
466
-
467
- `hide_sums` : whether to hide sums. If this is not given, use
468
- the :attr:`hide_sums <lino.core.tables.Table.hide_sums>` of
469
- the :attr:`actor`.
470
- """
471
- ar = self
472
- tble.attrib.update(self.renderer.tableattrs)
473
- tble.attrib.setdefault("name", self.bound_action.full_name())
474
-
475
- grid = ar.ah.grid_layout.main
476
- # from lino.core.widgets import GridWidget
477
- # if not isinstance(grid, GridWidget):
478
- # raise Exception("20160529 %r is not a GridElement", grid)
479
- columns = grid.columns
480
- fields, headers, cellwidths = ar.get_field_info(column_names)
481
- columns = fields
482
-
483
- sums = [fld.zero for fld in columns]
484
- if not self.ah.actor.hide_headers:
485
- headers = [
486
- x
487
- for x in grid.headers2html(
488
- self, columns, headers, header_links, **self.renderer.cellattrs
489
- )
490
- ]
491
- # if cellwidths and self.renderer.is_interactive:
492
- if cellwidths:
493
- totwidth = sum([int(w) for w in cellwidths])
494
- widths = [str(int(int(w) * 100 / totwidth)) + "%" for w in cellwidths]
495
- for i, td in enumerate(headers):
496
- # td.set('width', six.text_type(cellwidths[i]))
497
- td.set("width", widths[i])
498
- tble.head.append(xghtml.E.tr(*headers))
499
- # ~ print 20120623, ar.actor
500
- recno = 0
501
- for obj in data_iterator:
502
- cells = ar.row2html(recno, columns, obj, sums, **self.renderer.cellattrs)
503
- if cells is not None:
504
- recno += 1
505
- tble.body.append(xghtml.E.tr(*cells))
506
-
507
- if recno == 0:
508
- tble.clear()
509
- tble.body.append(str(ar.no_data_text))
510
-
511
- if hide_sums is None:
512
- hide_sums = ar.actor.hide_sums
513
-
514
- if not hide_sums:
515
- has_sum = False
516
- for i in sums:
517
- if i:
518
- has_sum = True
519
- break
520
- if has_sum:
521
- cells = ar.sums2html(columns, sums, **self.renderer.cellattrs)
522
- tble.body.append(xghtml.E.tr(*cells))
523
-
524
- def get_field_info(ar, column_names=None):
525
- """
526
- Return a tuple `(fields, headers, widths)` which expresses which
527
- columns, headers and widths the user wants for this
528
- request. If `self` has web request info (:attr:`request` is
529
- not None), checks for GET parameters cn, cw and ch. Also
530
- calls the tables's :meth:`override_column_headers
531
- <lino.core.actors.Actor.override_column_headers>` method.
532
- """
533
- from lino.modlib.users.utils import with_user_profile
534
- from lino.core.layouts import ColumnsLayout
535
-
536
- def getit():
537
- if ar.request is None:
538
- columns = None
539
- else:
540
- data = getrqdata(ar.request)
541
- columns = [str(x) for x in data.getlist(constants.URL_PARAM_COLUMNS)]
542
- if columns:
543
- all_widths = data.getlist(constants.URL_PARAM_WIDTHS)
544
- hiddens = [
545
- (x == "true") for x in data.getlist(constants.URL_PARAM_HIDDENS)
546
- ]
547
- fields = []
548
- widths = []
549
- ah = ar.actor.get_handle()
550
- for i, cn in enumerate(columns):
551
- col = None
552
- for e in ah.grid_layout.main.columns:
553
- if e.name == cn:
554
- col = e
555
- break
556
- if col is None:
557
- raise Exception(
558
- "No column named %r in %s"
559
- % (cn, ar.ah.grid_layout.main.columns)
560
- )
561
- if not hiddens[i]:
562
- fields.append(col)
563
- widths.append(int(all_widths[i]))
564
- else:
565
- if column_names:
566
- ll = ColumnsLayout(column_names, datasource=ar.actor)
567
- lh = ll.get_layout_handle()
568
- columns = lh.main.columns
569
- columns = [e for e in columns if not e.hidden]
570
- else:
571
- ah = ar.actor.get_request_handle(ar)
572
-
573
- columns = ah.grid_layout.main.columns
574
- # print(20160530, ah, columns, ah.grid_layout.main)
575
-
576
- # render them so that babelfields in hidden_languages
577
- # get hidden:
578
- for e in columns:
579
- e.value = e.ext_options()
580
- # try:
581
- # e.value = e.ext_options()
582
- # except AttributeError as ex:
583
- # raise AttributeError("20160529 %s : %s" % (e, ex))
584
- #
585
- columns = [e for e in columns if not e.value.get("hidden", False)]
586
-
587
- columns = [e for e in columns if not e.hidden]
588
-
589
- # if str(ar.actor) == "isip.ExamPolicies":
590
- # from lino.modlib.extjs.elems import is_hidden_babel_field
591
- # print("20180103", [c.name for c in columns])
592
- # print("20180103", [c.field for c in columns])
593
- # print("20180103", [c.value['hidden'] for c in columns])
594
- # print("20180103", [
595
- # is_hidden_babel_field(c.field) for c in columns])
596
- # print("20180103", [
597
- # getattr(c.field, '_babel_language', None)
598
- # for c in columns])
599
- widths = ["%d" % (col.width or col.preferred_width) for col in columns]
600
- # print("20180831 {}".format(widths))
601
- # ~ 20130415 widths = ["%d%%" % (col.width or col.preferred_width) for col in columns]
602
- # ~ fields = [col.field._lino_atomizer for col in columns]
603
- fields = columns
604
-
605
- headers = [column_header(col) for col in fields]
606
-
607
- # if str(ar.actor).endswith("DailySlave"):
608
- # print("20181022", fields[0].field.verbose_name)
609
-
610
- oh = ar.actor.override_column_headers(ar)
611
- if oh:
612
- for i, e in enumerate(columns):
613
- header = oh.get(e.name, None)
614
- if header is not None:
615
- headers[i] = header
616
- # ~ print 20120507, oh, headers
617
-
618
- return fields, headers, widths
619
-
620
- u = ar.get_user()
621
- if u is None:
622
- return getit()
623
- else:
624
- return with_user_profile(u.user_type, getit)
625
-
626
- def row2html(self, recno, columns, row, sums, **cellattrs):
627
- has_numeric_value = False
628
- cells = []
629
- for i, col in enumerate(columns):
630
- v = col.field._lino_atomizer.full_value_from_object(row, self)
631
- if v is None:
632
- td = E.td(**cellattrs)
633
- else:
634
- nv = col.value2num(v)
635
- if nv != 0:
636
- sums[i] += nv
637
- has_numeric_value = True
638
- td = col.value2html(self, v, **cellattrs)
639
- # print("20240506 {} {}".format(col.__class__, tostring(td)))
640
- col.apply_cell_format(td)
641
- self.actor.apply_cell_format(self, row, col, recno, td)
642
- cells.append(td)
643
- if self.actor.hide_zero_rows and not has_numeric_value:
644
- return None
645
- return cells
646
-
647
- def row2text(self, fields, row, sums):
648
- """
649
- Render the given `row` into an iteration of text cells, using the given
650
- list of `fields` and collecting sums into `sums`.
651
- """
652
- # print(20160530, fields)
653
- for i, fld in enumerate(fields):
654
- if fld.field is not None:
655
- sf = get_atomizer(row.__class__, fld.field, fld.field.name)
656
- # print(20160530, fld.field.name, sf)
657
- if False:
658
- try:
659
- getter = sf.full_value_from_object
660
- v = getter(row, self)
661
- except Exception as e:
662
- raise Exception("20150218 %s: %s" % (sf, e))
663
- # was used to find bug 20130422:
664
- yield "%s:\n%s" % (fld.field, e)
665
- continue
666
- else:
667
- getter = sf.full_value_from_object
668
- v = getter(row, self)
669
-
670
- if v is None:
671
- # if not v:
672
- yield ""
673
- else:
674
- sums[i] += fld.value2num(v)
675
- # # In case you want the field name in error message:
676
- # try:
677
- # sums[i] += fld.value2num(v)
678
- # except Exception as e:
679
- # raise e.__class__("%s %s" % (fld.field, e))
680
- yield fld.format_value(self, v)
681
-
682
- def sums2html(self, columns, sums, **cellattrs):
683
- sums = {fld.name: sums[i] for i, fld in enumerate(columns)}
684
- return [
685
- fld.sum2html(self, sums, i, **cellattrs) for i, fld in enumerate(columns)
686
- ]
687
-
688
- def get_title(self):
689
- # print(20200125, self.title, self.master_instance)
690
- if self.title is not None:
691
- return self.title
692
- # if self.master_instance is not None:
693
- # self.master_instance
694
- return self.actor.get_title(self)
695
-
696
- def get_status(self, **kw):
697
- """Extends :meth:`lino.core.requests.ActorRequest.get_status`."""
698
- if self._status is not None and not kw:
699
- return self._status
700
- # print("20230331 712 get_status() {}".format(self.subst_user))
701
- self._status = kw = super().get_status(**kw)
702
- bp = kw["base_params"]
703
- if self.quick_search:
704
- bp[constants.URL_PARAM_FILTER] = self.quick_search
705
-
706
- if self.order_by:
707
- sort = self.order_by[0]
708
- if sort.startswith("-"):
709
- sort = sort[1:]
710
- bp[constants.URL_PARAM_SORTDIR] = "DESC"
711
- bp[constants.URL_PARAM_SORT] = sort
712
-
713
- if self.known_values:
714
- for k, v in self.known_values.items():
715
- if self.actor.known_values.get(k, None) != v:
716
- bp[k] = v
717
- if self.master_instance is not None:
718
- if isinstance(self.master_instance, (models.Model, TableRow)):
719
- bp[constants.URL_PARAM_MASTER_PK] = self.master_instance.pk
720
- if ContentType is not None and isinstance(
721
- self.master_instance, models.Model
722
- ):
723
- assert not self.master_instance._meta.abstract
724
- mt = ContentType.objects.get_for_model(
725
- self.master_instance.__class__
726
- ).pk
727
- bp[constants.URL_PARAM_MASTER_TYPE] = mt
728
- # else:
729
- # logger.warning("20141205 %s %s",
730
- # self.master_instance,
731
- # ContentType)
732
- else: # if self.master is None:
733
- # e.g. in accounting.MovementsByMatch the master is a `str`
734
- bp[constants.URL_PARAM_MASTER_PK] = self.master_instance
735
- # raise Exception("No master key for {} {!r}".format(
736
- # self.master_instance.__class__, self.master_instance))
737
- return kw
738
-
739
- def __repr__(self):
740
- kw = dict()
741
- if self.master_instance is not None:
742
- kw.update(master_instance=obj2str(self.master_instance))
743
- if self.filter is not None:
744
- kw.update(filter=repr(self.filter))
745
- if self.known_values:
746
- kw.update(known_values=self.known_values)
747
- if self.requesting_panel:
748
- kw.update(requesting_panel=self.requesting_panel)
749
- u = self.get_user()
750
- if u is not None:
751
- kw.update(user=u.username)
752
- if False: # self.request:
753
- kw.update(request=format_request(self.request))
754
- return "<%s %s(%s)>" % (
755
- self.__class__.__name__,
756
- self.bound_action.full_name(),
757
- kw,
758
- )