1 | # -*- coding: utf-8 -*- |
---|
2 | # |
---|
3 | # Copyright (C) 2012 Thomas Doering, falkb |
---|
4 | # |
---|
5 | |
---|
6 | from genshi.builder import tag |
---|
7 | from genshi.filters.transform import Transformer |
---|
8 | from trac.ticket.model import Ticket |
---|
9 | from simplemultiproject.model import * |
---|
10 | from trac.util.text import to_unicode |
---|
11 | from trac.core import * |
---|
12 | from trac.web.api import IRequestFilter, ITemplateStreamFilter |
---|
13 | from operator import itemgetter |
---|
14 | from simplemultiproject.model import smp_filter_settings |
---|
15 | |
---|
16 | class SmpTimelineProjectFilter(Component): |
---|
17 | """Allows for filtering by 'Project' |
---|
18 | """ |
---|
19 | _old_render_fn = [] |
---|
20 | _current_project = [] |
---|
21 | _read_idx = -1 |
---|
22 | |
---|
23 | implements(IRequestFilter, ITemplateStreamFilter) |
---|
24 | |
---|
25 | def __init__(self): |
---|
26 | self.__SmpModel = SmpModel(self.env) |
---|
27 | |
---|
28 | # IRequestFilter methods |
---|
29 | |
---|
30 | def pre_process_request(self, req, handler): |
---|
31 | return handler |
---|
32 | |
---|
33 | def post_process_request(self, req, template, data, content_type): |
---|
34 | if template == 'timeline.html': |
---|
35 | filter_off = False |
---|
36 | displayed_projects = self._filtered_projects(req) |
---|
37 | if not displayed_projects: #no filter means likely more than 1 project, so we insert the project name |
---|
38 | filter_off = True |
---|
39 | displayed_projects = [project[1] for project in self.__SmpModel.get_all_projects()] |
---|
40 | |
---|
41 | if displayed_projects: |
---|
42 | filtered_events = [] |
---|
43 | tickettypes = ("newticket", "editedticket", "closedticket", "attachment", "reopenedticket") |
---|
44 | self._old_render_fn = [] |
---|
45 | self._current_project = [] |
---|
46 | self._read_idx = -1 |
---|
47 | for event in data['events']: |
---|
48 | if event['kind'] in tickettypes: |
---|
49 | resource = event['kind'] == "attachment" and event['data'][0].parent or event['data'][0] |
---|
50 | if resource.realm == "ticket": |
---|
51 | ticket = Ticket( self.env, resource.id ) |
---|
52 | project = ticket.get_value_or_default('project') |
---|
53 | if not project: |
---|
54 | if filter_off: #display all tickets without a set project |
---|
55 | filtered_events.append(event) |
---|
56 | elif project in displayed_projects: |
---|
57 | if len(displayed_projects) > 1: #only if more than 1 filtered project |
---|
58 | #store the old render function and the project to be inserted |
---|
59 | self._old_render_fn.append(event['render']) |
---|
60 | self._current_project.append(project) |
---|
61 | #redirect to our new render function (which will insert the project name) |
---|
62 | event['render'] = self._render_ticket_event |
---|
63 | #add to the list of displayed events |
---|
64 | filtered_events.append(event) |
---|
65 | elif resource.realm == "wiki": |
---|
66 | filtered_events.append(event) |
---|
67 | else: |
---|
68 | filtered_events.append(event) |
---|
69 | |
---|
70 | data['events'] = filtered_events |
---|
71 | |
---|
72 | return template, data, content_type |
---|
73 | |
---|
74 | # ITemplateStreamFilter methods |
---|
75 | |
---|
76 | def filter_stream(self, req, method, filename, stream, data): |
---|
77 | if filename == 'timeline.html': |
---|
78 | filter_projects = self._filtered_projects(req) |
---|
79 | |
---|
80 | filter = Transformer('//form[@id="prefs"]/div[1]') |
---|
81 | stream = stream | filter.before(tag.label("Filter Projects:")) | filter.before(tag.br()) | filter.before(self._projects_field_input(req, filter_projects)) | filter.before(tag.br()) | filter.before(tag.br()) |
---|
82 | |
---|
83 | return stream |
---|
84 | |
---|
85 | # Internal |
---|
86 | def _render_ticket_event(self, field, context): |
---|
87 | if (field == 'url'): |
---|
88 | self._read_idx += 1 #next index now |
---|
89 | |
---|
90 | if self._read_idx < len(self._old_render_fn): |
---|
91 | #call the original render function |
---|
92 | output = self._old_render_fn[self._read_idx](field, context) |
---|
93 | |
---|
94 | if field == 'title': #now it's time to insert the project name |
---|
95 | #split the whole string until we can insert |
---|
96 | splitted_output = to_unicode(output).split("</em>") |
---|
97 | tooltip = splitted_output[0].split('\"') |
---|
98 | ticket_no = splitted_output[0].split('>') |
---|
99 | if len(tooltip) == 3: #it's a ticket |
---|
100 | #now rebuild the puzzle by inserting the name |
---|
101 | output = tag('Ticket' + ' ', tag.em(ticket_no[1], title=tooltip[1]), ' ', tag.span(self._current_project[self._read_idx], style="background-color: #ffffd0;"), splitted_output[1]) |
---|
102 | elif len(tooltip) == 1 and len(splitted_output) == 3: #it's an attachment |
---|
103 | output += tag(' ', tag.span(self._current_project[self._read_idx], style="background-color: #ffffd0;")) |
---|
104 | return output |
---|
105 | |
---|
106 | def _projects_field_input(self, req, selectedcomps): |
---|
107 | cursor = self.__SmpModel.get_all_projects_filtered_by_conditions(req) |
---|
108 | |
---|
109 | sorted_project_names_list = sorted(cursor, key=itemgetter(1)) |
---|
110 | number_displayed_entries = len(sorted_project_names_list)+1 # +1 for special entry 'All' |
---|
111 | if number_displayed_entries > 15: |
---|
112 | number_displayed_entries = 15 |
---|
113 | |
---|
114 | select = tag.select(name="filter-projects", id="filter-projects", multiple="multiple", size=("%s" % number_displayed_entries), style="overflow:auto;") |
---|
115 | select.append(tag.option("All", value="All")) |
---|
116 | |
---|
117 | for component in sorted_project_names_list: |
---|
118 | project = to_unicode(component[1]) |
---|
119 | if selectedcomps and project in selectedcomps: |
---|
120 | select.append(tag.option(project, value=project, selected="selected")) |
---|
121 | else: |
---|
122 | select.append(tag.option(project, value=project)) |
---|
123 | return select |
---|
124 | |
---|
125 | def _filtered_projects(self, req): |
---|
126 | filtered_projects = smp_filter_settings(req, 'timeline', 'projects') |
---|
127 | |
---|
128 | return filtered_projects |
---|