Example #1
0
    def get_content(self, req, entry, data):

        # Make sure that repository is a Subversion repository, since
        # we're relying for now on Subversion to provide WebDAV
        # support.
        if not is_subversion_repository(data.get('repos')):
            return None
        # don't show up in the menu if the user is browsing a non-HEAD revision
        if data.get('stickyrev'):
            return None

        reponame = data['reponame'] or ''
        ext = os.path.splitext(entry.name)[1][1:]

        if not entry.isdir and ext in self.office_file_extensions:

            path = self.get_subversion_path(entry)
            href = self.get_subversion_href(data, path)

            if re.search('(MSIE |Trident/)',
                         req.environ.get('HTTP_USER_AGENT', '')):
                # for IE, we'll use ActiveX as this may work with older Office installations
                return tag.a(tag.i(class_="fa fa-edit"),
                             ' %s with Microsoft Office' % self._verb,
                             href=href,
                             class_=self._link_class)
            else:
                # otherwise, let's try https://msdn.microsoft.com/en-us/library/office/dn906146.aspx
                # which is Office 2010 SP2 and above
                application = self.office_file_extensions[ext]
                return tag.a(tag.i(class_="fa fa-edit"),
                             ' %s with Microsoft %s' %
                             (self._verb, application.title()),
                             href="ms-%s:%s|u|%s" %
                             (application, self._href_mode, href))
Example #2
0
 def product_media_data(icons, product):
     return dict(href=product.href(),
                 thumb=icons.get(product.prefix, no_thumbnail),
                 title=product.name,
                 description=format_to_html(self.env,
                                            product_ctx(product),
                                            product.description),
                 links={'extras': (([{'href': req.href.products(
                                         product.prefix, action='edit'),
                                      'title': _('Edit product %(prefix)s',
                                                 prefix=product.prefix),
                                      'icon': tag.i(class_='icon-edit'),
                                      'label': _('Edit')},]
                                    if 'PRODUCT_MODIFY' in req.perm
                                    else []) +
                                   [{'href': product.href(),
                                     'title': _('Home page'),
                                     'icon': tag.i(class_='icon-home'),
                                     'label': _('Home')},
                                    {'href': product.href.dashboard(),
                                     'title': _('Tickets dashboard'),
                                     'icon': tag.i(class_='icon-tasks'),
                                     'label': _('Tickets')},
                                    {'href': product.href.wiki(),
                                     'title': _('Wiki'),
                                     'icon': tag.i(class_='icon-book'),
                                     'label': _('Wiki')}]),
                        'main': {'href': product.href(),
                                 'title': None,
                                 'icon': tag.i(class_='icon-chevron-right'),
                                 'label': _('Browse')}})
    def get_content(self, req, entry, data):
        
        # Make sure that repository is a Subversion repository, since
        # we're relying for now on Subversion to provide WebDAV
        # support.
        if not is_subversion_repository(data.get('repos')):
            return None
        # don't show up in the menu if the user is browsing a non-HEAD revision
        if data.get('stickyrev'):
            return None

        reponame = data['reponame'] or ''
        ext = os.path.splitext(entry.name)[1][1:]

        if not entry.isdir and ext in self.office_file_extensions:

            path = self.get_subversion_path(entry)
            href = self.get_subversion_href(data, path)

            if re.search('(MSIE |Trident/)',
                         req.environ.get('HTTP_USER_AGENT', '')):
                # for IE, we'll use ActiveX as this may work with older Office installations
                return tag.a(tag.i(class_="fa fa-edit"),
                             ' %s with Microsoft Office' % self._verb,
                             href=href,
                             class_=self._link_class)
            else:
                # otherwise, let's try https://msdn.microsoft.com/en-us/library/office/dn906146.aspx
                # which is Office 2010 SP2 and above
                application = self.office_file_extensions[ext]
                return tag.a(tag.i(class_="fa fa-edit"),
                             ' %s with Microsoft %s' % (self._verb, application.title()),
                             href="ms-%s:%s|u|%s" % (application, self._href_mode, href))
Example #4
0
    def expand_macro(self, formatter, name, content):
        req = formatter.req
        query_string = ""
        argv, kwargs = parse_args(content, strict=False)

        if "order" not in kwargs:
            kwargs["order"] = "id"
        if "max" not in kwargs:
            kwargs["max"] = "0"  # unlimited by default

        query_string = "&".join(["%s=%s" % item for item in kwargs.iteritems()])
        query = Query.from_string(self.env, query_string)

        tickets = query.execute(req)
        tickets = [t for t in tickets if "TICKET_VIEW" in req.perm("ticket", t["id"])]
        ticket_ids = [t["id"] for t in tickets]

        # locate the tickets
        geoticket = GeoTicket(self.env)
        locations = geoticket.locate_tickets(ticket_ids, req)

        if not locations:
            return tag.div(tag.b("MapTickets: "), "No locations found for ", tag.i(content))

        data = dict(locations=Markup(simplejson.dumps(locations)), query_href=query.get_href(req), query_string=content)

        # set an id for the map
        map_id = req.environ.setdefault("MapTicketsId", 0) + 1
        req.environ["MapTicketsId"] = map_id
        data["map_id"] = "tickets-map-%d" % map_id

        return Chrome(self.env).render_template(req, "map_tickets.html", data, None, fragment=True)
Example #5
0
    def get_navigation_items(self, req):
        if req.authname and req.authname != 'anonymous':
            # Use the same names as LoginModule to avoid duplicates.
            yield ('metanav', 'login',
                   _('logged in as %(user)s', user=req.authname))
            logout_href = req.href('%s/logout' % self.auth_path_prefix)
            from pkg_resources import parse_version
            if parse_version(trac.__version__) < parse_version('1.0.2'):
                yield ('metanav', 'logout', tag.a(_('Logout'), logout_href))
            else:
                yield ('metanav', 'logout',
                       tag.form(tag.div(
                           tag.button(_('Logout'),
                                      name='logout',
                                      type='submit')),
                                action=logout_href,
                                method='post',
                                id='logout',
                                class_='trac-logout'))
        else:
            text = _('GitHub Login')
            if self.fontawesome_url:
                add_script(req, self.fontawesome_url)
                text = tag(tag.i(class_='fab fa-github-alt'), ' ', text)

            yield ('metanav', 'github_login',
                   tag.a(text,
                         href=req.href('%s/login' % self.auth_path_prefix)))
    def filter_stream(self, req, method, filename, stream, data):
        if filename == 'browser.html' and req.method == 'GET':

            # we can only work from the 'dir' view at the moment
            if data.get('file'):
                return stream

            # TODO check that contextmenu's InternalNameHolder is enabled, as our js needs it?
            add_stylesheet(req, 'sourcesharer/filebox.css')
            add_javascript(req, 'sourcesharer/filebox.js')
            # Render the filebox template for stream insertion

            # TODO introduce a new interface to allow putting extra buttons into this filebox?
            tmpl = TemplateLoader(self.get_templates_dirs()).load('filebox.html')
            filebox = tmpl.generate(href=req.href, reponame=data['reponame'] or '', rev=data['rev'], files=[])
            # Wrap and float dirlist table, add filebox div
            # TODO change the id names, left/right seems a bit generic to assume we can have to ourselves
            stream |= Transformer('//table[@id="dirlist"]').wrap(tag.div(id="outer",style="clear:both")).wrap(tag.div(id="left"))
            stream |= Transformer('//div[@id="outer"]').append(tag.div(filebox, id="right"))

            is_svn_repo = False
            if 'repos' in data:
                is_svn_repo = isinstance(data.get('repos'), 
                                    (SvnCachedRepository, 
                                    SubversionRepository)) or False
            if is_svn_repo:
                add_ctxtnav(req, tag.a(_(tag.i(class_="fa fa-envelope-o")), " Send", href="", title=_("Send selected files"), id='share-files', class_='alt-button share-files-multiple'),
                    category='ctxtnav', order=10)

        return stream
 def get_content(self, req, entry, data):
     if 'BUSINESSINTELLIGENCE_TRANSFORMATION_EXECUTE' in req.perm:
         if entry.path.startswith("define-reports/"):
             transform = entry.path.split("/")[1]
             return tag.a(tag.i(class_="fa fa-cog"),
                          ' Regenerate with %s (provide parameters)' % transform, 
                          href=req.href.businessintelligence() + "#" + transform)
 def get_content(self, req, entry, data):
     if 'BUSINESSINTELLIGENCE_TRANSFORMATION_EXECUTE' in req.perm:
         if entry.path.startswith("define-reports/"):
             transform = entry.path.split("/")[1]
             return tag.a(tag.i(class_="fa fa-cog"),
                          ' Regenerate with %s (default parameters)' % transform, 
                          href=req.href.businessintelligence(action='execute',
                                                             transform=transform,
                                                             returnto=req.href.browser(entry.path)))
    def get_content(self, req, entry, data):
        # Make sure that repository is a Subversion repository.
        if not is_subversion_repository(data.get('repos')):
            return None
        if data.get('stickyrev'):
            return None

        path = self.get_subversion_path(entry)
        href = self.get_subversion_href(data, path)

        # create a url which uses the tsvncmd protocol and repobrowser
        tortoise_href = "tsvncmd:command:repobrowser?path:" + href
        return tag.a(_(tag.i(class_="fa fa-code-fork")),' Browse With TortoiseSVN',
                                    href=tortoise_href, id_='browse-with-tortoise')
    def get_content(self, req, entry, data):
        # Make sure that repository is a Subversion repository.
        if not is_subversion_repository(data.get('repos')):
            return None
        if data.get('stickyrev'):
            return None

        if self.env.is_component_enabled("svnurls.svnurls.svnurls"):
            # They are already providing links to subversion, so we won't duplicate them.
            return None

        path = self.get_subversion_path(entry)
        href = self.get_subversion_href(data, path)

        return tag.a(_(tag.i(class_="fa fa-globe")),' Subversion URL', href=href, class_='external svn')
