Ejemplo n.º 1
0
    def _render_widget(self, wp, name, ctx, options):
        """Render widget without failing.
        """
        if wp is None:
            data = {'msglabel': _('Warning'),
                    'msgbody': _('Unknown widget %(name)s', name=name)}
            return 'widget_alert.html', {'title': '', 'data': data}, ctx

        try:
            return wp.render_widget(name, ctx, options)
        except Exception, exc:
            log_entry = str(uuid4())
            exccls = exc.__class__
            self.log.exception(
                "- %s - Error rendering widget %s with options %s",
                log_entry, name, options)
            data = {
                'msgtype': 'error',
                'msglabel': 'Error',
                'msgbody': _('Exception raised while rendering widget. '
                             'Contact your administrator for further details.'),
                'msgdetails': [
                    ('Widget name', name),
                    ('Exception type', tag.code(exccls.__name__)),
                    ('Log entry ID', log_entry),
                ],
            }
            return 'widget_alert.html', {
                'title': _('Widget error'),
                'data': data
            }, ctx
Ejemplo n.º 2
0
 def check_field_name():
     if fieldnm is None:
         raise InvalidWidgetArgument('field', 'Missing ticket field')
     tsys = self.env[TicketSystem]
     if tsys is None:
         raise TracError(_('Error loading ticket system (disabled?)'))
     for field in tsys.get_ticket_fields():
         if field['name'] == fieldnm:
             _field.append(field)
             break
     else:
         if fieldnm in field_maps:
             admin_suffix = field_maps.get(fieldnm)['admin_url']
             if 'TICKET_ADMIN' in req.perm and admin_suffix is not None:
                 hint = _('You can add one or more '
                          '<a href="%(url)s">here</a>.',
                         url=req.href.admin('ticket', admin_suffix))
             else:
                 hint = _('Contact your administrator for further details')
             return 'widget_alert.html', \
                     {
                         'title' : Markup(field_maps[fieldnm]['title']),
                         'data' : dict(msgtype='info',
                             msglabel="Note",
                             msgbody=Markup(_('''No values are
                                 defined for ticket field
                                 <em>%(field)s</em>. %(hint)s''',
                                 field=fieldnm, hint=hint))
                             )
                     }, context
         else:
             raise InvalidWidgetArgument('field',
                     'Unknown ticket field %s' % (fieldnm,))
     return None
Ejemplo n.º 3
0
 def _do_wiki_upgrade(self):
     """Move all wiki pages starting with Trac prefix to unbranded user
     guide pages.
     """
     wiki_admin = WikiAdmin(self.env)
     pages = wiki_admin.get_wiki_list()
     for old_name in pages:
         if old_name.startswith('Trac'):
             new_name = wiki.new_name(old_name)
             if not new_name:
                 continue
             if new_name in pages:
                 printout(
                     _(
                         'Ignoring %(page)s : '
                         'The page %(new_page)s already exists',
                         page=old_name,
                         new_page=new_name))
                 continue
             try:
                 wiki_admin._do_rename(old_name, new_name)
             except AdminCommandError, exc:
                 printout(
                     _('Error moving %(page)s : %(message)s',
                       page=old_name,
                       message=unicode(exc)))
             else:
                 # On success, rename links in other pages
                 self._do_wiki_rename_links(old_name, new_name)
                 # On success, insert redirection page
                 redirection = WikiPage(self.env, old_name)
                 redirection.text = _('See [wiki:"%(name)s"].',
                                      name=new_name)
                 comment = 'Bloodhound guide update'
                 redirection.save('bloodhound', comment, '0.0.0.0')
Ejemplo n.º 4
0
 def __call__(self, value):
     """Perform the actual conversion
     """
     if value not in self.choices:
         raise InvalidWidgetArgument('',
             _('Expected one of `%s` but got `%s`') % (self.choices, value),
             title=_('Enum conversion error'))
     return value
Ejemplo n.º 5
0
 def __call__(self, value):
     """Perform the actual conversion
     """
     if value not in self.choices:
         raise InvalidWidgetArgument(
             '',
             _('Expected one of `%s` but got `%s`') % (self.choices, value),
             title=_('Enum conversion error'))
     return value
Ejemplo n.º 6
0
 def render_widget(self, name, context, options):
     """Render widget considering given options.
     """
     if name == 'WidgetDoc':
         add_stylesheet(context.req, 'dashboard/css/docs.css')
         widget_name, = self.bind_params(options,
                                         self.get_widget_params(name),
                                         'urn')
         if widget_name is not None:
             try:
                 providers = [([widget_name],
                               self.resolve_widget(widget_name))]
             except LookupError:
                 return 'widget_alert.html', {
                     'title': _('Widget documentation'),
                     'data': {
                         'msglabel': 'Alert',
                         'msgbody': 'Unknown identifier',
                         'msgdetails': [('Widget name', widget_name)]
                     }
                 }, context
         else:
             providers = [(provider.get_widgets(), provider) \
                     for provider in self.widget_providers]
         metadata = [self._prepare_doc_metadata(self.widget_metadata(wnm, p)) \
                 for widgets, p in providers for wnm in widgets]
         docs_resource = Resource('wiki', 'BloodhoundWidgets')
         insert_docs = resource_exists(self.env, docs_resource) and \
                 not (context.resource and \
                 docs_resource == context.resource)
         return 'widget_doc.html', {
             'title':
             _('Widget documentation'),
             'data': {
                 'items': metadata
             },
             'ctxtnav': [
                 tag.a(tag.i(class_='icon-info-sign'),
                       ' ',
                       _('Help'),
                       href=get_resource_url(self.env, docs_resource,
                                             context.href))
             ] if insert_docs else [],
         }, context
     else:
         raise InvalidIdentifier('Widget name MUST match any of ' +
                                 ', '.join(self.get_widgets()),
                                 title='Invalid widget identifier')
Ejemplo n.º 7
0
    def expand_macro(self, formatter, name, args):
        curpage = formatter.resource.id

        # scoped TOC (e.g. TranslateRu/Guide or 0.X/Guide ...)
        prefix = ''
        guideprefix = GUIDE_NAME + '/'
        data = {
            'guide': GUIDE_NAME,
        }
        idx = curpage.find('/')
        if idx > 0:
            prefix = curpage[:idx + 1]
        if prefix.endswith(guideprefix):
            prefix = prefix[:len(prefix) - len(guideprefix)]
        ws = WikiSystem(self.env)
        return tag.div(
            tag.h4(_('Table of Contents')),
            tag.ul([
                tag.li(tag.a(title,
                             href=formatter.href.wiki(prefix + ref % data),
                             class_=(not ws.has_page(prefix + ref % data)
                                     and 'missing')),
                       class_=(prefix + ref % data == curpage and 'active'))
                for ref, title in self.TOC
            ]),
            class_='wiki-toc')
