source: timingandestimationplugin/branches/trac0.12-Permissions/timingandestimationplugin/tande_filters.py

Last change on this file was 10267, checked in by Russ Tyndall, 12 years ago

added a timeline filter to remove information about private fields from the timeline

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