Example #11
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')
Example #12
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')
Example #13
0
    def expand_macro(self, formatter, name, content):
        req = formatter.req
        query_string = ''
        argv, kwargs = parse_args(content, strict=False)

        if 'order' not in kwargs:
            kwargs['order'] = 'id'
        if 'max' not in kwargs:
            kwargs['max'] = '0'  # unlimited by default

        query_string = '&'.join(
            ['%s=%s' % item for item in kwargs.iteritems()])
        query = Query.from_string(self.env, query_string)

        tickets = query.execute(req)
        tickets = [
            t for t in tickets if 'TICKET_VIEW' in req.perm('ticket', t['id'])
        ]
        ticket_ids = [t['id'] for t in tickets]

        # locate the tickets
        geoticket = GeoTicket(self.env)
        locations = geoticket.locate_tickets(ticket_ids, req)

        if not locations:
            return tag.div(tag.b('MapTickets: '), "No locations found for ",
                           tag.i(content))

        data = dict(locations=Markup(simplejson.dumps(locations)),
                    query_href=query.get_href(req),
                    query_string=content)

        # set an id for the map
        map_id = req.environ.setdefault('MapTicketsId', 0) + 1
        req.environ['MapTicketsId'] = map_id
        data['map_id'] = 'tickets-map-%d' % map_id

        return Chrome(self.env).render_template(req,
                                                'map_tickets.html',
                                                data,
                                                None,
                                                fragment=True)
 def filter_stream(self, req, method, filename, stream, data):
     if 'default_user_query' in req.session:
         # Inject js for replacing all standard query links
         add_script_data(req, (
             ('queryHref', req.href.query()),
             ('replacementQueryHref', req.session['default_user_query'])))
         stream |= Transformer('html/head').append(
             self._replace_query_links_js)
     if req.path_info == '/query':
         # Add a link to the ribbon for setting the default query
         add_ctxtnav(req, tag.a(tag.i(class_='fa fa-bookmark'),
                                _(" Set as default"),
                                id_='set-default-query',
                                title=_("Make this your default query")))
         # Add js to redirect the form when the link is clicked
         add_script_data(req, (
             ('defaultUserQueryAction', req.href('defaultuserquery')),))
         stream |= Transformer('html/head').append(
             self._redirect_and_submit_js)
     return stream
Example #15
0
 def filter_stream(self, req, method, filename, stream, data):
     if "WIKI_ADMIN" in req.perm and filename == "wiki_view.html":
         remote_url = self._get_config("url", "")
         if remote_url:
             pagename = req.args.get("page", "WikiStart")
             dao = WikiSyncDao(self.env)
             item = dao.find(pagename)
             if not item:
                 item = dao.factory(name=pagename)
             params = {
                 "model": item,
                 "remote_url": remote_url,
                 "req": req
             }
             add_ctxtnav(req,
                 tag.span(
                     tag.a(
                         tag.i(
                             item.status.upper(),
                             class_="status"
                         ),
                         Markup("&darr;"),
                         href="#",
                         class_=item.status,
                         id_="wikisync-panel-toggle"
                     ),
                     class_="wikisync"
                 )
             )
             self._render_assets(req)
             stream |= Transformer('.//body').prepend(
                 Chrome(self.env).load_template(
                     "wikisync_page.html"
                 ).generate(**params)
             )
     return stream
Example #16
0
    def render_fdx_subset(self,fdx,start_with_scene,end_with_scene,mode,formatter):
        theoutput = tag.div(class_="scrippet"+mode)