Ejemplo n.º 8
0
 def render_widget(self, name, context, options):
     """Render widget considering given options.
     """
     if name == 'WidgetDoc':
         add_stylesheet(context.req, 'dashboard/css/docs.css')
         widget_name, = self.bind_params(options,
                 self.get_widget_params(name), 'urn')
         if widget_name is not None:
             try:
                 providers = [([widget_name],
                         self.resolve_widget(widget_name))]
             except LookupError:
                 return 'widget_alert.html', {
                         'title': _('Widget documentation'),
                         'data': {
                                 'msglabel': 'Alert',
                                 'msgbody': 'Unknown identifier',
                                 'msgdetails': [
                                         ('Widget name', widget_name)
                                     ]
                               }
                     }, context
         else:
             providers = [(provider.get_widgets(), provider) \
                     for provider in self.widget_providers]
         metadata = [self._prepare_doc_metadata(self.widget_metadata(wnm, p)) \
                 for widgets, p in providers for wnm in widgets]
         docs_resource = Resource('wiki', 'BloodhoundWidgets')
         insert_docs = resource_exists(self.env, docs_resource) and \
                 not (context.resource and \
                 docs_resource == context.resource)
         return 'widget_doc.html', {
                     'title': _('Widget documentation'),
                     'data': {
                             'items': metadata
                         },
                     'ctxtnav': [tag.a(tag.i(class_='icon-info-sign'),
                                 ' ', _('Help'),
                                 href=get_resource_url(
                                         self.env, docs_resource,
                                         context.href)
                             )] if insert_docs else [],
                 }, context
     else:
         raise InvalidIdentifier('Widget name MUST match any of ' +
                     ', '.join(self.get_widgets()),
                 title='Invalid widget identifier')
Ejemplo n.º 9
0
 def _load_fixture_from_file(self, fname):
     """Calls _load_fixture with an open file"""
     try:
         fp = open(fname, mode='r')
         self._load_fixture(fp)
         fp.close()
     except IOError:
         printout(_("The file '%(fname)s' does not exist", fname=fname))
Ejemplo n.º 10
0
    def _get_product_info(self, product, href, resource, max_):
        penv = ProductEnvironment(self.env, product.prefix)
        results = []

        # some queries return a list/tuple, some a generator,
        # hence count() to get the result length
        def count(iter_):
            try:
                return len(iter_)
            except TypeError:
                return sum(1 for _ in iter_)

        query = resource['type'].select(penv)
        for q in itertools.islice(query, max_):
            q.url = href(resource['name'], q.name) \
                if resource.get('hrefurl') \
                else Query.from_string(penv,
                    '%s=%s&%s&col=%s' % (resource['name'], q.name,
                                         self.COMMON_QUERY, resource['name'])
            ).get_href(href)
            q.ticket_count = penv.db_query("""
                SELECT COUNT(*) FROM ticket WHERE ticket.%s='%s'
                AND ticket.status <> 'closed'
                """ % (resource['name'], q.name))[0][0]

            results.append(q)

        # add a '(No <milestone/component/version>)' entry if there are
        # tickets without an assigned resource in the product
        ticket_count = penv.db_query(
            """SELECT COUNT(*) FROM ticket WHERE %s=''
               AND status <> 'closed'""" % (resource['name'],))[0][0]
        if ticket_count != 0:
            q = resource['type'](penv)
            q.name = '(No %s)' % (resource['name'],)
            q.url = Query.from_string(penv,
               'status=!closed&col=id&col=summary&col=owner'
               '&col=status&col=priority&order=priority&%s='
               % (resource['name'],)
            ).get_href(href)
            q.ticket_count = ticket_count
            results.append(q)

        results.sort(key=lambda x: x.ticket_count, reverse=True)

        # add a link to the resource list if there are
        # more than max resources defined
        if count(query) > max_:
            q = resource['type'](penv)
            q.name = _('... more')
            q.ticket_count = None
            q.url = href(resource['name']) if resource.get('hrefurl') \
                else href.dashboard()
            results.append(q)

        return results
Ejemplo n.º 11
0
 def get_timeline_filters(self, req):
     gen = self.provider.get_timeline_filters(req)
     if self.context.resource.realm == 'ticket' and \
             isinstance(self.provider, TicketModule) and \
             'TICKET_VIEW' in req.perm:
         # ensure ticket_details appears once if this is a query on a ticket
         gen = list(gen)
         if not [g for g in gen if g[0] == 'ticket_details']:
             gen.append(('ticket_details', _("Ticket updates"), False))
     return gen
Ejemplo n.º 12
0
 def post_process_request(self, req, template, data, content_type):
     """Inject dashboard helpers in data.
     """
     if data is not None:
         data['bhdb'] = DashboardChrome(self.env)
         if isinstance(req.perm.env, ProductEnvironment) \
                 and not 'resourcepath_template' in data \
                 and 'product_list' in data:
             data['resourcepath_template'] = 'bh_path_general.html'
     for item in req.chrome['nav'].get('mainnav', []):
         self.log.debug('%s' % (item, ))
         if item['name'] == 'tickets':
             item['label'] = tag.a(_(self.mainnav_label),
                                   href=req.href.dashboard())
             if item['active'] and \
                     not ReportModule(self.env).match_request(req):
                 add_ctxtnav(req, _('Reports'), href=req.href.report())
             break
     return template, data, content_type
Ejemplo n.º 13
0
 def get_timeline_filters(self, req):
     gen = self.provider.get_timeline_filters(req)
     if self.context.resource.realm == 'ticket' and \
             isinstance(self.provider, TicketModule) and \
             'TICKET_VIEW' in req.perm:
         # ensure ticket_details appears once if this is a query on a ticket
         gen = list(gen)
         if not [g for g in gen if g[0] == 'ticket_details']:
             gen.append(('ticket_details', _("Ticket updates"), False))
     return gen
