Example #1
0
 def abs_href(self):
     """The application URL"""
     if not self.base_url:
         urlpattern = MultiProductSystem(self.parent).product_base_url
         if not urlpattern:
             self.log.warn("product_base_url option not set in "
                           "configuration, generated links may be "
                           "incorrect")
             urlpattern = 'products/$(prefix)s'
         envname = os.path.basename(self.parent.path)
         prefix = unicode_quote(self.product.prefix, safe="")
         name = unicode_quote(self.product.name, safe="")
         url = urlpattern.replace('$(', '%(') \
                         .replace('%(envname)s', envname) \
                         .replace('%(prefix)s', prefix) \
                         .replace('%(name)s', name)
         if urlsplit(url).netloc:
             #  Absolute URLs
             _abs_href = Href(url)
         else:
             # Relative URLs
             parent_href = Href(self.parent.abs_href(),
                                path_safe="/!~*'()%",
                                query_safe="!~*'()%")
             _abs_href = Href(parent_href(url))
     else:
         _abs_href = Href(self.base_url)
     return _abs_href
Example #2
0
 def __add__(self, rhs):
     if not rhs:
         return self.base or '/'
     if rhs.startswith('?'):
         return (self.base or '/') + \
                unicode_quote(rhs, self._printable_safe)
     if not rhs.startswith('/'):
         rhs = '/' + rhs
     return self.base + unicode_quote(rhs, self._printable_safe)
Example #3
0
 def _path_to_url(self, path):
     '''Encode a path in the repos as a fully qualified URL.
     '''
     repos = self._get_repos_direct_object()
     url_parts =  ['file:///', 
                   unicode_quote(repos.path.lstrip('/')),
                   '/',
                   unicode_quote(path.lstrip('/')),
                   ]
     return ''.join(url_parts)
Example #4
0
 def login_as(self, username, password=None):
     if not password:
         password = username
     
     assert 'http://' == self.url[:7]
     unused, protocol, host_and_rest = self.url.partition('http://')
     new_url = "%s%s:%s@%s/login" % (protocol, unicode_quote(username), unicode_quote(password), host_and_rest)
     self.windmill.open(url=new_url)
     self.windmill.waits.forPageLoad()
     self.windmill.waits.forElement(jquery="('ul.metanav li:contains(Logged in as %s)')[0]" % username)
     
     self.windmill.asserts.assertText(
         xpath="//*[contains(@class, 'metanav')]/li[1]", 
         validator='Logged in as %s' % username)
Example #5
0
    def create_milestone(self, name=None, due=None):
        """Creates the specified milestone, with a random name if none is
        provided.  Returns the name of the milestone.
        """
        if name == None:
            name = random_unique_camel()
        milestone_url = self.url + "/admin/ticket/milestones"
        tc.go(milestone_url)
        tc.url(milestone_url)
        tc.formvalue('addmilestone', 'name', name)
        if due:
            # TODO: How should we deal with differences in date formats?
            tc.formvalue('addmilestone', 'duedate', due)
        tc.submit()
        tc.notfind(internal_error)
        tc.notfind('Milestone .* already exists')
        tc.url(milestone_url)
        tc.find(name)

        # Make sure it's on the roadmap.
        tc.follow('Roadmap')
        tc.url(self.url + "/roadmap")
        tc.find('Milestone:.*%s' % name)
        tc.follow(name)
        tc.url('%s/milestone/%s' % (self.url, unicode_quote(name)))
        if not due:
            tc.find('No date set')

        return name
    def import_wiki_attachments(self, template_path):
        """Imports wiki attachments from template using the Attachment API."""

        # check that there are attachments to import
        template_attachment_path = os.path.join(template_path, 'attachments', 'wiki')
        if os.path.isdir(template_attachment_path):

            # clear the wiki attachment table
            @self.env.with_transaction()
            def clear_attachments(db):
                """Clears any wiki attachments from the current attachment table."""

                cursor = db.cursor()
                cursor.execute("DELETE FROM attachment WHERE type='wiki'")

            # move attachment file into the env and insert database row
            filepath = os.path.join(template_path, 'attachment.xml')
            tree = ET.ElementTree(file=filepath)
            for att in tree.getroot():
                attachment = Attachment(self.env, 'wiki', att.attrib['parent_id'])
                attachment.description = att.text
                try:
                    fileobj = open(os.path.join(template_attachment_path, 
                               att.attrib['parent_id'], unicode_quote(att.attrib['name'])))
                    attachment.insert(att.attrib['name'], fileobj, att.attrib['size'])
                except IOError:
                    self.log.info("Unable to import attachment %s", att.attrib['name'])
Example #7
0
 def web_upload(self, req):
     # TODO: handle file upload when POSTing
     files = []
     for fname in sorted(os.listdir(self._dump_path())):
         fpath = os.path.join(self._dump_path(), fname)
         fstat = os.stat(fpath)
         files.append({"name": fname, "url": unicode_quote(fname), "size": fstat.st_size, "date": fstat.st_mtime})
     return {"max_size": self.max_size, "files": files, "action": "upload"}
Example #8
0
 def download_as_csv(self):
     url_template = '%(prefix)s/%(backlog)s'
     backlog_path = url_template % dict(prefix=BACKLOG_URL, backlog='Product Backlog')
     url = self.tester.url + unicode_quote(backlog_path) + '?format=csv'
     tc.go(url)
     tc.code(200)
     csv_export = tc.show()
     csvfile = CSVFile(StringIO(csv_export), None, 'UTF-8')
     return csvfile
Example #9
0
def move_attachment_file(env, parent_realm, parent_id, filename):
    old_path = os.path.join(env.path, 'attachments', parent_realm,
                            unicode_quote(parent_id))
    if filename:
        old_path = os.path.join(old_path, unicode_quote(filename))
    old_path = os.path.normpath(old_path)
    if os.path.isfile(old_path):
        new_path = Attachment._get_path(env.path, parent_realm, parent_id,
                                        filename)
        try:
            os.renames(old_path, new_path)
        except OSError:
            printerr(_("Unable to move attachment from:\n\n"
                       "  %(old_path)s\n\nto:\n\n  %(new_path)s\n",
                       old_path=old_path, new_path=new_path))
            raise
    else:
        env.log.warning("Can't find file for 'attachment:%s:%s:%s', ignoring",
                        filename, parent_realm, parent_id)
Example #10
0
 def runTest(self):
     tc.go(self.tester.url + '/admin/ticket/milestones/' + unicode_quote(self.milestone_name()))
     new_name = self.milestone_name() + 'Renamed'
     tc.formvalue('modifymilestone', 'name', new_name)
     tc.submit('save')
     tc.code(200)
     # Now we expect that the ticket and the sprint have updated milestone
     ticket_page = self.tester.navigate_to_ticket_page(self.tkt_id)
     self.assert_equals(new_name, ticket_page.milestone())
     self.tester.go_to_sprint_edit_page("SprintFor" + self.milestone_name())
     tc.find('for milestone %s</h1>' % new_name)
    def insert(self, filename, fileobj, size, t=None, db=None):
        # FIXME: `t` should probably be switched to `datetime` too
        if not db:
            db = self.env.get_db_cnx()
            handle_ta = True
        else:
            handle_ta = False

        self.size = size and int(size) or 0
        timestamp = int(t or time.time())
        self.date = datetime.fromtimestamp(timestamp, utc)

        # Make sure the path to the attachment is inside the environment
        # attachments directory
        attachments_dir = os.path.join(os.path.normpath(self.env.path),
                                       'attachments')
        commonprefix = os.path.commonprefix([attachments_dir, self.path])
        assert commonprefix == attachments_dir

        if not os.access(self.path, os.F_OK):
            os.makedirs(self.path)
        filename = unicode_quote(filename)
        path, targetfile = create_unique_file(os.path.join(self.path,
                                                           filename))
        try:
            # Note: `path` is an unicode string because `self.path` was one.
            # As it contains only quoted chars and numbers, we can use `ascii`
            basename = os.path.basename(path).encode('ascii')
            filename = unicode_unquote(basename)

            cursor = db.cursor()
            cursor.execute("INSERT INTO attachment "
                           "VALUES (%s,%s,%s,%s,%s,%s,%s,%s)",
                           (self.parent_realm, self.parent_id, filename,
                            self.size, timestamp, self.description,
                            self.author, self.ipnr))
            shutil.copyfileobj(fileobj, targetfile)
            self.resource.id = self.filename = filename

            self.env.log.info('New attachment: %s by %s', self.title,
                              self.author)

            if handle_ta:
                db.commit()

            targetfile.close()

            for listener in AttachmentModule(self.env).change_listeners:
                listener.attachment_added(self)

        finally:
            if not targetfile.closed:
                targetfile.close()
Example #12
0
def files(pgc,myc,users):

    print "Starting files"

    pgc.execute("""SELECT * FROM files""");
    count = 0
    while (1):
        row = pgc.fetchone()
        if row == None:
            break
        
        # create the file
        attached_to_pagename = row[6]
        file_blob = str(row[1])
        file_blob.decode('latin1')
        
        path = ATTACHMENTS+'/'+unicode_quote(attached_to_pagename)
        if not os.access(path, os.F_OK):
            os.makedirs(path)
        filename = path+'/'+row[0]
        outfile = open(filename,'w')
        outfile.write(file_blob)
        outfile.close()
        print "Wrote %s \n" % filename

        # insert into database
        upload_time = int(row[2])
        if users.has_key(row[3]):
            username = users[row[3]]
        else:
            username = ''
        
        sql = "INSERT INTO attachment VALUES ('wiki',%s,%s,%s,%s,%s,%s,%s)"
        try:
            myc.execute(sql,(attached_to_pagename,
                             row[0],
                             len(file_blob),
                             upload_time,
                             '',
                             username,
                             row[5]))
        except:
            print "Error inserting "+row[0]+"\n"
            continue
        count += 1

    mydb.commit()
    print "Finished %d rows of files" % count