#        self.log.debug("FDX: %s START: %d END %d" % (fdx,start_with_scene,end_with_scene))
        fdx_obj = self._get_src(self.env, formatter.req, *fdx)
        fd_doc = cElementTree.fromstring(fdx_obj.getStream().read())
        renderParagraphs = False
        for fd_content in fd_doc.findall("Content"):
            for fd_paragraph in fd_content.findall("Paragraph"):
                ptype = fd_paragraph.get('Type')
                if ptype == "Action":
                    ptype = "action"
                elif ptype == "Character":
                    ptype = "character"
                elif ptype == "Dialogue":
                    ptype = "dialogue"
                elif ptype == "Parenthetical":
                    ptype = "parenthetical"
                elif ptype == "Shot":
                    ptype = "shot"
                elif ptype == "Scene Heading":
                    if int(fd_paragraph.get('Number')) == start_with_scene:
                        renderParagraphs = True
                    if int(fd_paragraph.get('Number')) == end_with_scene:
                        renderParagraphs = False
                    ptype = "sceneheader"
                elif ptype == "Transition":
                    ptype = "transition"
                elif ptype == "Teaser/Act One":
                    ptype = "header"
                elif ptype == "New Act":
                    ptype = "header"
                elif ptype == "End Of Act":
                    ptype = "header"
                else:
                    ptype = "action"
                #UNHANDLED FOR THE MOMENT
                #Show/Ep. Title
                ptext = []
                for fd_text in fd_paragraph.findall("Text"):
                    text_style = fd_text.get('Style')
                    if fd_text.text != None:
                        if "FADE IN:" in fd_text.text.upper():
                            fd_text.text = fd_text.text.upper()
                        if ptype in ["character","transition","sceneheader","header","shot"]:
                            fd_text.text = fd_text.text.upper()
                        #clean smart quotes
                        fd_text.text = fd_text.text.replace(u"\u201c", "\"").replace(u"\u201d", "\"") #strip double curly quotes
                        fd_text.text  = fd_text.text.replace(u"\u2018", "'").replace(u"\u2019", "'").replace(u"\u02BC", "'") #strip single curly quotes
                        ptext.append({"style":text_style,"text":fd_text.text})
                content = []
                for block in ptext:
                    if block["style"] == "Italic":
                        
                        content.append(tag.i(block["text"]))
                    elif block["style"] == "Underline":
                        content.append(tag.u(block["text"]))
                    elif block["style"] == "Bold":
                        content.append(tag.b(block["text"]))
                    elif block["style"] == "Bold+Underline":
                        content.append(tag.b(tag.u(block["text"])))
                    else:
                        content.append(block["text"])
                if renderParagraphs:
                    theoutput.append(tag.p(content,class_=ptype+mode))
        return theoutput
    def filter_stream(self, req, method, filename, stream, data):
        """
        Check for alert messages to show authenticated user.

        If there are any project messages that the authenticated user has not 
        seen, which are selected to be viewed as alert based notifications, 
        we add the necessary mark-up and javscript.
        """

        if req.authname != 'anonymous':

            timeout_exceeded = self._timeout_limit_exceeded(req)
            if timeout_exceeded or timeout_exceeded is None:

                # we can check for alert notifications
                unagreed = ProjectMessage.get_unagreed_messages(self.env, 
                            req.authname, 'Alert')
                if unagreed:
                    # we only shown one notification at a time currently
                    msg = unagreed[0]
                    msg['message'] = format_to_html(self.env, 
                                        Context.from_request(req), msg['message'])
                    alert_markup = tag(
                                    tag.div(
                                        tag.i(
                                            class_="alert-icon fa fa-info-circle"
                                        ),
                                        tag.ul(
                                            tag.li(msg['message'],
                                                class_="alert-message"
                                            ),
                                        ),
                                        tag.button(msg['button'],
                                            class_="close btn btn-mini",
                                            type="button",
                                            data_dismiss="alert"
                                        ),
                                        class_="project-message cf alert alert-info alert-dismissable individual"
                                    ),
                                    tag.form(
                                        tag.input(
                                            name="name",
                                            value=msg['name'],
                                            type="text",
                                        ),
                                        tag.input(
                                            name="agree",
                                            value=True,
                                            type="text",
                                        ),
                                        class_="hidden",
                                        method="post",
                                        action="",
                                    ),
                                  )

                    stream |= Transformer("//*[@id='main']/*[1]").before(alert_markup)
                    add_script(req, 'projectmessage/js/project_message.js')

                # if the timeout has been exceeded or does not exist yet, 
                # and there are no notifications to show, we update the 
                # session attribute table
                if not ProjectMessage.get_unagreed_messages(self.env, req.authname):
                    stamp = str(to_utimestamp(datetime.now(pytz.utc)))
                    req.session['project_message_timeout'] = stamp
                    req.session.save()

        return stream
    def process_request(self, req):
        if 'action' in req.args:
            transform_id = self._generate_running_transformation_id()
            parameters = {}
            for k in req.args:
                if k.startswith("parameter:"):
                    parameter_name = k.split(":",2)[1]
                    parameter_value = req.args[k]
                    parameters[parameter_name] = parameter_value
            req.perm.require("BUSINESSINTELLIGENCE_TRANSFORMATION_EXECUTE")
            if req.args['action'] == "execute_async":
                # execute the transformation 
                thread.start_new_thread(self._do_execute_transformation, 
                                        (req.args['transform'],), 
                                        {'transformation_id': transform_id, 'parameters': parameters})
                # send transform_id generated via uuid back to JS via JSON
                # we have to do this after we invoke a new thread as req.send()
                # returns from this function and stops further flow control
                req.send(to_json({'transform_id': transform_id}), 'text/json')
            elif req.args['action'] == "execute_download":
                filename, stat, filestream = self._do_execute_transformation(req.args['transform'], transformation_id=transform_id, store=False, return_bytes_handle=True, parameters=parameters)
                req.send_response(200)
                req.send_header('Content-Type', mimetypes.guess_type(filename)[0] or 'application/octet-stream')
                req.send_header('Content-Length', stat.st_size)
                req.send_header('Content-Disposition', content_disposition('attachment', filename))
                req.end_headers()
                while True:
                    bytes = filestream.read(4096)
                    if not bytes:
                        break
                    req.write(bytes)
                filestream.close()
                raise RequestDone

            elif req.args['action'] == "execute":
                self._do_execute_transformation(req.args['transform'], transformation_id=transform_id, parameters=parameters)
            elif req.args['action'] == 'check_status':
                if 'uuid' not in req.args:
                    raise KeyError
                running_transformations = json.loads(req.args['uuid'])
                req.send(to_json(self._generate_status_response(running_transformations)), 'text/json')
            else:
                add_warning(req, "No valid action found")
                req.redirect(req.href.businessintelligence())
            if req.get_header('X-Requested-With') == 'XMLHttpRequest':
                req.send_response(200)
                req.send_header('Content-Length', 0)
                req.end_headers()
                return
            else:
                if 'returnto' in req.args:
                    req.redirect(req.args['returnto'])
                else:
                    req.redirect(req.href.businessintelligence())
        else:
            req.perm.require("BUSINESSINTELLIGENCE_TRANSFORMATION_LIST")
            data = {'transformations': self._list_transformation_files(listall=False)}
            add_script(req, 'contextmenu/contextmenu.js')
            add_script(req, 'businessintelligenceplugin/js/business-intelligence.js')
            add_stylesheet(req, 'common/css/browser.css')
            add_ctxtnav(req, tag.a(tag.i(class_="fa fa-upload"), ' Upload Transformations', id="uploadbutton"))
            add_ctxtnav(req, tag.a(tag.i(class_="fa fa-calendar"), ' Schedule Transformations', id="schedulebutton"))
            add_ctxtnav(req, tag.a(tag.i(class_="fa fa-cog"), ' Running Transformations', id="runningbutton"))

            return "listtransformations.html", data, None
    def get_timeline_markup(self, req, call, maxrows=10):
        """
        Generates the markup needed when this component is called both 
        explicitly inside wiki pages and implicitly by the ISideBarBoxProvider.

        Note this code uses methods from the trac TimelineModule module.
        """

        chrome = Chrome(self.env)

        # last 14 days should be enough
        stop = datetime.now(req.tz)
        start = stop - timedelta(days=50)

        # use code from trac/timeline to generate event data
        timeline = TimelineModule(self.env)
        available_filters, filters = timeline.get_filters(req)
        include_authors, exclude_authors = timeline.authors()
        events = timeline.get_events(req, start, stop, filters, available_filters, 
                                     include_authors, exclude_authors, maxrows)
        show_gravatar = self.config.get('avatar','mode').lower() != 'off'

        # create the mark up
        context = Context.from_request(req)
        event_list = tag.ul(class_="timeline-list no-style")
        for event in events:
            event_title = event['render']('title', context)
            event_url = event['render']('url', context)
            event_list.append(tag.li(
                show_gravatar and tag.img(
                    src=req.href.avatar(event['author'] if event['author'] else 'anonymous'),
                    class_="avatar",
                ) or "",
                tag.span(
                    chrome.authorinfo(req, event['author']),
                    class_="author"
                ),
                tag.span(
                    pretty_age(event['date']),
                    class_="date",
                ),
                tag.div(
                    tag.i(class_="event-type fa fa-" + event['kind']),
                    tag.a(
                        event_title,
                        href=event_url,
                    ),
                    class_="event-summary"
                ),
                class_="cf"
            ))

        # if the markup is being generated via ISideBarBoxProvider we don't 
        # need to add a span3 class 
        div_classes = "box-sidebar"
        if call == "macro":
            div_classes += " span3 right"
        return tag.div(
                    tag.h3(
                        tag.i(
                            class_="fa fa-calendar"
                        ),
                        " Recent Project Activity"
                    ),
                    event_list,
                    class_=div_classes,
                )