Ejemplo n.º 14
0
 def __call__(self, value):
     """Perform the actual conversion
     """
     if isinstance(value, basestring):
         return value.split(self.sep)
     else:
         try:
             return list(value)
         except Exception, exc:
             raise InvalidWidgetArgument(error=exc,
                                         title=_('List conversion error'))
Ejemplo n.º 15
0
 def post_process_request(self, req, template, data, content_type):
     """Inject dashboard helpers in data.
     """
     if data is not None:
         data['bhdb'] = DashboardChrome(self.env)
         if isinstance(req.perm.env, ProductEnvironment) \
                 and not 'resourcepath_template' in data \
                 and 'product_list' in data:
             data['resourcepath_template'] = 'bh_path_general.html'
     for item in req.chrome['nav'].get('mainnav', []):
         self.log.debug('%s' % (item,))
         if item['name'] == 'tickets':
             item['label'] = tag.a(_(self.mainnav_label),
                                   href=req.href.dashboard())
             if item['active'] and \
                     not ReportModule(self.env).match_request(req):
                 add_ctxtnav(req, _('Reports'),
                             href=req.href.report())
             break
     return template, data, content_type
Ejemplo n.º 16
0
 def __call__(self, value):
     """Perform the actual conversion
     """
     if isinstance(value, basestring):
         return value.split(self.sep)
     else:
         try:
             return list(value)
         except Exception, exc:
             raise InvalidWidgetArgument(error=exc,
                     title=_('List conversion error'))
Ejemplo n.º 17
0
 def process_request(self, req):
     req.perm.require('PRODUCT_VIEW')
     # Initially this will render static widgets. With time it will be
     # more and more dynamic and flexible.
     if self.env[QueryModule] is not None:
         add_ctxtnav(req, _('Custom Query'), req.href.query())
     if self.env[ReportModule] is not None:
         add_ctxtnav(req, _('Reports'), req.href.report())
     context = Context.from_request(req)
     template, layout_data = self.expand_layout_data(context,
         'bootstrap_grid',
         self.DASHBOARD_SCHEMA if isinstance(self.env, ProductEnvironment)
         else self.DASHBOARD_GLOBAL_SCHEMA
     )
     widgets = self.expand_widget_data(context, layout_data)
     return template, {
         'context': Context.from_request(req),
         'layout': layout_data,
         'widgets': widgets,
         'title': _(self.mainnav_label),
         'default': {'height': self.default_widget_height or None}
     }, None
Ejemplo n.º 18
0
    def render_widget(self, name, context, options):
        """Gather product list and render data in compact view
        """
        data = {}
        req = context.req
        title = ''
        params = ('max', 'cols')
        max_, cols = self.bind_params(name, options, *params)

        if not isinstance(self.env, ProductEnvironment):
            for p in Product.select(self.env):
                if 'PRODUCT_VIEW' in req.perm(Neighborhood('product', p.prefix)):
                    penv = ProductEnvironment(self.env, p.prefix)
                    phref = ProductEnvironment.resolve_href(penv, self.env)
                    for resource in (
                        {'type': Milestone, 'name': 'milestone', 'hrefurl': True},
                        {'type': Component, 'name': 'component'},
                        {'type': Version, 'name': 'version'},
                    ):
                        setattr(p, resource['name'] + 's',
                                self._get_product_info(p, phref, resource, max_))
                    p.owner_link = Query.from_string(self.env,
                        'status!=closed&col=id&col=summary&col=owner'
                        '&col=status&col=priority&order=priority'
                        '&group=product&owner=%s' % (p._data['owner'] or '', )
                    ).get_href(phref)
                    p.href = phref()
                    data.setdefault('product_list', []).append(p)
            title = _('Products')

        data['colseq'] = itertools.cycle(xrange(cols - 1, -1, -1)) if cols \
            else itertools.repeat(1)

        return 'widget_product.html', {
            'title': title,
            'data': data,
            'ctxtnav': [tag.a(_('More'), href=req.href('products'))],
        }, context
Ejemplo n.º 19
0
 def process_request(self, req):
     req.perm.require('PRODUCT_VIEW')
     # Initially this will render static widgets. With time it will be
     # more and more dynamic and flexible.
     if self.env[QueryModule] is not None:
         add_ctxtnav(req, _('Custom Query'), req.href.query())
     if self.env[ReportModule] is not None:
         add_ctxtnav(req, _('Reports'), req.href.report())
     context = Context.from_request(req)
     template, layout_data = self.expand_layout_data(
         context, 'bootstrap_grid',
         self.DASHBOARD_SCHEMA if isinstance(self.env, ProductEnvironment)
         else self.DASHBOARD_GLOBAL_SCHEMA)
     widgets = self.expand_widget_data(context, layout_data)
     return template, {
         'context': Context.from_request(req),
         'layout': layout_data,
         'widgets': widgets,
         'title': _(self.mainnav_label),
         'default': {
             'height': self.default_widget_height or None
         }
     }, None
Ejemplo n.º 20
0
 def __call__(self, value, fmt=None):
     """Perform the actual conversion
     """
     if isinstance(value, (date, time, datetime, timedelta)):
         return value
     elif isinstance(value, basestring):
         try:
             return parse_date(value, self.tz)
         except TracError, exc:
             try:
                 fmt = fmt or self.fmt
                 return datetime.strptime(value, fmt)
             except:
                 raise InvalidWidgetArgument(
                         error=exc, title=_('Datetime conversion error'))
Ejemplo n.º 21
0
 def __call__(self, value, fmt=None):
     """Perform the actual conversion
     """
     if isinstance(value, (date, time, datetime, timedelta)):
         return value
     elif isinstance(value, basestring):
         try:
             return parse_date(value, self.tz)
         except TracError, exc:
             try:
                 fmt = fmt or self.fmt
                 return datetime.strptime(value, fmt)
             except:
                 raise InvalidWidgetArgument(
                     error=exc, title=_('Datetime conversion error'))