Example #13
0
    def render_macro(self, req, name, content):
        args = content.split(",")

        part = model.BlogPart(self.env, args[0])
        if part:
            body = part.body
            argnum = int(part.argnum)
            i = 1
            for arg in args[1:]:
                a = unicode_quote(arg)
                body = body.replace("%" + "arg_%d" % (i) + "%", a)
                i = i + 1

            return body
        else:
            return "Not Impremented %s" % args[0]
Example #14
0
 def _do_dump(self, directory, *names):
     if not names:
         names = ['*']
     pages = self.get_wiki_list()
     if not os.path.isdir(directory):
         if not os.path.exists(directory):
             os.mkdir(directory)
         else:
             raise AdminCommandError(_("'%(name)s' is not a directory",
                                       name=path_to_unicode(directory)))
     for p in pages:
         if any(p == name or (name.endswith('*')
                              and p.startswith(name[:-1]))
                for name in names):
             dst = os.path.join(directory, unicode_quote(p, ''))
             printout(' %s => %s' % (p, dst))
             self.export_page(p, dst)
Example #15
0
    def go_to_report(self, id, args=None):
        """Surf to the specified report.

        Assumes the report exists. Report variables will be appended if
        specified.

        :param id: id of the report
        :param args: may optionally specify a dictionary of arguments to
                     be encoded as a query string
        """
        report_url = self.url + "/report/%s" % id
        if args:
            arglist = []
            for param, value in args.items():
                arglist.append('%s=%s' % (param.upper(), unicode_quote(value)))
            report_url += '?' + '&'.join(arglist)
        tc.go(report_url)
        tc.url(report_url.encode('string-escape').replace('?', '\?'))
Example #16
0
 def __init__(self, page_name, page_version, template, env, options):
     self.page_name = page_name
     self.page_version = page_version
     self.template = template
     self.env = env
     self.options = options
     self.template_dirs = [
         self.env.get_templates_dir(),
         resource_filename(__name__, 'templates'),
     ]
     self.template_dirs.insert(0, os.path.join(self.env.path,
             'attachments', "wiki", unicode_quote(self.page_name)))
     self.xml = {
         "content": "",
         "styles": "",
     }
     self.tmpdir = tempfile.mkdtemp(prefix="trac-odtexport")
     self.styles = {}
     self.autostyles = {}
     self.style_name_re = re.compile('style:name="([^"]+)"') 
     self.fonts = {}
     self.zfile = None
Example #17
0
    def __call__(self, *args, **kw):
        href = self.base
        if href and href[-1] == '/':
            href = href[:-1]
        params = []

        def add_param(name, value):
            if type(value) in (list, tuple):
                for i in [i for i in value if i != None]:
                    params.append((name, i))
            elif v != None:
                params.append((name, value))

        if args:
            lastp = args[-1]
            if lastp and type(lastp) is dict:
                for k,v in lastp.items():
                    add_param(k, v)
                args = args[:-1]
            elif lastp and type(lastp) in (list, tuple):
                for k,v in lastp:
                    add_param(k, v)
                args = args[:-1]

        # build the path
        path = '/'.join([unicode_quote(unicode(arg).strip('/')) for arg in args
                         if arg != None])
        if path:
            href += '/' + path

        # assemble the query string
        for k,v in kw.items():
            add_param(k, v)

        if params:
            href += '?' + unicode_urlencode(params)

        return href
    def __init__(self):
        api_url = unicode_quote(self.job_url, '/%:@')
        if api_url and api_url[-1] != '/':
            api_url += '/'
        api_url += 'api/xml'

        pwd_mgr = urllib2.HTTPPasswordMgrWithDefaultRealm()
        pwd_mgr.add_password(None, api_url, self.username, self.password)

        b_auth = urllib2.HTTPBasicAuthHandler(pwd_mgr)
        d_auth = urllib2.HTTPDigestAuthHandler(pwd_mgr)

        self.url_opener = urllib2.build_opener(b_auth, d_auth)

        self.env.log.debug("registered auth-handler for '%s', username='******'",
                           api_url, self.username)

        if '/job/' in api_url:
            path = '/*/build[timestamp>=%(start)s][timestamp<=%(stop)s]'
            depth = 1
            if self.disp_mod:
                path += ('|/*/module/build'
                         '[timestamp>=%(start)s][timestamp<=%(stop)s]')
                depth += 1
        else:
            path = '/*/job/build[timestamp>=%(start)s][timestamp<=%(stop)s]'
            depth = 2
            if self.disp_mod:
                path += ('|/*/job/module/build'
                         '[timestamp>=%(start)s][timestamp<=%(stop)s]')
                depth += 1

        self.info_url = ('%s?xpath=%s&depth=%s'
                         '&exclude=//action|//artifact|//changeSet'
                         '&wrapper=builds' %
                         (api_url.replace('%', '%%'), path, depth))

        self.env.log.debug("Build-info url: '%s'", self.info_url)
Example #19
0
    def __call__(self, *args, **kw):
        href = self.base
        params = []

        def add_param(name, value):
            if isinstance(value, (list, tuple)):
                for i in [i for i in value if i is not None]:
                    params.append((name, i))
            elif value is not None:
                params.append((name, value))

        if args:
            lastp = args[-1]
            if isinstance(lastp, dict):
                for k, v in lastp.items():
                    add_param(k, v)
                args = args[:-1]
            elif isinstance(lastp, (list, tuple)):
                for k, v in lastp:
                    add_param(k, v)
                args = args[:-1]

        # build the path
        path = '/'.join(unicode_quote(unicode(arg).strip('/'), self.path_safe)
                        for arg in args if arg is not None)
        if path:
            href += '/' + slashes_re.sub('/', path).lstrip('/')
        elif not href:
            href = '/'

        # assemble the query string
        for k, v in kw.items():
            add_param(k[:-1] if k.endswith('_') else k, v)
        if params:
            href += '?' + unicode_urlencode(params, self.query_safe)

        return href
Example #20
0
    def __init__(self):
        # get base api url
        api_url = unicode_quote(self.job_url, '/%:@')
        if api_url and api_url[-1] != '/':
            api_url += '/'
        api_url += 'api/python'

        # set up http authentication
        if self.username and self.api_token:
            handlers = [
                self.HTTPOpenHandlerBasicAuthNoChallenge(self.username,
                                                         self.api_token)
            ]
        elif self.username and self.password:
            pwd_mgr = urllib2.HTTPPasswordMgrWithDefaultRealm()
            pwd_mgr.add_password(None, api_url, self.username, self.password)

            b_auth = urllib2.HTTPBasicAuthHandler(pwd_mgr)
            d_auth = urllib2.HTTPDigestAuthHandler(pwd_mgr)

            handlers = [ b_auth, d_auth, self.HudsonFormLoginHandler(self) ]
        else:
            handlers = []

        self.url_opener = urllib2.build_opener(*handlers)
        if handlers:
            self.env.log.debug("registered auth-handlers for '%s', " \
                               "username='******'", api_url, self.username)

        # construct tree=... parameter to query for the desired items
        tree = '%(b)s'
        if self.disp_mod:
            tree += ',modules[%(b)s]'
        if '/job/' not in api_url:
            tree = 'jobs[' + tree + ']'

        items = 'builds[building,timestamp,duration,result,description,url,' \
                'fullDisplayName'

        elems = []
        if self.list_changesets:
            elems.append('revision')
            elems.append('id')
        if 'author' in self.disp_culprit or 'authors' in self.disp_culprit:
            elems.append('user')
            elems.append('author[fullName]')
        if elems:
            items += ',changeSet[items[%s]]' % ','.join(elems)

        if 'culprit' in self.disp_culprit or 'culprits' in self.disp_culprit:
            items += ',culprits[fullName]'

        if 'starter' in self.disp_culprit:
            items += ',actions[causes[userName]]'

        items += ']'

        # assemble final url
        tree = tree % {'b': items}
        self.info_url = '%s?tree=%s' % (api_url, tree)

        self.env.log.debug("Build-info url: '%s'", self.info_url)