Example #20
0
File: io.py Project: trucgiao91/woo
 def __call__(self,obj,depth=0,dontRender=False):
     from genshi.builder import tag
     if depth>self.maxDepth: raise RuntimeError("Maximum nesting depth %d exceeded"%self.maxDepth)
     kw=self.padding.copy()
     if depth>0: kw.update(width='100%')
     # was [1:] to omit leading woo./wooExtra., but that is not desirable
     objInExtra=obj.__class__.__module__.startswith('wooExtra.')
     if self.hideWooExtra and objInExtra: head=tag.span(tag.b(obj.__class__.__name__),title=_ensureUnicode(obj.__class__.__doc__))
     else: 
         head=tag.b('.'.join(obj.__class__.__module__.split('.')[0:])+'.'+obj.__class__.__name__)
         head=tag.a(head,href=woo.document.makeObjectUrl(obj),title=_ensureUnicode(obj.__class__.__doc__))
     ret=tag.table(tag.th(head,colspan=3,align='left'),frame='box',rules='all',**kw)
     # get all attribute traits first
     traits=obj._getAllTraits()
     for trait in traits:
         if trait.hidden or (self.hideNoGui and trait.noGui) or trait.noDump or (trait.hideIf and eval(trait.hideIf,globals(),{'self':obj})): continue
         # start new group (additional line)
         if trait.startGroup:
             ret.append(tag.tr(tag.td(tag.i(u'▸ %s'%_ensureUnicode(trait.startGroup)),colspan=3)))
         attr=getattr(obj,trait.name)
         if self.showDoc: tr=tag.tr(tag.td(_ensureUnicode(trait.doc)))
         else:
             try:
                 if self.hideWooExtra and objInExtra: label=tag.span(tag.b(trait.name),title=_ensureUnicode(trait.doc))
                 else: label=tag.a(trait.name,href=woo.document.makeObjectUrl(obj,trait.name),title=_ensureUnicode(trait.doc))
                 tr=tag.tr(tag.td(label))
             except UnicodeEncodeError:
                 print('ERROR: UnicodeEncodeError while formatting the attribute ',obj.__class__.__name__+'.'+trait.name)
                 print('ERROR: the docstring is',trait.doc)
                 raise
         # tr=tag.tr(tag.td(trait.name if not self.showDoc else trait.doc.decode('utf-8')))
         # nested object
         if isinstance(attr,woo.core.Object):
             tr.append([tag.td(self(attr,depth+1),align='justify'),tag.td()])
         # sequence of objects (no units here)
         elif hasattr(attr,'__len__') and len(attr)>0 and isinstance(attr[0],woo.core.Object):
             tr.append(tag.td(tag.ol([tag.li(self(o,depth+1)) for o in attr])))
         else:
             # !! make deepcopy so that the original object is not modified !!
             import copy
             attr=copy.deepcopy(attr)
             if not trait.multiUnit: # the easier case
                 if not trait.prefUnit: unit=u'−'
                 else:
                     unit=_ensureUnicode(trait.prefUnit[0][0])
                     # create new list, where entries are multiplied by the multiplier
                     if type(attr)==list: attr=[a*trait.prefUnit[0][1] for a in attr]
                     else: attr=attr*trait.prefUnit[0][1]
             else: # multiple units
                 unit=[]
                 wasList=isinstance(attr,list)
                 if not wasList: attr=[attr] # handle uniformly
                 for i in range(len(attr)):
                     attr[i]=[attr[i][j]*trait.prefUnit[j][1] for j in range(len(attr[i]))]
                 for pu in trait.prefUnit:
                     unit.append(_ensureUnicode(pu[0]))
                 if not wasList: attr=attr[0]
                 unit=', '.join(unit)
             # sequence type, or something similar                
             if hasattr(attr,'__len__') and not isinstance(attr,(str,unicode,bytes)):
                 if len(attr)>0:
                     tr.append(tag.td(self.htmlSeq(attr,insideTable=False),align='right'))
                 else:
                     tr.append(tag.td(tag.i('[empty]'),align='right'))
             else:
                 tr.append(tag.td(float2str(attr) if isinstance(attr,float) else str(attr),align='right'))
             if unit:
                 tr.append(tag.td(unit,align='right'))
         ret.append(tr)
     if depth>0 or dontRender: return ret
     r1=ret.generate().render('xhtml',encoding='ascii')
     if isinstance(r1,bytes): r1=r1.decode('ascii')
     return r1+u'\n'
    def filter_stream(self, req, method, filename, stream, data):
        """
        We add JavaScript files and other DOM elements via the filter stream, 
        but actually query the DB and parse the data after the initial page 
        load via AJAX for a page load performance boost. See IRequestHandler 
        methods.

        We only add these scripts and elements if the milestone has a minimum 
        of atleast one ticket.

        We also only render the completed work DOM elements if a minimum of 
        one ticket has been closed.
        """

        if req.path_info.startswith('/milestone/'):

            milestone = self._get_milestone(req.args['id'])
            if milestone and self._milestone_has_ticket(milestone.name):

                add_script(req, 'common/js/jqPlot/jquery.jqplot.js')
                add_script(req, 'common/js/jqPlot/excanvas.min.js')
                add_script(req, 'common/js/jqPlot/plugins/jqplot.pieRenderer.min.js')
                add_stylesheet(req, 'common/js/jqPlot/jquery.jqplot.css')
                add_stylesheet(req, 'workload/css/workload.css')
                add_script(req, 'workload/js/workload.js')
                add_script_data(req, {'milestone_name': milestone.name})

                if not milestone.completed:
                    workload_tag = tag(
                        tag.h2("Remaining Work ", class_="inline-block"),
                        tag.i(id_='workload-help',class_='fa fa-question-circle color-muted'),
                        tag.div(
                            tag.div(id_='milestone-workload',
                                    class_='milestone-info span6 center',
                                    style="display:inline;"
                            ),
                            tag.div(id_='milestone-workload-hours',
                                    class_='milestone-info span6 center',
                                    style="display:inline;"
                            ),
                        id_="workload-charts",
                        class_="row-fluid"
                        ),
                        tag.div(
                            tag.p("Remaining work pie charts are generated to help projects recognise the effort necessary to complete the milestone."),
                            tag.p("The open tickets chart counts the number of open tickets each project member has to complete within the milestone."),
                            tag.p("The remaining hours chart reflects the cumulative estimated hours of efforts required to close these tickets."),
                            tag.p("In both charts only the top {0} members with the highest ticket/hours count are displayed. Remaining members have their data aggregated into a 'other' group.".format(self.user_limit)),
                            id_="workload-dialog", class_="hidden"
                        ),
                    )
                    stream = stream | Transformer("//*[@id='field-analysis']").after(workload_tag)

                if self._milestone_has_closed_ticket(milestone.name):

                    workdone_tag = tag(
                        tag.h2("Completed Work ", class_="inline-block"),
                        tag.i(id_='workdone-help', class_='fa fa-question-circle color-muted'),
                        tag.div(
                            tag.div(id_='milestone-workdone',
                                    class_='milestone-info span6 center',
                                    style="display:inline;"
                            ),
                            tag.div(id_='milestone-workdone-hours',
                                    class_='milestone-info span6 center',
                                    style="display:inline;"
                            ),
                        id_="workdone-charts",
                        class_="row-fluid"
                        ),
                        tag.div(
                            tag.p("Completed work pie charts are generated to help projects analyse the contribution of members during the milestone."),
                            tag.p("The closed tickets charts counts the number of tickets each project member has completed during the milestone."),
                            tag.p("The hours logged chart reflects the cumulative hours of work that were required to close these tickets."),
                            tag.p("In both charts only the top {0} members with the highest ticket/hours count are displayed. Remaining members have their data aggregated into a 'other' group.".format(self.user_limit)),
                            id_="workdone-dialog", class_="hidden"
                        ),
                    )

                    div = 'workload-charts' if not milestone.completed else 'field-analysis'
                    stream = stream | Transformer("//*[@id='{0}']".format(div)).after(workdone_tag)

        return stream
    def filter_stream(self, req, method, filename, stream, data):
        if re.match('/milestone/[^ ]', req.path_info):
            help_page_url = req.href.help('DefineGuide', 'DefineAgile', 'BurndownCharts')
            stream = stream | Transformer("//*[@id='milestone-overview']").after(tag(
                                                                                    tag.h2("Burn Down Chart ", 
                                                                                        tag.a(
                                                                                            tag.i(class_="fa fa-question-circle color-muted", id_="burndown_more_info"),
                                                                                        href=help_page_url, target="_blank")
                                                                                        ), 
                                                                                    tag.div(id_='milestone-burndown', class_='milestone-info')
                                                                                    )
                                                                                )

        return stream
    def post_process_request(self, req, template, data, content_type):
        """The actual burndown chart is generated and rendered after page load 
        using a AJAX call which is picked up by the match_request() and 
        process_request() methods.

        This method determines if we should send that AJAX call, by 
        checking the milestone has both a start and end date. If so, the 
        jqPlot JS files are loaded and the script data 'render_burndown'  
        passed via JSON."""

        # check we are on an individual milestone page
        if req.path_info.startswith("/milestone/") and req.args.get('id') \
            and "stats" in data:

            milestone = self._get_milestone(req)
            if milestone:
                # Load the burn down JS file
                add_script(req, 'burndown/js/burndown.js')

                if milestone.start:
                    approx_start_date = False
                else:
                    # no milestone start value so try and estimate start date
                    approx_start_date = self.guess_start_date(milestone)
                    if not approx_start_date:
                        # no milestone start or estimated start date
                        # dont show a burn down chart
                        add_script_data(req, {'render_burndown': False})
                        return template, data, content_type

                # If we do have a start date (explicit or implied), 
                # tell JS it should send a request via JSON and use 
                # the default effort value
                add_script_data(req, {
                                        'render_burndown': True,
                                        'milestone_name': milestone.name,
                                        'print_burndown': False,
                                        'effort_units': self.unit_value,
                                        'approx_start_date': approx_start_date,
                                      })
                                       

                # Add a burndown unit option to the context nav
                add_ctxtnav(req, tag.div(
                                    tag.a(
                                        tag.i(class_="fa fa-bar-chart "),
                                    " Burn Down Units"),
                                    tag.ul(
                                        tag.li(
                                            tag.a('Tickets', href=None, id_="tickets-metric"),
                                        ),
                                        tag.li(
                                            tag.a('Hours', href=None, id_="hours-metric"),
                                        ),
                                        tag.li(
                                            tag.a('Story Points', href=None, id_="points-metric"),
                                        ),
                                        class_="styled-dropdown fixed-max"
                                    ),
                                    class_="dropdown-toggle inline block",
                                  )
                            )

                # Add a print link to the context nav
                add_ctxtnav(req, tag.a(
                                    tag.i(class_="fa fa-print"),
                                " Print Burn Down", id_="print-burndown"))

                # Adds jqPlot library needed by burndown charts
                self._add_static_files(req)

        return template, data, content_type
    def get_box(self, req):
        if req.authname == "anonymous":
            return

        # hoped that ITicketGroupStatsProvider should tell us what the
        # set of "active" statuses, but seems
        # not. DefaultTicketGroupStatsProvider has an idea, from the
        # ini file, but we want to provide new grouping from the
        # LogicaOrderTracker module so it has to be at the interface
        # level rather than component level.
        
        db = self.env.get_read_db()
        cursor = db.cursor()

        counts_ul = tag.ul()
        cursor.execute("""SELECT status, COUNT(status)
                          FROM ticket
                          WHERE owner = %s
                          GROUP BY status
                          ORDER BY 
                            CASE WHEN status = 'assigned'
                              THEN 1
                              ELSE 0
                            END DESC,
                            CASE WHEN status = 'closed'
                              THEN 1
                              ELSE 0
                            END ASC,
                            status ASC""", (req.authname,))
        for status, count in cursor:
            link = tag(tag.span(class_="ticket-state state-" + status), tag.a(status,href=req.href.query(owner=req.authname,
                                                    status=status)))
            counts_ul.append(tag.li(link, ": ", count))


        recent_ul = tag.ul()
        cursor.execute("""SELECT id
                          FROM ticket
                          WHERE ticket.owner = %s
                          GROUP BY id
                          ORDER BY max(changetime) DESC""", 
                       (req.authname,))
        
        shown_count = 0
        ts = TicketSystem(self.env)
        for ticket,  in cursor:
            resource = Resource('ticket', ticket)
            if "TICKET_VIEW" in req.perm(resource):
                shown_count = shown_count + 1
                if shown_count > 5:
                    break
                compact = ts.get_resource_description(resource, 'compact')
                summary = ts.get_resource_description(resource, 'summary')
                link = tag.a(tag.strong(compact), " ", tag.span(summary), href=req.href.ticket(ticket))
                recent_ul.append(tag.li(link))
       

        return tag(
          tag.div(
            tag.h3(
              tag.i(
                class_="fa fa-ticket"
              ),
            " Your Ticket Counts"
            ),
            counts_ul, class_='box-sidebar color-none', id="sidebar-count"
          ),
          tag.div(
            tag.h3(
              tag.i(
                class_="fa fa-star"
              ),
            " Your Recently Modified Tickets"
            ),
            recent_ul, class_='box-sidebar', id="sidebar-recent"
          )
        )