Ejemplo n.º 22
0
    def _render_widget(self, wp, name, ctx, options):
        """Render widget without failing.
        """
        if wp is None:
            data = {
                'msglabel': _('Warning'),
                'msgbody': _('Unknown widget %(name)s', name=name)
            }
            return 'widget_alert.html', {'title': '', 'data': data}, ctx

        try:
            return wp.render_widget(name, ctx, options)
        except Exception, exc:
            log_entry = str(uuid4())
            exccls = exc.__class__
            self.log.exception(
                "- %s - Error rendering widget %s with options %s", log_entry,
                name, options)
            data = {
                'msgtype':
                'error',
                'msglabel':
                'Error',
                'msgbody':
                _('Exception raised while rendering widget. '
                  'Contact your administrator for further details.'),
                'msgdetails': [
                    ('Widget name', name),
                    ('Exception type', tag.code(exccls.__name__)),
                    ('Log entry ID', log_entry),
                ],
            }
            return 'widget_alert.html', {
                'title': _('Widget error'),
                'data': data
            }, ctx
Ejemplo n.º 23
0
    def expand_macro(self, formatter, name, args):
        curpage = formatter.resource.id

        # scoped TOC (e.g. TranslateRu/Guide or 0.X/Guide ...)
        prefix = ''
        guideprefix =  GUIDE_NAME + '/'
        data = {'guide': GUIDE_NAME,}
        idx = curpage.find('/')
        if idx > 0:
            prefix = curpage[:idx+1]
        if prefix.endswith(guideprefix):
            prefix = prefix[:len(prefix)-len(guideprefix)]
        ws = WikiSystem(self.env)
        return tag.div(
            tag.h4(_('Table of Contents')),
            tag.ul([tag.li(tag.a(title,
                                 href=formatter.href.wiki(prefix+ref % data),
                                 class_=(not ws.has_page(prefix+ref % data) and
                                         'missing')),
                           class_=(prefix+ref % data== curpage and 'active'))
                    for ref, title in self.TOC]),
            class_='wiki-toc')
Ejemplo n.º 24
0
    def _prepare_doc_metadata(self, spec):
        """Transform widget metadata into a format suitable to render
        documentation.
        """
        def plabel(p):
            v = p.get('type', str)
            module = getattr(v, '__module__', None)
            if module in (None, '__builtin__'):
                return getattr(v, '__name__', None) or v
            else:
                # FIXME: Improve e.g. for enum fields
                if not isclass(v):
                    v = v.__class__
                return tag.span(v.__name__, title='in ' + module)

        return {
                'title': spec['urn'],
                'desc': '\n'.join(l.strip()
                                   for l in spec['desc'].splitlines()),
                'sections': [
                        {
                            'title': _('Parameters'),
                            'entries': [
                                    {
                                        'caption': pnm,
                                        'summary': '\n'.join(
                                                l.strip() for l in \
                                                p.get('desc').splitlines()),
                                        'details': [
                                                ('Type', plabel(p)),
                                                ('Required', p.get('required',
                                                                   False)),
                                                ('Default', p.get('default')),
                                            ]
                                    }
                                for pnm, p in spec['params'].iteritems()]
                        }
                    ]
            }
Ejemplo n.º 25
0
    def _prepare_doc_metadata(self, spec):
        """Transform widget metadata into a format suitable to render
        documentation.
        """
        def plabel(p):
            v = p.get('type', str)
            module = getattr(v, '__module__', None)
            if module in (None, '__builtin__'):
                return getattr(v, '__name__', None) or v
            else:
                # FIXME: Improve e.g. for enum fields
                if not isclass(v):
                    v = v.__class__
                return tag.span(v.__name__, title='in ' + module)

        return {
                'title': spec['urn'],
                'desc': '\n'.join(l.strip()
                                   for l in spec['desc'].splitlines()),
                'sections': [
                        {
                            'title': _('Parameters'),
                            'entries': [
                                    {
                                        'caption': pnm,
                                        'summary': '\n'.join(
                                                l.strip() for l in \
                                                p.get('desc').splitlines()),
                                        'details': [
                                                ('Type', plabel(p)),
                                                ('Required', p.get('required',
                                                                   False)),
                                                ('Default', p.get('default')),
                                            ]
                                    }
                                for pnm, p in spec['params'].iteritems()]
                        }
                    ]
            }
Ejemplo n.º 26
0
 def _load_fixture(self, fp):
     """Extract fixture data from a file like object, expecting json"""
     # Only delete if we think it unlikely that there is data to lose
     with self.env.db_query as db:
         if db('SELECT * FROM ' + db.quote('ticket')):
             printout(
                 _("This command is only intended to run on fresh "
                   "environments as it will overwrite the database.\n"
                   "If it is safe to lose bloodhound data, delete the "
                   "environment and re-run python bloodhound_setup.py "
                   "before attempting to load the fixture again."))
             return
     data = json.load(fp)
     with self.env.db_transaction as db:
         for tab, cols, vals in data:
             db("DELETE FROM " + db.quote(tab))
         for tab, cols, vals in data:
             printout("Populating %s table" % tab)
             db.executemany(
                 "INSERT INTO %s (%s) VALUES (%s)" %
                 (db.quote(tab), ','.join([db.quote(c)
                                           for c in cols]), ','.join(
                                               ['%s'] * len(cols))), vals)
             printout("%d records added" % len(vals))
Ejemplo n.º 27
0
                raise TracError('Report module not available (disabled?)')
            if trac_version < trac_tags[0]:
                args = fakereq, self.env.get_db_cnx(), rptid
            else:
                args = fakereq, rptid
            data = rptmdl._render_view(*args)[1]
        except ResourceNotFound, exc:
            raise InvalidIdentifier(unicode(exc))
        except RequestDone:
            raise TracError('Cannot execute report. Redirection needed')
        except TracError, exc:
            if data is not None:
                exc.title = data.get('title', 'TracReports')
            raise
        else:
            title = data.get('title', '%s {%s}' % (_('Report'), rptid))
            rptctx = Context.from_request(fakereq, 'report', rptid)
            return 'widget_grid.html', \
                    {
                        'title' : title,
                        'data' : data,
                        'ctxtnav' : [
                            tag.a(_('More'), href=req.href('report', rptid)),
                            ('REPORT_MODIFY' in req.perm(rptctx.resource)) and \
                                tag.a(_('Edit'), href=req.href('report', rptid, action='edit')) or None,
                            ],
                        'altlinks' : fakereq.chrome.get('links', {}).get('alternate')
                    }, \
                    rptctx

    render_widget = pretty_wrapper(render_widget, check_widget_name)
Ejemplo n.º 28
0
 def __init__(self, argname, message, title=None, show_traceback=False):
     message = _("Invalid argument") + " `" + argname + "`. " + message
     TracError.__init__(self, message, title, show_traceback)
     self.argname = argname
