def expand_macro(self, formatter, name, content): env = self.env req = formatter.req args, kw = parse_args(content) # Use macro arguments (most likely wiki macro calls). realms = 'realm' in kw and kw['realm'].split('|') or [] tag_system = TagSystem(env) all_realms = [p.get_taggable_realm() for p in tag_system.tag_providers] self.all_realms = all_realms self.realms = realms if name == 'TagCloud': args.append(' or '.join(['realm:%s' % r for r in realms])) all_tags = tag_system.get_all_tags(req, ' '.join(args)) mincount = 'mincount' in kw and kw['mincount'] or None return self.render_cloud(req, all_tags, caseless_sort=self.caseless_sort, mincount=mincount) elif name == 'ListTagged': if _OBSOLETE_ARGS_RE.search(content): data = {'warning': 'obsolete_args'} else: data = {'warning': None} context = formatter.context # Use TagsQuery arguments (most likely wiki macro calls). cols = 'cols' in kw and kw['cols'] or self.default_cols format = 'format' in kw and kw['format'] or self.default_format query = args and args[0].strip() or None if query and not realms: # First read query arguments (most likely a web-UI call). for realm in all_realms: if re.search('(^|\W)realm:%s(\W|$)' % (realm), query): realms = realms and realms.append(realm) or [realm] if not realms: # Apply ListTagged defaults to macro call w/o realm. realms = list(set(all_realms) - set(self.exclude_realms)) if not realms: return '' else: self.query = query self.realms = realms query = '(%s) (%s)' % (query or '', ' or '.join( ['realm:%s' % (r) for r in realms])) env.log.debug('LISTTAGGED_QUERY: ' + query) query_result = tag_system.query(req, query) if not query_result: return '' def _link(resource): if resource.realm == 'tag': # Keep realm selection in tag links. return builder.a(resource.id, href=self.get_href(req, tag=resource)) elif resource.realm == 'ticket': # Return resource link including ticket status dependend # class to allow for common Trac ticket link style. ticket = Ticket(env, resource.id) return builder.a('#%s' % ticket.id, class_=ticket['status'], href=formatter.href.ticket(ticket.id), title=shorten_line(ticket['summary'])) return render_resource_link(env, context, resource, 'compact') if format == 'table': cols = [ col for col in cols.split('|') if col in self.supported_cols ] # Use available translations from Trac core. try: labels = TicketSystem(env).get_ticket_field_labels() labels['id'] = _('Id') except AttributeError: # Trac 0.11 neither has the attribute nor uses i18n. labels = {'id': 'Id', 'description': 'Description'} labels['realm'] = _('Realm') labels['tags'] = _('Tags') headers = [{'label': labels.get(col)} for col in cols] data.update({'cols': cols, 'headers': headers}) results = sorted(query_result, key=lambda r: \ embedded_numbers(to_unicode(r[0].id))) results = self._paginate(req, results) rows = [] for resource, tags in results: desc = tag_system.describe_tagged_resource(req, resource) tags = sorted(tags) if tags: rendered_tags = [ _link(Resource('tag', tag)) for tag in tags ] if 'oldlist' == format: resource_link = _link(resource) else: desc = desc or \ get_resource_description(env, resource, context=context) resource_link = builder.a(desc, href=get_resource_url( env, resource, context.href)) if 'table' == format: cells = [] for col in cols: if col == 'id': cells.append(_link(resource)) # Don't duplicate links to resource in both. elif col == 'description' and 'id' in cols: cells.append(desc) elif col == 'description': cells.append(resource_link) elif col == 'realm': cells.append(resource.realm) elif col == 'tags': cells.append( builder([(tag, ' ') for tag in rendered_tags])) rows.append({'cells': cells}) continue rows.append({ 'desc': desc, 'rendered_tags': None, 'resource_link': _link(resource) }) data.update({ 'format': format, 'paginator': results, 'results': rows, 'tags_url': req.href('tags') }) # Work around a bug in trac/templates/layout.html, that causes a # TypeError for the wiki macro call, if we use add_link() alone. add_stylesheet(req, 'common/css/search.css') return Chrome(env).render_template(req, 'listtagged_results.html', data, 'text/html', True)
def expand_macro(self, formatter, name, content, realms=[]): """Evaluate macro call and render results. Calls from web-UI come with pre-processed realm selection. """ env = self.env req = formatter.req tag_system = TagSystem(env) all_realms = tag_system.get_taggable_realms() if not all_realms: # Tag providers are required, no result without at least one. return '' args, kw = parse_args(content) query = args and args[0].strip() or None if not realms: # Check macro arguments for realms (typical wiki macro call). realms = 'realm' in kw and kw['realm'].split('|') or [] if query: # Add realms from query expression. realms.extend(query_realms(query, all_realms)) # Remove redundant realm selection for performance. if set(realms) == all_realms: query = re.sub('(^|\W)realm:\S+(\W|$)', ' ', query).strip() if name == 'TagCloud': # Set implicit 'all tagged realms' as default. if not realms: realms = all_realms if query: all_tags = Counter() # Require per resource query including view permission checks. for resource, tags in tag_system.query(req, query): all_tags.update(tags) else: # Allow faster per tag query, side steps permission checks. all_tags = tag_system.get_all_tags(req, realms=realms) mincount = 'mincount' in kw and kw['mincount'] or None return self.render_cloud(req, all_tags, caseless_sort=self.caseless_sort, mincount=mincount, realms=realms) elif name == 'ListTagged': if content and _OBSOLETE_ARGS_RE.search(content): data = {'warning': 'obsolete_args'} else: data = {'warning': None} context = formatter.context # Use TagsQuery arguments (most likely wiki macro calls). cols = 'cols' in kw and kw['cols'] or self.default_cols format = 'format' in kw and kw['format'] or self.default_format if not realms: # Apply ListTagged defaults to macro call w/o realm. realms = list(set(all_realms)-set(self.exclude_realms)) if not realms: return '' query = '(%s) (%s)' % (query or '', ' or '.join(['realm:%s' % (r) for r in realms])) query_result = tag_system.query(req, query) excludes = [exc.strip() for exc in kw.get('exclude', '' ).split(':') if exc.strip()] if excludes and query_result: filtered_result = [(resource, tags) for resource, tags in query_result if not any(fnmatchcase(resource.id, exc) for exc in excludes)] query_result = filtered_result if not query_result: return '' def _link(resource): if resource.realm == 'tag': # Keep realm selection in tag links. return builder.a(resource.id, href=self.get_href(req, realms, tag=resource)) elif resource.realm == 'ticket': # Return resource link including ticket status dependend # class to allow for common Trac ticket link style. ticket = Ticket(env, resource.id) return builder.a('#%s' % ticket.id, class_=ticket['status'], href=formatter.href.ticket(ticket.id), title=shorten_line(ticket['summary'])) return render_resource_link(env, context, resource, 'compact') if format == 'table': cols = [col for col in cols.split('|') if col in self.supported_cols] # Use available translations from Trac core. try: labels = TicketSystem(env).get_ticket_field_labels() labels['id'] = _('Id') except AttributeError: # Trac 0.11 neither has the attribute nor uses i18n. labels = {'id': 'Id', 'description': 'Description'} labels['realm'] = _('Realm') labels['tags'] = _('Tags') headers = [{'label': labels.get(col)} for col in cols] data.update({'cols': cols, 'headers': headers}) results = sorted(query_result, key=lambda r: \ embedded_numbers(to_unicode(r[0].id))) results = self._paginate(req, results, realms) rows = [] for resource, tags in results: desc = tag_system.describe_tagged_resource(req, resource) tags = sorted(tags) wiki_desc = format_to_oneliner(env, context, desc) if tags: rendered_tags = [_link(Resource('tag', tag)) for tag in tags] if 'oldlist' == format: resource_link = _link(resource) else: resource_link = builder.a(wiki_desc, href=get_resource_url( env, resource, context.href)) if 'table' == format: cells = [] for col in cols: if col == 'id': cells.append(_link(resource)) # Don't duplicate links to resource in both. elif col == 'description' and 'id' in cols: cells.append(wiki_desc) elif col == 'description': cells.append(resource_link) elif col == 'realm': cells.append(resource.realm) elif col == 'tags': cells.append( builder([(tag, ' ') for tag in rendered_tags])) rows.append({'cells': cells}) continue rows.append({'desc': wiki_desc, 'rendered_tags': None, 'resource_link': _link(resource)}) data.update({'format': format, 'paginator': results, 'results': rows, 'tags_url': req.href('tags')}) # Work around a bug in trac/templates/layout.html, that causes a # TypeError for the wiki macro call, if we use add_link() alone. add_stylesheet(req, 'common/css/search.css') return Chrome(env).render_template( req, 'listtagged_results.html', data, 'text/html', True)
def display_html(self, req, query): """returns the HTML according to a query for /hours view""" db = self.env.get_db_cnx() # The most recent query is stored in the user session; orig_list = None orig_time = datetime.now(utc) query_time = int(req.session.get('query_time', 0)) query_time = datetime.fromtimestamp(query_time, utc) query_constraints = unicode(query.constraints) if query_constraints != req.session.get('query_constraints') \ or query_time < orig_time - timedelta(hours=1): tickets = query.execute(req, db) # New or outdated query, (re-)initialize session vars req.session['query_constraints'] = query_constraints req.session['query_tickets'] = ' '.join( [str(t['id']) for t in tickets]) else: orig_list = [ int(id) for id in req.session.get('query_tickets', '').split() ] tickets = query.execute(req, db, orig_list) orig_time = query_time context = Context.from_request(req, 'query') ticket_data = query.template_data(context, tickets, orig_list, orig_time, req) # For clients without JavaScript, we add a new constraint here if # requested constraints = ticket_data['clauses'][0] if 'add' in req.args: field = req.args.get('add_filter') if field: constraint = constraints.setdefault(field, {}) constraint.setdefault('values', []).append('') # FIXME: '' not always correct (e.g. checkboxes) req.session['query_href'] = query.get_href(context.href) req.session['query_time'] = to_timestamp(orig_time) req.session['query_tickets'] = ' '.join( [str(t['id']) for t in tickets]) # data dictionary for genshi data = {} # get data for saved queries query_id = req.args.get('query_id') if query_id: try: query_id = int(query_id) except ValueError: add_warning( req, "query_id should be an integer, you put '%s'" % query_id) query_id = None if query_id: data['query_id'] = query_id query_data = self.get_query(query_id) data['query_title'] = query_data['title'] data['query_description'] = query_data['description'] data.setdefault('report', None) data.setdefault('description', None) data['all_columns'] = query.get_all_columns() + self.get_columns() # Don't allow the user to remove the id column data['all_columns'].remove('id') data['all_textareas'] = query.get_all_textareas() # need to re-get the cols because query will remove our fields cols = req.args.get('col') if isinstance(cols, basestring): cols = [cols] if not cols: cols = query.get_columns() + self.get_default_columns() data['col'] = cols now = datetime.now() # get the date range for the query if 'from_year' in req.args: from_date = get_date(req.args['from_year'], req.args.get('from_month'), req.args.get('from_day')) else: from_date = datetime(now.year, now.month, now.day) from_date = from_date - timedelta(days=7) # 1 week ago, by default if 'to_year' in req.args: to_date = get_date(req.args['to_year'], req.args.get('to_month'), req.args.get('to_day'), end_of_day=True) else: to_date = now data['prev_week'] = from_date - timedelta(days=7) data['months'] = list(enumerate(calendar.month_name)) data['years'] = range(now.year, now.year - 10, -1) data['days'] = range(1, 32) data['users'] = get_all_users(self.env) data['cur_worker_filter'] = req.args.get('worker_filter', '*any') data['from_date'] = from_date data['to_date'] = to_date ticket_ids = [t['id'] for t in tickets] # generate data for ticket_times time_records = self.get_ticket_hours( ticket_ids, from_date=from_date, to_date=to_date, worker_filter=data['cur_worker_filter']) data['query'] = ticket_data['query'] data['context'] = ticket_data['context'] data['row'] = ticket_data['row'] if 'comments' in req.args.get('row', []): data['row'].append('comments') data['constraints'] = ticket_data['clauses'] our_labels = dict([(f['name'], f['label']) for f in self.fields]) labels = TicketSystem(self.env).get_ticket_field_labels() labels.update(our_labels) data['labels'] = labels order = req.args.get('order') desc = bool(req.args.get('desc')) data['order'] = order data['desc'] = desc headers = [{ 'name': col, 'label': labels.get(col), 'href': self.get_href(query, req.args, context.href, order=col, desc=(col == order and not desc)) } for col in cols] data['headers'] = headers data['fields'] = ticket_data['fields'] data['modes'] = ticket_data['modes'] # group time records time_records_by_ticket = {} for record in time_records: id = record['ticket'] if id not in time_records_by_ticket: time_records_by_ticket[id] = [] time_records_by_ticket[id].append(record) data['extra_group_fields'] = dict(ticket=dict(name='ticket', type='select', label='Ticket'), worker=dict(name='worker', type='select', label='Worker')) num_items = 0 data['groups'] = [] # merge ticket data into ticket_time records for key, tickets in ticket_data['groups']: ticket_times = [] total_time = 0 total_estimated_time = 0 for ticket in tickets: records = time_records_by_ticket.get(ticket['id'], []) [rec.update(ticket) for rec in records] ticket_times += records # sort ticket_times, if needed if order in our_labels: ticket_times.sort(key=lambda x: x[order], reverse=desc) data['groups'].append((key, ticket_times)) num_items += len(ticket_times) data['double_count_warning'] = '' # group by ticket id or other time_ticket fields if necessary if req.args.get('group') in data['extra_group_fields']: query.group = req.args.get('group') if not query.group == "id": data[ 'double_count_warning'] = "Warning: estimated hours may be counted more than once if a ticket appears in multiple groups" tickets = data['groups'][0][1] groups = {} for time_rec in tickets: key = time_rec[query.group] if not key in groups: groups[key] = [] groups[key].append(time_rec) data['groups'] = sorted(groups.items()) total_times = dict( (k, self.format_hours(sum(rec['seconds_worked'] for rec in v))) for k, v in data['groups']) total_estimated_times = {} for key, records in data['groups']: seen_tickets = set() est = 0 for record in records: # do not double-count tickets id = record['ticket'] if id in seen_tickets: continue seen_tickets.add(id) estimatedhours = record.get('estimatedhours') or 0 try: estimatedhours = float(estimatedhours) except ValueError: estimatedhours = 0 est += estimatedhours * 3600 total_estimated_times[key] = self.format_hours(est) data['total_times'] = total_times data['total_estimated_times'] = total_estimated_times # format records for record in time_records: if 'seconds_worked' in record: record['seconds_worked'] = self.format_hours( record['seconds_worked']) # XXX misleading name if 'time_started' in record: record['time_started'] = self.format_date( record['time_started']) if 'time_submitted' in record: record['time_submitted'] = self.format_date( record['time_submitted']) data['query'].num_items = num_items data['labels'] = TicketSystem(self.env).get_ticket_field_labels() data['labels'].update(labels) data['can_add_hours'] = req.perm.has_permission('TICKET_ADD_HOURS') data['multiproject'] = self.env.is_component_enabled(MultiprojectHours) from web_ui import TracUserHours data['user_hours'] = self.env.is_component_enabled(TracUserHours) # return the rss, if requested if req.args.get('format') == 'rss': return self.queryhours2rss(req, data) # return the csv, if requested if req.args.get('format') == 'csv': self.queryhours2csv(req, data) # add rss link rss_href = req.href(req.path_info, format='rss') add_link(req, 'alternate', rss_href, _('RSS Feed'), 'application/rss+xml', 'rss') # add csv link add_link(req, 'alternate', req.href(req.path_info, format='csv', **req.args), 'CSV', 'text/csv', 'csv') # add navigation of weeks prev_args = dict(req.args) next_args = dict(req.args) prev_args['from_year'] = (from_date - timedelta(days=7)).year prev_args['from_month'] = (from_date - timedelta(days=7)).month prev_args['from_day'] = (from_date - timedelta(days=7)).day prev_args['to_year'] = from_date.year prev_args['to_month'] = from_date.month prev_args['to_day'] = from_date.day next_args['from_year'] = to_date.year next_args['from_month'] = to_date.month next_args['from_day'] = to_date.day next_args['to_year'] = (to_date + timedelta(days=7)).year next_args['to_month'] = (to_date + timedelta(days=7)).month next_args['to_day'] = (to_date + timedelta(days=7)).day add_link(req, 'prev', self.get_href(query, prev_args, context.href), _('Prev Week')) add_link(req, 'next', self.get_href(query, next_args, context.href), _('Next Week')) prevnext_nav(req, _('Prev Week'), _('Next Week')) add_ctxtnav(req, 'Cross-Project Hours', req.href.hours('multiproject')) add_ctxtnav( req, 'Hours by User', req.href.hours('user', from_day=from_date.day, from_month=from_date.month, from_year=from_date.year, to_day=to_date.year, to_month=to_date.month, to_year=to_date.year)) add_ctxtnav(req, 'Saved Queries', req.href.hours('query/list')) add_stylesheet(req, 'common/css/report.css') add_script(req, 'common/js/query.js') return ('hours_timeline.html', data, 'text/html')
def expand_macro(self, formatter, name, content): env = self.env req = formatter.req args, kw = parse_args(content) # Use macro arguments (most likely wiki macro calls). realms = 'realm' in kw and kw['realm'].split('|') or [] tag_system = TagSystem(env) all_realms = [p.get_taggable_realm() for p in tag_system.tag_providers] self.all_realms = all_realms self.realms = realms if name == 'TagCloud': args.append(' or '.join(['realm:%s' % r for r in realms])) all_tags = tag_system.get_all_tags(req, ' '.join(args)) mincount = 'mincount' in kw and kw['mincount'] or None return self.render_cloud(req, all_tags, caseless_sort=self.caseless_sort, mincount=mincount) elif name == 'ListTagged': if _OBSOLETE_ARGS_RE.search(content): data = {'warning': 'obsolete_args'} else: data = {'warning': None} context=formatter.context # Use TagsQuery arguments (most likely wiki macro calls). cols = 'cols' in kw and kw['cols'] or self.default_cols format = 'format' in kw and kw['format'] or self.default_format query = args and args[0].strip() or None if query and not realms: # First read query arguments (most likely a web-UI call). for realm in all_realms: if re.search('(^|\W)realm:%s(\W|$)' % (realm), query): realms = realms and realms.append(realm) or [realm] if not realms: # Apply ListTagged defaults to macro call w/o realm. realms = list(set(all_realms)-set(self.exclude_realms)) if not realms: return '' else: self.query = query self.realms = realms query = '(%s) (%s)' % (query or '', ' or '.join(['realm:%s' % (r) for r in realms])) env.log.debug('LISTTAGGED_QUERY: ' + query) query_result = tag_system.query(req, query) if not query_result: return '' def _link(resource): if resource.realm == 'tag': # Keep realm selection in tag links. return builder.a(resource.id, href=self.get_href(req, tag=resource)) elif resource.realm == 'ticket': # Return resource link including ticket status dependend # class to allow for common Trac ticket link style. ticket = Ticket(env, resource.id) return builder.a('#%s' % ticket.id, class_=ticket['status'], href=formatter.href.ticket(ticket.id), title=shorten_line(ticket['summary'])) return render_resource_link(env, context, resource, 'compact') if format == 'table': cols = [col for col in cols.split('|') if col in self.supported_cols] # Use available translations from Trac core. try: labels = TicketSystem(env).get_ticket_field_labels() labels['id'] = _('Id') except AttributeError: # Trac 0.11 neither has the attribute nor uses i18n. labels = {'id': 'Id', 'description': 'Description'} labels['realm'] = _('Realm') labels['tags'] = _('Tags') headers = [{'label': labels.get(col)} for col in cols] data.update({'cols': cols, 'headers': headers}) results = sorted(query_result, key=lambda r: \ embedded_numbers(to_unicode(r[0].id))) results = self._paginate(req, results) rows = [] for resource, tags in results: desc = tag_system.describe_tagged_resource(req, resource) tags = sorted(tags) if tags: rendered_tags = [_link(Resource('tag', tag)) for tag in tags] if 'oldlist' == format: resource_link = _link(resource) else: desc = desc or \ get_resource_description(env, resource, context=context) resource_link = builder.a(desc, href=get_resource_url( env, resource, context.href)) if 'table' == format: cells = [] for col in cols: if col == 'id': cells.append(_link(resource)) # Don't duplicate links to resource in both. elif col == 'description' and 'id' in cols: cells.append(desc) elif col == 'description': cells.append(resource_link) elif col == 'realm': cells.append(resource.realm) elif col == 'tags': cells.append( builder([(tag, ' ') for tag in rendered_tags])) rows.append({'cells': cells}) continue rows.append({'desc': desc, 'rendered_tags': None, 'resource_link': _link(resource)}) data.update({'format': format, 'paginator': results, 'results': rows, 'tags_url': req.href('tags')}) # Work around a bug in trac/templates/layout.html, that causes a # TypeError for the wiki macro call, if we use add_link() alone. add_stylesheet(req, 'common/css/search.css') return Chrome(env).render_template( req, 'listtagged_results.html', data, 'text/html', True)
def expand_macro(self, formatter, name, content, realms=[]): """Evaluate macro call and render results. Calls from web-UI come with pre-processed realm selection. """ env = self.env req = formatter.req tag_system = TagSystem(env) all_realms = tag_system.get_taggable_realms() if not all_realms: # Tag providers are required, no result without at least one. return '' args, kw = parse_args(content) query = args and args[0].strip() or None if not realms: # Check macro arguments for realms (typical wiki macro call). realms = 'realm' in kw and kw['realm'].split('|') or [] if query: # Add realms from query expression. realms.extend(query_realms(query, all_realms)) # Remove redundant realm selection for performance. if set(realms) == all_realms: query = re.sub('(^|\W)realm:\S+(\W|$)', ' ', query).strip() if name == 'TagCloud': # Set implicit 'all tagged realms' as default. if not realms: realms = all_realms if query: all_tags = Counter() # Require per resource query including view permission checks. for resource, tags in tag_system.query(req, query): all_tags.update(tags) else: # Allow faster per tag query, side steps permission checks. all_tags = tag_system.get_all_tags(req, realms=realms) mincount = 'mincount' in kw and kw['mincount'] or None return self.render_cloud(req, all_tags, caseless_sort=self.caseless_sort, mincount=mincount, realms=realms) elif name == 'ListTagged': if content and _OBSOLETE_ARGS_RE.search(content): data = {'warning': 'obsolete_args'} else: data = {'warning': None} context = formatter.context # Use TagsQuery arguments (most likely wiki macro calls). cols = 'cols' in kw and kw['cols'] or self.default_cols format = 'format' in kw and kw['format'] or self.default_format if not realms: # Apply ListTagged defaults to macro call w/o realm. realms = list(set(all_realms) - set(self.exclude_realms)) if not realms: return '' query = '(%s) (%s)' % (query or '', ' or '.join( ['realm:%s' % (r) for r in realms])) query_result = tag_system.query(req, query) excludes = [ exc.strip() for exc in kw.get('exclude', '').split(':') if exc.strip() ] if excludes and query_result: filtered_result = [(resource, tags) for resource, tags in query_result if not any( fnmatchcase(resource.id, exc) for exc in excludes)] query_result = filtered_result if not query_result: return '' def _link(resource): if resource.realm == 'tag': # Keep realm selection in tag links. return builder.a(resource.id, href=self.get_href(req, realms, tag=resource)) elif resource.realm == 'ticket': # Return resource link including ticket status dependend # class to allow for common Trac ticket link style. ticket = Ticket(env, resource.id) return builder.a('#%s' % ticket.id, class_=ticket['status'], href=formatter.href.ticket(ticket.id), title=shorten_line(ticket['summary'])) return render_resource_link(env, context, resource, 'compact') if format == 'table': cols = [ col for col in cols.split('|') if col in self.supported_cols ] # Use available translations from Trac core. try: labels = TicketSystem(env).get_ticket_field_labels() labels['id'] = _('Id') except AttributeError: # Trac 0.11 neither has the attribute nor uses i18n. labels = {'id': 'Id', 'description': 'Description'} labels['realm'] = _('Realm') labels['tags'] = _('Tags') headers = [{'label': labels.get(col)} for col in cols] data.update({'cols': cols, 'headers': headers}) try: results = sorted( query_result, key=lambda r: embedded_numbers(to_unicode(r[0].id))) except (InvalidQuery, InvalidTagRealm), e: return system_message(_("ListTagged macro error"), e) results = self._paginate(req, results, realms) rows = [] for resource, tags in results: desc = tag_system.describe_tagged_resource(req, resource) tags = sorted(tags) wiki_desc = format_to_oneliner(env, context, desc) if tags: rendered_tags = [ _link(Resource('tag', tag)) for tag in tags ] if 'oldlist' == format: resource_link = _link(resource) else: resource_link = builder.a(wiki_desc, href=get_resource_url( env, resource, context.href)) if 'table' == format: cells = [] for col in cols: if col == 'id': cells.append(_link(resource)) # Don't duplicate links to resource in both. elif col == 'description' and 'id' in cols: cells.append(wiki_desc) elif col == 'description': cells.append(resource_link) elif col == 'realm': cells.append(resource.realm) elif col == 'tags': cells.append( builder([(tag, ' ') for tag in rendered_tags])) rows.append({'cells': cells}) continue rows.append({ 'desc': wiki_desc, 'rendered_tags': None, 'resource_link': _link(resource) }) data.update({ 'format': format, 'paginator': results, 'results': rows, 'tags_url': req.href('tags') }) # Work around a bug in trac/templates/layout.html, that causes a # TypeError for the wiki macro call, if we use add_link() alone. add_stylesheet(req, 'common/css/search.css') return Chrome(env).render_template(req, 'listtagged_results.html', data, 'text/html', True)
def display_html(self, req, query): """returns the HTML according to a query for /hours view""" db = self.env.get_db_cnx() # The most recent query is stored in the user session; orig_list = None orig_time = datetime.now(utc) query_time = int(req.session.get("query_time", 0)) query_time = datetime.fromtimestamp(query_time, utc) query_constraints = unicode(query.constraints) if query_constraints != req.session.get("query_constraints") or query_time < orig_time - timedelta(hours=1): tickets = query.execute(req, db) # New or outdated query, (re-)initialize session vars req.session["query_constraints"] = query_constraints req.session["query_tickets"] = " ".join([str(t["id"]) for t in tickets]) else: orig_list = [int(id) for id in req.session.get("query_tickets", "").split()] tickets = query.execute(req, db, orig_list) orig_time = query_time context = Context.from_request(req, "query") ticket_data = query.template_data(context, tickets, orig_list, orig_time, req) # For clients without JavaScript, we add a new constraint here if # requested constraints = ticket_data["clauses"][0] if "add" in req.args: field = req.args.get("add_filter") if field: constraint = constraints.setdefault(field, {}) constraint.setdefault("values", []).append("") # FIXME: '' not always correct (e.g. checkboxes) req.session["query_href"] = query.get_href(context.href) req.session["query_time"] = to_timestamp(orig_time) req.session["query_tickets"] = " ".join([str(t["id"]) for t in tickets]) # data dictionary for genshi data = {} # get data for saved queries query_id = req.args.get("query_id") if query_id: try: query_id = int(query_id) except ValueError: add_warning(req, "query_id should be an integer, you put '%s'" % query_id) query_id = None if query_id: data["query_id"] = query_id query_data = self.get_query(query_id) data["query_title"] = query_data["title"] data["query_description"] = query_data["description"] data.setdefault("report", None) data.setdefault("description", None) data["all_columns"] = query.get_all_columns() + self.get_columns() # Don't allow the user to remove the id column data["all_columns"].remove("id") data["all_textareas"] = query.get_all_textareas() # need to re-get the cols because query will remove our fields cols = req.args.get("col") if isinstance(cols, basestring): cols = [cols] if not cols: cols = query.get_columns() + self.get_default_columns() data["col"] = cols now = datetime.now() # get the date range for the query if "from_year" in req.args: from_date = get_date(req.args["from_year"], req.args.get("from_month"), req.args.get("from_day")) else: from_date = datetime(now.year, now.month, now.day) from_date = from_date - timedelta(days=7) # 1 week ago, by default if "to_year" in req.args: to_date = get_date(req.args["to_year"], req.args.get("to_month"), req.args.get("to_day"), end_of_day=True) else: to_date = now data["prev_week"] = from_date - timedelta(days=7) data["months"] = list(enumerate(calendar.month_name)) data["years"] = range(now.year, now.year - 10, -1) data["days"] = range(1, 32) data["users"] = get_all_users(self.env) data["cur_worker_filter"] = req.args.get("worker_filter", "*any") data["from_date"] = from_date data["to_date"] = to_date ticket_ids = [t["id"] for t in tickets] # generate data for ticket_times time_records = self.get_ticket_hours( ticket_ids, from_date=from_date, to_date=to_date, worker_filter=data["cur_worker_filter"] ) data["query"] = ticket_data["query"] data["context"] = ticket_data["context"] data["row"] = ticket_data["row"] if "comments" in req.args.get("row", []): data["row"].append("comments") data["constraints"] = ticket_data["clauses"] our_labels = dict([(f["name"], f["label"]) for f in self.fields]) labels = TicketSystem(self.env).get_ticket_field_labels() labels.update(our_labels) data["labels"] = labels order = req.args.get("order") desc = bool(req.args.get("desc")) data["order"] = order data["desc"] = desc headers = [ { "name": col, "label": labels.get(col), "href": self.get_href(query, req.args, context.href, order=col, desc=(col == order and not desc)), } for col in cols ] data["headers"] = headers data["fields"] = ticket_data["fields"] data["modes"] = ticket_data["modes"] # group time records time_records_by_ticket = {} for record in time_records: id = record["ticket"] if id not in time_records_by_ticket: time_records_by_ticket[id] = [] time_records_by_ticket[id].append(record) data["extra_group_fields"] = dict( ticket=dict(name="ticket", type="select", label="Ticket"), worker=dict(name="worker", type="select", label="Worker"), ) num_items = 0 data["groups"] = [] # merge ticket data into ticket_time records for key, tickets in ticket_data["groups"]: ticket_times = [] total_time = 0 total_estimated_time = 0 for ticket in tickets: records = time_records_by_ticket.get(ticket["id"], []) [rec.update(ticket) for rec in records] ticket_times += records # sort ticket_times, if needed if order in our_labels: ticket_times.sort(key=lambda x: x[order], reverse=desc) data["groups"].append((key, ticket_times)) num_items += len(ticket_times) data["double_count_warning"] = "" # group by ticket id or other time_ticket fields if necessary if req.args.get("group") in data["extra_group_fields"]: query.group = req.args.get("group") if not query.group == "id": data[ "double_count_warning" ] = "Warning: estimated hours may be counted more than once if a ticket appears in multiple groups" tickets = data["groups"][0][1] groups = {} for time_rec in tickets: key = time_rec[query.group] if not key in groups: groups[key] = [] groups[key].append(time_rec) data["groups"] = sorted(groups.items()) total_times = dict((k, self.format_hours(sum(rec["seconds_worked"] for rec in v))) for k, v in data["groups"]) total_estimated_times = {} for key, records in data["groups"]: seen_tickets = set() est = 0 for record in records: # do not double-count tickets id = record["ticket"] if id in seen_tickets: continue seen_tickets.add(id) estimatedhours = record.get("estimatedhours") or 0 try: estimatedhours = float(estimatedhours) except ValueError: estimatedhours = 0 est += estimatedhours * 3600 total_estimated_times[key] = self.format_hours(est) data["total_times"] = total_times data["total_estimated_times"] = total_estimated_times # format records for record in time_records: if "seconds_worked" in record: record["seconds_worked"] = self.format_hours(record["seconds_worked"]) # XXX misleading name if "time_started" in record: record["time_started"] = self.format_date(record["time_started"]) if "time_submitted" in record: record["time_submitted"] = self.format_date(record["time_submitted"]) data["query"].num_items = num_items data["labels"] = TicketSystem(self.env).get_ticket_field_labels() data["labels"].update(labels) data["can_add_hours"] = req.perm.has_permission("TICKET_ADD_HOURS") data["multiproject"] = self.env.is_component_enabled(MultiprojectHours) from web_ui import TracUserHours data["user_hours"] = self.env.is_component_enabled(TracUserHours) # return the rss, if requested if req.args.get("format") == "rss": return self.queryhours2rss(req, data) # return the csv, if requested if req.args.get("format") == "csv": self.queryhours2csv(req, data) # add rss link rss_href = req.href(req.path_info, format="rss") add_link(req, "alternate", rss_href, _("RSS Feed"), "application/rss+xml", "rss") # add csv link add_link(req, "alternate", req.href(req.path_info, format="csv", **req.args), "CSV", "text/csv", "csv") # add navigation of weeks prev_args = dict(req.args) next_args = dict(req.args) prev_args["from_year"] = (from_date - timedelta(days=7)).year prev_args["from_month"] = (from_date - timedelta(days=7)).month prev_args["from_day"] = (from_date - timedelta(days=7)).day prev_args["to_year"] = from_date.year prev_args["to_month"] = from_date.month prev_args["to_day"] = from_date.day next_args["from_year"] = to_date.year next_args["from_month"] = to_date.month next_args["from_day"] = to_date.day next_args["to_year"] = (to_date + timedelta(days=7)).year next_args["to_month"] = (to_date + timedelta(days=7)).month next_args["to_day"] = (to_date + timedelta(days=7)).day add_link(req, "prev", self.get_href(query, prev_args, context.href), _("Prev Week")) add_link(req, "next", self.get_href(query, next_args, context.href), _("Next Week")) prevnext_nav(req, _("Prev Week"), _("Next Week")) add_ctxtnav(req, "Cross-Project Hours", req.href.hours("multiproject")) add_ctxtnav( req, "Hours by User", req.href.hours( "user", from_day=from_date.day, from_month=from_date.month, from_year=from_date.year, to_day=to_date.year, to_month=to_date.month, to_year=to_date.year, ), ) add_ctxtnav(req, "Saved Queries", req.href.hours("query/list")) add_stylesheet(req, "common/css/report.css") add_script(req, "common/js/query.js") return ("hours_timeline.html", data, "text/html")