Example #25
0
    def render(self, context, mimetype, content, filename=None, url=None):
        add_stylesheet(context.req, 'scrippets/css/scrippets-full.css')
        if hasattr(content, 'read'):
            content = content.read()
        mode = "-full"
        theoutput = tag.div(class_="scrippet" + mode)
        fd_doc = cElementTree.fromstring(content)
        for fd_content in fd_doc.findall("Content"):
            for fd_paragraph in fd_content.findall("Paragraph"):
                ptype = fd_paragraph.get('Type')
                if ptype == "Action":
                    ptype = "action"
                elif ptype == "Character":
                    ptype = "character"
                elif ptype == "Dialogue":
                    ptype = "dialogue"
                elif ptype == "Parenthetical":
                    ptype = "parenthetical"
                elif ptype == "Scene Heading":
                    ptype = "sceneheader"
                elif ptype == "Shot":
                    ptype = "shot"
                elif ptype == "Transition":
                    ptype = "transition"
                elif ptype == "Teaser/Act One":
                    ptype = "header"
                elif ptype == "New Act":
                    ptype = "header"
                elif ptype == "End Of Act":
                    ptype = "header"
                else:
                    ptype = "action"
                #UNHANDLED FOR THE MOMENT
                #Show/Ep. Title
                ptext = []
                for fd_text in fd_paragraph.findall("Text"):
                    text_style = fd_text.get('Style')
                    if fd_text.text != None:
                        if "FADE IN:" in fd_text.text.upper():
                            fd_text.text = fd_text.text.upper()
                        if ptype in [
                                "character", "transition", "sceneheader",
                                "header", "shot"
                        ]:
                            fd_text.text = fd_text.text.upper()
                        #clean smart quotes
                        fd_text.text = fd_text.text.replace(
                            u"\u201c",
                            "\"").replace(u"\u201d",
                                          "\"")  #strip double curly quotes
                        fd_text.text = fd_text.text.replace(
                            u"\u2018", "'").replace(u"\u2019", "'").replace(
                                u"\u02BC", "'")  #strip single curly quotes
                        ptext.append({
                            "style": text_style,
                            "text": fd_text.text
                        })
                content = []
                for block in ptext:
                    if block["style"] == "Italic":

                        content.append(tag.i(block["text"]))
                    elif block["style"] == "Underline":
                        content.append(tag.u(block["text"]))
                    elif block["style"] == "Bold":
                        content.append(tag.b(block["text"]))
                    elif block["style"] == "Bold+Underline":
                        content.append(tag.b(tag.u(block["text"])))
                    else:
                        content.append(block["text"])
                theoutput += tag.p(content, class_=ptype + mode)
        for fd_titlepage in fd_doc.findall("TitlePage"):
            for fd_content in fd_titlepage.findall("Content"):
                for fd_paragraph in fd_content.findall("Paragraph"):
                    ptype = fd_paragraph.get('Type')
                    for fd_text in fd_paragraph.findall("Text"):
                        text_style = fd_text.get('Style')
                        if fd_text.text != None:
                            self.log.debug("SCRIPPET: " + fd_text.text)

        return "%s" % theoutput