Ejemplo n.º 29
0
    def render_widget(self, name, context, options):
        """Count ocurrences of values assigned to given ticket field.
        """
        req = context.req
        params = ('field', 'query', 'verbose', 'threshold', 'max', 'title',
                  'view')
        fieldnm, query, verbose, threshold, maxitems, title, view = \
                self.bind_params(name, options, *params)

        field_maps = {'type': {'admin_url': 'type',
                               'title': _('Types'),
                               },
                      'status': {'admin_url': None,
                                 'title': _('Statuses'),
                                 },
                      'priority': {'admin_url': 'priority',
                                   'title': _('Priorities'),
                                   },
                      'milestone': {'admin_url': 'milestones',
                                    'title': _('Milestones'),
                                    },
                      'component': {'admin_url': 'components',
                                    'title': _('Components'),
                                    },
                      'version': {'admin_url': 'versions',
                                  'title': _('Versions'),
                                  },
                      'severity': {'admin_url': 'severity',
                                   'title': _('Severities'),
                                   },
                      'resolution': {'admin_url': 'resolution',
                                     'title': _('Resolutions'),
                                     },
                      }
        _field = []
        def check_field_name():
            if fieldnm is None:
                raise InvalidWidgetArgument('field', 'Missing ticket field')
            tsys = self.env[TicketSystem]
            if tsys is None:
                raise TracError(_('Error loading ticket system (disabled?)'))
            for field in tsys.get_ticket_fields():
                if field['name'] == fieldnm:
                    _field.append(field)
                    break
            else:
                if fieldnm in field_maps:
                    admin_suffix = field_maps.get(fieldnm)['admin_url']
                    if 'TICKET_ADMIN' in req.perm and admin_suffix is not None:
                        hint = _('You can add one or more '
                                 '<a href="%(url)s">here</a>.',
                                url=req.href.admin('ticket', admin_suffix))
                    else:
                        hint = _('Contact your administrator for further details')
                    return 'widget_alert.html', \
                            {
                                'title' : Markup(field_maps[fieldnm]['title']),
                                'data' : dict(msgtype='info',
                                    msglabel="Note",
                                    msgbody=Markup(_('''No values are
                                        defined for ticket field
                                        <em>%(field)s</em>. %(hint)s''',
                                        field=fieldnm, hint=hint))
                                    )
                            }, context
                else:
                    raise InvalidWidgetArgument('field',
                            'Unknown ticket field %s' % (fieldnm,))
            return None

        if query is None :
            data = check_field_name()
            if data is not None:
                return data
            field = _field[0]
            if field.get('custom'):
                sql = "SELECT COALESCE(value, ''), count(COALESCE(value, ''))" \
                        " FROM ticket_custom " \
                        " WHERE name='%(name)s' GROUP BY COALESCE(value, '')"
            else:
                sql = "SELECT COALESCE(%(name)s, ''), " \
                        "count(COALESCE(%(name)s, '')) FROM ticket " \
                        "GROUP BY COALESCE(%(name)s, '')"
            sql = sql % field
            # TODO : Implement threshold and max

            db_query = req.perm.env.db_query \
                if isinstance(req.perm.env, ProductEnvironment) \
                else req.perm.env.db_direct_query
            with db_query as db:
                cursor = db.cursor()
                cursor.execute(sql)
                items = cursor.fetchall()

            QUERY_COLS = ['id', 'summary', 'owner', 'type', 'status', 'priority']
            item_link= lambda item: req.href.query(col=QUERY_COLS + [fieldnm],
                                                    **{fieldnm:item[0]})
        else:
            query = Query.from_string(self.env, query, group=fieldnm)
            if query.group is None:
                data = check_field_name()
                if data is not None:
                    return data
                raise InvalidWidgetArgument('field',
                        'Invalid ticket field for ticket groups')

            fieldnm = query.group
            sql, v = query.get_sql()
            sql = "SELECT COALESCE(%(name)s, '') , count(COALESCE(%(name)s, ''))"\
                    "FROM (%(sql)s) AS foo GROUP BY COALESCE(%(name)s, '')" % \
                    { 'name' : fieldnm, 'sql' : sql }
            db = self.env.get_db_cnx()
            try :
                cursor = db.cursor()
                cursor.execute(sql, v)
                items = cursor.fetchall()
            finally:
                cursor.close()

            query_href = query.get_href(req.href)
            item_link= lambda item: query_href + \
                    '&' + unicode_urlencode([(fieldnm, item[0])])

        if fieldnm in self.DASH_ITEM_HREF_MAP:
            def dash_item_link(item):
                if item[0]:
                    args = self.DASH_ITEM_HREF_MAP[fieldnm] + (item[0],)
                    return req.href(*args)
                else:
                    return item_link(item)
        else:
            dash_item_link = item_link

        if title is None:
            heading = _(fieldnm.capitalize())
        else:
            heading = None

        return 'widget_cloud.html', \
                {
                    'title' : title,
                    'data' : dict(
                            bounds=minmax(items, lambda x: x[1]),
                            item_link=dash_item_link,
                            heading=heading,
                            items=items,
                            verbose=verbose,
                            view=view,
                        ),
                }, \
                context
Ejemplo n.º 30
0
                module = timemdl
            data = module.process_request(fakereq)[1]
        except TracError, exc:
            if data is not None:
                exc.title = data.get('title', _('Activity'))
            raise
        else:
            merge_links(srcreq=fakereq,
                        dstreq=req,
                        exclude=["stylesheet", "alternate"])
            if 'context' in data:
                # Needed for abbreviated messages in widget events (#340)
                wcontext.set_hints(**(data['context']._hints or {}))
            data['context'] = wcontext
            return 'widget_timeline.html', {
                'title': _('Activity'),
                'data': data,
                'altlinks': fakereq.chrome.get('links', {}).get('alternate')
            }, context

    render_widget = pretty_wrapper(render_widget, check_widget_name)


