def _format_link(self, formatter, ns, target, label): path, query, fragment = formatter.split_link(target) if path: href = formatter.href.search(q=path) if query: href += '&' + quote_query_string(query[1:]) else: href = formatter.href.search() + quote_query_string(query) href += fragment return tag.a(label, class_='search', href=href)
def test_quote(self): text = u'type=the Ü thing&component=comp\x7fonent' self.assertEqual('type=the+%C3%9C+thing&component=comp%7Fonent', quote_query_string(text))
class ReportModule(Component): implements(INavigationContributor, IPermissionRequestor, IRequestHandler, IWikiSyntaxProvider) items_per_page = IntOption('report', 'items_per_page', 100, """Number of tickets displayed per page in ticket reports, by default (''since 0.11'')""") items_per_page_rss = IntOption('report', 'items_per_page_rss', 0, """Number of tickets displayed in the rss feeds for reports (''since 0.11'')""") # INavigationContributor methods def get_active_navigation_item(self, req): return 'tickets' def get_navigation_items(self, req): if 'REPORT_VIEW' in req.perm: yield ('mainnav', 'tickets', tag.a(_('View Tickets'), href=req.href.report())) # IPermissionRequestor methods def get_permission_actions(self): actions = ['REPORT_CREATE', 'REPORT_DELETE', 'REPORT_MODIFY', 'REPORT_SQL_VIEW', 'REPORT_VIEW'] return actions + [('REPORT_ADMIN', actions)] # IRequestHandler methods def match_request(self, req): match = re.match(r'/report(?:/(?:([0-9]+)|-1))?$', req.path_info) if match: if match.group(1): req.args['id'] = match.group(1) return True def process_request(self, req): # did the user ask for any special report? id = int(req.args.get('id', -1)) if id != -1: req.perm('report', id).require('REPORT_VIEW') else: req.perm.require('REPORT_VIEW') data = {} action = req.args.get('action', 'view') if req.method == 'POST': if action == 'new': self._do_create(req) elif action == 'delete': self._do_delete(req, id) elif action == 'edit': self._do_save(req, id) else: raise HTTPBadRequest(_("Invalid request arguments.")) elif action in ('copy', 'edit', 'new'): template = 'report_edit.html' data = self._render_editor(req, id, action == 'copy') Chrome(self.env).add_wiki_toolbars(req) elif action == 'delete': template = 'report_delete.html' data = self._render_confirm_delete(req, id) elif id == -1: template, data, content_type = self._render_list(req) if content_type: # i.e. alternate format return template, data, content_type if action == 'clear': if 'query_href' in req.session: del req.session['query_href'] if 'query_tickets' in req.session: del req.session['query_tickets'] else: template, data, content_type = self._render_view(req, id) if content_type: # i.e. alternate format return template, data, content_type from trac.ticket.query import QueryModule show_query_link = 'TICKET_VIEW' in req.perm and \ self.env.is_component_enabled(QueryModule) if id != -1 or action == 'new': add_ctxtnav(req, _('Available Reports'), href=req.href.report()) add_link(req, 'up', req.href.report(), _('Available Reports')) elif show_query_link: add_ctxtnav(req, _('Available Reports')) # Kludge: only show link to custom query if the query module # is actually enabled if show_query_link: add_ctxtnav(req, _('Custom Query'), href=req.href.query()) data['query_href'] = req.href.query() data['saved_query_href'] = req.session.get('query_href') else: data['query_href'] = None add_stylesheet(req, 'common/css/report.css') return template, data, None # Internal methods def _do_create(self, req): req.perm.require('REPORT_CREATE') if 'cancel' in req.args: req.redirect(req.href.report()) title = req.args.get('title', '') query = req.args.get('query', '') description = req.args.get('description', '') with self.env.db_transaction as db: cursor = db.cursor() cursor.execute(""" INSERT INTO report (title,query,description) VALUES (%s,%s,%s) """, (title, query, description)) report_id = db.get_last_id(cursor, 'report') add_notice(req, _("The report has been created.")) req.redirect(req.href.report(report_id)) def _do_delete(self, req, id): req.perm('report', id).require('REPORT_DELETE') if 'cancel' in req.args: req.redirect(req.href.report(id)) self.env.db_transaction("DELETE FROM report WHERE id=%s", (id,)) add_notice(req, _("The report {%(id)d} has been deleted.", id=id)) req.redirect(req.href.report()) def _do_save(self, req, id): """Save report changes to the database""" req.perm('report', id).require('REPORT_MODIFY') if 'cancel' not in req.args: title = req.args.get('title', '') query = req.args.get('query', '') description = req.args.get('description', '') self.env.db_transaction(""" UPDATE report SET title=%s, query=%s, description=%s WHERE id=%s """, (title, query, description, id)) add_notice(req, _("Your changes have been saved.")) req.redirect(req.href.report(id)) def _render_confirm_delete(self, req, id): req.perm('report', id).require('REPORT_DELETE') title = self.get_report(id)[0] return {'title': _("Delete Report {%(num)s} %(title)s", num=id, title=title), 'action': 'delete', 'report': {'id': id, 'title': title}} def _render_editor(self, req, id, copy): if id != -1: req.perm('report', id).require('REPORT_MODIFY') title, description, query = self.get_report(id) else: req.perm.require('REPORT_CREATE') title = description = query = '' # an explicitly given 'query' parameter will override the saved query query = req.args.get('query', query) if copy: title += ' (copy)' if copy or id == -1: data = {'title': _('Create New Report'), 'action': 'new', 'error': None} else: data = {'title': _('Edit Report {%(num)d} %(title)s', num=id, title=title), 'action': 'edit', 'error': req.args.get('error')} data['report'] = {'id': id, 'title': title, 'sql': query, 'description': description} return data def _render_list(self, req): """Render the list of available reports.""" sort = req.args.get('sort', 'report') asc = as_bool(req.args.get('asc', 1)) format = req.args.get('format') rows = self.env.db_query(""" SELECT id, title, description FROM report ORDER BY %s %s """ % ('title' if sort == 'title' else 'id', '' if asc else 'DESC')) rows = [(id, title, description) for id, title, description in rows if 'REPORT_VIEW' in req.perm('report', id)] if format == 'rss': data = {'rows': rows} return 'report_list.rss', data, 'application/rss+xml' elif format == 'csv': self._send_csv(req, ['report', 'title', 'description'], rows, mimetype='text/csv', filename='reports.csv') elif format == 'tab': self._send_csv(req, ['report', 'title', 'description'], rows, '\t', mimetype='text/tab-separated-values', filename='reports.tsv') def report_href(**kwargs): return req.href.report(sort=req.args.get('sort'), asc='1' if asc else '0', **kwargs) add_link(req, 'alternate', auth_link(req, report_href(format='rss')), _('RSS Feed'), 'application/rss+xml', 'rss') add_link(req, 'alternate', report_href(format='csv'), _('Comma-delimited Text'), 'text/plain') add_link(req, 'alternate', report_href(format='tab'), _('Tab-delimited Text'), 'text/plain') reports = [(id, title, description, 'REPORT_MODIFY' in req.perm('report', id), 'REPORT_DELETE' in req.perm('report', id)) for id, title, description in rows] data = {'reports': reports, 'sort': sort, 'asc': asc} return 'report_list.html', data, None _html_cols = set(['__class__', '__style__', '__color__', '__fgcolor__', '__bgcolor__', '__grouplink__']) def _render_view(self, req, id): """Retrieve the report results and pre-process them for rendering.""" title, description, sql = self.get_report(id) try: args = self.get_var_args(req) except ValueError, e: raise TracError(_("Report failed: %(error)s", error=e)) # If this is a saved custom query, redirect to the query module # # A saved query is either an URL query (?... or query:?...), # or a query language expression (query:...). # # It may eventually contain newlines, for increased clarity. # query = ''.join([line.strip() for line in sql.splitlines()]) if query and (query[0] == '?' or query.startswith('query:?')): query = query if query[0] == '?' else query[6:] report_id = 'report=%s' % id if 'report=' in query: if not report_id in query: err = _('When specified, the report number should be ' '"%(num)s".', num=id) req.redirect(req.href.report(id, action='edit', error=err)) else: if query[-1] != '?': query += '&' query += report_id req.redirect(req.href.query() + quote_query_string(query)) elif query.startswith('query:'): try: from trac.ticket.query import Query, QuerySyntaxError query = Query.from_string(self.env, query[6:], report=id) req.redirect(query.get_href(req)) except QuerySyntaxError, e: req.redirect(req.href.report(id, action='edit', error=to_unicode(e)))
def _render_view(self, req, id): """Retrieve the report results and pre-process them for rendering.""" r = Report(self.env, id) title, description, sql = r.title, r.description, r.query # If this is a saved custom query, redirect to the query module # # A saved query is either an URL query (?... or query:?...), # or a query language expression (query:...). # # It may eventually contain newlines, for increased clarity. # query = ''.join(line.strip() for line in sql.splitlines()) if query and (query[0] == '?' or query.startswith('query:?')): query = query if query[0] == '?' else query[6:] report_id = 'report=%s' % id if 'report=' in query: if report_id not in query: err = _('When specified, the report number should be ' '"%(num)s".', num=id) req.redirect(req.href.report(id, action='edit', error=err)) else: if query[-1] != '?': query += '&' query += report_id req.redirect(req.href.query() + quote_query_string(query)) elif query.startswith('query:'): from trac.ticket.query import Query, QuerySyntaxError try: query = Query.from_string(self.env, query[6:], report=id) except QuerySyntaxError as e: req.redirect(req.href.report(id, action='edit', error=to_unicode(e))) else: req.redirect(query.get_href(req.href)) format = req.args.get('format') if format == 'sql': self._send_sql(req, id, title, description, sql) title = '{%i} %s' % (id, title) report_resource = Resource(self.realm, id) req.perm(report_resource).require('REPORT_VIEW') context = web_context(req, report_resource) page = req.args.getint('page', 1) default_max = {'rss': self.items_per_page_rss, 'csv': 0, 'tab': 0}.get(format, self.items_per_page) max = req.args.getint('max') limit = as_int(max, default_max, min=0) # explict max takes precedence offset = (page - 1) * limit sort_col = req.args.get('sort', '') asc = req.args.getint('asc', 0, min=0, max=1) args = {} def report_href(**kwargs): """Generate links to this report preserving user variables, and sorting and paging variables. """ params = args.copy() if sort_col: params['sort'] = sort_col if page != 1: params['page'] = page if max != default_max: params['max'] = max params.update(kwargs) params['asc'] = 1 if params.get('asc', asc) else None return req.href.report(id, params) data = {'action': 'view', 'report': {'id': id, 'resource': report_resource}, 'context': context, 'title': title, 'description': description, 'max': limit, 'args': args, 'show_args_form': False, 'message': None, 'paginator': None, 'report_href': report_href} try: args = self.get_var_args(req) sql = self.get_default_var_args(args, sql) except ValueError as e: data['message'] = _("Report failed: %(error)s", error=e) return 'report_view.html', data, None data.update({'args': args, 'title': sub_vars(title, args), 'description': sub_vars(description or '', args)}) try: res = self.execute_paginated_report(req, id, sql, args, limit, offset) except TracError as e: data['message'] = _("Report failed: %(error)s", error=e) else: if len(res) == 2: e, sql = res data['message'] = \ tag_("Report execution failed: %(error)s %(sql)s", error=tag.pre(exception_to_unicode(e)), sql=tag(tag.hr(), tag.pre(sql, style="white-space: pre"))) if data['message']: return 'report_view.html', data, None cols, results, num_items, missing_args, limit_offset = res need_paginator = limit > 0 and limit_offset need_reorder = limit_offset is None results = [list(row) for row in results] numrows = len(results) paginator = None if need_paginator: paginator = Paginator(results, page - 1, limit, num_items) data['paginator'] = paginator if paginator.has_next_page: add_link(req, 'next', report_href(page=page + 1), _('Next Page')) if paginator.has_previous_page: add_link(req, 'prev', report_href(page=page - 1), _('Previous Page')) pagedata = [] shown_pages = paginator.get_shown_pages(21) for p in shown_pages: pagedata.append([report_href(page=p), None, str(p), _('Page %(num)d', num=p)]) fields = ['href', 'class', 'string', 'title'] paginator.shown_pages = [dict(zip(fields, p)) for p in pagedata] paginator.current_page = {'href': None, 'class': 'current', 'string': str(paginator.page + 1), 'title': None} numrows = paginator.num_items # Place retrieved columns in groups, according to naming conventions # * _col_ means fullrow, i.e. a group with one header # * col_ means finish the current group and start a new one field_labels = TicketSystem(self.env).get_ticket_field_labels() header_groups = [[]] for idx, col in enumerate(cols): if col in field_labels: title = field_labels[col] else: title = col.strip('_').capitalize() header = { 'col': col, 'title': title, 'hidden': False, 'asc': None, } if col == sort_col: if asc: data['asc'] = asc data['sort'] = sort_col header['asc'] = bool(asc) if not paginator and need_reorder: # this dict will have enum values for sorting # and will be used in sortkey(), if non-empty: sort_values = {} if sort_col in ('status', 'resolution', 'priority', 'severity'): # must fetch sort values for that columns # instead of comparing them as strings with self.env.db_query as db: for name, value in db( "SELECT name, %s FROM enum WHERE type=%%s" % db.cast('value', 'int'), (sort_col,)): sort_values[name] = value def sortkey(row): val = row[idx] # check if we have sort_values, then use them as keys. if sort_values: return sort_values.get(val) # otherwise, continue with string comparison: if isinstance(val, basestring): val = val.lower() return val results = sorted(results, key=sortkey, reverse=not asc) header_group = header_groups[-1] if col.startswith('__') and col.endswith('__'): # __col__ header['hidden'] = True elif col[0] == '_' and col[-1] == '_': # _col_ header_group = [] header_groups.append(header_group) header_groups.append([]) elif col[0] == '_': # _col header['hidden'] = True elif col[-1] == '_': # col_ header_groups.append([]) header_group.append(header) # Structure the rows and cells: # - group rows according to __group__ value, if defined # - group cells the same way headers are grouped chrome = Chrome(self.env) row_groups = [] authorized_results = [] prev_group_value = None for row_idx, result in enumerate(results): col_idx = 0 cell_groups = [] row = {'cell_groups': cell_groups} realm = TicketSystem.realm parent_realm = '' parent_id = '' email_cells = [] for header_group in header_groups: cell_group = [] for header in header_group: value = cell_value(result[col_idx]) cell = {'value': value, 'header': header, 'index': col_idx} col = header['col'] col_idx += 1 # Detect and create new group if col == '__group__' and value != prev_group_value: prev_group_value = value # Brute force handling of email in group by header row_groups.append( (value and chrome.format_author(req, value), [])) # Other row properties row['__idx__'] = row_idx if col in self._html_cols: row[col] = value if col in ('report', 'ticket', 'id', '_id'): row['id'] = value # Special casing based on column name col = col.strip('_') if col in ('reporter', 'cc', 'owner'): email_cells.append(cell) elif col == 'realm': realm = value elif col == 'parent_realm': parent_realm = value elif col == 'parent_id': parent_id = value cell_group.append(cell) cell_groups.append(cell_group) if parent_realm: resource = Resource(realm, row.get('id'), parent=Resource(parent_realm, parent_id)) else: resource = Resource(realm, row.get('id')) # FIXME: for now, we still need to hardcode the realm in the action if resource.realm.upper() + '_VIEW' not in req.perm(resource): continue authorized_results.append(result) if email_cells: for cell in email_cells: emails = chrome.format_emails(context.child(resource), cell['value']) result[cell['index']] = cell['value'] = emails row['resource'] = resource if row_groups: row_group = row_groups[-1][1] else: row_group = [] row_groups = [(None, row_group)] row_group.append(row) data.update({'header_groups': header_groups, 'row_groups': row_groups, 'numrows': numrows}) if format == 'rss': data['context'] = web_context(req, report_resource, absurls=True) return 'report.rss', data, 'application/rss+xml' elif format == 'csv': filename = 'report_%s.csv' % id if id else 'report.csv' self._send_csv(req, cols, authorized_results, mimetype='text/csv', filename=filename) elif format == 'tab': filename = 'report_%s.tsv' % id if id else 'report.tsv' self._send_csv(req, cols, authorized_results, '\t', mimetype='text/tab-separated-values', filename=filename) else: p = page if max is not None else None add_link(req, 'alternate', auth_link(req, report_href(format='rss', page=None)), _('RSS Feed'), 'application/rss+xml', 'rss') add_link(req, 'alternate', report_href(format='csv', page=p), _('Comma-delimited Text'), 'text/plain') add_link(req, 'alternate', report_href(format='tab', page=p), _('Tab-delimited Text'), 'text/plain') if 'REPORT_SQL_VIEW' in req.perm(self.realm, id): add_link(req, 'alternate', req.href.report(id=id, format='sql'), _('SQL Query'), 'text/plain') # reuse the session vars of the query module so that # the query navigation links on the ticket can be used to # navigate report results as well try: req.session['query_tickets'] = \ ' '.join(str(int(row['id'])) for rg in row_groups for row in rg[1]) req.session['query_href'] = \ req.session['query_href'] = report_href() # Kludge: we have to clear the other query session # variables, but only if the above succeeded for var in ('query_constraints', 'query_time'): if var in req.session: del req.session[var] except (ValueError, KeyError): pass if set(data['args']) - {'USER'}: data['show_args_form'] = True # Add values of all select-type ticket fields for autocomplete. fields = TicketSystem(self.env).get_ticket_fields() arg_values = {} for arg in set(data['args']) - {'USER'}: attrs = fields.by_name(arg.lower()) if attrs and 'options' in attrs: arg_values[attrs['name']] = attrs['options'] if arg_values: add_script_data(req, arg_values=arg_values) Chrome(self.env).add_jquery_ui(req) if missing_args: add_warning(req, _( 'The following arguments are missing: %(args)s', args=", ".join(missing_args))) return 'report_view.html', data, None
def test_quote(self): text = u"type=the Ü thing&component=comp\x7fonent" self.assertEqual("type=the+%C3%9C+thing&component=comp%7Fonent", quote_query_string(text))
def _render_view(self, req, id): """Retrieve the report results and pre-process them for rendering.""" title, description, sql = self.get_report(id) try: args = self.get_var_args(req) except ValueError as e: raise TracError(_("Report failed: %(error)s", error=e)) # If this is a saved custom query, redirect to the query module # # A saved query is either an URL query (?... or query:?...), # or a query language expression (query:...). # # It may eventually contain newlines, for increased clarity. # query = ''.join([line.strip() for line in sql.splitlines()]) if query and (query[0] == '?' or query.startswith('query:?')): query = query if query[0] == '?' else query[6:] report_id = 'report=%s' % id if 'report=' in query: if not report_id in query: err = _('When specified, the report number should be ' '"%(num)s".', num=id) req.redirect(req.href.report(id, action='edit', error=err)) else: if query[-1] != '?': query += '&' query += report_id req.redirect(req.href.query() + quote_query_string(query)) elif query.startswith('query:'): try: from trac.ticket.query import Query, QuerySyntaxError query = Query.from_string(self.env, query[6:], report=id) req.redirect(query.get_href(req.href)) except QuerySyntaxError as e: req.redirect(req.href.report(id, action='edit', error=to_unicode(e))) format = req.args.get('format') if format == 'sql': self._send_sql(req, id, title, description, sql) title = '{%i} %s' % (id, title) report_resource = Resource('report', id) req.perm(report_resource).require('REPORT_VIEW') context = web_context(req, report_resource) page = int(req.args.get('page', '1')) default_max = {'rss': self.items_per_page_rss, 'csv': 0, 'tab': 0}.get(format, self.items_per_page) max = req.args.get('max') limit = as_int(max, default_max, min=0) # explict max takes precedence offset = (page - 1) * limit sort_col = req.args.get('sort', '') asc = req.args.get('asc', 1) asc = bool(int(asc)) # string '0' or '1' to int/boolean def report_href(**kwargs): """Generate links to this report preserving user variables, and sorting and paging variables. """ params = args.copy() if sort_col: params['sort'] = sort_col params['page'] = page if max: params['max'] = max params.update(kwargs) params['asc'] = '1' if params.get('asc', asc) else '0' return req.href.report(id, params) data = {'action': 'view', 'report': {'id': id, 'resource': report_resource}, 'context': context, 'title': sub_vars(title, args), 'description': sub_vars(description or '', args), 'max': limit, 'args': args, 'show_args_form': False, 'message': None, 'paginator': None, 'report_href': report_href, } res = self.execute_paginated_report(req, id, sql, args, limit, offset) if len(res) == 2: e, sql = res data['message'] = \ tag_("Report execution failed: %(error)s %(sql)s", error=tag.pre(exception_to_unicode(e)), sql=tag(tag.hr(), tag.pre(sql, style="white-space: pre"))) return 'report_view.html', data, None cols, results, num_items, missing_args, limit_offset = res need_paginator = limit > 0 and limit_offset need_reorder = limit_offset is None results = [list(row) for row in results] numrows = len(results) paginator = None if need_paginator: paginator = Paginator(results, page - 1, limit, num_items) data['paginator'] = paginator if paginator.has_next_page: add_link(req, 'next', report_href(page=page + 1), _('Next Page')) if paginator.has_previous_page: add_link(req, 'prev', report_href(page=page - 1), _('Previous Page')) pagedata = [] shown_pages = paginator.get_shown_pages(21) for p in shown_pages: pagedata.append([report_href(page=p), None, str(p), _('Page %(num)d', num=p)]) fields = ['href', 'class', 'string', 'title'] paginator.shown_pages = [dict(zip(fields, p)) for p in pagedata] paginator.current_page = {'href': None, 'class': 'current', 'string': str(paginator.page + 1), 'title': None} numrows = paginator.num_items # Place retrieved columns in groups, according to naming conventions # * _col_ means fullrow, i.e. a group with one header # * col_ means finish the current group and start a new one field_labels = TicketSystem(self.env).get_ticket_field_labels() header_groups = [[]] for idx, col in enumerate(cols): if col in field_labels: title = field_labels[col] else: title = col.strip('_').capitalize() header = { 'col': col, 'title': title, 'hidden': False, 'asc': None, } if col == sort_col: header['asc'] = asc if not paginator and need_reorder: # this dict will have enum values for sorting # and will be used in sortkey(), if non-empty: sort_values = {} if sort_col in ('status', 'resolution', 'priority', 'severity'): # must fetch sort values for that columns # instead of comparing them as strings with self.env.db_query as db: for name, value in db( "SELECT name, %s FROM enum WHERE type=%%s" % db.cast('value', 'int'), (sort_col,)): sort_values[name] = value def sortkey(row): val = row[idx] # check if we have sort_values, then use them as keys. if sort_values: return sort_values.get(val) # otherwise, continue with string comparison: if isinstance(val, basestring): val = val.lower() return val results = sorted(results, key=sortkey, reverse=(not asc)) header_group = header_groups[-1] if col.startswith('__') and col.endswith('__'): # __col__ header['hidden'] = True elif col[0] == '_' and col[-1] == '_': # _col_ header_group = [] header_groups.append(header_group) header_groups.append([]) elif col[0] == '_': # _col header['hidden'] = True elif col[-1] == '_': # col_ header_groups.append([]) header_group.append(header) # Structure the rows and cells: # - group rows according to __group__ value, if defined # - group cells the same way headers are grouped chrome = Chrome(self.env) row_groups = [] authorized_results = [] prev_group_value = None for row_idx, result in enumerate(results): col_idx = 0 cell_groups = [] row = {'cell_groups': cell_groups} realm = self.realm parent_realm = '' parent_id = '' email_cells = [] for header_group in header_groups: cell_group = [] for header in header_group: value = cell_value(result[col_idx]) cell = {'value': value, 'header': header, 'index': col_idx} col = header['col'] col_idx += 1 # Detect and create new group if col == '__group__' and value != prev_group_value: prev_group_value = value # Brute force handling of email in group by header row_groups.append( (value and chrome.format_author(req, value), [])) # Other row properties row['__idx__'] = row_idx if col in self._html_cols: row[col] = value if col in ('report', 'ticket', 'id', '_id'): row['id'] = value # Special casing based on column name col = col.strip('_') if col in ('reporter', 'cc', 'owner'): email_cells.append(cell) elif col == 'realm': realm = value elif col == 'parent_realm': parent_realm = value elif col == 'parent_id': parent_id = value cell_group.append(cell) cell_groups.append(cell_group) if parent_realm: resource = Resource(realm, row.get('id'), parent=Resource(parent_realm, parent_id)) else: resource = Resource(realm, row.get('id')) # FIXME: for now, we still need to hardcode the realm in the action if resource.realm.upper()+'_VIEW' not in req.perm(resource): continue authorized_results.append(result) if email_cells: for cell in email_cells: emails = chrome.format_emails(context.child(resource), cell['value']) result[cell['index']] = cell['value'] = emails row['resource'] = resource if row_groups: row_group = row_groups[-1][1] else: row_group = [] row_groups = [(None, row_group)] row_group.append(row) data.update({'header_groups': header_groups, 'row_groups': row_groups, 'numrows': numrows}) if format == 'rss': data['context'] = web_context(req, report_resource, absurls=True) return 'report.rss', data, 'application/rss+xml' elif format == 'csv': filename = 'report_%s.csv' % id if id else 'report.csv' self._send_csv(req, cols, authorized_results, mimetype='text/csv', filename=filename) elif format == 'tab': filename = 'report_%s.tsv' % id if id else 'report.tsv' self._send_csv(req, cols, authorized_results, '\t', mimetype='text/tab-separated-values', filename=filename) else: p = page if max is not None else None add_link(req, 'alternate', auth_link(req, report_href(format='rss', page=None)), _('RSS Feed'), 'application/rss+xml', 'rss') add_link(req, 'alternate', report_href(format='csv', page=p), _('Comma-delimited Text'), 'text/plain') add_link(req, 'alternate', report_href(format='tab', page=p), _('Tab-delimited Text'), 'text/plain') if 'REPORT_SQL_VIEW' in req.perm('report', id): add_link(req, 'alternate', req.href.report(id=id, format='sql'), _('SQL Query'), 'text/plain') # reuse the session vars of the query module so that # the query navigation links on the ticket can be used to # navigate report results as well try: req.session['query_tickets'] = \ ' '.join([str(int(row['id'])) for rg in row_groups for row in rg[1]]) req.session['query_href'] = \ req.session['query_href'] = report_href() # Kludge: we have to clear the other query session # variables, but only if the above succeeded for var in ('query_constraints', 'query_time'): if var in req.session: del req.session[var] except (ValueError, KeyError): pass if set(data['args']) - set(['USER']): data['show_args_form'] = True add_script(req, 'common/js/folding.js') if missing_args: add_warning(req, _( 'The following arguments are missing: %(args)s', args=", ".join(missing_args))) return 'report_view.html', data, None