Ejemplo n.º 1
0
    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)
Ejemplo n.º 2
0
    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)
Ejemplo n.º 3
0
    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')
Ejemplo n.º 4
0
    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)
Ejemplo n.º 5
0
    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)
Ejemplo n.º 6
0
    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")