Example #21
0
    def dispatch(self, req):
        """Find a registered handler that matches the request and let
        it process it.

        In addition, this method initializes the data dictionary
        passed to the the template and adds the web site chrome.
        """
        self.log.debug('Dispatching %r', req)
        chrome = Chrome(self.env)

        # Setup request callbacks for lazily-evaluated properties
        req.callbacks.update({
            'authname': self.authenticate,
            'chrome': chrome.prepare_request,
            'perm': self._get_perm,
            'session': self._get_session,
            'locale': self._get_locale,
            'lc_time': self._get_lc_time,
            'tz': self._get_timezone,
            'form_token': self._get_form_token,
            'use_xsendfile': self._get_use_xsendfile,
            'xsendfile_header': self._get_xsendfile_header,
        })

        try:
            try:
                # Select the component that should handle the request
                chosen_handler = None
                try:
                    for handler in self.handlers:
                        if handler.match_request(req):
                            chosen_handler = handler
                            break
                    if not chosen_handler:
                        if not req.path_info or req.path_info == '/':
                            chosen_handler = self.default_handler
                    # pre-process any incoming request, whether a handler
                    # was found or not
                    chosen_handler = \
                        self._pre_process_request(req, chosen_handler)
                except TracError, e:
                    raise HTTPInternalError(e)
                if not chosen_handler:
                    if req.path_info.endswith('/'):
                        # Strip trailing / and redirect
                        target = unicode_quote(req.path_info.rstrip('/'))
                        if req.query_string:
                            target += '?' + req.query_string
                        req.redirect(req.href + target, permanent=True)
                    raise HTTPNotFound('No handler matched request to %s',
                                       req.path_info)

                req.callbacks['chrome'] = partial(chrome.prepare_request,
                                                  handler=chosen_handler)

                # Protect against CSRF attacks: we validate the form token
                # for all POST requests with a content-type corresponding
                # to form submissions
                if req.method == 'POST':
                    ctype = req.get_header('Content-Type')
                    if ctype:
                        ctype, options = cgi.parse_header(ctype)
                    if ctype in ('application/x-www-form-urlencoded',
                                 'multipart/form-data') and \
                            req.args.get('__FORM_TOKEN') != req.form_token:
                        if self.env.secure_cookies and req.scheme == 'http':
                            msg = _('Secure cookies are enabled, you must '
                                    'use https to submit forms.')
                        else:
                            msg = _('Do you have cookies enabled?')
                        raise HTTPBadRequest(_('Missing or invalid form token.'
                                               ' %(msg)s', msg=msg))

                # Process the request and render the template
                resp = chosen_handler.process_request(req)
                if resp:
                    if len(resp) == 2: # old Clearsilver template and HDF data
                        self.log.error("Clearsilver template are no longer "
                                       "supported (%s)", resp[0])
                        raise TracError(
                            _("Clearsilver templates are no longer supported, "
                              "please contact your Trac administrator."))
                    # Genshi
                    template, data, content_type = \
                              self._post_process_request(req, *resp)
                    if 'hdfdump' in req.args:
                        req.perm.require('TRAC_ADMIN')
                        # debugging helper - no need to render first
                        out = StringIO.StringIO()
                        pprint(data, out)
                        req.send(out.getvalue(), 'text/plain')

                    output = chrome.render_template(
                            req, template, data, content_type,
                            iterable=chrome.use_chunked_encoding)
                    req.send(output, content_type or 'text/html')
                else:
                    self._post_process_request(req)
            except RequestDone:
                raise
            except:
                # post-process the request in case of errors
                err = sys.exc_info()
                try:
                    self._post_process_request(req)
                except RequestDone:
                    raise
                except Exception, e:
                    self.log.error("Exception caught while post-processing"
                                   " request: %s",
                                   exception_to_unicode(e, traceback=True))
                raise err[0], err[1], err[2]
    def expand_macro(self, formatter, name, content, args=None):
        req = formatter.req
        # prepare options
        options, query_args = parse_options(self.env, content,
                                            copy.copy(DEFAULT_OPTIONS))

        query_args[self.remaining_field + "!"] = None
        tickets = execute_query(self.env, req, query_args)

        sum = 0.0
        estimations = {}
        for ticket in tickets:
            if ticket['status'] in self.closed_states:
                continue
            try:
                estimation = float(ticket[self.remaining_field])
                owner = ticket['owner']
                sum += estimation
                if owner in estimations:
                    estimations[owner] += estimation
                else:
                    estimations[owner] = estimation
            except:
                pass

        estimations_string = []
        labels = []
        for owner, estimation in estimations.iteritems():
            # Note: Unconditional obfuscation of owner in case it represents
            # an email adress, and as the chart API doesn't support SSL
            # (plain http transfer only, from either client or server).
            labels.append("%s %g%s" % (obfuscate_email_address(owner),
                                       round(estimation, 2),
                                       self.estimation_suffix))
            estimations_string.append(str(int(estimation)))

        # Title
        title = 'Workload'

        # calculate remaining work time
        if options.get('today') and options.get('enddate'):
            currentdate = options['today']
            day = timedelta(days=1)
            days_remaining = 0
            while currentdate <= options['enddate']:
                if currentdate.weekday() < 5:
                    days_remaining += 1
                currentdate += day
            title += ' %g%s (~%s workdays left)' % (round(sum, 2),
                                                    self.estimation_suffix,
                                                    days_remaining)

        chart_args = unicode_urlencode(
            {'chs': '%sx%s' % (options['width'], options['height']),
             'chf': 'bg,s,00000000',
             'chd': 't:%s' % ",".join(estimations_string),
             'cht': 'p3',
             'chtt': title,
             'chl': "|".join(labels),
             'chco': options['color']})
        self.log.debug("WorkloadChart data: %s", chart_args)
        if self.serverside_charts:
            return tag.image(
                src="%s?data=%s" % (req.href.estimationtools('chart'),
                                    unicode_quote(chart_args)),
                alt="Workload Chart (server)")
        else:
            return tag.image(
                src="http://chart.googleapis.com/chart?%s" % chart_args,
                alt="Workload Chart (client)")
Example #23
0
    def runTest(self):
        self._tester.login_as(Usernames.admin)
        page_url = self._tester.url + '/admin/agilo/teams'
        #TODO: find a way to test with umlaut without compromising the encoding
        team_name = self.classname() + 'Team'
        utf8_team_name = team_name.encode('UTF-8')
        team_desc = "'''Testdescription'''"
        self._tester.create_new_team(team_name)

        # set description
        tc.fv('modcomp', 'description', team_desc)
        tc.submit('save')

        # back at list view
        tc.url(page_url)
        tc.code(200)

        # add a new team and team member
        #TODO: find a way to test team names with umlaut without assuming the
        # locale of the testing system is UTF-8.de_DE
        member_name = self.classname() + 'Member'
        utf8_member_name = member_name.encode('UTF-8')
        member_desc = "Goldmember"

        team_name2 = "T-team"
        tc.fv('addteam', 'name', team_name2)
        tc.submit('add')
        tc.code(200)

        tc.fv('modcomp', 'team_member', utf8_member_name)
        tc.fv('modcomp', 'member_description', member_desc)
        tc.submit('add')
        tc.code(200)

        try:
            # IF the accountmanagerplugin is enabled has to appear
            #       the Confirm user creation frame
            tc.find('Create new user')
        except TwillAssertionError:
            account_manager_plugin_enabled = False
        else:
            account_manager_plugin_enabled = True
            tc.fv('modcomp', 'createUser_ok', 'click')
            tc.submit('createUser_ok')
            tc.code(200)

        # correct team selected?
        tc.find('<option selected="selected">%s</option>' % team_name2)
        tc.find(utf8_member_name)
        tc.find(member_desc)

        # set new value for mondays and tuesdays
        tc.fv('modcomp', 'ts_0', '0')
        tc.fv('modcomp', 'ts_1', '')
        tc.submit('save')

        # back at team page
        tc.url("%s/%s" % (page_url, quote(team_name2)))
        tc.find(utf8_member_name)
        # three days x 6h
        tc.find('18.0h')

        user_not_confirmed = None
        if account_manager_plugin_enabled:
            # start to add a new user as team member but cancel at last
            user_not_confirmed = 'user-not-confirmed'
            tc.fv('modcomp', 'team_member', user_not_confirmed)
            tc.submit('add')
            # abort user creation
            tc.find('Create new user')
            tc.fv('modcomp', 'createUser_cancel', 'click')
            tc.submit('createUser_cancel')

        # back at team page
        tc.url("%s/%s" % (page_url, quote(team_name2)))
        tc.find(utf8_member_name)
        if user_not_confirmed is not None:
            tc.notfind(user_not_confirmed)

        # change team
        tc.follow(utf8_member_name)
        tc.fv('modcomp', 'team', utf8_team_name)
        tc.submit('save')
        tc.notfind(utf8_member_name)
        tc.go("%s/%s" % (page_url, unicode_quote(team_name)))
        tc.find(utf8_member_name)

        # --------- unassigned team members functionality --------
        # add another team member to team 1
        member_name2 = 'member #2'
        tc.fv('modcomp', 'team_member', member_name2)
        tc.submit('add')

        if account_manager_plugin_enabled:
            tc.find('Create new user')
            tc.find(member_name2)
            tc.fv('modcomp', 'createUser_ok', 'click')
            tc.submit('createUser_ok')

        tc.fv('modcomp', 'member_description', '')
        tc.submit('save')

        # back at team list, delete first team member
        tc.url("%s/%s" % (page_url, unicode_quote(team_name)))
        tc.fv('modcomp', 'delete', utf8_member_name)
        tc.submit('save')
        # same url, should not list this member anymore
        tc.url("%s/%s" % (page_url, unicode_quote(team_name)))
        tc.notfind(utf8_member_name)

        # select the other one but cancel back to team list. Formvalue
        # must use True here because Twill treats checkboxes differently
        # when there are several with the same name or only one
        tc.fv('modcomp', 'delete', True)
        tc.submit('cancel')
        tc.go(page_url)
        tc.follow('Unassigned team members')

        # shows the member we deleted, not the one we canceled on
        tc.url(page_url + "/unassigned")
        tc.find(utf8_member_name)
        tc.notfind(member_name2)

        # completely delete the team member
        tc.fv('modcomp', 'delete', True)
        tc.submit('remove')

        # no team members without teams anymore
        tc.url(page_url)
        tc.notfind('unassigned')

        # now delete the teams
        tc.fv('team_table', 'delete', utf8_team_name)
        tc.fv('team_table', 'delete', team_name2)
        tc.submit()

        tc.notfind(utf8_team_name)
        tc.notfind(team_name2)
        tc.find('unassigned')