Example #26
0
 def render(self, context, mimetype, content, filename=None, url=None):
     add_stylesheet(context.req, 'scrippets/css/scrippets-full.css')
     if hasattr(content, 'read'):
         content = content.read()
     mode = "-full"
     theoutput = tag.div(class_="scrippet"+mode)
     fd_doc = cElementTree.fromstring(content)
     for fd_content in fd_doc.findall("Content"):
         for fd_paragraph in fd_content.findall("Paragraph"):
             ptype = fd_paragraph.get('Type')
             if ptype == "Action":
                 ptype = "action"
             elif ptype == "Character":
                 ptype = "character"
             elif ptype == "Dialogue":
                 ptype = "dialogue"
             elif ptype == "Parenthetical":
                 ptype = "parenthetical"
             elif ptype == "Scene Heading":
                 ptype = "sceneheader"
             elif ptype == "Shot":
                 ptype = "shot"
             elif ptype == "Transition":
                 ptype = "transition"
             elif ptype == "Teaser/Act One":
                 ptype = "header"
             elif ptype == "New Act":
                 ptype = "header"
             elif ptype == "End Of Act":
                 ptype = "header"
             else:
                 ptype = "action"
             #UNHANDLED FOR THE MOMENT
             #Show/Ep. Title
             ptext = []
             for fd_text in fd_paragraph.findall("Text"):
                 text_style = fd_text.get('Style')
                 if fd_text.text != None:
                     if "FADE IN:" in fd_text.text.upper():
                         fd_text.text = fd_text.text.upper()
                     if ptype in ["character","transition","sceneheader","header","shot"]:
                         fd_text.text = fd_text.text.upper()
                     #clean smart quotes
                     fd_text.text = fd_text.text.replace(u"\u201c", "\"").replace(u"\u201d", "\"") #strip double curly quotes
                     fd_text.text  = fd_text.text.replace(u"\u2018", "'").replace(u"\u2019", "'").replace(u"\u02BC", "'") #strip single curly quotes
                     ptext.append({"style":text_style,"text":fd_text.text})
             content = []
             for block in ptext:
                 if block["style"] == "Italic":
                     
                     content.append(tag.i(block["text"]))
                 elif block["style"] == "Underline":
                     content.append(tag.u(block["text"]))
                 elif block["style"] == "Bold":
                     content.append(tag.b(block["text"]))
                 elif block["style"] == "Bold+Underline":
                     content.append(tag.b(tag.u(block["text"])))
                 else:
                     content.append(block["text"])
             theoutput += tag.p(content,class_=ptype+mode)
     for fd_titlepage in fd_doc.findall("TitlePage"):
         for fd_content in fd_titlepage.findall("Content"):
             for fd_paragraph in fd_content.findall("Paragraph"):
                 ptype = fd_paragraph.get('Type')
                 for fd_text in fd_paragraph.findall("Text"):
                     text_style = fd_text.get('Style')
                     if fd_text.text != None:
                         self.log.debug("SCRIPPET: " + fd_text.text)
                 
     
     return "%s" % theoutput
    def process_request(self, req):

        req.perm.assert_permission('TICKET_VIEW')

        # set the default user query
        if req.path_info == '/taskboard/set-default-query' and req.method == 'POST':
            self._set_default_query(req)

        # these headers are only needed when we update tickets via ajax
        req.send_header("Cache-Control", "no-cache, no-store, must-revalidate")
        req.send_header("Pragma", "no-cache")
        req.send_header("Expires", 0)

        xhr = req.get_header('X-Requested-With') == 'XMLHttpRequest'

        group_by = req.args.get("group", "status")

        user_saved_query = False

        milestones = Milestone.select_names_select2(self.env, include_complete=False)

        # Try to find a user selected milestone in request - if not found 
        # check session_attribute for a user saved default, and if that is also
        # not found and fall back on the most upcoming milestone by due date
        milestone = req.args.get("milestone")
        milestone_not_found = False
        if milestone:
            try:
                Milestone(self.env, milestone)
            except ResourceNotFound:
                milestone_not_found = True
                milestone = None

        if not milestone:
            # try and find a user saved default
            default_milestone = req.session.get('taskboard_user_default_milestone')
            if default_milestone:
                milestone = default_milestone
                group_by = req.session.get('taskboard_user_default_group')
                user_saved_query = True

            # fall back to most imminent milestone by due date
            elif len(milestones["results"]):
                milestone = milestones["results"][0]["text"]

        # Ajax post
        if req.args.get("ticket") and xhr:
            result = self.save_change(req, milestone)
            req.send(to_json(result), 'text/json')
        else:
            data = {}
            constr = {}

            if milestone:
                constr['milestone'] = [milestone]

            # Ajax update: tickets changed between a period
            if xhr:
                from_iso = req.args.get("from", "")
                to_iso = req.args.get("to", "")
                if from_iso and to_iso:
                    constr['changetime'] = [from_iso + ".." + to_iso]

            # Get all tickets by milestone and specify ticket fields to retrieve
            cols = self._get_display_fields(req, user_saved_query)
            tickets = self._get_permitted_tickets(req, constraints=constr, 
                                                  columns=cols)
            sorted_cols = sorted([f for f in self.valid_display_fields
                    if f['name'] not in ('summary', 'type')],
                    key=lambda f: f.get('label'))

            if tickets:
                s_data = self.get_ticket_data(req, milestone, group_by, tickets)
                s_data['total_tickets'] = len(tickets)
                s_data['display_fields'] = cols
                data['cur_group'] = s_data['groupName']
            else:
                s_data = {}
                data['cur_group'] = group_by

            if xhr:
                if constr.get("changetime"):
                    s_data['otherChanges'] = \
                        self.all_other_changes(req, tickets, constr['changetime'])

                req.send(to_json(s_data), 'text/json')
            else:
                s_data.update({
                    'formToken': req.form_token,
                    'milestones': milestones,
                    'milestone': milestone,
                    'group': group_by,
                    'default_columns': self.default_display_fields
                })
                data.update({
                    'milestone_not_found': milestone_not_found,
                    'current_milestone': milestone,
                    'group_by_fields': self.valid_grouping_fields,
                    'fields': dict((f['name'], f) for f in self.valid_display_fields),
                    'all_columns': [f['name'] for f in sorted_cols],
                    'col': cols,
                    'condensed': self._show_condensed_view(req, user_saved_query)
                })

                add_script(req, 'agiletools/js/update_model.js')
                add_script(req, 'agiletools/js/taskboard.js')
                add_script(req, 'common/js/query.js')
                add_script_data(req, s_data)

                add_stylesheet(req, 'agiletools/css/taskboard.css')
                add_stylesheet(req, 'common/css/ticket.css')
                add_ctxtnav(req, tag.a(tag.i(class_='fa fa-bookmark'),
                                       _(" Set as default"),
                                       id_='set-default-query',
                                       title=_("Make this your default taskboard")))
                return "taskboard.html", data, None