class FilteredTimeline:
    """This is a class (not a component ;) aimed at overriding some parts of
    TimelineModule without patching it in order to inject code needed to filter
    timeline events according to rendering context. It acts as a wrapper on top
    of TimelineModule.
    """
    def __init__(self, env, context, keep_mismatched=False):
        """Initialization
Ejemplo n.º 31
0
                                '__idx__' : idxs.next(),
                                'cell_groups' : [[
                                        {
                                            'header' : h,
                                            'index' : hidx,
                                            'value' : t[h['col']]
                                        } \
                                    for hidx, h in enumerate(headers)]],
                                'id' : t['id'],
                                'resource' : Resource('ticket', t['id']),
                                'href': t['href']
                            } for t in tickets]) \
                                for group_value, tickets in data['groups'] ]))
            return 'widget_grid.html', \
                    {
                        'title' : title or _('Custom Query'),
                        'data' : data,
                        'ctxtnav' : [
                                tag.a(_('More'),
                                    href=more_link_href)],
                        'altlinks' : fakereq.chrome.get('links', {}).get('alternate')
                    }, \
                    qryctx

    render_widget = pretty_wrapper(render_widget, check_widget_name)

#--------------------------------------
# Query functions and methods
#--------------------------------------

def exec_query(env, req, qstr='status!=closed'):
Ejemplo n.º 32
0
    def render_widget(self, name, context, options):
        """Gather timeline events and render data in compact view
        """
        data = None
        req = context.req
        try:
            timemdl = self.env[TimelineModule]
            admin_page = tag.a(_("administration page."),
                               title=_("Plugin Administration Page"),
                               href=req.href.admin('general/plugin'))
            if timemdl is None:
                return 'widget_alert.html', {
                    'title':  _("Activity"),
                    'data': {
                        'msglabel': _("Warning"),
                        'msgbody':
                            tag_("The TimelineWidget is disabled because the "
                                 "Timeline component is not available. "
                                  "Is the component disabled? "
                                  "You can enable from the %(page)s",
                                  page=admin_page),
                        'dismiss': False,
                    }
                }, context

            params = ('from', 'daysback', 'doneby', 'precision', 'filters',
                      'max', 'realm', 'id')
            start, days, user, precision, filters, count, realm, rid = \
                self.bind_params(name, options, *params)
            if context.resource.realm == 'ticket':
                if days is None:
                    # calculate a long enough time daysback
                    ticket = Ticket(self.env, context.resource.id)
                    ticket_age = datetime.now(utc) - ticket.time_created
                    days = ticket_age.days + 1
                if count is None:
                    # ignore short count for ticket feeds
                    count = 0
            if count is None:
                count = self.default_count

            fakereq = dummy_request(self.env, req.authname)
            fakereq.args = {
                'author': user or '',
                'daysback': days or '',
                'max': count,
                'precision': precision,
                'user': user
            }
            if filters:
                fakereq.args.update(dict((k, True) for k in filters))
            if start is not None:
                fakereq.args['from'] = start.strftime('%x %X')

            wcontext = context.child()
            if (realm, rid) != (None, None):
                # Override rendering context
                resource = Resource(realm, rid)
                if resource_exists(self.env, resource) or \
                        realm == rid == '':
                    wcontext = context.child(resource)
                    wcontext.req = req
                else:
                    self.log.warning("TimelineWidget: Resource %s not found",
                                     resource)
            # FIXME: Filter also if existence check is not conclusive ?
            if resource_exists(self.env, wcontext.resource):
                module = FilteredTimeline(self.env, wcontext)
                self.log.debug('Filtering timeline events for %s',
                               wcontext.resource)
            else:
                module = timemdl
            data = module.process_request(fakereq)[1]
        except TracError, exc:
            if data is not None:
                exc.title = data.get('title', _('Activity'))
            raise
Ejemplo n.º 33
0
            else:
                module = timemdl
            data = module.process_request(fakereq)[1]
        except TracError, exc:
            if data is not None:
                exc.title = data.get('title', _('Activity'))
            raise
        else:
            merge_links(srcreq=fakereq, dstreq=req,
                        exclude=["stylesheet", "alternate"])
            if 'context' in data:
                # Needed for abbreviated messages in widget events (#340)
                wcontext.set_hints(**(data['context']._hints or {}))
            data['context'] = wcontext
            return 'widget_timeline.html', {
                'title': _('Activity'),
                'data': data,
                'altlinks': fakereq.chrome.get('links', {}).get('alternate')
            }, context

    render_widget = pretty_wrapper(render_widget, check_widget_name)


class FilteredTimeline:
    """This is a class (not a component ;) aimed at overriding some parts of
    TimelineModule without patching it in order to inject code needed to filter
    timeline events according to rendering context. It acts as a wrapper on top
    of TimelineModule.
    """
    def __init__(self, env, context, keep_mismatched=False):
        """Initialization
Ejemplo n.º 34
0
                raise TracError('Report module not available (disabled?)')
            if trac_version < trac_tags[0]:
                args = fakereq, self.env.get_db_cnx(), rptid
            else:
                args = fakereq, rptid
            data = rptmdl._render_view(*args)[1]
        except ResourceNotFound, exc:
            raise InvalidIdentifier(unicode(exc))
        except RequestDone:
            raise TracError('Cannot execute report. Redirection needed')
        except TracError, exc:
            if data is not None:
                exc.title = data.get('title', 'TracReports')
            raise
        else:
            title = data.get('title', '%s {%s}' % (_('Report'), rptid))
            rptctx = Context.from_request(fakereq, 'report', rptid)
            return 'widget_grid.html', \
                    {
                        'title' : title,
                        'data' : data,
                        'ctxtnav' : [
                            tag.a(_('More'), href=req.href('report', rptid)),
                            ('REPORT_MODIFY' in req.perm(rptctx.resource)) and \
                                tag.a(_('Edit'), href=req.href('report', rptid, action='edit')) or None,
                            ],
                        'altlinks' : fakereq.chrome.get('links', {}).get('alternate')
                    }, \
                    rptctx

    render_widget = pretty_wrapper(render_widget, check_widget_name)
Ejemplo n.º 35
0
        elif isinstance(value, basestring):
            try:
                return parse_date(value, self.tz)
            except TracError, exc:
                try:
                    fmt = fmt or self.fmt
                    return datetime.strptime(value, fmt)
                except:
                    raise InvalidWidgetArgument(
                        error=exc, title=_('Datetime conversion error'))
        elif isinstance(value, int):
            return datetime.utcfromtimestamp(value)
        else:
            raise InvalidWidgetArgument("Invalid format `%s` for value `%s`" %
                                        (fmt, value),
                                        title=_('Datetime conversion error'))


class ListField:
    """Convert list field
    """
    def __init__(self, sep=','):
        """Initialize list field converter

        :param sep:       character used to delimit list items
        """
        self.sep = sep

    def __call__(self, value):
        """Perform the actual conversion
        """