Example #24
0
    def runTest(self):
        self._tester.login_as(Usernames.admin)
        page_url = self._tester.url + '/admin/agilo/teams'
        #TODO: find a way to test with umlaut without compromising the encoding
        team_name = self.classname() + 'Team'
        utf8_team_name = team_name.encode('UTF-8')
        team_desc = "'''Testdescription'''"
        self._tester.create_new_team(team_name)

        # set description
        tc.fv('modcomp', 'description', team_desc)
        tc.submit('save')
        
        # back at list view
        tc.url(page_url)
        tc.code(200)
        
        # add a new team and team member
        #TODO: find a way to test team names with umlaut without assuming the
        # locale of the testing system is UTF-8.de_DE
        member_name = self.classname() + 'Member'
        utf8_member_name = member_name.encode('UTF-8')
        member_desc = "Goldmember"

        team_name2 = "T-team"
        tc.fv('addteam', 'name', team_name2)
        tc.submit('add')
        tc.code(200)
        
        tc.fv('modcomp', 'team_member', utf8_member_name)
        tc.fv('modcomp', 'member_description', member_desc)
        tc.submit('add')
        tc.code(200)
        
        try:
            # IF the accountmanagerplugin is enabled has to appear 
            #       the Confirm user creation frame
            tc.find('Create new user')
        except TwillAssertionError:
            account_manager_plugin_enabled = False
        else:
            account_manager_plugin_enabled = True
            tc.fv('modcomp', 'createUser_ok', 'click')
            tc.submit('createUser_ok')
            tc.code(200)

        # correct team selected?
        tc.find('<option selected="selected">%s</option>' % team_name2)
        tc.find(utf8_member_name)
        tc.find(member_desc)
        
        # set new value for mondays and tuesdays
        tc.fv('modcomp', 'ts_0', '0')
        tc.fv('modcomp', 'ts_1', '')
        tc.submit('save')
        
        # back at team page
        tc.url("%s/%s" % (page_url, quote(team_name2)))
        tc.find(utf8_member_name)
        # three days x 6h
        tc.find('18.0h')
        
        user_not_confirmed = None
        if account_manager_plugin_enabled:
            # start to add a new user as team member but cancel at last
            user_not_confirmed = 'user-not-confirmed'
            tc.fv('modcomp', 'team_member', user_not_confirmed)
            tc.submit('add')
            # abort user creation
            tc.find('Create new user')
            tc.fv('modcomp', 'createUser_cancel', 'click')
            tc.submit('createUser_cancel')
        
        # back at team page
        tc.url("%s/%s" % (page_url, quote(team_name2)))
        tc.find(utf8_member_name)
        if user_not_confirmed is not None:
            tc.notfind(user_not_confirmed)
        
        # change team
        tc.follow(utf8_member_name)
        tc.fv('modcomp', 'team', utf8_team_name)
        tc.submit('save')
        tc.notfind(utf8_member_name)
        tc.go("%s/%s" % (page_url, unicode_quote(team_name)))
        tc.find(utf8_member_name)
        
        # --------- unassigned team members functionality --------
        # add another team member to team 1
        member_name2 = 'member #2'
        tc.fv('modcomp', 'team_member', member_name2)
        tc.submit('add')

        if account_manager_plugin_enabled:
            tc.find('Create new user')
            tc.find(member_name2)
            tc.fv('modcomp', 'createUser_ok', 'click')
            tc.submit('createUser_ok')

        tc.fv('modcomp', 'member_description', '')
        tc.submit('save')
        
        # back at team list, delete first team member
        tc.url("%s/%s" % (page_url, unicode_quote(team_name)))
        tc.fv('modcomp', 'delete', utf8_member_name)
        tc.submit('save')
        # same url, should not list this member anymore
        tc.url("%s/%s" % (page_url, unicode_quote(team_name)))
        tc.notfind(utf8_member_name)
        
        # select the other one but cancel back to team list. Formvalue
        # must use True here because Twill treats checkboxes differently
        # when there are several with the same name or only one
        tc.fv('modcomp', 'delete', True)
        tc.submit('cancel')
        tc.go(page_url)
        tc.follow('Unassigned team members')
        
        # shows the member we deleted, not the one we canceled on
        tc.url(page_url + "/unassigned")
        tc.find(utf8_member_name)
        tc.notfind(member_name2)
        
        # completely delete the team member
        tc.fv('modcomp', 'delete', True)
        tc.submit('remove')
        
        # no team members without teams anymore
        tc.url(page_url)
        tc.notfind('unassigned')
        
        # now delete the teams
        tc.fv('team_table', 'delete', utf8_team_name)
        tc.fv('team_table', 'delete', team_name2)
        tc.submit()
        
        tc.notfind(utf8_team_name)
        tc.notfind(team_name2)
        tc.find('unassigned')
Example #25
0
    def expand_macro(self, formatter, name, content):
        # args will be null if the macro is called without parenthesis.
        if not content:
            return ''
        # parse arguments
        # we expect the 1st argument to be a filename (filespec)
        args = [stripws(arg) for arg
                             in self._split_args_re.split(content)[1::2]]
        # strip unicode white-spaces and ZWSPs are copied from attachments
        # section (#10668)
        filespec = args.pop(0)

        # style information
        attr = {}
        style = {}
        link = ''
        # helper for the special case `source:`
        #
        from trac.versioncontrol.web_ui import BrowserModule
        # FIXME: somehow use ResourceSystem.get_known_realms()
        #        ... or directly trac.wiki.extract_link
        try:
            browser_links = [res[0] for res in
                             BrowserModule(self.env).get_link_resolvers()]
        except Exception:
            browser_links = []
        while args:
            arg = args.pop(0)
            if self._size_re.match(arg):
                # 'width' keyword
                attr['width'] = arg
            elif arg == 'nolink':
                link = None
            elif arg.startswith('link='):
                val = arg.split('=', 1)[1]
                elt = extract_link(self.env, formatter.context, val.strip())
                elt = find_element(elt, 'href')
                link = None
                if elt is not None:
                    link = elt.attrib.get('href')
            elif arg in ('left', 'right'):
                style['float'] = arg
            elif arg == 'center':
                style['margin-left'] = style['margin-right'] = 'auto'
                style['display'] = 'block'
                style.pop('margin', '')
            elif arg in ('top', 'bottom', 'middle'):
                style['vertical-align'] = arg
            else:
                match = self._attr_re.match(arg)
                if match:
                    key, val = match.groups()
                    if (key == 'align' and
                            val in ('left', 'right', 'center')) or \
                        (key == 'valign' and
                            val in ('top', 'middle', 'bottom')):
                        args.append(val)
                    elif key in ('margin-top', 'margin-bottom'):
                        style[key] = ' %dpx' % int(val)
                    elif key in ('margin', 'margin-left', 'margin-right') \
                             and 'display' not in style:
                        style[key] = ' %dpx' % int(val)
                    elif key == 'border':
                        style['border'] = ' %dpx solid' % int(val)
                    else:
                        m = self._quoted_re.search(val)  # unquote "..." and '...'
                        if m:
                            val = m.group(1)
                        attr[str(key)] = val  # will be used as a __call__ kwd

        if self._quoted_re.match(filespec):
            filespec = filespec.strip('\'"')
        # parse filespec argument to get realm and id if contained.
        parts = [i.strip('''['"]''')
                 for i in self._split_filespec_re.split(filespec)[1::2]]
        url = raw_url = desc = None
        attachment = None
        if parts and parts[0] in ('http', 'https', 'ftp', 'data'):  # absolute
            raw_url = url = filespec
            desc = url.rsplit('?')[0]
        elif filespec.startswith('//'):       # server-relative
            raw_url = url = filespec[1:]
            desc = url.rsplit('?')[0]
        elif filespec.startswith('/'):        # project-relative
            params = ''
            if '?' in filespec:
                filespec, params = filespec.rsplit('?', 1)
            url = formatter.href(filespec)
            if params:
                url += '?' + params
            raw_url, desc = url, filespec
        elif len(parts) == 3:                 # realm:id:attachment-filename
            #                                 # or intertrac:realm:id
            realm, id, filename = parts
            intertrac_target = "%s:%s" % (id, filename)
            it = formatter.get_intertrac_url(realm, intertrac_target)
            if it:
                url, desc = it
                raw_url = url + unicode_quote('?format=raw')
            else:
                attachment = Resource(realm, id).child('attachment', filename)
        elif len(parts) == 2:
            realm, filename = parts
            if realm in browser_links:  # source:path
                # TODO: use context here as well
                rev = None
                if '@' in filename:
                    filename, rev = filename.rsplit('@', 1)
                url = formatter.href.browser(filename, rev=rev)
                raw_url = formatter.href.browser(filename, rev=rev,
                                                 format='raw')
                desc = filespec
            else:  # #ticket:attachment or WikiPage:attachment
                # FIXME: do something generic about shorthand forms...
                realm = None
                id, filename = parts
                if id and id[0] == '#':
                    realm = 'ticket'
                    id = id[1:]
                elif id == 'htdocs':
                    raw_url = url = formatter.href.chrome('site', filename)
                    desc = os.path.basename(filename)
                elif id == 'shared':
                    raw_url = url = formatter.href.chrome('shared', filename)
                    desc = os.path.basename(filename)
                else:
                    realm = 'wiki'
                if realm:
                    attachment = Resource(realm, id).child('attachment',
                                                           filename)
        elif len(parts) == 1:  # it's an attachment of the current resource
            attachment = formatter.resource.child('attachment', filespec)
        else:
            raise TracError(_("No filespec given"))
        if attachment and 'ATTACHMENT_VIEW' in formatter.perm(attachment):
            url = get_resource_url(self.env, attachment, formatter.href)
            raw_url = get_resource_url(self.env, attachment, formatter.href,
                                       format='raw')
            try:
                desc = get_resource_summary(self.env, attachment)
            except ResourceNotFound:
                raw_url = chrome_resource_path(formatter.context.req,
                                               'common/attachment.png')
                desc = _('No image "%(id)s" attached to %(parent)s',
                         id=attachment.id,
                         parent=get_resource_name(self.env, attachment.parent))
        for key in ('title', 'alt'):
            if desc and key not in attr:
                attr[key] = desc
        if style:
            attr['style'] = '; '.join('%s:%s' % (k, escape(v))
                                      for k, v in style.iteritems())
        result = tag.img(src=raw_url, **attr)
        if link is not None:
            result = tag.a(result, href=link or url,
                           style='padding:0; border:none')
        return result
Example #26
0
 def test_unicode_quote(self):
     self.assertEqual(u'the%20%C3%9C%20thing',
                      unicode_quote(u'the Ü thing'))
     self.assertEqual(u'%2520%C3%9C%20%2520', unicode_quote(u'%20Ü %20'))
