| 1 | import re |
|---|
| 2 | import dbhelper |
|---|
| 3 | from trac import util |
|---|
| 4 | from trac.web.api import ITemplateStreamFilter |
|---|
| 5 | from trac.core import * |
|---|
| 6 | from genshi.core import * |
|---|
| 7 | from genshi.builder import tag |
|---|
| 8 | from genshi.filters.transform import Transformer |
|---|
| 9 | |
|---|
| 10 | import sys |
|---|
| 11 | if sys.version_info < (2, 4, 0): |
|---|
| 12 | from sets import Set as set |
|---|
| 13 | |
|---|
| 14 | def split_stream(stream): |
|---|
| 15 | """splits the stream based on toplevel START / END tags""" |
|---|
| 16 | cl = [] |
|---|
| 17 | res = [] |
|---|
| 18 | num_start=0 |
|---|
| 19 | for kind, data, pos in stream: |
|---|
| 20 | cl.append((kind, data, pos)) |
|---|
| 21 | if kind == Stream.START: |
|---|
| 22 | num_start = num_start+1 |
|---|
| 23 | elif kind == Stream.END: |
|---|
| 24 | num_start = num_start-1 |
|---|
| 25 | if num_start == 0: |
|---|
| 26 | res.append(Stream(cl)) |
|---|
| 27 | cl=[] |
|---|
| 28 | if cl != []: |
|---|
| 29 | res.append(Stream(cl)) |
|---|
| 30 | return res |
|---|
| 31 | |
|---|
| 32 | |
|---|
| 33 | class RowFilter(object): |
|---|
| 34 | """A genshi filter that operates on table rows, completely hiding any that |
|---|
| 35 | are in the billing_reports table.""" |
|---|
| 36 | |
|---|
| 37 | def __init__(self, comp): |
|---|
| 38 | self.component = comp |
|---|
| 39 | rows = dbhelper.get_all(comp, "SELECT id FROM custom_report")[1] |
|---|
| 40 | if rows: |
|---|
| 41 | self.billing_reports = set([x[0] for x in rows]) |
|---|
| 42 | else: |
|---|
| 43 | self.billing_reports = set() |
|---|
| 44 | self.component.log.debug('self.billing_reports= %r' % self.billing_reports) |
|---|
| 45 | |
|---|
| 46 | def __call__(self, row_stream): |
|---|
| 47 | #stream = Stream(list(row_stream)) |
|---|
| 48 | def tryInt(v): |
|---|
| 49 | try: |
|---|
| 50 | return int(v) |
|---|
| 51 | except: |
|---|
| 52 | return None |
|---|
| 53 | streams = split_stream(row_stream) |
|---|
| 54 | #report_urls = [tryInt(i.get('href').split('/')[-1]) for i in stream.select('td[@class="report"]/a/@href')] |
|---|
| 55 | #self.component.log.debug("ReportRowFilter: #%s#, %r" % (len(streams), list(report_urls))) |
|---|
| 56 | for stream in streams: |
|---|
| 57 | show_row = True |
|---|
| 58 | try: |
|---|
| 59 | report_url = stream.select('td[@class="report"]/a/@href').render() |
|---|
| 60 | id = tryInt(report_url.split('/')[-1]) |
|---|
| 61 | self.component.log.debug("Report row filter: about to filter: %s not in %s : %s" % (id, self.billing_reports, not id in self.billing_reports) ) |
|---|
| 62 | show_row = not id in self.billing_reports |
|---|
| 63 | except Exception, e: |
|---|
| 64 | self.component.log.exception("Report row filter failed") |
|---|
| 65 | show_row = True #Dont Hide Error Rows? |
|---|
| 66 | if show_row: |
|---|
| 67 | for kind,data,pos in stream: |
|---|
| 68 | yield kind,data,pos |
|---|
| 69 | |
|---|
| 70 | # This can go away once they fix http://genshi.edgewall.org/ticket/136 |
|---|
| 71 | # At that point we should use Transformer.filter |
|---|
| 72 | class FilterTransformation(object): |
|---|
| 73 | """Apply a normal stream filter to the selection. The filter is called once |
|---|
| 74 | for each contiguous block of marked events.""" |
|---|
| 75 | |
|---|
| 76 | def __init__(self, filter): |
|---|
| 77 | """Create the transform. |
|---|
| 78 | |
|---|
| 79 | :param filter: The stream filter to apply. |
|---|
| 80 | """ |
|---|
| 81 | self.filter = filter |
|---|
| 82 | |
|---|
| 83 | def __call__(self, stream): |
|---|
| 84 | """Apply the transform filter to the marked stream. |
|---|
| 85 | |
|---|
| 86 | :param stream: The marked event stream to filter |
|---|
| 87 | """ |
|---|
| 88 | def flush(queue): |
|---|
| 89 | if queue: |
|---|
| 90 | for event in self.filter(queue): |
|---|
| 91 | yield event |
|---|
| 92 | del queue[:] |
|---|
| 93 | |
|---|
| 94 | queue = [] |
|---|
| 95 | for mark, event in stream: |
|---|
| 96 | if mark: |
|---|
| 97 | queue.append(event) |
|---|
| 98 | else: |
|---|
| 99 | for e in flush(queue): |
|---|
| 100 | yield None,e |
|---|
| 101 | yield None,event |
|---|
| 102 | for event in flush(queue): |
|---|
| 103 | yield None,event |
|---|
| 104 | |
|---|
| 105 | class ReportsFilter(Component): |
|---|
| 106 | """Remove all billing reports from the reports list.""" |
|---|
| 107 | implements(ITemplateStreamFilter) |
|---|
| 108 | |
|---|
| 109 | def match_stream(self, req, method, filename, stream, data): |
|---|
| 110 | return filename == 'report_view.html' |
|---|
| 111 | |
|---|
| 112 | def filter_stream(self, req, method, filename, stream, data): |
|---|
| 113 | return stream | Transformer( |
|---|
| 114 | '//table[@class="listing reports"]/tbody/tr' |
|---|
| 115 | ).apply(FilterTransformation(RowFilter(self))) |
|---|
| 116 | |
|---|
| 117 | |
|---|
| 118 | #@staticmethod |
|---|
| 119 | def disable_field(field_stream): |
|---|
| 120 | value = Stream(field_stream).select('@value').render() |
|---|
| 121 | |
|---|
| 122 | for kind,data,pos in tag.span(value, id="field-totalhours").generate(): |
|---|
| 123 | yield kind,data,pos |
|---|
| 124 | |
|---|
| 125 | class TotalHoursFilter(Component): |
|---|
| 126 | """Disable editing of the Total Hours field so that we don't need Javascript.""" |
|---|
| 127 | implements(ITemplateStreamFilter) |
|---|
| 128 | |
|---|
| 129 | def match_stream(self, req, method, filename, stream, data): |
|---|
| 130 | self.log.debug("matching: ticket.html") |
|---|
| 131 | return filename == 'ticket.html' |
|---|
| 132 | |
|---|
| 133 | def filter_stream(self, req, method, filename, stream, data): |
|---|
| 134 | return stream | Transformer( |
|---|
| 135 | '//input[@id="field-totalhours" and @type="text" and @name="field_totalhours"]' |
|---|
| 136 | ).apply(FilterTransformation(disable_field)) |
|---|
| 137 | |
|---|
| 138 | |
|---|
| 139 | |
|---|
| 140 | |
|---|
| 141 | |
|---|
| 142 | class TimeClickFilter(Component): |
|---|
| 143 | """Add a javascript onclick button to add the time since the change into the add hours box """ |
|---|
| 144 | implements(ITemplateStreamFilter) |
|---|
| 145 | |
|---|
| 146 | def match_stream(self, req, method, filename, stream, data): |
|---|
| 147 | self.log.debug("matching: ticket.html") |
|---|
| 148 | return filename == 'ticket.html' |
|---|
| 149 | |
|---|
| 150 | def filter_stream(self, req, method, filename, stream, data): |
|---|
| 151 | if not self.env.config.getbool('timingandestimationplugin','show_add_time_buttons'): |
|---|
| 152 | return stream |
|---|
| 153 | def add_time_click(field_stream): |
|---|
| 154 | time = Stream(field_stream).select('@title').render() |
|---|
| 155 | time = time.split(" ")[0] # get the time out of the title |
|---|
| 156 | #render the guts of the h3 without changes |
|---|
| 157 | for kind,data,pos in field_stream: |
|---|
| 158 | yield kind,data,pos |
|---|
| 159 | #create the button tag that to add hours |
|---|
| 160 | btn = tag.input(None, type='submit', title="add time elapsed since this comment", value="Add Time", time=time, onclick="onClickOfADateElement(this.getAttribute('time'));return false;") |
|---|
| 161 | #create a div with the class that all the other buttons have |
|---|
| 162 | new_stream = tag.div(btn, **{"class":"inlinebuttons"}) |
|---|
| 163 | #output new div/button |
|---|
| 164 | for kind,data,pos in new_stream: |
|---|
| 165 | yield kind,data,pos |
|---|
| 166 | |
|---|
| 167 | return stream | Transformer( |
|---|
| 168 | # we want the text and the nodes so that we can insert at the end |
|---|
| 169 | '//h3[@class="change"]/*|//h3[@class="change"]/text()' |
|---|
| 170 | ).apply(FilterTransformation(add_time_click)) |
|---|
| 171 | |
|---|
| 172 | |
|---|
| 173 | |
|---|
| 174 | |
|---|
| 175 | |
|---|