-
Notifications
You must be signed in to change notification settings - Fork 1
/
columns.py
819 lines (688 loc) · 30.4 KB
/
columns.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
# encoding: utf-8
from collective.eeafaceted.z3ctable import _
from collective.eeafaceted.z3ctable.interfaces import IFacetedColumn
from collective.excelexport.exportables.dexterityfields import get_exportable_for_fieldname
from datetime import date
from DateTime import DateTime
from plone import api
from Products.CMFPlone.utils import base_hasattr
from Products.CMFPlone.utils import safe_unicode
from z3c.form.interfaces import IDataConverter
from z3c.form.interfaces import IDataManager
from z3c.table import column
from z3c.table.header import SortingColumnHeader
from zope.component import getMultiAdapter
from zope.component import queryUtility
from zope.i18n import translate
from zope.interface import implements
from zope.schema.interfaces import IVocabularyFactory
import html
import os
import pkg_resources
import urllib
try:
api.env.get_distribution('imio.prettylink')
from imio.prettylink.interfaces import IPrettyLink
HAS_PRETTYLINK = True
except pkg_resources.DistributionNotFound:
HAS_PRETTYLINK = False
try:
api.env.get_distribution('collective.z3cform.datagridfield')
from collective.z3cform.datagridfield.datagridfield import DataGridField
HAS_Z3CFORM_DATAGRIDFIELD = True
except pkg_resources.DistributionNotFound:
HAS_Z3CFORM_DATAGRIDFIELD = False
EMPTY_STRING = '__empty_string__'
EMPTY_DATE = date(1950, 1, 1)
class BaseColumn(column.GetAttrColumn):
implements(IFacetedColumn)
sort_index = None
# as we use setUpColumns, weight is 1 for every columns
weight = 1
# we can use a icon for header
header_image = None
# we can inject some javascript in the header
header_js = None
# header help message
header_help = None
# enable caching, needs to be implemented by Column
use_caching = True
# escape
escape = True
@property
def cssClasses(self):
"""Generate a default CSS class for each <th> and <td> so we can skin it if necessary."""
return {'th': 'th_header_{0}'.format(self.__name__),
'td': 'td_cell_{0}'.format(self.__name__), }
def getCSSClasses(self, item):
return self.cssClasses
def renderCell(self, item):
return getattr(item, self.attrName.decode('utf8'))
def _getObject(self, item):
"""Caching for getObject."""
itemUID = item.UID
if not hasattr(self.table, '_v_cached_objects'):
self.table._v_cached_objects = {}
if itemUID not in self.table._v_cached_objects:
self.table._v_cached_objects[itemUID] = item._unrestrictedGetObject()
return self.table._v_cached_objects[itemUID]
def _get_cached_result(self, value):
if getattr(self, '_cached_result', None):
if hasattr(value, '__iter__'):
value = '_'.join(value)
return self._cached_result.get(value, None)
def _store_cached_result(self, value, result):
""" """
if getattr(self, '_cached_result', None) is None:
self._cached_result = {}
if hasattr(value, '__iter__'):
value = '_'.join(value)
self._cached_result[value] = result
class BaseColumnHeader(SortingColumnHeader):
def render(self):
header = translate(self.column.header,
domain='collective.eeafaceted.z3ctable',
context=self.request)
# header can be an image or a text
if self.column.header_image:
header = u'<img src="{0}/{1}" title="{2}" />'.format(self.table.portal_url,
self.column.header_image,
header)
# inject some javascript in the header?
if self.column.header_js:
header = self.column.header_js + header
# include help message
if self.column.header_help:
header_help = translate(
self.column.header_help,
domain='collective.eeafaceted.z3ctable',
context=self.request)
header = u'<acronym title="{0}">{1}</acronym>'.format(
header_help, header)
# a column can specifically declare that it is not sortable
# by setting sort_index to -1
if not self.column.sort_index == -1:
sort_on_name = self.table.sorting_criterion_name
if sort_on_name and (self.column.sort_index or self.column.attrName):
order_arrow = self.order_arrow
faceted_url = self.faceted_url
query_string = self.query_string
if order_arrow:
query = self.table.query.get('sort_order', 'ascending')
contray_sort_order = (query == 'ascending' and 'descending' or 'ascending')
contray_sort_order_msgid = "Sort {0}".format(contray_sort_order)
sort_msg = translate(contray_sort_order_msgid,
domain='collective.eeafaceted.z3ctable',
context=self.request,
default=contray_sort_order_msgid)
html = u'<span>{0}</span><a class="sort_arrow_enabled" href="{1}#{2}" title="{3}">{4}</a>'
return html.format(header, faceted_url, query_string, sort_msg, order_arrow)
else:
sort_ascending_msg = translate("Sort ascending",
domain='collective.eeafaceted.z3ctable',
context=self.request,
default="Sort ascending")
sort_descending_msg = translate("Sort descending",
domain='collective.eeafaceted.z3ctable',
context=self.request,
default="Sort descending")
html = (u'<span>{0}</span><a class="sort_arrow_disabled" href="{1}#{2}" title="{3}">{4}</a>'
'<a class="sort_arrow_disabled" href="{5}#{6}" title="{7}"><span>{8}</span></a>')
return html.format(header, faceted_url, query_string, sort_ascending_msg, '▲',
faceted_url, query_string + '&reversed=on', sort_descending_msg,
'▼')
return header
@property
def sort_on(self):
return self.column.sort_index or self.column.attrName
@property
def faceted_url(self):
"""Take HTTP_REFERER as faceted page is done using a XHRequest we will have
the real complete URL including ending '/' or '/view' and click on sorting
will not redirect anymore."""
return self.request.get('HTTP_REFERER') or '/'.join(self.request.get('URL').split('/')[:-1])
@property
def query_string(self):
query = self.request_query
sort_on_name = self.table.sorting_criterion_name
if (self.table.query.get('sort_on', '') == self.sort_on or self.table.sortOn == self.column.id) and \
self.table.query.get('sort_order', 'ascending') == 'ascending':
query.update({'reversed': 'on'})
elif 'reversed' in query:
del query['reversed']
query.update({sort_on_name: self.sort_on})
if 'version' in query:
del query['version']
# make sure we handle multiple valued parameters correctly
# eea.facetednavigation needs this : ?b_start=0&c6=state1&c6=state2
# not ?b_start=0&c6:list=state1&c6:list=state2 nor ?b_start=0&c6=state1+state2
return urllib.urlencode(query, doseq=True)
@property
def request_query(self):
query = dict(self.request.form)
return {k.replace('[]', ''): v for k, v in query.items()}
@property
def order_arrow(self):
sort_on = self.table.query.get('sort_on', '')
sort_order = self.table.query.get('sort_order', 'ascending')
if sort_on == self.sort_on or \
self.table.sortOn == self.column.id:
if sort_order == 'ascending':
return '▲'
else:
return '▼'
return u''
class AwakeObjectGetAttrColumn(BaseColumn):
"""Column that will wake the object then getattr attrName on it."""
# column not sortable
sort_index = -1
def renderCell(self, item):
obj = self._getObject(item)
try:
result = getattr(obj, self.attrName)
return safe_unicode(result)
except AttributeError:
return u'-'
class AwakeObjectMethodColumn(BaseColumn):
"""Column that will wake the object then call attrName on it."""
# column not sortable
sort_index = -1
params = {}
weight = 40
def renderCell(self, item):
obj = self._getObject(item)
try:
result = getattr(obj, self.attrName)(**self.params)
return safe_unicode(result)
except AttributeError:
return u'-'
class RelationTitleColumn(BaseColumn):
""" Dexterity relation value column """
sort_index = -1
def getLinkedObjects(self, item):
# z3c.relationfield.relation.RelationValue or [z3c.relationfield.relation.RelationValue, ...]
obj = self._getObject(item)
rels = getattr(obj, self.attrName, None)
if not rels:
return None
if isinstance(rels, (list, tuple)):
ret = []
for rel in rels:
if not rel.isBroken():
ret.append(rel.to_object)
return ret
elif not rels.isBroken():
return rels.to_object
return None
def target_display(self, obj):
""" Return an html link """
return u'<a href="{0}">{1}</a>'.format(obj.absolute_url(), obj.Title().decode('utf8'))
def renderCell(self, item):
targets = self.getLinkedObjects(item)
if not targets:
return u'-'
if isinstance(targets, (list, tuple)):
ret = []
for target in targets:
ret.append(u'<li>{0}</li>'.format(self.target_display(target)))
return u'<ul>\n{0}\n</ul>'.format('\n'.join(ret))
else:
return self.target_display(targets)
def get_user_fullname(username):
"""Get fullname without using getMemberInfo that is slow slow slow..."""
storage = api.portal.get_tool('acl_users').mutable_properties._storage
data = storage.get(username, None)
if data is not None:
return data.get('fullname', '') or username
else:
return username
class MemberIdColumn(BaseColumn):
""" """
attrName = 'Creator'
weight = 20
ignored_value = EMPTY_STRING
def renderCell(self, item):
value = self.getValue(item)
if not value or value == self.ignored_value:
return u'-'
value = get_user_fullname(value)
return safe_unicode(value)
class DateColumn(BaseColumn):
""" """
long_format = False
time_only = False
ignored_value = EMPTY_DATE
# not necessary to escape, everything is generated
escape = False
def renderCell(self, item):
value = self.getValue(item)
if isinstance(value, DateTime):
value = value.asdatetime().date()
if not value or value == 'None' or value == self.ignored_value:
return u'-'
if self.use_caching:
res = self._get_cached_result(value)
if res:
return res
res = api.portal.get_localized_time(datetime=value, long_format=self.long_format, time_only=self.time_only)
if self.use_caching:
self._store_cached_result(value, res)
return res
class I18nColumn(BaseColumn):
"""GetAttrColumn which translates its content."""
i18n_domain = 'plone'
msgid_prefix = ''
weight = 30
def renderCell(self, item):
value = self.getValue(item)
if value == self.defaultValue:
return u'-'
return translate("{0}{1}".format(self.msgid_prefix, value),
domain=self.i18n_domain,
context=self.request)
class BooleanColumn(I18nColumn):
""" """
i18n_domain = 'collective.eeafaceted.z3ctable'
msgid_prefix = 'boolean_value_'
def getCSSClasses(self, item):
cssClasses = self.cssClasses
cssClasses['td'] = cssClasses['td'] + ' bool_value_{0}'.format(
str(self.getValue(item)).lower())
return cssClasses
class BrowserViewCallColumn(BaseColumn):
"""A column that display the result of a given browser view name call."""
# column not sortable
sort_index = -1
params = {}
view_name = None
def renderCell(self, item):
if not self.view_name:
raise KeyError('A "view_name" must be defined for column "{0}" !'.format(self.attrName))
# avoid double '//' that breaks (un)restrictedTraverse, moreover path can not be unicode
path = os.path.join(item.getPath(), self.view_name).encode('utf-8')
return self.table.portal.unrestrictedTraverse(path)(**self.params)
class VocabularyColumn(BaseColumn):
"""A column that is aware of a vocabulary and that will get value to display from it."""
# named utility
vocabulary = None
ignored_value = EMPTY_STRING
# we manage escape here manually
escape = False
def renderCell(self, item):
value = self.getValue(item)
if not value or value == self.ignored_value:
return u'-'
# caching when several same values in same column
if self.use_caching:
res = self._get_cached_result(value)
if res:
return res
# the vocabulary instance is cached
if not hasattr(self, '_cached_vocab_instance'):
if not self.vocabulary:
raise KeyError('A "vocabulary" must be defined for column "{0}" !'.format(
self.attrName))
factory = queryUtility(IVocabularyFactory, self.vocabulary)
if not factory:
raise KeyError('The vocabulary "{0}" used for column "{1}" was not found !'.format(
self.vocabulary, self.attrName))
self._cached_vocab_instance = factory(self.context)
# make sure we have an iterable
if not hasattr(value, '__iter__'):
value = [value]
res = []
for v in value:
try:
res.append(html.escape(safe_unicode(self._cached_vocab_instance.getTerm(v).title)))
except LookupError:
# in case an element is not in the vocabulary, add the value
res.append(safe_unicode(v))
res = ', '.join(res)
if self.use_caching:
self._store_cached_result(value, res)
return res
class AbbrColumn(VocabularyColumn):
"""A column that will display a <abbr> HTML tag and that will show a full version on hover.
It is aware of 2 vocabularies, one to manage abbreviation and one to manage full value."""
# named utility
full_vocabulary = None
separator = u', '
# we manage escape here manually
escape = False
def renderCell(self, item):
value = self.getValue(item)
if not value:
return u'-'
# caching when several same values in same column
if self.use_caching:
res = self._get_cached_result(value)
if res:
return res
# the vocabulary instances are cached
if not hasattr(self, '_cached_full_vocab_instance'):
if not self.vocabulary or not self.full_vocabulary:
raise KeyError(
'A "vocabulary" and a "full_vocabulary" must be defined for column "{0}" !'.format(self.attrName))
acronym_factory = queryUtility(IVocabularyFactory, self.vocabulary)
if not acronym_factory:
raise KeyError(
'The vocabulary "{0}" used for column "{1}" was not found !'.format(self.vocabulary,
self.attrName))
full_factory = queryUtility(IVocabularyFactory, self.full_vocabulary)
if not full_factory:
raise KeyError(
'The vocabulary "{0}" used for column "{1}" was not found !'.format(self.full_vocabulary,
self.attrName))
self._cached_acronym_vocab_instance = acronym_factory(self.context)
self._cached_full_vocab_instance = full_factory(self.context)
# make sure we have an iterable
if not hasattr(value, '__iter__'):
value = [value]
res = []
for v in value:
try:
tag_title = self._cached_full_vocab_instance.getTerm(v).title
tag_title = tag_title.replace("'", "'")
res.append(u"<abbr title='{0}'>{1}</abbr>".format(
html.escape(safe_unicode(tag_title)),
html.escape(safe_unicode(self._cached_acronym_vocab_instance.getTerm(v).title))))
except LookupError:
# in case an element is not in the vocabulary, add the value
res.append(html.escape(safe_unicode(v)))
res = self.separator.join(res)
if self.use_caching:
self._store_cached_result(value, res)
return res
class ColorColumn(I18nColumn):
"""A column that is aimed to display a background color
and a help message on hover."""
# no real color is applied but a generated CSS class
cssClassPrefix = 'column'
# Hide the head cell but fill it with spaces so it does
# not shrink to nothing if table is too large
header = u' '
# we manage escape here manually
escape = False
def renderCell(self, item):
"""Display a message."""
translated_msg = super(ColorColumn, self).renderCell(item)
return u'<div title="{0}"> </div>'.format(html.escape(translated_msg))
def getCSSClasses(self, item):
"""Generate a CSS class to apply on the TD depending on the value."""
return {'td': "{0}_{1}_{2}".format(self.cssClassPrefix,
str(self.attrName),
html.escape(self.getValue(item)))}
class CheckBoxColumn(BaseColumn):
"""
Display a checkbox.
"""
name = 'select_item'
checked_by_default = True
attrName = 'UID'
weight = 100
# not necessary to escape, everything is generated
escape = False
def renderHeadCell(self):
""" """
title = translate('select_unselect_items',
domain='collective.eeafaceted.z3ctable',
context=self.request,
default="Select/unselect all")
return u'<input type="checkbox" id="select_unselect_items" onClick="toggleCheckboxes(this, \'{0}\')" ' \
u'title="{1}" name="{0}" {2}/>'.format(
self.name, title, self.checked_by_default and "checked " or "")
def renderCell(self, item):
""" """
return u'<label class="select-item-label"><input type="checkbox" name="%s" value="%s" %s/></label>' \
% (self.name, self.getValue(item), self.checked_by_default and "checked " or "")
def getCSSClasses(self, item):
""" """
return {'td': '{0}_checkbox'.format(self.name)}
class DxWidgetRenderColumn(BaseColumn):
"""A column that display the result of a dx widget."""
# column not sortable
sort_index = -1
params = {}
field_name = None
view_name = 'view'
prefix = None
def renderCell(self, item):
if not self.field_name:
raise KeyError('A "field_name" must be defined for column "{0}" !'.format(self.attrName))
view = self.context.restrictedTraverse('{0}/{1}'.format(item.getPath(), self.view_name))
view.updateFieldsFromSchemata()
# to increase velocity, we escape all other fields. Faster than making new field.Fields
for field in view.fields:
if field != self.field_name:
view.fields = view.fields.omit(field)
# we update the widgets for the kept field
# passing a string parameter that can be used in overrided updateWidgets
# to know that we are in this particular rendering case
view.updateWidgets(prefix=self.prefix)
try:
widget = view.widgets[self.field_name]
except KeyError:
raise KeyError('The field_name "{0}" is not available for column "{1}" !'.format(self.field_name,
self.attrName))
return widget.render() # unicode
class ElementNumberColumn(BaseColumn):
header = u''
# not necessary to escape, everything is generated
escape = False
def renderCell(self, item):
""" """
start = 1
# if we have a batch, use it
if base_hasattr(self.table, 'batch'):
start = self.table.batch.start
values_from = start - 1
values_to = values_from + self.table.batch.length
values_uids = [v.UID for v in self.table.values._sequence[
values_from:values_to]]
else:
# this column may also be used with more classical z3c.table
# where there is no batch and we display the entire table
values_uids = [v.UID for v in self.table.values]
return start + values_uids.index(item.UID)
############################################################
# Custom columns ####
############################################################
class CreationDateColumn(DateColumn):
""" """
sort_index = 'created'
weight = 10
long_format = True
class ModificationDateColumn(DateColumn):
""" """
sort_index = 'modified'
weight = 10
long_format = True
class TitleColumn(BaseColumn):
""" """
header = _('header_Title')
sort_index = 'sortable_title'
weight = 0
# we manage escape here manually
escape = False
def renderCell(self, item):
value = self.getValue(item)
if not value:
value = u'-'
value = safe_unicode(value)
return u'<a href="{0}">{1}</a>'.format(item.getURL(), html.escape(value))
class PrettyLinkColumn(TitleColumn):
"""A column that displays the IPrettyLink.getLink column.
This rely on imio.prettylink."""
# escape is managed by imio.prettylink
escape = False
params = {}
@property
def cssClasses(self):
"""Generate a CSS class for each <th> so we can skin it if necessary."""
cssClasses = super(PrettyLinkColumn, self).cssClasses.copy() or {}
cssClasses.update({'td': 'pretty_link', })
return cssClasses
def contentValue(self, item):
""" """
return None
def getPrettyLink(self, obj):
pl = IPrettyLink(obj)
for k, v in self.params.items():
setattr(pl, k, v)
pl.contentValue = self.contentValue(obj)
return pl.getLink()
def renderCell(self, item):
""" """
return self.getPrettyLink(self._getObject(item))
class PrettyLinkWithAdditionalInfosColumn(PrettyLinkColumn):
"""A column that displays the PrettyLinkColumn column
and includes additional informations.
This only works when used with DX content types."""
# additional infos config
ai_widget_render_pattern = u'<div class="discreet {2} {3}">' \
u'<label class="horizontal">{0}</label>\n<div class="{4}">{1}</div></div>'
# "*" means every non empty fields
ai_included_fields = "*"
ai_excluded_fields = []
ai_extra_fields = ['id', 'UID', 'description']
ai_highlighted_fields = []
ai_highligh_css_class = "highlight"
ai_generate_css_class_fields = []
simplified_datagridfield = False
def get_ai_included_fields(self):
""" """
return self.ai_included_fields
def get_ai_excluded_fields(self):
""" """
return self.ai_excluded_fields
def _field_css_class(self, widget):
"""Compute a field CSS class based on field name and value."""
field_css_class = ''
if widget.__name__ in self.ai_generate_css_class_fields:
field_css_class = '{0}_{1}'.format(
widget.__name__, str(widget.field.get(widget.context)).lower())
return field_css_class
def additional_infos(self, item):
""" """
res = u'<div class="additional-infos">'
# caching
obj = self._getObject(item)
# Manage ai_extra_fields
for extra_field in self.ai_extra_fields:
if base_hasattr(obj, extra_field):
value = getattr(obj, extra_field)
if callable(value):
value = value()
if value:
res += u'<div class="discreet"><label class="horizontal">{0}</label>' \
'<div class="type-textarea-widget">{1}</div></div>'.format(extra_field.capitalize(), value)
if not self.use_caching or getattr(self, '_cached_view', None) is None:
view = obj.restrictedTraverse('view')
self._cached_view = view
view.update()
# handle widgets
widgets = view.widgets.values()
for group in view.groups:
widgets.extend(group.widgets.values())
else:
view = self._cached_view
view.context = obj
for widget in view.widgets.values():
widget.context = view.context
converter = IDataConverter(widget)
dm = getMultiAdapter((view.context, widget.field), IDataManager)
value = dm.get()
if value:
if self.simplified_datagridfield and \
HAS_Z3CFORM_DATAGRIDFIELD and \
isinstance(widget, DataGridField):
widget._value = value
else:
converted = converter.toWidgetValue(value)
# special behavior for datagridfield where setting the value
# will updateWidgets and is slow/slow/slow...
widget.value = converted
else:
widget.value = None
widgets = view.widgets.values()
for widget in widgets:
if widget.__name__ not in self.get_ai_excluded_fields() and \
(self.ai_included_fields == "*" or widget.__name__ in self.get_ai_included_fields()) and \
widget.value not in (None, '', '--NOVALUE--', u'', (), [], (''), [''], ['--NOVALUE--']):
widget_name = widget.__name__
css_class = widget_name in self.ai_highlighted_fields and self.ai_highligh_css_class or ''
field_css_class = self._field_css_class(widget)
translated_label = translate(widget.label, context=self.request)
# render the widget
if self.simplified_datagridfield and \
HAS_Z3CFORM_DATAGRIDFIELD and \
isinstance(widget, DataGridField):
_rendered_value = self._render_datagridfield(view, widget)
else:
_rendered_value = widget.render()
field_type_css_class = "type-{0}".format(widget.klass.split(' ')[0])
res += self.ai_widget_render_pattern.format(
translated_label, _rendered_value, css_class, field_css_class, field_type_css_class)
res += "</div>"
return res
def _render_datagridfield(self, view, widget):
'''Datagridfield is so slow to render we can not use widget rendering,
we will use a collective.excelexport exportable to render it.'''
exportable = get_exportable_for_fieldname(view.context, widget.__name__, self.request)
_rendered_value = exportable.render_value(view.context)
# format exportable value
_rendered_value = u'<div class="table-col-datagrid-header">{0}'.format(_rendered_value)
_rendered_value = _rendered_value.replace(
' : ', ' : </div><div class="table-col-datagrid-value">')
# end of row
_rendered_value = _rendered_value.replace(
'\n', '</div><br/><br/><div class="table-col-datagrid-header">')
_rendered_value = _rendered_value.replace(
' / ', '</div><br/><div class="table-col-datagrid-header">')
_rendered_value = u'{0}</div>'.format(_rendered_value)
return _rendered_value
def renderCell(self, item):
""" """
rendered_cell = super(PrettyLinkWithAdditionalInfosColumn, self).renderCell(item)
return rendered_cell + self.additional_infos(item)
class RelationPrettyLinkColumn(RelationTitleColumn, PrettyLinkColumn):
"""
A column displaying related items with IPrettyLink.getLink
"""
params = {}
def target_display(self, obj):
return PrettyLinkColumn.getPrettyLink(self, obj)
class ActionsColumn(BrowserViewCallColumn):
"""
A column displaying available actions of the listed item.
This rely on imio.actionspanel.
"""
header_js = '<script type="text/javascript">jQuery(document).ready(initializeOverlays);' \
'jQuery(document).ready(preventDefaultClick);</script>'
view_name = 'actions_panel'
params = {'showHistory': True, 'showActions': True}
# not necessary to escape, everything is generated
escape = False
class IconsColumn(BaseColumn):
"""
Column displaying icons with title tag
"""
attrName = None
defaultValue = [] # default value for self.getValue()
separator = ' '
def values(self, item):
return self.getValue(item)
def titleValue(self, item, val):
return val
def classValue(self, item, val):
return ''
def srcValue(self, item, val):
return '{}/{}'.format(self.table.portal_url, val)
def renderCell(self, item):
tags = []
for val in self.values(item):
tag = u"""<img title="{}" class="{}" src="{}" />""".format(self.titleValue(item, val),
self.classValue(item, val),
self.srcValue(item, val))
tags.append(tag)
return self.separator.join(tags)