def _create_facebook_button(self, url, locale): width = self._facebook_width font = self._facebook_font layout = self._facebook_layout params = { 'width': width, 'font': font, 'layout': layout, 'send': 'false', 'action': 'like' } if url: params['url'] = url if locale: params['locale'] = locale src = '//www.facebook.com/plugins/like.php?' + unicode_urlencode( params) style = 'border:none;overflow:hidden;width:%(width)spx;height:21px' \ % {'width': width} return [ tag.iframe(src=src, scrolling='no', frameborder='0', style=style, allowTransparency='true'), ]
def execute_query(env, req, query_args): # set maximum number of returned tickets to 0 to get all tickets at once query_args['max'] = 0 # urlencode the args, converting back a few vital exceptions: # see the authorized fields in the query language in # http://trac.edgewall.org/wiki/TracQuery#QueryLanguage query_string = unicode_urlencode(query_args)\ .replace('%21=', '!=')\ .replace('%21%7E=', '!~=')\ .replace('%7E=', '~=')\ .replace('%5E=', '^=')\ .replace('%24=', '$=')\ .replace('%21%5E=', '!^=')\ .replace('%21%24=', '!$=')\ .replace('%7C', '|')\ .replace('+', ' ')\ .replace('%23', '#')\ .replace('%28', '(')\ .replace('%29', ')')\ .replace('%2F', '/') env.log.debug("query_string: %s" % query_string) query = Query.from_string(env, query_string) tickets = query.execute(req) tickets = [t for t in tickets if ('TICKET_VIEW' or 'TICKET_VIEW_CC') in req.perm('ticket', t['id'])] return tickets
def _post(self, url, req, author, content, ip): # Split up author into name and email, if possible author_name, author_email = parseaddr(author) if not author_name and not author_email: author_name = author elif not author_name and author_email.find('@') < 1: author_name = author author_email = None params = { 'blog': req.base_url, 'user_ip': ip, 'user_agent': req.get_header('User-Agent'), 'referrer': req.get_header('Referer') or 'unknown', 'comment_author': author_name, 'comment_type': 'trac', 'comment_content': content } if author_email: params['comment_author_email'] = author_email for k, v in req.environ.items(): if k.startswith('HTTP_') and k not in self.noheaders: params[k] = v urlreq = urllib2.Request(url, unicode_urlencode(params), {'User-Agent': user_agent}) resp = urllib2.urlopen(urlreq) return resp.read()
def execute_query(env, req, query_args): # set maximum number of returned tickets to 0 to get all tickets at once query_args['max'] = 0 # urlencode the args, converting back a few vital exceptions: # see the authorized fields in the query language in # http://trac.edgewall.org/wiki/TracQuery#QueryLanguage query_string = unicode_urlencode(query_args).replace('%21=', '!=') \ .replace('%21%7E=', '!~=') \ .replace('%7E=', '~=') \ .replace('%5E=', '^=') \ .replace('%24=', '$=') \ .replace('%21%5E=', '!^=') \ .replace('%21%24=', '!$=') \ .replace('%7C', '|') \ .replace('+', ' ') \ .replace('%23', '#') \ .replace('%28', '(') \ .replace('%29', ')') env.log.debug("query_string: %s", query_string) query = Query.from_string(env, query_string) tickets = query.execute(req) tickets = [ t for t in tickets if ('TICKET_VIEW' or 'TICKET_VIEW_CC') in req.perm('ticket', t['id']) ] return tickets
def post_process_request(self, req, template, data, content_type): """Adds a 'Lint' context navigation menu item in source view and links to the annotation in report summary. """ if not 'BUILD_VIEW' in req.perm: return template, data, content_type resource = data and data.get('context') \ and data.get('context').resource or None if not resource or not isinstance(resource, Resource): pass elif resource.realm == 'source' and data.get('file') \ and not req.args.get('annotate') == 'lint': add_ctxtnav(req, 'Lint', title='Annotate file with lint result ' 'data (if available)', href=req.href.browser(resource.id, annotate='lint', rev=data.get('rev'))) elif resource.realm == 'build' and data.get('build', {}).get('steps'): # in report summary, set link to lint annotation steps = data['build']['steps'] rev = data['build']['rev'] for step in steps: for report in step.get('reports', []): if report.get('category') != 'lint': continue for item in report.get('data', {}).get('data', []): href = item.get('href') if not href or 'annotate' in href: continue sep = ('?' in href) and '&' or '?' param = {'rev': rev, 'annotate': 'lint'} href = href + sep + unicode_urlencode(param) item['href'] = href + '#Lint1' return template, data, content_type
def test_unicode_urlencode(self): self.assertEqual( 'thing=%C3%9C&%C3%9C=thing&%C3%9Cthing', unicode_urlencode({ u'Ü': 'thing', 'thing': u'Ü', u'Üthing': empty }))
def pre_process_request(self, req, handler): rmodule = ReportModule(self.env) #report's match request. if it's gonna be true then we'll stick in our translator, #but only if there's a report id (i.e. it's actually a report page) if rmodule.match_request(req) and req.args.get('id', -1) != -1 and req.args.get('action', 'view') == 'view': href = '' params = rmodule.get_var_args(req) if params: href = '&' + unicode_urlencode(params) add_link(req, 'alternate', '?format=rss&changes=true' + href, _('Changes RSS Feed'), 'application/xhtml+xml', 'rss') return handler
def _add_alternate_links(self, req): params = {} for arg in req.args.keys(): if not arg.isupper(): continue params[arg] = req.args.get(arg) if "USER" not in params: params["USER"] = req.authname if "sort" in req.args: params["sort"] = req.args["sort"] if "asc" in req.args: params["asc"] = req.args["asc"] href = "" if params: href = "&" + unicode_urlencode(params) add_link(req, "alternate", "?format=xlshtml" + href, "Excel HTML", "application/vnd.ms-excel", "html")
def _add_alternate_links(self, req): params = {} for arg in req.args.keys(): if not arg.isupper(): continue params[arg] = req.args.get(arg) if 'USER' not in params: params['USER'] = req.authname if 'sort' in req.args: params['sort'] = req.args['sort'] if 'asc' in req.args: params['asc'] = req.args['asc'] href = '' if params: href = '&' + unicode_urlencode(params) add_link(req, 'alternate', '?format=xls' + href, _("Excel"), 'application/vnd.ms-excel')
def add_alternate_links(self, req, args): params = args if req.args.has_key('sort'): params['sort'] = req.args['sort'] if req.args.has_key('asc'): params['asc'] = req.args['asc'] href = '' if params: href = '&' + unicode_urlencode(params) add_link(req, 'alternate', '?format=rss' + href, 'RSS Feed', 'application/rss+xml', 'rss') add_link(req, 'alternate', '?format=csv' + href, 'Comma-delimited Text', 'text/plain') add_link(req, 'alternate', '?format=tab' + href, 'Tab-delimited Text', 'text/plain') if req.perm.has_permission('REPORT_SQL_VIEW'): add_link(req, 'alternate', '?format=sql', 'SQL Query', 'text/plain')
def _create_facebook_button(self, url, locale): width = self._facebook_width font = self._facebook_font layout = self._facebook_layout params = {'width': width, 'font': font, 'layout': layout, 'send': 'false', 'action': 'like'} if url: params['url'] = url if locale: params['locale'] = locale src = '//www.facebook.com/plugins/like.php?' + unicode_urlencode(params) style = 'border:none;overflow:hidden;width:%(width)spx;height:21px' \ % {'width': width} return [ tag.iframe(src=src, scrolling='no', frameborder='0', style=style, allowTransparency='true'), ]
def add_alternate_links(self, req, args): params = args.copy() if 'sort' in req.args: params['sort'] = req.args['sort'] if 'asc' in req.args: params['asc'] = req.args['asc'] href = '' if params: href = '&' + unicode_urlencode(params) add_link(req, 'alternate', '?format=rss' + href, _('RSS Feed'), 'application/rss+xml', 'rss') add_link(req, 'alternate', '?format=csv' + href, _('Comma-delimited Text'), 'text/plain') add_link(req, 'alternate', '?format=tab' + href, _('Tab-delimited Text'), 'text/plain') if 'REPORT_SQL_VIEW' in req.perm: add_link(req, 'alternate', '?format=sql', _('SQL Query'), 'text/plain')
def _add_alternate_links(self, req): params = {} for arg in req.args.keys(): if not arg.isupper(): continue params[arg] = req.args.get(arg) if 'USER' not in params: params['USER'] = req.authname if 'sort' in req.args: params['sort'] = req.args['sort'] if 'asc' in req.args: params['asc'] = req.args['asc'] href = '' if params: href = '&' + unicode_urlencode(params) format = get_excel_format(self.env) mimetype = get_excel_mimetype(format) add_link(req, 'alternate', '?format=' + format + href, _("Excel"), mimetype)
def verify_key(self, req, api_url=None, api_key=None): if api_url is None: api_url = self.api_url if api_key is None: api_key = self.api_key if api_key != self.verified_key: self.log.debug("Verifying Akismet API key") params = {'blog': req.base_url, 'key': api_key} req = urllib2.Request('http://%sverify-key' % api_url, unicode_urlencode(params), {'User-Agent': user_agent}) resp = urllib2.urlopen(req).read() if resp.strip().lower() == 'valid': self.log.debug("Akismet API key is valid") self.verified = True self.verified_key = api_key return self.verified_key is not None
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)) # Skip Jinja2 context (#13244) # Only needed for Jinja versions 2.11.0 and 2.11.1 if args and isinstance(args[0], Jinja2Context): args = args[1:] 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
def execute_query(env, req, query_args): # set maximum number of returned tickets to 0 to get all tickets at once query_args['max'] = 0 # urlencode the args, converting back a few vital exceptions: query_string = unicode_urlencode(query_args)\ .replace('%21=', '!=')\ .replace('%7C', '|')\ .replace('+', ' ')\ .replace('%23', '#')\ .replace('%28', '(')\ .replace('%29', ')') env.log.debug("query_string: %s" % query_string) query = Query.from_string(env, query_string) tickets = query.execute(req) tickets = [t for t in tickets if ('TICKET_VIEW' or 'TICKET_VIEW_CC') in req.perm('ticket', t['id'])] return tickets
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 execute_query(env, req, query_args): # set maximum number of returned tickets to 0 to get all tickets at once query_args['max'] = 0 # urlencode the args, converting back a few vital exceptions: query_string = unicode_urlencode(query_args)\ .replace('%21=', '!=')\ .replace('%7C', '|')\ .replace('+', ' ')\ .replace('%23', '#')\ .replace('%28', '(')\ .replace('%29', ')') env.log.debug("query_string: %s" % query_string) query = Query.from_string(env, query_string) tickets = query.execute(req) tickets = [ t for t in tickets if ('TICKET_VIEW' or 'TICKET_VIEW_CC') in req.perm('ticket', t['id']) ] return tickets
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 __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 += '/' + path 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
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
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)")
def filter_stream(self, req, method, filename, stream, data): # self.list_namespaces() # generate TracLink string resource = None if filename in ['ticket.html', 'wiki_view.html', 'report_view.html', 'milestone_view.html', 'agilo_ticket_view.html'] \ and 'context' in data: resource = copy(data['context'].resource) elif filename in ['search.html']: # search: resource = Resource('search', data['query']) elif filename in ['browser.html']: # source: resource = copy(data['context'].resource) if resource.parent and resource.parent.realm == 'repository': resource.id = '%s/%s' % (resource.parent.id, resource.id) resource.parent = None elif filename in ['revisionlog.html']: # log: resource = copy(data['context'].resource) resource.realm = 'log' if resource.parent and resource.parent.realm == 'repository': resource.id = '%s/%s' % (resource.parent.id, resource.id) resource.parent = None revranges = data.get('revranges', None) rev = data.get('rev', None) if revranges: resource.version = '%s:%s' % (revranges.a, revranges.b) elif rev: resource.version = rev elif filename in ['attachment.html']: if isinstance(data['attachment'], Attachment): # attachment: resource = copy(data['attachment'].resource) else: pass # attachment list page of the ticket; no TracLinks defined elif filename in ['timeline.html']: # timeline: resource = Resource('timeline', format_datetime(data['precisedate'], 'iso8601')) elif filename in ['changeset.html']: if data['changeset']: # changeset: resource = copy(data['context'].resource) if resource.parent and resource.parent.realm == 'repository': resource.id = '%s/%s' % (resource.id, resource.parent.id) # OK, I know resource.parent = None if data['restricted']: resource.id = '%s/%s' % (resource.id, data['new_path']) else: # diff: args = req.args old_path, new_path = args.get('old_path', ''), args.get('new_path', '') old_rev, new_rev = args.get('old'), args.get('new') if old_path == new_path: # diff:path@1:3 style resource = Resource('diff', old_path, '%s:%s' % (old_rev, new_rev)) else: # diff:path@1//path@3 style if old_rev: old_path += '@%s' % old_rev if new_rev: new_path += '@%s' % new_rev resource = Resource('diff', '%s//%s' % (old_path, new_path)) elif filename in ['query.html']: if 'report_resource' in data: resource = copy(data['report_resource']) else: resource = Resource('query', data['query'].to_string().replace("\n", "")[7:]) else: pass # link hash TODO: check wiki_view for agilo if filename in ['browser.html', 'changeset.html', 'ticket.html', 'agilo_ticket_view.html', 'wiki_view.html']: add_script(req, 'traclinks/js/jquery.ba-hashchange.js') add_script(req, 'traclinks/js/onhashchange.js') # if resource: traclinks = '%s' % (resource.id) if resource.version != None: traclinks += (resource.realm == 'ticket' and '?version=%s' or '@%s') % resource.version parent = resource.parent while parent and parent.id: traclinks += ':%s:%s' % (parent.realm, parent.id) if parent.version != None: traclinks += '@%s' % parent.version parent = parent.parent if ' ' in traclinks: traclinks = '"%s"' % traclinks # surround quote if needed traclinks = '%s:%s' % (resource.realm, traclinks) # new ticket template if resource.id == None and resource.realm == 'ticket': query_string = unicode_urlencode( [(k, v) for k, v in data['ticket'].values.iteritems() if v not in (None, '')]) traclinks = '[/newticket?%s]' % query_string #return stream | Transformer('//input[@id="proj-search"]').attr('placeholder', traclinks).attr('size', '50') _input = tag.input(value=traclinks, readonly='', style=self.style) div = tag.div(_input, id="banner-traclink", style="float:right") return stream | Transformer('//div[@id="header"]').before(div) return stream
def expand_macro(self, formatter, name, content): # prepare options req = formatter.req options, query_args = parse_options(self.env.get_db_cnx(), 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) # remove weekends if not options['weekends']: for date in timetable.keys(): if date.weekday() >= 5: del timetable[date] # scale data xdata, ydata, maxhours = self._scale_data(timetable, options) # 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': gridlinesdata = "100.0,100.0,1,0" # create top and right bounding line by using grid 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' % (",".join(xdata), ",".join(ydata), expecteddata), 'cht': 'lxy', 'chxt': 'x,x,y', 'chxl': bottomaxis, 'chxr': leftaxis, 'chm': "|".join(weekends), 'chg': gridlinesdata, 'chco': '%s,%s' % (options['color'], options['colorexpected']), 'chtt': title }) self.log.debug("BurndownChart 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="Burndown Chart (server)") else: return tag.image(src="http://chart.googleapis.com/chart?%s" % chart_args, alt="Burndown Chart (client)")
def render_widget(self, name, context, options): """Count ocurrences of values assigned to given ticket field. """ req = context.req params = ('field', 'query', 'verbose', 'threshold', 'max', 'title', 'view') fieldnm, query, verbose, threshold, maxitems, title, view = \ self.bind_params(name, options, *params) field_maps = { 'type': { 'admin_url': 'type', 'title': 'Types', }, 'status': { 'admin_url': None, 'title': 'Statuses', }, 'priority': { 'admin_url': 'priority', 'title': 'Priorities', }, 'milestone': { 'admin_url': 'milestones', 'title': 'Milestones', }, 'component': { 'admin_url': 'components', 'title': 'Components', }, 'version': { 'admin_url': 'versions', 'title': 'Versions', }, 'severity': { 'admin_url': 'severity', 'title': 'Severities', }, 'resolution': { 'admin_url': 'resolution', 'title': 'Resolutions', }, } _field = [] def check_field_name(): if fieldnm is None: raise InvalidWidgetArgument('field', 'Missing ticket field') tsys = self.env[TicketSystem] if tsys is None: raise TracError(_('Error loading ticket system (disabled?)')) for field in tsys.get_ticket_fields(): if field['name'] == fieldnm: _field.append(field) break else: if fieldnm in field_maps: admin_suffix = field_maps.get(fieldnm)['admin_url'] if 'TICKET_ADMIN' in req.perm and admin_suffix is not None: hint = _( 'You can add one or more ' '<a href="%(url)s">here</a>.', url=req.href.admin('ticket', admin_suffix)) else: hint = _( 'Contact your administrator for further details') return 'widget_alert.html', \ { 'title' : Markup(_('%(field)s', field=field_maps[fieldnm]['title'])), 'data' : dict(msgtype='info', msglabel="Note", msgbody=Markup(_('''No values are defined for ticket field <em>%(field)s</em>. %(hint)s''', field=fieldnm, hint=hint)) ) }, context else: raise InvalidWidgetArgument( 'field', 'Unknown ticket field %s' % (fieldnm, )) return None if query is None: data = check_field_name() if data is not None: return data field = _field[0] if field.get('custom'): sql = "SELECT COALESCE(value, ''), count(COALESCE(value, ''))" \ " FROM ticket_custom " \ " WHERE name='%(name)s' GROUP BY COALESCE(value, '')" else: sql = "SELECT COALESCE(%(name)s, ''), " \ "count(COALESCE(%(name)s, '')) FROM ticket " \ "GROUP BY COALESCE(%(name)s, '')" sql = sql % field # TODO : Implement threshold and max db_query = req.perm.env.db_query \ if isinstance(req.perm.env, ProductEnvironment) \ else req.perm.env.db_direct_query with db_query as db: cursor = db.cursor() cursor.execute(sql) items = cursor.fetchall() QUERY_COLS = [ 'id', 'summary', 'owner', 'type', 'status', 'priority' ] item_link = lambda item: req.href.query(col=QUERY_COLS + [fieldnm], **{fieldnm: item[0]}) else: query = Query.from_string(self.env, query, group=fieldnm) if query.group is None: data = check_field_name() if data is not None: return data raise InvalidWidgetArgument( 'field', 'Invalid ticket field for ticket groups') fieldnm = query.group sql, v = query.get_sql() sql = "SELECT COALESCE(%(name)s, '') , count(COALESCE(%(name)s, ''))"\ "FROM (%(sql)s) AS foo GROUP BY COALESCE(%(name)s, '')" % \ { 'name' : fieldnm, 'sql' : sql } db = self.env.get_db_cnx() try: cursor = db.cursor() cursor.execute(sql, v) items = cursor.fetchall() finally: cursor.close() query_href = query.get_href(req.href) item_link= lambda item: query_href + \ '&' + unicode_urlencode([(fieldnm, item[0])]) if fieldnm in self.DASH_ITEM_HREF_MAP: def dash_item_link(item): if item[0]: args = self.DASH_ITEM_HREF_MAP[fieldnm] + (item[0], ) return req.href(*args) else: return item_link(item) else: dash_item_link = item_link if title is None: heading = _(fieldnm.capitalize()) else: heading = None return 'widget_cloud.html', \ { 'title' : title, 'data' : dict( bounds=minmax(items, lambda x: x[1]), item_link=dash_item_link, heading=heading, items=items, verbose=verbose, view=view, ), }, \ context
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)")
def test_unicode_urlencode(self): self.assertEqual( "thing=%C3%9C&%C3%9C=thing&%C3%9Cthing", unicode_urlencode({u"Ü": "thing", "thing": u"Ü", u"Üthing": empty}) )
def test_unicode_urlencode(self): self.assertEqual('thing=%C3%9C&%C3%9C=thing&%C3%9Cthing', unicode_urlencode({u'Ü': 'thing', 'thing': u'Ü', u'Üthing': empty}))
def render_widget(self, name, context, options): """Count ocurrences of values assigned to given ticket field. """ req = context.req params = ('field', 'query', 'verbose', 'threshold', 'max', 'title', 'view') fieldnm, query, verbose, threshold, maxitems, title, view = \ self.bind_params(name, options, *params) field_maps = {'type': {'admin_url': 'type', 'title': 'Types', }, 'status': {'admin_url': None, 'title': 'Statuses', }, 'priority': {'admin_url': 'priority', 'title': 'Priorities', }, 'milestone': {'admin_url': 'milestones', 'title': 'Milestones', }, 'component': {'admin_url': 'components', 'title': 'Components', }, 'version': {'admin_url': 'versions', 'title': 'Versions', }, 'severity': {'admin_url': 'severity', 'title': 'Severities', }, 'resolution': {'admin_url': 'resolution', 'title': 'Resolutions', }, } _field = [] def check_field_name(): if fieldnm is None: raise InvalidWidgetArgument('field', 'Missing ticket field') tsys = self.env[TicketSystem] if tsys is None: raise TracError(_('Error loading ticket system (disabled?)')) for field in tsys.get_ticket_fields(): if field['name'] == fieldnm: _field.append(field) break else: if fieldnm in field_maps: admin_suffix = field_maps.get(fieldnm)['admin_url'] if 'TICKET_ADMIN' in req.perm and admin_suffix is not None: hint = _('You can add one or more ' '<a href="%(url)s">here</a>.', url=req.href.admin('ticket', admin_suffix)) else: hint = _('Contact your administrator for further details') return 'widget_alert.html', \ { 'title' : Markup(_('%(field)s', field=field_maps[fieldnm]['title'])), 'data' : dict(msgtype='info', msglabel="Note", msgbody=Markup(_('''No values are defined for ticket field <em>%(field)s</em>. %(hint)s''', field=fieldnm, hint=hint)) ) }, context else: raise InvalidWidgetArgument('field', 'Unknown ticket field %s' % (fieldnm,)) return None if query is None : data = check_field_name() if data is not None: return data field = _field[0] if field.get('custom'): sql = "SELECT COALESCE(value, ''), count(COALESCE(value, ''))" \ " FROM ticket_custom " \ " WHERE name='%(name)s' GROUP BY COALESCE(value, '')" else: sql = "SELECT COALESCE(%(name)s, ''), " \ "count(COALESCE(%(name)s, '')) FROM ticket " \ "GROUP BY COALESCE(%(name)s, '')" sql = sql % field # TODO : Implement threshold and max db_query = req.perm.env.db_query \ if isinstance(req.perm.env, ProductEnvironment) \ else req.perm.env.db_direct_query with db_query as db: cursor = db.cursor() cursor.execute(sql) items = cursor.fetchall() QUERY_COLS = ['id', 'summary', 'owner', 'type', 'status', 'priority'] item_link= lambda item: req.href.query(col=QUERY_COLS + [fieldnm], **{fieldnm:item[0]}) else: query = Query.from_string(self.env, query, group=fieldnm) if query.group is None: data = check_field_name() if data is not None: return data raise InvalidWidgetArgument('field', 'Invalid ticket field for ticket groups') fieldnm = query.group sql, v = query.get_sql() sql = "SELECT COALESCE(%(name)s, '') , count(COALESCE(%(name)s, ''))"\ "FROM (%(sql)s) AS foo GROUP BY COALESCE(%(name)s, '')" % \ { 'name' : fieldnm, 'sql' : sql } db = self.env.get_db_cnx() try : cursor = db.cursor() cursor.execute(sql, v) items = cursor.fetchall() finally: cursor.close() query_href = query.get_href(req.href) item_link= lambda item: query_href + \ '&' + unicode_urlencode([(fieldnm, item[0])]) if fieldnm in self.DASH_ITEM_HREF_MAP: def dash_item_link(item): if item[0]: args = self.DASH_ITEM_HREF_MAP[fieldnm] + (item[0],) return req.href(*args) else: return item_link(item) else: dash_item_link = item_link if title is None: heading = _(fieldnm.capitalize()) else: heading = None return 'widget_cloud.html', \ { 'title' : title, 'data' : dict( bounds=minmax(items, lambda x: x[1]), item_link=dash_item_link, heading=heading, items=items, verbose=verbose, view=view, ), }, \ context
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)")
def filter_stream(self, req, method, filename, stream, data): # self.list_namespaces() # generate TracLink string resource = None if filename in ['ticket.html', 'wiki_view.html', 'report_view.html', 'milestone_view.html', 'agilo_ticket_view.html'] \ and 'context' in data: resource = data['context'].resource elif filename in ['search.html']: # search: resource = Resource('search', data['query']) elif filename in ['browser.html']: # source: resource = data['context'].resource if resource.parent and resource.parent.realm == 'repository': resource.id = '%s/%s' % (resource.parent.id, resource.id) resource.parent = None elif filename in ['revisionlog.html']: # log: resource = data['context'].resource resource.realm = 'log' if resource.parent and resource.parent.realm == 'repository': resource.id = '%s/%s' % (resource.parent.id, resource.id) resource.parent = None revranges = data.get('revranges', None) rev = data.get('rev', None) if revranges: resource.version = '%s:%s' % (revranges.a, revranges.b) elif rev: resource.version = rev elif filename in ['attachment.html']: if isinstance(data['attachment'], Attachment): # attachment: resource = data['attachment'].resource else: pass # attachment list page of the ticket; no TracLinks defined elif filename in ['timeline.html']: # timeline: resource = Resource( 'timeline', format_datetime(data['precisedate'], 'iso8601')) elif filename in ['changeset.html']: if data['changeset']: # changeset: resource = data['context'].resource if resource.parent and resource.parent.realm == 'repository': resource.id = '%s/%s' % (resource.id, resource.parent.id ) # OK, I know resource.parent = None if data['restricted']: resource.id = '%s/%s' % (resource.id, data['new_path']) else: # diff: args = req.args old_path, new_path = args.get('old_path', ''), args.get('new_path', '') old_rev, new_rev = args.get('old'), args.get('new') if old_path == new_path: # diff:path@1:3 style resource = Resource('diff', old_path, '%s:%s' % (old_rev, new_rev)) else: # diff:path@1//path@3 style if old_rev: old_path += '@%s' % old_rev if new_rev: new_path += '@%s' % new_rev resource = Resource('diff', '%s//%s' % (old_path, new_path)) elif filename in ['query.html']: if 'report_resource' in data: resource = data['report_resource'] else: resource = Resource( 'query', data['query'].to_string().replace("\n", "")[7:]) else: pass # link hash TODO: check wiki_view for agilo if filename in [ 'browser.html', 'ticket.html', 'agilo_ticket_view.html', 'wiki_view.html' ]: add_script(req, 'traclinks/js/jquery.ba-hashchange.js') add_script(req, 'traclinks/js/onhashchange.js') # if resource: traclinks = '%s' % (resource.id) if resource.version != None: traclinks += (resource.realm == 'ticket' and '?version=%s' or '@%s') % resource.version if resource.parent and resource.parent.id: traclinks += ':%s:%s' % (resource.parent.realm, resource.parent.id) if resource.parent.version != None: traclinks += '@%s' % resource.parent.version if ' ' in traclinks: traclinks = '"%s"' % traclinks # surround quote if needed traclinks = '%s:%s' % (resource.realm, traclinks) # new ticket template if resource.id == None and resource.realm == 'ticket': query_string = unicode_urlencode([ (k, v) for k, v in data['ticket'].values.iteritems() if v not in (None, '') ]) traclinks = '[/newticket?%s]' % query_string return stream | Transformer('//input[@id="proj-search"]').attr( 'value', traclinks).attr('size', '50') return stream
def process_request(self, req): """The appropriate mode of operation is inferred from the request parameters: * If `new_path` and `old_path` are equal (or `old_path` is omitted) and `new` and `old` are equal (or `old` is omitted), then we're about to view a revision Changeset: `chgset` is True. Furthermore, if the path is not the root, the changeset is ''restricted'' to that path (only the changes affecting that path, its children or its ancestor directories will be shown). * In any other case, the set of changes corresponds to arbitrary differences between path@rev pairs. If `new_path` and `old_path` are equal, the ''restricted'' flag will also be set, meaning in this case that the differences between two revisions are restricted to those occurring on that path. In any case, either path@rev pairs must exist. """ req.perm.assert_permission('CHANGESET_VIEW') # -- retrieve arguments new_path = req.args.get('new_path') new = req.args.get('new') old_path = req.args.get('old_path') old = req.args.get('old') if old and '@' in old: old_path, old = unescape(old).split('@') if new and '@' in new: new_path, new = unescape(new).split('@') # -- normalize and check for special case repos = self.env.get_repository(req.authname) new_path = repos.normalize_path(new_path) new = repos.normalize_rev(new) repos.authz.assert_permission_for_changeset(new) old_path = repos.normalize_path(old_path or new_path) old = repos.normalize_rev(old or new) if old_path == new_path and old == new: # revert to Changeset old_path = old = None diff_options = get_diff_options(req) # -- setup the `chgset` and `restricted` flags, see docstring above. chgset = not old and not old_path if chgset: restricted = new_path not in ('', '/') # (subset or not) else: restricted = old_path == new_path # (same path or not) # -- redirect if changing the diff options if req.args.has_key('update'): if chgset: if restricted: req.redirect(req.href.changeset(new, new_path)) else: req.redirect(req.href.changeset(new)) else: req.redirect(req.href.changeset(new, new_path, old=old, old_path=old_path)) # -- preparing the diff arguments if chgset: prev = repos.get_node(new_path, new).get_previous() if prev: prev_path, prev_rev = prev[:2] else: prev_path, prev_rev = new_path, repos.previous_rev(new) diff_args = DiffArgs(old_path=prev_path, old_rev=prev_rev, new_path=new_path, new_rev=new) else: if not new: new = repos.youngest_rev elif not old: old = repos.youngest_rev if not old_path: old_path = new_path diff_args = DiffArgs(old_path=old_path, old_rev=old, new_path=new_path, new_rev=new) if chgset: chgset = repos.get_changeset(new) message = chgset.message or '--' if self.wiki_format_messages: message = wiki_to_html(message, self.env, req, escape_newlines=True) else: message = html.PRE(message) req.check_modified(chgset.date, [ diff_options[0], ''.join(diff_options[1]), repos.name, repos.rev_older_than(new, repos.youngest_rev), message, pretty_timedelta(chgset.date, None, 3600)]) else: message = None # FIXME: what date should we choose for a diff? req.hdf['changeset'] = diff_args format = req.args.get('format') if format in ['diff', 'zip']: req.perm.assert_permission('FILE_VIEW') # choosing an appropriate filename rpath = new_path.replace('/','_') if chgset: if restricted: filename = 'changeset_%s_r%s' % (rpath, new) else: filename = 'changeset_r%s' % new else: if restricted: filename = 'diff-%s-from-r%s-to-r%s' \ % (rpath, old, new) elif old_path == '/': # special case for download (#238) filename = '%s-r%s' % (rpath, old) else: filename = 'diff-from-%s-r%s-to-%s-r%s' \ % (old_path.replace('/','_'), old, rpath, new) if format == 'diff': self._render_diff(req, filename, repos, diff_args, diff_options) return elif format == 'zip': self._render_zip(req, filename, repos, diff_args) return # -- HTML format self._render_html(req, repos, chgset, restricted, message, diff_args, diff_options) if chgset: diff_params = 'new=%s' % new else: diff_params = unicode_urlencode({'new_path': new_path, 'new': new, 'old_path': old_path, 'old': old}) add_link(req, 'alternate', '?format=diff&'+diff_params, 'Unified Diff', 'text/plain', 'diff') add_link(req, 'alternate', '?format=zip&'+diff_params, 'Zip Archive', 'application/zip', 'zip') add_stylesheet(req, 'common/css/changeset.css') add_stylesheet(req, 'common/css/diff.css') add_stylesheet(req, 'common/css/code.css') return 'changeset.cs', None