Example #28
0
	def __call__(self,obj,depth=0,dontRender=False):
		from genshi.builder import tag
		if depth>self.maxDepth: raise RuntimeError("Maximum nesting depth %d exceeded"%self.maxDepth)
		kw=self.padding.copy()
		if depth>0: kw.update(width='100%')
		# was [1:] to omit leading woo./wooExtra., but that is not desirable
		head=tag.b('.'.join(obj.__class__.__module__.split('.')[0:])+'.'+obj.__class__.__name__)
		head=tag.a(head,href=woo.document.makeObjectUrl(obj),title=_ensureUnicode(obj.__class__.__doc__))
		ret=tag.table(tag.th(head,colspan=3,align='left'),frame='box',rules='all',**kw)
		# get all attribute traits first
		traits=obj._getAllTraits()
		for trait in traits:
			if trait.hidden or (self.hideNoGui and trait.noGui) or trait.noDump or (trait.hideIf and eval(trait.hideIf,globals(),{'self':obj})): continue
			# start new group (additional line)
			if trait.startGroup:
				ret.append(tag.tr(tag.td(tag.i(u'▸ %s'%_ensureUnicode(trait.startGroup)),colspan=3)))
			attr=getattr(obj,trait.name)
			if self.showDoc: tr=tag.tr(tag.td(_ensureUnicode(trait.doc)))
			else:
				try:
					tr=tag.tr(tag.td(tag.a(trait.name,href=woo.document.makeObjectUrl(obj,trait.name),title=_ensureUnicode(trait.doc))))
				except UnicodeEncodeError:
					print 'ERROR: UnicodeEncodeError while formatting the attribute ',obj.__class__.__name__+'.'+trait.name
					print 'ERROR: the docstring is',trait.doc
					raise
			# tr=tag.tr(tag.td(trait.name if not self.showDoc else trait.doc.decode('utf-8')))
			# nested object
			if isinstance(attr,woo.core.Object):
				tr.append([tag.td(self(attr,depth+1),align='justify'),tag.td()])
			# sequence of objects (no units here)
			elif hasattr(attr,'__len__') and len(attr)>0 and isinstance(attr[0],woo.core.Object):
				tr.append(tag.td(tag.ol([tag.li(self(o,depth+1)) for o in attr])))
			else:
				# !! make deepcopy so that the original object is not modified !!
				import copy
				attr=copy.deepcopy(attr)
				if not trait.multiUnit: # the easier case
					if not trait.prefUnit: unit=u'−'
					else:
						unit=_ensureUnicode(trait.prefUnit[0][0])
						# create new list, where entries are multiplied by the multiplier
						if type(attr)==list: attr=[a*trait.prefUnit[0][1] for a in attr]
						else: attr=attr*trait.prefUnit[0][1]
				else: # multiple units
					unit=[]
					wasList=isinstance(attr,list)
					if not wasList: attr=[attr] # handle uniformly
					for i in range(len(attr)):
						attr[i]=[attr[i][j]*trait.prefUnit[j][1] for j in range(len(attr[i]))]
					for pu in trait.prefUnit:
						unit.append(_ensureUnicode(pu[0]))
					if not wasList: attr=attr[0]
					unit=', '.join(unit)
				# sequence type, or something similar				
				if hasattr(attr,'__len__') and not isinstance(attr,str):
					if len(attr)>0:
						tr.append(tag.td(self.htmlSeq(attr,insideTable=False),align='right'))
					else:
						tr.append(tag.td(tag.i('[empty]'),align='right'))
				else:
					tr.append(tag.td(float2str(attr) if isinstance(attr,float) else str(attr),align='right'))
				if unit:
					tr.append(tag.td(unit,align='right'))
			ret.append(tr)
		if depth>0 or dontRender: return ret
		return ret.generate().render('xhtml',encoding='ascii')+b'\n'
    def process_request(self, req):
        offset = req.args.get("offset",0)
        page = req.args.get('page', 1)
        try:
            offset = int(offset)
        except:
            raise TracError(_('Invalid offset used: %(offset)s', offset=offset))        
        
        try:
            page = int(page)
        except:
            raise TracError(_('Invalid page used: %(page)s', page=page))
                
        offset = (page - 1) * self.limit
        
        add_stylesheet(req, 'mailinglist/css/mailinglist.css')
        add_javascript(req, 'mailinglist/mailinglist.js')
            
        mailinglists = [m for m in Mailinglist.select(self.env)
                        if "MAILINGLIST_VIEW" in req.perm(m.resource)]

        data = {"mailinglists": mailinglists,
                "offset": offset,
                "limit": self.limit}

        if req.method == 'POST':

            if 'subscribe' in req.args:
                subscribe = True
                unsubscribe = False
                mailinglist_email = req.args.get('subscribe')
            elif 'unsubscribe' in req.args:
                subscribe = False
                unsubscribe = True
                mailinglist_email = req.args.get('unsubscribe')
            else:
                # at the moment we only post subscription info to
                # mailing list page - so if there is none in req.args we 
                # can just redirect to mailing list page
                req.redirect(req.href.mailinglist())

            # get mailing list object and check permissions
            mailinglist = Mailinglist.select_by_address(self.env,
                                                    mailinglist_email, localpart=True)
            req.perm(mailinglist.resource).require("MAILINGLIST_VIEW")

            if subscribe:
                mailinglist.subscribe(user=req.authname)
                # subscribe does not return a value to indicate if it 
                # was successful, so we have to explicitly check
                if mailinglist.is_subscribed(req.authname):
                    add_notice(req, _('You have been subscribed to %s.' % mailinglist.name))
                else:
                    add_notice(req, _('Unable to subscribe to %s.' % mailinglist.name))
            elif unsubscribe:
                mailinglist.unsubscribe(user=req.authname)
                # unsubscribe does not return a value to indicate if it 
                # was successful, so we have to explicitly check
                if not mailinglist.is_subscribed(req.authname):
                    add_notice(req, _('You have been unsubscribed from %s.' % mailinglist.name))
                else:
                    add_notice(req, _('Unable to unsubscribe from %s.' % mailinglist.name))

            if req.path_info.endswith('/mailinglist'):
                 # overview mailing list page
                req.redirect(req.href.mailinglist())
            elif 'conversationid' in req.args:
                # individual mailing list conversation log
                req.redirect(req.href.mailinglist(mailinglist_email, req.args['conversationid']))
            else:
                # individual mailing list homepage
                req.redirect(req.href.mailinglist(mailinglist_email))

        #for mailinglist in mailinglists:
        #    add_ctxtnav(req,
        #                _("List: %s") % mailinglist.name,
        #                req.href.mailinglist(mailinglist.emailaddress))
		
        if 'messageid' in req.args:
            message = MailinglistMessage(self.env, req.args['messageid'])
            # leaks the subject of the email in the error, wonder if
            # that's a problem...
            req.perm(message.resource).require("MAILINGLIST_VIEW")
            if req.args.get('format') == "raw":
                req.send_header('Content-Disposition', 'attachment')
                req.send_response(200)
                content = message.raw.bytes
                req.send_header('Content-Type', 'application/mbox')
                req.send_header('Content-Length', len(content))
                req.end_headers()
                if req.method != 'HEAD':
                    req.write(content)
                return

            context = Context.from_request(req, message.resource)
            
            data['message'] = message
            data['attachments'] = AttachmentModule(self.env).attachment_data(context)

            add_link(req, 'up', get_resource_url(self.env, message.conversation.resource, req.href,
                                                 offset=data['offset']),
                     _("Back to conversation"))

            prevnext_nav(req, _("Newer message"), _("Older message"), 
                         _("Back to conversation"))

            raw_href = get_resource_url(self.env, message.resource,
                                        req.href, format='raw')
            add_link(req, 'alternate', raw_href, _('mbox'), "application/mbox")

            if 'MAILINGLIST_ADMIN' in req.perm:
                add_ctxtnav(req, tag.a(tag.i(class_="fa fa-cog"), ' Manage List',
                href=req.href.admin('mailinglist', 'lists', message.conversation.mailinglist.emailaddress),
                title='Manage and subscribe users to the %s mailing list' % message.conversation.mailinglist.name))

            return 'mailinglist_message.html', data, None
            
        if 'conversationid' in req.args:
            conversation = MailinglistConversation(self.env, req.args['conversationid'])
            # also leaks the subject of the first email in the error message
            req.perm(conversation.resource).require("MAILINGLIST_VIEW")
            data['conversation'] = conversation
            data['attachmentselect'] = partial(Attachment.select, self.env)
            
            results = Paginator(conversation.messages(), page - 1, self.limit)
            if results.has_next_page:
                next_href = get_resource_url(self.env, conversation.resource, req.href, page=page + 1) 
                add_link(req, 'next', next_href, _('Next Page'))

            if results.has_previous_page:
                prev_href = get_resource_url(self.env, conversation.resource, req.href, page=page - 1) 
                add_link(req, 'prev', prev_href, _('Previous Page'))
            
            shown_pages = results.get_shown_pages()
            pagedata = [{'href': get_resource_url(self.env,
                                                  conversation.resource,
                                                  req.href, page=page),
                         'class': None, 'string': str(page),
                         'title': _('Page %(num)d', num=page)}
                        for page in shown_pages]
            results.shown_pages = pagedata
            results.current_page = {'href': None, 'class': 'current',
                                    'string': str(results.page + 1),
                                    'title': None}
            data['paginator'] = results
            add_link(req, 'up', get_resource_url(self.env, conversation.mailinglist.resource, req.href,
                                                 offset=data['offset']),
                     _("List of conversations"))

            prevnext_nav(req, _("Newer conversation"), _("Older conversation"), 
                         _("Back to list of conversations"))

            if 'MAILINGLIST_ADMIN' in req.perm:
                add_ctxtnav(req, tag.a(tag.i(class_="fa fa-cog"), ' Manage List',
                href=req.href.admin('mailinglist', 'lists', conversation.mailinglist.emailaddress),
                title='Manage and subscribe users to the %s mailing list' % conversation.mailinglist.name))


            # Check if user is already subscribed to mailing list 
            # and add the appropriate subscribe / unsubscribe ribbon option
            if conversation.mailinglist.is_subscribed(req.authname):
                add_ctxtnav(req, tag.form(tag.input(tag.a(tag.i(class_='fa fa-eye-slash'),
                ' Unsubscribe', title='Unsubscribe from the %s mailing list' % conversation.mailinglist.name, id='subscribe-link'),
                name='unsubscribe', value=conversation.mailinglist.emailaddress, class_='hidden'),
                method_='post', action='', id='subscribe-form', class_='hidden'))
            else:
                add_ctxtnav(req, tag.form(tag.input(tag.a(tag.i(class_='fa fa-eye'),
                ' Subscribe', title='Subscribe to the %s mailing list' % conversation.mailinglist.name, id='subscribe-link'),
                name='subscribe', value=conversation.mailinglist.emailaddress, class_='hidden'),
                method_='post', action='', id='subscribe-form', class_='hidden'))

            return 'mailinglist_conversation.html', data, None

        elif 'listname' in req.args:
            mailinglist = Mailinglist.select_by_address(self.env,
                                                        req.args['listname'], localpart=True)
            # leaks the name of the mailinglist
            req.perm(mailinglist.resource).require("MAILINGLIST_VIEW")

            data['mailinglist'] = mailinglist

            results = Paginator(mailinglist.conversations(),
                            page - 1,
                            self.limit)

            if results.has_next_page:
                next_href = get_resource_url(self.env, mailinglist.resource, req.href, page=page + 1) 
                add_link(req, 'next', next_href, _('Next Page'))

            if results.has_previous_page:
                prev_href = get_resource_url(self.env, mailinglist.resource, req.href, page=page - 1) 
                add_link(req, 'prev', prev_href, _('Previous Page'))

            shown_pages = results.get_shown_pages()
            pagedata = [{'href': get_resource_url(self.env,
                                                  mailinglist.resource,
                                                  req.href, page=page),
                         'class': None, 'string': str(page),
                         'title': _('Page %(num)d', num=page)}
                        for page in shown_pages]
            results.shown_pages = pagedata
            results.current_page = {'href': None, 'class': 'current',
                                    'string': str(results.page + 1),
                                    'title': None}
            data['paginator'] = results

            if data['offset'] + data['limit'] < mailinglist.count_conversations():
                add_link(req, 'next',
                         get_resource_url(self.env, mailinglist.resource, req.href,
                                          offset=data['offset']+data['limit']),
                         _("Older conversations"))

            if offset > 0:
                add_link(req, 'prev',
                         get_resource_url(self.env, mailinglist.resource, req.href,
                                          offset=data['offset']-data['limit']),
                         _("Newer conversations"))

            add_link(req, 'up', req.href.mailinglist(), _("List of mailinglists"))

            prevnext_nav(req, _("Newer conversations"), _("Older conversations"), ("Back to Mailinglists"))

            if 'MAILINGLIST_ADMIN' in req.perm:
                add_ctxtnav(req, tag.a(tag.i(class_="fa fa-cog"), ' Manage List',
                href=req.href.admin('mailinglist', 'lists', mailinglist.emailaddress),
                title='Manage and subscribe users to the %s mailing list' % mailinglist.name))

            # Check if user is already subscribed to mailing list 
            # and add the appropriate subscribe / unsubscribe ribbon option
            if mailinglist.is_subscribed(req.authname):
                add_ctxtnav(req, tag.form(tag.input(tag.a(tag.i(class_='fa fa-eye-slash'),
                ' Unsubscribe', title='Unsubscribe from the %s mailing list' % mailinglist.name, id='subscribe-link'),
                name='unsubscribe', value=mailinglist.emailaddress, class_='hidden'),
                method_='post', action='', id='subscribe-form', class_='hidden'))
            else:
                add_ctxtnav(req, tag.form(tag.input(tag.a(tag.i(class_='fa fa-eye'),
                ' Subscribe', title='Subscribe to the %s mailing list' % mailinglist.name, id='subscribe-link'),
                name='subscribe', value=mailinglist.emailaddress, class_='hidden'),
                method_='post', action='', id='subscribe-form', class_='hidden'))

            return 'mailinglist_conversations.html', data, None

        else:
            return 'mailinglist_list.html', data, None