source: timingandestimationplugin/branches/trac1.0-Permissions/timingandestimationplugin/tande_filters.py

Last change on this file was 14580, checked in by Russ Tyndall, 8 years ago

Replace CSV filter with one that doesnt require monkey patching (or does this in a more controlled manner) v1.4.7b fix #12305

File size: 8.1 KB
Line 
1from trac.web.api import ITemplateStreamFilter
2from trac.core import *
3import genshi
4from genshi.core import *
5from genshi.builder import tag
6from genshi.filters.transform import Transformer
7from blackmagic import *
8from StringIO import StringIO
9import csv
10from trac.mimeview.api import (IContentConverter)
11from trac.resource import Resource
12from trac.web.chrome import (Chrome, web_context)
13from trac.util.translation import _
14import re
15
16
17# also in blackmagic ... not sure how to guarantee that one of them
18# will be run in time other than to do it in both places
19def textOf(self, **keys):
20    return self.render('text', None, **keys)
21Stream.textOf = textOf
22
23
24def denied_fields(comp, req):
25    fields = comp.config.getlist(csection, 'fields', [])
26    for field in fields:
27        comp.log.debug('found : %s' % field)
28        perms = comp.config.getlist(csection, '%s.permission' % field, [])
29        #comp.log.debug('read permission config: %s has %s' % (field, perms))
30        for (perm, denial) in [s.split(":") for s in perms]:
31            perm = perm.upper()
32            comp.log.debug('testing permission: %s:%s should act= %s' %
33                           (field, perm, (not req.perm.has_permission(perm)
34                                          or perm == "ALWAYS")))
35            if (not req.perm.has_permission(perm) or perm == "ALWAYS") \
36                    and denial.lower() in ["remove", "hide"]:
37                label = comp.env.config.get(
38                    'ticket-custom', field + '.label', field).lower().strip()
39                yield (field, label)
40
41
42# Basically overwriting QueryModule.export_csv = new_csv_export
43class TandEFilteredQueryConversions(Component):
44    implements(IContentConverter)
45
46    # IContentConverter methods
47    def get_supported_conversions(self):
48        yield ('csv', _('Comma-delimited Text'), 'csv',
49               'trac.ticket.Query', 'text/csv', 9)  # higher than QueryModule
50        yield ('tab', _('Tab-delimited Text'), 'tsv',
51               'trac.ticket.Query', 'text/tab-separated-values', 9)
52
53    def convert_content(self, req, mimetype, query, key):
54        if key == 'csv':
55            return self._export_csv(req, query, mimetype='text/csv')
56        elif key == 'tab':
57            return self._export_csv(req, query, '\t',
58                                    mimetype='text/tab-separated-values')
59
60    # Internal methods
61    def _filtered_columns(self, req, cols):
62        # find the columns that should be hidden
63        denied = [field for (field, label) in denied_fields(self, req)]
64        return [c for c in cols if c not in denied]
65
66    def _export_csv(self, req, query, sep=',', mimetype='text/plain'):
67        self.log.debug("T&E plugin has overridden QueryModule.csv_export"
68                       " so to enforce field permissions")
69        # !!!    BEGIN COPIED CONTENT - from trac1.0/trac/ticket/query.py
70        content = StringIO()
71        content.write('\xef\xbb\xbf')   # BOM
72        cols = query.get_columns()
73        # !!!    T&E patch
74        cols = self._filtered_columns(req, cols)
75        # !!!END T&E patch
76        writer = csv.writer(content, delimiter=sep, quoting=csv.QUOTE_MINIMAL)
77        writer.writerow([unicode(c).encode('utf-8') for c in cols])
78
79        context = web_context(req)
80        results = query.execute(req)
81        for result in results:
82            ticket = Resource('ticket', result['id'])
83            if 'TICKET_VIEW' in req.perm(ticket):
84                values = []
85                for col in cols:
86                    value = result[col]
87                    if col in ('cc', 'owner', 'reporter'):
88                        value = Chrome(self.env).format_emails(
89                            context.child(ticket), value)
90                    elif col in query.time_fields:
91                        value = format_datetime(value, '%Y-%m-%d %H:%M:%S',
92                                                tzinfo=req.tz)
93                    values.append(unicode(value).encode('utf-8'))
94                writer.writerow(values)
95        return (content.getvalue(), '%s;charset=utf-8' % mimetype)
96
97
98class TicketFormatFilter(Component):
99    """Filtering the streams to alter the base format of the ticket"""
100    implements(ITemplateStreamFilter)
101
102    def filter_stream(self, req, method, filename, stream, data):
103        self.log.debug("TicketFormatFilter executing") 
104        if not filename == 'ticket.html':
105            self.log.debug("TicketFormatFilter not the correct template")
106            return stream
107       
108        self.log.debug("TicketFormatFilter disabling totalhours and removing header hours")
109        stream = disable_field(stream, "totalhours")
110        stream = remove_header(stream, "hours")
111        return stream
112
113class QueryColumnPermissionFilter(Component):
114    """ Filtering the stream to remove """
115    implements(ITemplateStreamFilter)   
116   
117    ## ITemplateStreamFilter
118   
119    def filter_stream(self, req, method, filename, stream, data):
120        if not filename == "query.html":
121            self.log.debug('Not a query returning')
122            return stream
123
124        def make_col_helper(field):
125            def column_helper (column_stream):
126                s =  Stream(column_stream)
127                val = s.select('//input/@value').render()
128                if val.lower() != field.lower(): #if we are the field just skip it
129                    #identity stream filter
130                    for kind, data, pos in s:
131                        yield kind, data, pos       
132            return column_helper
133
134        for (field, label) in denied_fields(self, req):
135            # remove from the list of addable
136            stream = stream | Transformer(
137                '//select[@id="add_filter"]/option[@value="%s"]' % field
138                ).replace(" ")
139
140            # remove from the list of columns
141            stream = stream | Transformer(
142                '//fieldset[@id="columns"]/div/label'
143                ).filter(make_col_helper(field))
144                   
145            # remove from the results table
146            stream = stream | Transformer(
147                '//th[@class="%s"]' % field
148                ).replace(" ")
149            stream = stream | Transformer(
150                '//td[@class="%s"]' % field
151                ).replace(" ")
152           
153            # remove from the filters
154            stream = stream | Transformer(
155                '//tr[@class="%s"]' % field
156                ).replace(" ")
157        return stream
158
159commasRE = re.compile(r',\s(,\s)+', re.I)
160class TimelinePermissionFilter(Component):
161    """ Filtering the stream to remove fields from the timeline of changes """
162    implements(ITemplateStreamFilter)
163   
164    ## ITemplateStreamFilter
165   
166    def filter_stream(self, req, method, filename, stream, data):
167        if not filename == "timeline.html":
168            self.log.debug('Not a timeline, returning')
169            return stream
170        denied = [label for (field, label) in denied_fields(self, req)]
171        def helper(field_stream):
172            try:
173                s = Stream(field_stream)
174                # without None as the second value we get str instead of unicode
175                # and that causes things to break sometimes
176                f = s.select('//text()').textOf(strip_markup=True).lower()
177                self.log.debug('Timeline Filter: is %r in %r, skip?%r',
178                               f, denied, f in denied )
179                if f not in denied: #if we are the field just skip it
180                #identity stream filter
181                    for kind, data, pos in s:
182                        yield kind, data, pos
183            except Exception, e:
184                self.log.exception('Timeline: Stream Filter Exception');
185                raise e
186
187        def comma_cleanup(stream):
188            text = Stream(stream).textOf()
189            self.log.debug( 'Timeline: Commas %r %r' , text, commasRE.sub( text, ', ' ) );
190            text = commasRE.sub( ', ' , text)
191            for kind, data, pos in tag(text):
192                yield kind, data, pos
193
194        stream = stream | Transformer('//dd[@class="editedticket"]/i').filter(helper)
195        stream = stream | Transformer('//dd[@class="editedticket"]/text()').filter(comma_cleanup)
196                   
197        return stream
Note: See TracBrowser for help on using the repository browser.