Example #27
0
    def expand_macro(self, formatter, name, content):
        # args will be null if the macro is called without parenthesis.
        if not content:
            return ''
        # parse arguments
        # we expect the 1st argument to be a filename (filespec)
        args = content.split(',')
        if len(args) == 0:
            raise Exception("No argument.")
        filespec = args.pop(0)

        # style information
        size_re = re.compile('[0-9]+(%|px)?$')
        attr_re = re.compile('(align|valign|border|width|height|alt'
                             '|margin(?:-(?:left|right|top|bottom))?'
                             '|title|longdesc|class|id|usemap)=(.+)')
        quoted_re = re.compile("(?:[\"'])(.*)(?:[\"'])$")
        attr = {}
        style = {}
        link = ''
        # helper for the special case `source:`
        #
        from trac.versioncontrol.web_ui import BrowserModule
        # FIXME: somehow use ResourceSystem.get_known_realms()
        #        ... or directly trac.wiki.extract_link
        try:
            browser_links = [res[0] for res in
                             BrowserModule(self.env).get_link_resolvers()]
        except Exception:
            browser_links = []
        while args:
            arg = args.pop(0).strip()
            if size_re.match(arg):
                # 'width' keyword
                attr['width'] = arg
            elif arg == 'nolink':
                link = None
            elif arg.startswith('link='):
                val = arg.split('=', 1)[1]
                elt = extract_link(self.env, formatter.context, val.strip())
                elt = find_element(elt, 'href')
                link = None
                if elt is not None:
                    link = elt.attrib.get('href')
            elif arg in ('left', 'right'):
                style['float'] = arg
            elif arg == 'center':
                style['margin-left'] = style['margin-right'] = 'auto'
                style['display'] = 'block'
                style.pop('margin', '')
            elif arg in ('top', 'bottom', 'middle'):
                style['vertical-align'] = arg
            else:
                match = attr_re.match(arg)
                if match:
                    key, val = match.groups()
                    if (key == 'align' and 
                            val in ('left', 'right', 'center')) or \
                        (key == 'valign' and \
                            val in ('top', 'middle', 'bottom')):
                        args.append(val)
                    elif key in ('margin-top', 'margin-bottom'):
                        style[key] = ' %dpx' % int(val)
                    elif key in ('margin', 'margin-left', 'margin-right') \
                             and 'display' not in style:
                        style[key] = ' %dpx' % int(val)
                    elif key == 'border':
                        style['border'] = ' %dpx solid' % int(val)
                    else:
                        m = quoted_re.search(val) # unquote "..." and '...'
                        if m:
                            val = m.group(1)
                        attr[str(key)] = val # will be used as a __call__ kwd

        # parse filespec argument to get realm and id if contained.
        parts = filespec.split(':')
        url = raw_url = desc = None
        attachment = None
        if (parts and parts[0] in ('http', 'https', 'ftp')): # absolute
            raw_url = url = desc = filespec
        elif filespec.startswith('//'):       # server-relative
            raw_url = url = desc = filespec[1:]
        elif filespec.startswith('/'):        # project-relative
            # use href, but unquote to allow args (use default html escaping)
            raw_url = url = desc = unquote(formatter.href(filespec))
        elif len(parts) == 3:                 # realm:id:attachment-filename
            #                                 # or intertrac:realm:id 
            realm, id, filename = parts
            intertrac_target = "%s:%s" % (id, filename)
            it = formatter.get_intertrac_url(realm, intertrac_target)
            if it:
                url, desc = it
                raw_url = url + unicode_quote('?format=raw')
            else:
                attachment = Resource(realm, id).child('attachment', filename)
        elif len(parts) == 2:
            realm, filename = parts
            if realm in browser_links:   # source:path
                # TODO: use context here as well
                rev = None
                if '@' in filename:
                    filename, rev = filename.rsplit('@', 1)
                url = formatter.href.browser(filename, rev=rev)
                raw_url = formatter.href.browser(filename, rev=rev,
                                                 format='raw')
                desc = filespec
            else: # #ticket:attachment or WikiPage:attachment
                # FIXME: do something generic about shorthand forms...
                realm = None
                id, filename = parts
                if id and id[0] == '#':
                    realm = 'ticket'
                    id = id[1:]
                elif id == 'htdocs':
                    raw_url = url = formatter.href.chrome('site', filename)
                    desc = os.path.basename(filename)
                else:
                    realm = 'wiki'
                if realm:
                    attachment = Resource(realm, id).child('attachment',
                                                           filename)
        elif len(parts) == 1: # it's an attachment of the current resource
            attachment = formatter.resource.child('attachment', filespec)
        else:
            raise TracError('No filespec given')
        if attachment and 'ATTACHMENT_VIEW' in formatter.perm(attachment):
            url = get_resource_url(self.env, attachment, formatter.href)
            raw_url = get_resource_url(self.env, attachment, formatter.href,
                                       format='raw')
            try:
                desc = get_resource_summary(self.env, attachment)
            except ResourceNotFound, e:
                raw_url = formatter.href.chrome('common/attachment.png')
                desc = _('No image "%(id)s" attached to %(parent)s',
                         id=attachment.id,
                         parent=get_resource_name(self.env, attachment.parent))
Example #28
0
    def dispatch(self, req):
        """Find a registered handler that matches the request and let
        it process it.

        In addition, this method initializes the data dictionary
        passed to the the template and adds the web site chrome.
        """
        self.log.debug('Dispatching %r', req)
        chrome = Chrome(self.env)

        try:
            # Select the component that should handle the request
            chosen_handler = None
            for handler in self._request_handlers.values():
                if handler.match_request(req):
                    chosen_handler = handler
                    break
            if not chosen_handler and req.path_info in ('', '/'):
                chosen_handler = self._get_valid_default_handler(req)
            # pre-process any incoming request, whether a handler
            # was found or not
            self.log.debug("Chosen handler is %s", chosen_handler)
            chosen_handler = self._pre_process_request(req, chosen_handler)
            if not chosen_handler:
                if req.path_info.endswith('/'):
                    # Strip trailing / and redirect
                    target = unicode_quote(req.path_info.rstrip('/'))
                    if req.query_string:
                        target += '?' + req.query_string
                    req.redirect(req.href + target, permanent=True)
                raise HTTPNotFound('No handler matched request to %s',
                                   req.path_info)

            req.callbacks['chrome'] = partial(chrome.prepare_request,
                                              handler=chosen_handler)

            # Protect against CSRF attacks: we validate the form token
            # for all POST requests with a content-type corresponding
            # to form submissions
            if req.method == 'POST':
                ctype = req.get_header('Content-Type')
                if ctype:
                    ctype, options = cgi.parse_header(ctype)
                if ctype in ('application/x-www-form-urlencoded',
                             'multipart/form-data') and \
                        req.args.get('__FORM_TOKEN') != req.form_token:
                    if self.env.secure_cookies and req.scheme == 'http':
                        msg = _('Secure cookies are enabled, you must '
                                'use https to submit forms.')
                    else:
                        msg = _('Do you have cookies enabled?')
                    raise HTTPBadRequest(
                        _('Missing or invalid form token.'
                          ' %(msg)s', msg=msg))

            # Process the request and render the template
            resp = chosen_handler.process_request(req)
            if resp:
                resp = self._post_process_request(req, *resp)
                template, data, metadata, method = resp
                if 'hdfdump' in req.args:
                    req.perm.require('TRAC_ADMIN')
                    # debugging helper - no need to render first
                    out = io.BytesIO()
                    pprint(
                        {
                            'template': template,
                            'metadata': metadata,
                            'data': data
                        }, out)
                    req.send(out.getvalue(), 'text/plain')
                self.log.debug("Rendering response with template %s", template)
                iterable = chrome.use_chunked_encoding
                if isinstance(metadata, dict):
                    iterable = metadata.setdefault('iterable', iterable)
                    content_type = metadata.get('content_type')
                else:
                    content_type = metadata
                output = chrome.render_template(req,
                                                template,
                                                data,
                                                metadata,
                                                iterable=iterable,
                                                method=method)
                # TODO (1.5.1) remove iterable and method parameters
                req.send(output, content_type or 'text/html')
            else:
                self.log.debug("Empty or no response from handler. "
                               "Entering post_process_request.")
                self._post_process_request(req)
        except RequestDone:
            raise
        except Exception as e:
            # post-process the request in case of errors
            err = sys.exc_info()
            try:
                self._post_process_request(req)
            except RequestDone:
                pass
            except TracError as e2:
                self.log.warning(
                    "Exception caught while post-processing"
                    " request: %s", exception_to_unicode(e2))
            except Exception as e2:
                if not (type(e) is type(e2) and e.args == e2.args):
                    self.log.error(
                        "Exception caught while post-processing"
                        " request: %s", exception_to_unicode(e2,
                                                             traceback=True))
            if isinstance(e, PermissionError):
                raise HTTPForbidden(e)
            if isinstance(e, ResourceNotFound):
                raise HTTPNotFound(e)
            if isinstance(e, NotImplementedError):
                tb = traceback.extract_tb(err[2])[-1]
                self.log.warning("%s caught from %s:%d in %s: %s",
                                 e.__class__.__name__, tb[0], tb[1], tb[2],
                                 to_unicode(e) or "(no message)")
                raise HTTPInternalServerError(TracNotImplementedError(e))
            if isinstance(e, TracError):
                raise HTTPInternalServerError(e)
            raise err[0], err[1], err[2]
Example #29
0
 def test_unicode_unquote(self):
     u = u'the Ü thing'
     up = u'%20Ü %20'
     self.assertEqual(u, unicode_unquote(unicode_quote(u)))
     self.assertEqual(up, unicode_unquote(unicode_quote(up)))