Ejemplo n.º 36
0
 def __init__(self, argname, message, title=None, show_traceback=False):
     message = _("Invalid argument") + " `" + argname + "`. " + message
     TracError.__init__(self, message, title, show_traceback)
     self.argname = argname
Ejemplo n.º 37
0
    def render_widget(self, name, context, options):
        """Gather timeline events and render data in compact view
        """
        data = None
        req = context.req
        try:
            timemdl = self.env[TimelineModule]
            admin_page = tag.a(_("administration page."),
                               title=_("Plugin Administration Page"),
                               href=req.href.admin('general/plugin'))
            if timemdl is None:
                return 'widget_alert.html', {
                    'title': _("Activity"),
                    'data': {
                        'msglabel':
                        _("Warning"),
                        'msgbody':
                        tag_(
                            "The TimelineWidget is disabled because the "
                            "Timeline component is not available. "
                            "Is the component disabled? "
                            "You can enable from the %(page)s",
                            page=admin_page),
                        'dismiss':
                        False,
                    }
                }, context

            params = ('from', 'daysback', 'doneby', 'precision', 'filters',
                      'max', 'realm', 'id')
            start, days, user, precision, filters, count, realm, rid = \
                self.bind_params(name, options, *params)
            if context.resource.realm == 'ticket':
                if days is None:
                    # calculate a long enough time daysback
                    ticket = Ticket(self.env, context.resource.id)
                    ticket_age = datetime.now(utc) - ticket.time_created
                    days = ticket_age.days + 1
                if count is None:
                    # ignore short count for ticket feeds
                    count = 0
            if count is None:
                count = self.default_count

            fakereq = dummy_request(self.env, req.authname)
            fakereq.args = {
                'author': user or '',
                'daysback': days or '',
                'max': count,
                'precision': precision,
                'user': user
            }
            if filters:
                fakereq.args.update(dict((k, True) for k in filters))
            if start is not None:
                fakereq.args['from'] = start.strftime('%x %X')

            wcontext = context.child()
            if (realm, rid) != (None, None):
                # Override rendering context
                resource = Resource(realm, rid)
                if resource_exists(self.env, resource) or \
                        realm == rid == '':
                    wcontext = context.child(resource)
                    wcontext.req = req
                else:
                    self.log.warning("TimelineWidget: Resource %s not found",
                                     resource)
            # FIXME: Filter also if existence check is not conclusive ?
            if resource_exists(self.env, wcontext.resource):
                module = FilteredTimeline(self.env, wcontext)
                self.log.debug('Filtering timeline events for %s',
                               wcontext.resource)
            else:
                module = timemdl
            data = module.process_request(fakereq)[1]
        except TracError, exc:
            if data is not None:
                exc.title = data.get('title', _('Activity'))
            raise