Example #30
0
    def __init__(self):
        # get base api url
        api_url = unicode_quote(self.job_url, '/%:@')
        if api_url and api_url[-1] != '/':
            api_url += '/'
        api_url += 'api/python'

        # set up http authentication
        if self.username and self.api_token:
            handlers = [
                self.HTTPOpenHandlerBasicAuthNoChallenge(
                    self.username, self.api_token)
            ]
        elif self.username and self.password:
            pwd_mgr = urllib2.HTTPPasswordMgrWithDefaultRealm()
            pwd_mgr.add_password(None, api_url, self.username, self.password)

            b_auth = urllib2.HTTPBasicAuthHandler(pwd_mgr)
            d_auth = urllib2.HTTPDigestAuthHandler(pwd_mgr)

            handlers = [b_auth, d_auth, self.HudsonFormLoginHandler(self)]
        else:
            handlers = []

        self.url_opener = urllib2.build_opener(*handlers)
        if handlers:
            self.env.log.debug("registered auth-handlers for '%s', " \
                               "username='******'", api_url, self.username)

        # construct tree=... parameter to query for the desired items
        tree = '%(b)s'
        if self.disp_mod:
            tree += ',modules[%(b)s]'
        if '/job/' not in api_url:
            tree = 'jobs[' + tree + ']'

        items = 'builds[building,timestamp,duration,result,description,url,' \
                'fullDisplayName'

        elems = []
        if self.list_changesets:
            elems.append('revision')
            elems.append('id')
        if 'author' in self.disp_culprit or 'authors' in self.disp_culprit:
            elems.append('user')
            elems.append('author[fullName]')
        if elems:
            items += ',changeSet[items[%s]]' % ','.join(elems)

        if 'culprit' in self.disp_culprit or 'culprits' in self.disp_culprit:
            items += ',culprits[fullName]'

        if 'starter' in self.disp_culprit:
            items += ',actions[causes[userName]]'

        items += ']'

        # assemble final url
        tree = tree % {'b': items}
        self.info_url = '%s?tree=%s' % (api_url, tree)

        self.env.log.debug("Build-info url: '%s'", self.info_url)
    def expand_macro(self, formatter, name, content, args=None):

        # prepare options
        req = formatter.req
        options, query_args = parse_options(self.env, content,
                                            copy.copy(DEFAULT_OPTIONS))

        if not options['startdate']:
            raise TracError("No start date specified!")

        # minimum time frame is one day
        if options['startdate'] >= options['enddate']:
            options['enddate'] = options['startdate'] + timedelta(days=1)

        # calculate data
        timetable = self._calculate_timetable(options, query_args, req)
        timetable_spent = self._calculate_timetable_spent(options, query_args, req)
        
        # remove weekends
        if not options['weekends']:
            for date in timetable.keys():
                if date.weekday() >= 5:
                    del timetable[date]
                    del timetable_spent[date]

        # scale data
        xdata, ydata, maxhours = self._scale_data(timetable, options)
        xdata_spent, ydata_spent, maxhours_spent = self._scale_data(timetable_spent, options)
        if not options['spent']:
            spentdata = "|0,0|0,0"
        else:
            spentdata = "|%s|%s" % (",".join(xdata_spent), ",".join(ydata_spent))

        # build html for google chart api
        dates = sorted(timetable.keys())
        bottomaxis = "0:|" + "|".join([str(date.day) for date in dates]) + \
                     "|1:|%s/%s|%s/%s" % (dates[0].month, dates[0].year,
                                          dates[- 1].month, dates[- 1].year)
        leftaxis = "2,0,%s" % maxhours

        # add line for expected progress
        if options['expected'] == '0':
            expecteddata = ""
        else:
            expecteddata = "|0,100|%s,0" % (
                round(Decimal(options['expected']) * 100 / maxhours, 2))

        # prepare gridlines
        if options['gridlines'] == '0':
            # create top and right bounding line by using grid
            gridlinesdata = "100.0,100.0,1,0"
        else:
            gridlinesdata = "%s,%s" % (xdata[1], (
                round(Decimal(options['gridlines']) * 100 / maxhours, 4)))

        # mark weekends
        weekends = []
        saturday = None
        index = 0
        halfday = self._round(Decimal("0.5") / (len(dates) - 1))
        for date in dates:
            if date.weekday() == 5:
                saturday = index
            if saturday and date.weekday() == 6:
                weekends.append("R,%s,0,%s,%s" %
                                (options['wecolor'],
                                 self._round((Decimal(
                                     xdata[saturday]) / 100) - halfday),
                                 self._round(
                                     (Decimal(xdata[index]) / 100) + halfday)))
                saturday = None
            index += 1
        # special handling if time period starts with Sundays...
        if len(dates) > 0 and dates[0].weekday() == 6:
            weekends.append("R,%s,0,0.0,%s" % (options['wecolor'], halfday))
        # or ends with Saturday
        if len(dates) > 0 and dates[- 1].weekday() == 5:
            weekends.append(
                "R,%s,0,%s,1.0" % (options['wecolor'], Decimal(1) - halfday))

        # chart title
        title = options.get('title', None)
        if title is None and options.get('milestone'):
            title = options['milestone'].split('|')[0]

        chart_args = unicode_urlencode(
                    {'chs': '%sx%s' % (options['width'], options['height']),
                     'chf': 'c,s,%s|bg,s,00000000' % options['bgcolor'],
                     'chd': 't:%s|%s%s%s' % (",".join(xdata), ",".join(ydata), spentdata, expecteddata),
                     'cht': 'lxy',
                     'chxt': 'x,x,y',
                     'chxl': bottomaxis,
                     'chxr': leftaxis,
                     'chm': "|".join(weekends),
                     'chg': gridlinesdata,
                     'chco': '%s,%s,%s' % (options['color'], options['colorspent'], options['colorexpected']),
                     'chdl': 'Remaining|Spent|Estimated',
                     'chtt': title})
        self.log.debug("BurndownChart data: %s", chart_args)
        if self.serverside_charts:
            return tag.image(
                src="%s?data=%s" % (req.href.estimationtools('chart'),
                                    unicode_quote(chart_args)),
                alt="Burndown Chart (server)")
        else:
            return tag.image(
                src="http://chart.googleapis.com/chart?%s" % chart_args,
                alt="Burndown Chart (client)")
Example #32
0
 def test_unicode_quote(self):
     self.assertEqual(u'the%20%C3%9C%20thing',
                      unicode_quote(u'the Ü thing'))
     self.assertEqual(u'%2520%C3%9C%20%2520',
                      unicode_quote(u'%20Ü %20'))
Example #33
0
    def expand_macro(self, formatter, name, content):
        # args will be null if the macro is called without parenthesis.
        if not content:
            return ''
        # parse arguments
        # we expect the 1st argument to be a filename (filespec)
        args = content.split(',')
        if len(args) == 0:
            raise Exception("No argument.")
        # strip unicode white-spaces and ZWSPs are copied from attachments
        # section (#10668)
        filespec = stripws(args.pop(0))

        # style information
        size_re = re.compile('[0-9]+(%|px)?$')
        attr_re = re.compile('(align|valign|border|width|height|alt'
                             '|margin(?:-(?:left|right|top|bottom))?'
                             '|title|longdesc|class|id|usemap)=(.+)')
        quoted_re = re.compile("(?:[\"'])(.*)(?:[\"'])$")
        attr = {}
        style = {}
        link = ''
        # helper for the special case `source:`
        #
        from trac.versioncontrol.web_ui import BrowserModule
        # FIXME: somehow use ResourceSystem.get_known_realms()
        #        ... or directly trac.wiki.extract_link
        try:
            browser_links = [res[0] for res in
                             BrowserModule(self.env).get_link_resolvers()]
        except Exception:
            browser_links = []
        while args:
            arg = args.pop(0).strip()
            if size_re.match(arg):
                # 'width' keyword
                attr['width'] = arg
            elif arg == 'nolink':
                link = None
            elif arg.startswith('link='):
                val = arg.split('=', 1)[1]
                elt = extract_link(self.env, formatter.context, val.strip())
                elt = find_element(elt, 'href')
                link = None
                if elt is not None:
                    link = elt.attrib.get('href')
            elif arg in ('left', 'right'):
                style['float'] = arg
            elif arg == 'center':
                style['margin-left'] = style['margin-right'] = 'auto'
                style['display'] = 'block'
                style.pop('margin', '')
            elif arg in ('top', 'bottom', 'middle'):
                style['vertical-align'] = arg
            else:
                match = attr_re.match(arg)
                if match:
                    key, val = match.groups()
                    if (key == 'align' and
                            val in ('left', 'right', 'center')) or \
                        (key == 'valign' and \
                            val in ('top', 'middle', 'bottom')):
                        args.append(val)
                    elif key in ('margin-top', 'margin-bottom'):
                        style[key] = ' %dpx' % int(val)
                    elif key in ('margin', 'margin-left', 'margin-right') \
                             and 'display' not in style:
                        style[key] = ' %dpx' % int(val)
                    elif key == 'border':
                        style['border'] = ' %dpx solid' % int(val)
                    else:
                        m = quoted_re.search(val) # unquote "..." and '...'
                        if m:
                            val = m.group(1)
                        attr[str(key)] = val # will be used as a __call__ kwd

        # parse filespec argument to get realm and id if contained.
        parts = filespec.split(':')
        url = raw_url = desc = None
        attachment = None
        if (parts and parts[0] in ('http', 'https', 'ftp')): # absolute
            raw_url = url = filespec
            desc = url.rsplit('?')[0]
        elif filespec.startswith('//'):       # server-relative
            raw_url = url = filespec[1:]
            desc = url.rsplit('?')[0]
        elif filespec.startswith('/'):        # project-relative
            params = ''
            if '?' in filespec:
                filespec, params = filespec.rsplit('?', 1)
            url = formatter.href(filespec)
            if params:
                url += '?' + params
            raw_url, desc = url, filespec
        elif len(parts) == 3:                 # realm:id:attachment-filename
            #                                 # or intertrac:realm:id
            realm, id, filename = parts
            intertrac_target = "%s:%s" % (id, filename)
            it = formatter.get_intertrac_url(realm, intertrac_target)
            if it:
                url, desc = it
                raw_url = url + unicode_quote('?format=raw')
            else:
                attachment = Resource(realm, id).child('attachment', filename)
        elif len(parts) == 2:
            realm, filename = parts
            if realm in browser_links:   # source:path
                # TODO: use context here as well
                rev = None
                if '@' in filename:
                    filename, rev = filename.rsplit('@', 1)
                url = formatter.href.browser(filename, rev=rev)
                raw_url = formatter.href.browser(filename, rev=rev,
                                                 format='raw')
                desc = filespec
            else: # #ticket:attachment or WikiPage:attachment
                # FIXME: do something generic about shorthand forms...
                realm = None
                id, filename = parts
                if id and id[0] == '#':
                    realm = 'ticket'
                    id = id[1:]
                elif id == 'htdocs':
                    raw_url = url = formatter.href.chrome('site', filename)
                    desc = os.path.basename(filename)
                else:
                    realm = 'wiki'
                if realm:
                    attachment = Resource(realm, id).child('attachment',
                                                           filename)
        elif len(parts) == 1: # it's an attachment of the current resource
            attachment = formatter.resource.child('attachment', filespec)
        else:
            raise TracError('No filespec given')
        if attachment and 'ATTACHMENT_VIEW' in formatter.perm(attachment):
            url = get_resource_url(self.env, attachment, formatter.href)
            raw_url = get_resource_url(self.env, attachment, formatter.href,
                                       format='raw')
            try:
                desc = get_resource_summary(self.env, attachment)
            except ResourceNotFound, e:
                raw_url = formatter.href.chrome('common/attachment.png')
                desc = _('No image "%(id)s" attached to %(parent)s',
                         id=attachment.id,
                         parent=get_resource_name(self.env, attachment.parent))