Ejemplo n.º 38
0
class DashboardModule(Component):
    """Web frontend for dashboard infrastructure.
    """
    implements(IRequestHandler, IRequestFilter, INavigationContributor,
               ITemplateProvider)

    mainnav_label = Option('mainnav',
                           'tickets.label',
                           'Tickets',
                           """Dashboard label in mainnav""",
                           doc_domain='bhdashboard')
    default_widget_height = IntOption('widgets',
                                      'default_height',
                                      320,
                                      """Default widget height in pixels""",
                                      doc_domain='bhdashboard')

    def __init__(self, *args, **kwargs):
        locale_dir = pkg_resources.resource_filename(__name__, 'locale')
        add_domain(self.env.path, locale_dir)
        super(DashboardModule, self).__init__(*args, **kwargs)

    # IRequestFilter methods

    def pre_process_request(self, req, handler):
        """Always returns the request handler unchanged.
        """
        return handler

    def post_process_request(self, req, template, data, content_type):
        """Inject dashboard helpers in data.
        """
        if data is not None:
            data['bhdb'] = DashboardChrome(self.env)
            if isinstance(req.perm.env, ProductEnvironment) \
                    and not 'resourcepath_template' in data \
                    and 'product_list' in data:
                data['resourcepath_template'] = 'bh_path_general.html'
        for item in req.chrome['nav'].get('mainnav', []):
            self.log.debug('%s' % (item, ))
            if item['name'] == 'tickets':
                item['label'] = tag.a(_(self.mainnav_label),
                                      href=req.href.dashboard())
                if item['active'] and \
                        not ReportModule(self.env).match_request(req):
                    add_ctxtnav(req, _('Reports'), href=req.href.report())
                break
        return template, data, content_type

    # IRequestHandler methods
    def match_request(self, req):
        """Match dashboard prefix"""
        return bool(re.match(r'^/dashboard(/.)?', req.path_info))

    def process_request(self, req):
        req.perm.require('PRODUCT_VIEW')
        # Initially this will render static widgets. With time it will be
        # more and more dynamic and flexible.
        if self.env[QueryModule] is not None:
            add_ctxtnav(req, _('Custom Query'), req.href.query())
        if self.env[ReportModule] is not None:
            add_ctxtnav(req, _('Reports'), req.href.report())
        context = Context.from_request(req)
        template, layout_data = self.expand_layout_data(
            context, 'bootstrap_grid',
            self.DASHBOARD_SCHEMA if isinstance(self.env, ProductEnvironment)
            else self.DASHBOARD_GLOBAL_SCHEMA)
        widgets = self.expand_widget_data(context, layout_data)
        return template, {
            'context': Context.from_request(req),
            'layout': layout_data,
            'widgets': widgets,
            'title': _(self.mainnav_label),
            'default': {
                'height': self.default_widget_height or None
            }
        }, None

    # INavigationContributor methods
    def get_active_navigation_item(self, req):
        """Highlight dashboard mainnav item.
        """
        return 'tickets'

    def get_navigation_items(self, req):
        """Skip silently
        """
        return None

    # ITemplateProvider methods
    def get_htdocs_dirs(self):
        """List `htdocs` dirs for dashboard and widgets.
        """
        resource_filename = pkg_resources.resource_filename
        return [
            ('dashboard', resource_filename('bhdashboard', 'htdocs')),
            #('widgets', resource_filename('bhdashboard.widgets', 'htdocs'))
            ('layouts', resource_filename('bhdashboard.layouts', 'htdocs'))
        ]

    def get_templates_dirs(self):
        """List `templates` folders for dashboard and widgets.
        """
        resource_filename = pkg_resources.resource_filename
        return [
            resource_filename('bhdashboard.layouts', 'templates'),
            resource_filename('bhdashboard', 'templates'),
            resource_filename('bhdashboard.widgets', 'templates')
        ]

    # Temp vars
    DASHBOARD_GLOBAL_SCHEMA = DASHBOARD_SCHEMA = {
        'div': [{
            '_class':
            'row',
            'div': [{
                '_class':
                'span8',
                'widgets': [
                    'my tickets', 'active tickets', 'products', 'versions',
                    'milestones', 'components'
                ]
            }, {
                '_class': 'span4',
                'widgets': ['activity']
            }]
        }],
        'widgets': {
            'components': {
                'args': [
                    'TicketFieldValues', None, {
                        'args': {
                            'field': 'component',
                            'title': 'Components',
                            'verbose': True
                        }
                    }
                ]
            },
            'milestones': {
                'args': [
                    'TicketFieldValues', None, {
                        'args': {
                            'field': 'milestone',
                            'title': 'Milestones',
                            'verbose': True
                        }
                    }
                ]
            },
            'versions': {
                'args': [
                    'TicketFieldValues', None, {
                        'args': {
                            'field': 'version',
                            'title': 'Versions',
                            'verbose': True
                        }
                    }
                ]
            },
            'active tickets': {
                'args': [
                    'TicketQuery', None, {
                        'args': {
                            'max':
                            10,
                            'query':
                            'status=!closed&group=milestone'
                            '&col=id&col=summary&col=owner'
                            '&col=status&col=priority&'
                            'order=priority',
                            'title':
                            _('Active Tickets')
                        }
                    }
                ],
                'altlinks':
                False
            },
            'my tickets': {
                'args': [
                    'TicketQuery', None, {
                        'args': {
                            'max':
                            10,
                            'query':
                            'status=!closed&group=milestone'
                            '&col=id&col=summary&col=owner'
                            '&col=status&col=priority&'
                            'order=priority&'
                            'owner=$USER',
                            'title':
                            _('My Tickets')
                        }
                    }
                ],
                'altlinks':
                False
            },
            'activity': {
                'args': ['Timeline', None, {
                    'args': {}
                }]
            },
            'products': {
                'args': ['Product', None, {
                    'args': {
                        'max': 3,
                        'cols': 2
                    }
                }]
            },
        }
    }

    # global dashboard queries: add milestone column, group by product
    DASHBOARD_GLOBAL_SCHEMA['widgets']['active tickets']['args'][2]['args'][
        'query'] = (
            'status=!closed&group=product&col=id&col=summary&col=owner&col=status&'
            'col=priority&order=priority&col=milestone')
    DASHBOARD_GLOBAL_SCHEMA['widgets']['my tickets']['args'][2]['args'][
        'query'] = (
            'status=!closed&group=product&col=id&col=summary&col=owner&col=status&'
            'col=priority&order=priority&col=milestone&owner=$USER&')
    for widget in ('milestones', 'versions', 'components'):
        DASHBOARD_GLOBAL_SCHEMA['div'][0]['div'][0]['widgets'].remove(widget)

    # Public API
    def expand_layout_data(self, context, layout_name, schema, embed=False):
        """Determine the template needed to render a specific layout
        and the data needed to place the widgets at expected
        location.
        """
        layout = DashboardSystem(self.env).resolve_layout(layout_name)

        template = layout.expand_layout(layout_name, context, {
            'schema': schema,
            'embed': embed
        })['template']
        return template, schema

    def _render_widget(self, wp, name, ctx, options):
        """Render widget without failing.
        """
        if wp is None:
            data = {
                'msglabel': _('Warning'),
                'msgbody': _('Unknown widget %(name)s', name=name)
            }
            return 'widget_alert.html', {'title': '', 'data': data}, ctx

        try:
            return wp.render_widget(name, ctx, options)
        except Exception, exc:
            log_entry = str(uuid4())
            exccls = exc.__class__
            self.log.exception(
                "- %s - Error rendering widget %s with options %s", log_entry,
                name, options)
            data = {
                'msgtype':
                'error',
                'msglabel':
                'Error',
                'msgbody':
                _('Exception raised while rendering widget. '
                  'Contact your administrator for further details.'),
                'msgdetails': [
                    ('Widget name', name),
                    ('Exception type', tag.code(exccls.__name__)),
                    ('Log entry ID', log_entry),
                ],
            }
            return 'widget_alert.html', {
                'title': _('Widget error'),
                'data': data
            }, ctx
Ejemplo n.º 39
0
                                '__idx__' : idxs.next(),
                                'cell_groups' : [[
                                        {
                                            'header' : h,
                                            'index' : hidx,
                                            'value' : t[h['col']]
                                        } \
                                    for hidx, h in enumerate(headers)]],
                                'id' : t['id'],
                                'resource' : Resource('ticket', t['id']),
                                'href': t['href']
                            } for t in tickets]) \
                                for group_value, tickets in data['groups'] ]))
            return 'widget_grid.html', \
                    {
                        'title' : title or _('Custom Query'),
                        'data' : data,
                        'ctxtnav' : [
                                tag.a(_('More'),
                                    href=more_link_href)],
                        'altlinks' : fakereq.chrome.get('links', {}).get('alternate')
                    }, \
                    qryctx

    render_widget = pretty_wrapper(render_widget, check_widget_name)


#--------------------------------------
# Query functions and methods
#--------------------------------------
Ejemplo n.º 40
0
        elif isinstance(value, basestring):
            try:
                return parse_date(value, self.tz)
            except TracError, exc:
                try:
                    fmt = fmt or self.fmt
                    return datetime.strptime(value, fmt)
                except:
                    raise InvalidWidgetArgument(
                            error=exc, title=_('Datetime conversion error'))
        elif isinstance(value, int):
            return datetime.utcfromtimestamp(value)
        else:
            raise InvalidWidgetArgument(
                    "Invalid format `%s` for value `%s`" % (fmt, value),
                    title=_('Datetime conversion error'))

class ListField:
    """Convert list field
    """
    def __init__(self, sep=','):
        """Initialize list field converter

        :param sep:       character used to delimit list items
        """
        self.sep = sep

    def __call__(self, value):
        """Perform the actual conversion
        """
        if isinstance(value, basestring):