Example #34
0
 def _get_path(self):
     path = os.path.join(self.env.path, 'attachments', self.parent_type,
                         unicode_quote(self.parent_id))
     if self.filename:
         path = os.path.join(path, unicode_quote(self.filename))
     return os.path.normpath(path)
Example #35
0
 def test_unicode_unquote(self):
     u = u'the Ü thing'
     up = u'%20Ü %20'
     self.assertEqual(u, unicode_unquote(unicode_quote(u)))
     self.assertEqual(up, unicode_unquote(unicode_quote(up)))
Example #36
0
    def dispatch(self, req):
        """Find a registered handler that matches the request and let
        it process it.

        In addition, this method initializes the data dictionary
        passed to the the template and adds the web site chrome.
        """
        self.log.debug('Dispatching %r', req)
        chrome = Chrome(self.env)

        # Setup request callbacks for lazily-evaluated properties
        req.callbacks.update({
            'authname': self.authenticate,
            'chrome': chrome.prepare_request,
            'perm': self._get_perm,
            'session': self._get_session,
            'locale': self._get_locale,
            'lc_time': self._get_lc_time,
            'tz': self._get_timezone,
            'form_token': self._get_form_token,
            'use_xsendfile': self._get_use_xsendfile,
            'xsendfile_header': self._get_xsendfile_header,
        })

        try:
            try:
                # Select the component that should handle the request
                chosen_handler = None
                try:
                    for handler in self._request_handlers.values():
                        if handler.match_request(req):
                            chosen_handler = handler
                            break
                    if not chosen_handler and \
                            (not req.path_info or req.path_info == '/'):
                        chosen_handler = self._get_valid_default_handler(req)
                    # pre-process any incoming request, whether a handler
                    # was found or not
                    chosen_handler = \
                        self._pre_process_request(req, chosen_handler)
                except TracError as e:
                    raise HTTPInternalError(e)
                if not chosen_handler:
                    if req.path_info.endswith('/'):
                        # Strip trailing / and redirect
                        target = unicode_quote(req.path_info.rstrip('/'))
                        if req.query_string:
                            target += '?' + req.query_string
                        req.redirect(req.href + target, permanent=True)
                    raise HTTPNotFound('No handler matched request to %s',
                                       req.path_info)

                req.callbacks['chrome'] = partial(chrome.prepare_request,
                                                  handler=chosen_handler)

                # Protect against CSRF attacks: we validate the form token
                # for all POST requests with a content-type corresponding
                # to form submissions
                if req.method == 'POST':
                    ctype = req.get_header('Content-Type')
                    if ctype:
                        ctype, options = cgi.parse_header(ctype)
                    if ctype in ('application/x-www-form-urlencoded',
                                 'multipart/form-data') and \
                            req.args.get('__FORM_TOKEN') != req.form_token:
                        if self.env.secure_cookies and req.scheme == 'http':
                            msg = _('Secure cookies are enabled, you must '
                                    'use https to submit forms.')
                        else:
                            msg = _('Do you have cookies enabled?')
                        raise HTTPBadRequest(
                            _('Missing or invalid form token.'
                              ' %(msg)s',
                              msg=msg))

                # Process the request and render the template
                resp = chosen_handler.process_request(req)
                if resp:
                    if len(resp) == 2:  # old Clearsilver template and HDF data
                        self.log.error(
                            "Clearsilver template are no longer "
                            "supported (%s)", resp[0])
                        raise TracError(
                            _("Clearsilver templates are no longer supported, "
                              "please contact your Trac administrator."))
                    # Genshi
                    template, data, content_type, method = \
                        self._post_process_request(req, *resp)
                    if 'hdfdump' in req.args:
                        req.perm.require('TRAC_ADMIN')
                        # debugging helper - no need to render first
                        out = StringIO()
                        pprint(data, out)
                        req.send(out.getvalue(), 'text/plain')

                    output = chrome.render_template(
                        req,
                        template,
                        data,
                        content_type,
                        method=method,
                        iterable=chrome.use_chunked_encoding)
                    req.send(output, content_type or 'text/html')
                else:
                    self._post_process_request(req)
            except RequestDone:
                raise
            except:
                # post-process the request in case of errors
                err = sys.exc_info()
                try:
                    self._post_process_request(req)
                except RequestDone:
                    raise
                except Exception as e:
                    self.log.error(
                        "Exception caught while post-processing"
                        " request: %s", exception_to_unicode(e,
                                                             traceback=True))
                raise err[0], err[1], err[2]
        except PermissionError as e:
            raise HTTPForbidden(e)
        except ResourceNotFound as e:
            raise HTTPNotFound(e)
        except TracError as e:
            raise HTTPInternalError(e)
Example #37
0
 def test_unicode_quote(self):
     self.assertEqual(u"the%20%C3%9C%20thing", unicode_quote(u"the Ü thing"))
     self.assertEqual(u"%2520%C3%9C%20%2520", unicode_quote(u"%20Ü %20"))
Example #38
0
 def test_unicode_unquote(self):
     u = u"the Ü thing"
     up = u"%20Ü %20"
     self.assertEqual(u, unicode_unquote(unicode_quote(u)))
     self.assertEqual(up, unicode_unquote(unicode_quote(up)))
Example #39
0
    def expand_macro(self, formatter, name, content):
        args = None
        if content:
            content = stripws(content)
            # parse arguments
            # we expect the 1st argument to be a filename (filespec)
            args = [stripws(arg) for arg
                                 in self._split_args_re.split(content)[1::2]]
        if not args:
            return ''
        # strip unicode white-spaces and ZWSPs are copied from attachments
        # section (#10668)
        filespec = args.pop(0)

        # style information
        attr = {}
        style = {}
        link = ''
        # helper for the special case `source:`
        #
        from trac.versioncontrol.web_ui import BrowserModule
        # FIXME: somehow use ResourceSystem.get_known_realms()
        #        ... or directly trac.wiki.extract_link
        try:
            browser_links = [res[0] for res in
                             BrowserModule(self.env).get_link_resolvers()]
        except Exception:
            browser_links = []
        while args:
            arg = args.pop(0)
            if self._size_re.match(arg):
                # 'width' keyword
                attr['width'] = arg
            elif arg == 'nolink':
                link = None
            elif arg.startswith('link='):
                val = arg.split('=', 1)[1]
                elt = extract_link(self.env, formatter.context, val.strip())
                elt = find_element(elt, 'href')
                link = None
                if elt is not None:
                    link = elt.attrib.get('href')
            elif arg in ('left', 'right'):
                style['float'] = arg
            elif arg == 'center':
                style['margin-left'] = style['margin-right'] = 'auto'
                style['display'] = 'block'
                style.pop('margin', '')
            elif arg in ('top', 'bottom', 'middle'):
                style['vertical-align'] = arg
            else:
                match = self._attr_re.match(arg)
                if match:
                    key, val = match.groups()
                    if (key == 'align' and
                            val in ('left', 'right', 'center')) or \
                        (key == 'valign' and
                            val in ('top', 'middle', 'bottom')):
                        args.append(val)
                    elif key in ('margin-top', 'margin-bottom'):
                        style[key] = ' %dpx' % _arg_as_int(val, key, min=1)
                    elif key in ('margin', 'margin-left', 'margin-right') \
                             and 'display' not in style:
                        style[key] = ' %dpx' % _arg_as_int(val, key, min=1)
                    elif key == 'border':
                        style['border'] = ' %dpx solid' % _arg_as_int(val, key)
                    else:
                        m = self._quoted_re.search(val)  # unquote "..." and '...'
                        if m:
                            val = m.group(1)
                        attr[str(key)] = val  # will be used as a __call__ kwd

        if self._quoted_re.match(filespec):
            filespec = filespec.strip('\'"')
        # parse filespec argument to get realm and id if contained.
        parts = [i.strip('\'"')
                 for i in self._split_filespec_re.split(filespec)[1::2]]
        realm = parts[0] if parts else None
        url = raw_url = desc = None
        attachment = None
        interwikimap = InterWikiMap(self.env)
        if realm in ('http', 'https', 'ftp', 'data'):  # absolute
            raw_url = url = filespec
            desc = url.rsplit('?')[0]
        elif realm in interwikimap:
            url, desc = interwikimap.url(realm, ':'.join(parts[1:]))
            raw_url = url
        elif filespec.startswith('//'):       # server-relative
            raw_url = url = filespec[1:]
            desc = url.rsplit('?')[0]
        elif filespec.startswith('/'):        # project-relative
            params = ''
            if '?' in filespec:
                filespec, params = filespec.rsplit('?', 1)
            url = formatter.href(filespec)
            if params:
                url += '?' + params
            raw_url, desc = url, filespec
        elif len(parts) == 3:                 # realm:id:attachment-filename
            #                                 # or intertrac:realm:id
            realm, id, filename = parts
            intertrac_target = "%s:%s" % (id, filename)
            it = formatter.get_intertrac_url(realm, intertrac_target)
            if it:
                url, desc = it
                raw_url = url + unicode_quote('?format=raw')
            else:
                attachment = Resource(realm, id).child('attachment', filename)
        elif len(parts) == 2:
            realm, filename = parts
            if realm in browser_links:  # source:path
                # TODO: use context here as well
                rev = None
                if '@' in filename:
                    filename, rev = filename.rsplit('@', 1)
                url = formatter.href.browser(filename, rev=rev)
                raw_url = formatter.href.browser(filename, rev=rev,
                                                 format='raw')
                desc = filespec
            else:  # #ticket:attachment or WikiPage:attachment
                # FIXME: do something generic about shorthand forms...
                realm = None
                id, filename = parts
                if id and id[0] == '#':
                    realm = 'ticket'
                    id = id[1:]
                elif id == 'htdocs':
                    raw_url = url = formatter.href.chrome('site', filename)
                    desc = os.path.basename(filename)
                elif id == 'shared':
                    raw_url = url = formatter.href.chrome('shared', filename)
                    desc = os.path.basename(filename)
                else:
                    realm = 'wiki'
                if realm:
                    attachment = Resource(realm, id).child('attachment',
                                                           filename)
        elif len(parts) == 1:  # it's an attachment of the current resource
            attachment = formatter.resource.child('attachment', filespec)
        else:
            return system_message(_("No filespec given"))
        if attachment:
            try:
                desc = get_resource_summary(self.env, attachment)
            except ResourceNotFound:
                link = None
                raw_url = chrome_resource_path(formatter.context.req,
                                               'common/attachment.png')
                desc = _('No image "%(id)s" attached to %(parent)s',
                         id=attachment.id,
                         parent=get_resource_name(self.env, attachment.parent))
            else:
                if 'ATTACHMENT_VIEW' in formatter.perm(attachment):
                    url = get_resource_url(self.env, attachment,
                                           formatter.href)
                    raw_url = get_resource_url(self.env, attachment,
                                               formatter.href, format='raw')

        for key in ('title', 'alt'):
            if desc and key not in attr:
                attr[key] = desc
        if style:
            attr['style'] = '; '.join('%s:%s' % (k, escape(v))
                                      for k, v in style.iteritems())
        if not WikiSystem(self.env).is_safe_origin(raw_url,
                                                   formatter.context.req):
            attr['crossorigin'] = 'anonymous'  # avoid password prompt
        result = tag.img(src=raw_url, **attr)
        if link is not None:
            result = tag.a(result, href=link or url,
                           style='padding:0; border:none')
        return result
Example #40
0
    def expand_macro(self, formatter, name, content):
        req = formatter.req
        db = self.env.get_db_cnx()
        # prepare options
        options, query_args = parse_options(db, content,
                                            copy.copy(DEFAULT_OPTIONS))

        query_args[self.estimation_field + "!"] = None
        tickets = execute_query(self.env, req, query_args)

        sum = 0.0
        estimations = {}
        for ticket in tickets:
            if ticket['status'] in self.closed_states:
                continue
            try:
                estimation = float(ticket[self.estimation_field])
                owner = ticket['owner']
                sum += estimation
                if estimations.has_key(owner):
                    estimations[owner] += estimation
                else:
                    estimations[owner] = estimation
            except:
                pass

        estimations_string = []
        labels = []
        for owner, estimation in estimations.iteritems():
            # Note: Unconditional obfuscation of owner in case it represents
            # an email adress, and as the chart API doesn't support SSL
            # (plain http transfer only, from either client or server).
            labels.append("%s %g%s" %
                          (obfuscate_email_address(owner), round(
                              estimation, 2), self.estimation_suffix))
            estimations_string.append(str(int(estimation)))

        # Title
        title = 'Workload'

        # calculate remaining work time
        if options.get('today') and options.get('enddate'):
            currentdate = options['today']
            day = timedelta(days=1)
            days_remaining = 0
            while currentdate <= options['enddate']:
                if currentdate.weekday() < 5:
                    days_remaining += 1
                currentdate += day
            title += ' %g%s (~%s workdays left)' % (round(
                sum, 2), self.estimation_suffix, days_remaining)

        chart_args = unicode_urlencode({
            'chs':
            '%sx%s' % (options['width'], options['height']),
            'chf':
            'bg,s,00000000',
            'chd':
            't:%s' % ",".join(estimations_string),
            'cht':
            'p3',
            'chtt':
            title,
            'chl':
            "|".join(labels),
            'chco':
            options['color']
        })
        self.log.debug("WorkloadChart data: %s" % repr(chart_args))
        if self.serverside_charts:
            return tag.image(
                src="%s?data=%s" %
                (req.href.estimationtools('chart'), unicode_quote(chart_args)),
                alt="Workload Chart (server)")
        else:
            return tag.image(src="http://chart.googleapis.com/chart?%s" %
                             chart_args,
                             alt="Workload Chart (client)")
    def expand_macro(self, formatter, name, content):
        req = formatter.req
        db = self.env.get_db_cnx()
        # prepare options
        options, query_args = parse_options(db, content,
                                            copy.copy(DEFAULT_OPTIONS))

        query_args[self.estimation_field + "!"] = None
        tickets = execute_query(self.env, req, query_args)

        sum = 0.0
        estimations = {}
        for ticket in tickets:
            if ticket['status'] in self.closed_states:
                continue
            try:
                estimation = float(ticket[self.estimation_field] or 0.0)

                if options.get('remainingworkload'):

                    completion_cursor = db.cursor()

                    completion_cursor.execute(
                        "SELECT t.value AS totalhours, c.value AS complete, d.value AS due_close FROM ticket tk LEFT JOIN ticket_custom t ON (tk.id = t.ticket AND t.name = 'totalhours') LEFT JOIN ticket_custom c ON (tk.id = c.ticket AND c.name = 'complete') LEFT JOIN ticket_custom d ON (tk.id = d.ticket AND d.name = 'due_close') WHERE tk.id = %s"
                        % ticket['id'])

                    for row in completion_cursor:

                        ticket['totalhours'], ticket['complete'], ticket[
                            'due_close'] = row
                        break

                    # skip ticket ticket if due date is later than 'enddate':
                    if options.get('showdueonly'):

                        if not ticket['due_close']:
                            continue  # skip tickets with empty ETA when in 'showdueonly' mode
                        due_close = parse_date(ticket['due_close'],
                                               ["%Y/%m/%d"])
                        startdate = options.get('startdate')
                        enddate = options.get('enddate')

                        if startdate and startdate > due_close:
                            continue  # skip tickets with ETA in the past

                        if enddate and enddate < due_close:
                            continue  # skip tickets with ETA in the future

                        pass

                    totalhours = float(ticket['totalhours'] or 0.0)

                    completed = (float(ticket['complete'] or 0.0) /
                                 100) * estimation
                    completed_hours = min(estimation,
                                          max(totalhours, completed))

                    estimation -= completed_hours

                    pass

                owner = ticket['owner']

                sum += estimation
                if estimations.has_key(owner):
                    estimations[owner] += estimation
                else:
                    estimations[owner] = estimation
            except:
                raise

        # Title
        title = 'Workload'

        days_remaining = None

        # calculate remaining work time
        if options.get('today') and options.get('enddate'):
            currentdate = options['today']
            day = timedelta(days=1)
            days_remaining = 0
            while currentdate <= options['enddate']:
                if currentdate.weekday() < 5:
                    days_remaining += 1
                currentdate += day
            title += ' %g%s (~%s workdays left)' % (round(
                sum, 2), self.estimation_suffix, days_remaining)

        estimations_string = []
        labels = []
        workhoursperday = max(float(options.get('workhoursperday')), 0.0)
        chts = '000000'

        for owner, estimation in estimations.iteritems():
            # Note: Unconditional obfuscation of owner in case it represents
            # an email adress, and as the chart API doesn't support SSL
            # (plain http transfer only, from either client or server).
            label = "%s %g%s" % (obfuscate_email_address(owner),
                                 round(estimation, 2), self.estimation_suffix)

            if days_remaining != None:

                user_remaining_hours = days_remaining * workhoursperday

                if not user_remaining_hours or (estimation /
                                                user_remaining_hours) > 1:
                    label = "%s (~%g hours left)!" % (
                        label, round(user_remaining_hours, 2)
                    )  # user does not have enough hours left
                    chts = 'FF0000'  # set chart title style to red
                    pass
                pass

            labels.append(label)

            estimations_string.append(str(int(estimation)))

            pass

        chart_args = unicode_urlencode({
            'chs':
            '%sx%s' % (options['width'], options['height']),
            'chf':
            'bg,s,00000000',
            'chd':
            't:%s' % ",".join(estimations_string),
            'cht':
            'p3',
            'chtt':
            title,
            'chts':
            chts,
            'chl':
            "|".join(labels),
            'chco':
            options['color']
        })

        self.log.debug("WorkloadChart data: %s" % repr(chart_args))
        if self.serverside_charts:
            return tag.image(
                src="%s?data=%s" %
                (req.href.estimationtools('chart'), unicode_quote(chart_args)),
                alt="Workload Chart (server)")
        else:
            return tag.image(src="https://chart.googleapis.com/chart?%s" %
                             chart_args,
                             alt="Workload Chart (client)")