def get_user_setting(self, sid): """Returns tuple of (value, authenticated). Value is always True or None. This will work with Genshi template checkbox logic. """ with self.env.db_query as db: cursor = db.cursor() cursor.execute(""" SELECT value, authenticated FROM session_attribute WHERE sid=%s AND name=%s """, (sid, self._attr_name())) row = cursor.fetchone() if row: dists, v = decode(row[0]) value = as_bool(v) authenticated = as_bool(row[1]) else: dists = self.default['dists'] value = as_bool(self.default['value']) authenticated = False # We use None here so that Genshi templates check their checkboxes # properly and without confusion. return dists, value and True or None, authenticated
def check_permission(self, action, username, resource, perm): """Check that the action can be performed by username on the resource.""" # To prevent recursion if action in self.get_permission_actions(): return # To prevent recursion when used together with sensitive tickets if action == 'SENSITIVE_VIEW': return # Check whether we're dealing with a ticket resource while resource: if resource.realm == 'ticket': break resource = resource.parent if resource and resource.realm == 'ticket' and resource.id is not None: component_permission = 'COMPONENT_VIEW' # Default just to make check logic simpler bypass = False try: ticket = model.Ticket(self.env, int(resource.id)) should_check_permissions = not self.ticket_field_name or ticket.values.get(self.ticket_field_name, 0) if as_bool(should_check_permissions): if 'component' in ticket.values and ticket['component'] and self._get_permission_name(ticket['component']) in self.get_permission_actions(): component_permission = self._get_permission_name(ticket['component']) bypass = self._get_bypass(ticket, username) except ResourceNotFound: should_check_permissions = 1 # Fail safe to prevent a race condition if as_bool(should_check_permissions): if component_permission not in perm and 'COMPONENT_VIEW' not in perm and not bypass: return False
def check_permission(self, action, username, resource, perm): # We add the 'SENSITIVE_VIEW' pre-requisite for any action # other than 'SENSITIVE_VIEW' itself, as this would lead # to recursion. if action == 'SENSITIVE_VIEW': return # Check whether we're dealing with a ticket resource while resource: if resource.realm == 'ticket': break resource = resource.parent if resource and resource.realm == 'ticket' and resource.id is not None: bypass = False try: ticket = Ticket(self.env, int(resource.id)) sensitive = ticket['sensitive'] if as_bool(sensitive): bypass = self.bypass_sensitive_view(ticket, username) except ResourceNotFound: sensitive = 1 # Fail safe to prevent a race condition. if as_bool(sensitive): if 'SENSITIVE_VIEW' not in perm and not bypass: return False
def expand_macro(self, formatter, name, text): req = formatter.req context = formatter.context resource = context.resource # Process the arguments. args, kwargs = parse_args(text) if 'ticket' not in kwargs and len(args)>0: kwargs['ticket'] = args[0] elif 'ticket' not in kwargs and not len(args): kwargs['ticket'] = str( WikiPage(self.env, resource).name ) # This seems to provide the correct ticket id. try: kwargs['ticket'] = int( kwargs.get('ticket').lstrip('#') ) except ValueError: raise TracError('No ticket id supplied or it is not valid integer value.') ticket = Ticket( self.env, kwargs['ticket'] ) self.childtickets = {} # { parent -> children } - 1:n db = self.env.get_db_cnx() cursor = db.cursor() cursor.execute("SELECT ticket,value FROM ticket_custom WHERE name='parent'") for child,parent in cursor.fetchall(): if parent and re.match('#\d+',parent): self.childtickets.setdefault( int(parent.lstrip('#')), [] ).append(child) # First ticket has no indentation. ticket['indent'] = '0' # List of tickets that will be displayed. if as_bool( kwargs.get('root') ): self.tickets = [ ticket, ] else: self.tickets = [] # Do this neater! self.indent_children(ticket) def ticket_anchor(t): return tag.a( '#%s' % t.id, class_=t['status'], href=req.href.ticket(int(t.id)), title="%s : %s : %s" % (t['type'],t['owner'],t['status'])) def_list = tag.dl( [( tag.dt(ticket_anchor(t),style='padding-left: %spx;' % (t['indent']*20)), tag.dd("%s" % t['summary'])) for t in self.tickets], class_='wiki compact', ) if as_bool( kwargs.get('border') ): return tag.fieldset( def_list, tag.legend('Ticket Child Tree (#%s)' % ticket.id), class_='description', ) else: return tag.div(def_list)
def expand_macro(self, formatter, name, content): args, kwargs = parse_args(content) format = kwargs.get('format', 'compact') glob = kwargs.get('glob', '*') order = kwargs.get('order') desc = as_bool(kwargs.get('desc', 0)) rm = RepositoryManager(self.env) all_repos = dict(rdata for rdata in rm.get_all_repositories().items() if fnmatchcase(rdata[0], glob)) if format == 'table': repo = self._render_repository_index(formatter.context, all_repos, order, desc) add_stylesheet(formatter.req, 'common/css/browser.css') wiki_format_messages = self.config['changeset'] \ .getbool('wiki_format_messages') data = {'repo': repo, 'order': order, 'desc': 1 if desc else None, 'reponame': None, 'path': '/', 'stickyrev': None, 'wiki_format_messages': wiki_format_messages} from trac.web.chrome import Chrome return Chrome(self.env).render_template( formatter.req, 'repository_index.html', data, None, fragment=True) def get_repository(reponame): try: return rm.get_repository(reponame) except TracError: return all_repos = [(reponame, get_repository(reponame)) for reponame in all_repos] all_repos = sorted(((reponame, repos) for reponame, repos in all_repos if repos and not as_bool(repos.params.get('hidden')) and repos.is_viewable(formatter.perm)), reverse=desc) def repolink(reponame, repos): label = reponame or _('(default)') return Markup(tag.a(label, title=_('View repository %(repo)s', repo=label), href=formatter.href.browser(repos.reponame or None))) if format == 'list': return tag.dl([ tag(tag.dt(repolink(reponame, repos)), tag.dd(repos.params.get('description'))) for reponame, repos in all_repos]) else: # compact return Markup(', ').join([repolink(reponame, repos) for reponame, repos in all_repos])
def _do_remove_managed(self, name, delete): rm = RepositoryManager(self.env) repository = rm.get_repository(name) if not repository: raise AdminCommandError( _('Repository "%(name)s" does not exists', name=name)) rm.remove(repository, as_bool(delete))
def pre_process_request(self, req, handler): from trac.web.chrome import Chrome, add_warning if handler is not Chrome(self.env): for repo_info in self.get_all_repositories().values(): if not as_bool(repo_info.get('sync_per_request')): continue start = time.time() repo_name = repo_info['name'] or '(default)' try: repo = self.get_repository(repo_info['name']) repo.sync() except TracError as e: add_warning(req, _("Can't synchronize with repository \"%(name)s\" " "(%(error)s). Look in the Trac log for more " "information.", name=repo_name, error=to_unicode(e))) except Exception as e: add_warning(req, _("Failed to sync with repository \"%(name)s\": " "%(error)s; repository information may be out of " "date. Look in the Trac log for more information " "including mitigation strategies.", name=repo_name, error=to_unicode(e))) self.log.error( "Failed to sync with repository \"%s\"; You may be " "able to reduce the impact of this issue by " "configuring the sync_per_request option; see " "http://trac.edgewall.org/wiki/TracRepositoryAdmin" "#ExplicitSync for more detail: %s", repo_name, exception_to_unicode(e, traceback=True)) self.log.info("Synchronized '%s' repository in %0.2f seconds", repo_name, time.time() - start) return handler
def _do_set(self, section, option, value): self.config.set(section, option, value) if section == 'components' and as_bool(value): self.config.set_defaults(component=option) self.config.save() if section == 'inherit' and option == 'file': self.config.parse_if_needed(force=True) # Full reload
def set_user_setting(self, session, value=None, dists=('email',), save=True): """Sets session attribute to 1 or 0.""" session[self._attr_name()] = \ encode(dists, '1' if as_bool(value) else '0') if save: session.save()
def process_request(self, req): presel = req.args.get('preselected') if presel and (presel + '/').startswith(req.href.browser() + '/'): req.redirect(presel) path = req.args.get('path', '/') rev = req.args.get('rev', '') if rev.lower() in ('', 'head'): rev = None format = req.args.get('format') order = req.args.get('order', 'name').lower() desc = 'desc' in req.args xhr = req.get_header('X-Requested-With') == 'XMLHttpRequest' rm = RepositoryManager(self.env) all_repositories = rm.get_all_repositories() reponame, repos, path = rm.get_repository_by_path(path) # Repository index show_index = not reponame and path == '/' if show_index: if repos and (as_bool(all_repositories[''].get('hidden')) or not repos.is_viewable(req.perm)): repos = None if not repos and reponame: raise ResourceNotFound(_("Repository '%(repo)s' not found", repo=reponame)) if reponame and reponame != repos.reponame: # Redirect alias qs = req.query_string req.redirect(req.href.browser(repos.reponame or None, path) + ('?' + qs if qs else '')) reponame = repos.reponame if repos else None # Find node for the requested path/rev context = web_context(req) node = None changeset = None display_rev = lambda rev: rev if repos: try: if rev: rev = repos.normalize_rev(rev) # If `rev` is `None`, we'll try to reuse `None` consistently, # as a special shortcut to the latest revision. rev_or_latest = rev or repos.youngest_rev node = get_existing_node(req, repos, path, rev_or_latest) except NoSuchChangeset, e: raise ResourceNotFound(e, _('Invalid changeset number')) if node: try: # use changeset instance to retrieve branches and tags changeset = repos.get_changeset(node.rev) except NoSuchChangeset: pass context = context.child(repos.resource.child('source', path, version=rev_or_latest)) display_rev = repos.display_rev
def getbool(self, name, default=None): """Return the value as a boolean. Raise an `HTTPBadRequest` exception if an exception occurs while converting the value to a boolean. :param name: the name of the request parameter :keyword default: the value to return if the parameter is not specified. :since: 1.2 """ if name not in self: return default value = self[name] if isinstance(value, list): raise HTTPBadRequest( tag_("Invalid value for request argument " "%(name)s.", name=tag.em(name))) value = as_bool(value, None) if value is None: raise HTTPBadRequest( tag_("Invalid value for request argument " "%(name)s.", name=tag.em(name))) return value
def custom_fields(self): """Return the list of custom ticket fields available for tickets.""" fields = TicketFieldList() config = self.ticket_custom_section for name in [ option for option, value in config.options() if '.' not in option ]: field = { 'name': name, 'custom': True, 'type': config.get(name), 'order': config.getint(name + '.order', 0), 'label': config.get(name + '.label') or name.replace("_", " ").strip().capitalize(), 'value': config.get(name + '.value', '') } if field['type'] == 'select' or field['type'] == 'radio': field['options'] = config.getlist(name + '.options', sep='|') if not field['options']: continue if '' in field['options'] or \ field['name'] in self.allowed_empty_fields: field['optional'] = True if '' in field['options']: field['options'].remove('') elif field['type'] == 'checkbox': field['value'] = '1' if as_bool(field['value']) else '0' elif field['type'] == 'text': field['format'] = config.get(name + '.format', 'plain') field['max_size'] = config.getint(name + '.max_size', 0) elif field['type'] == 'textarea': field['format'] = config.get(name + '.format', 'plain') field['max_size'] = config.getint(name + '.max_size', 0) field['height'] = config.getint(name + '.rows') elif field['type'] == 'time': field['format'] = config.get(name + '.format', 'datetime') if field['name'] in self.reserved_field_names: self.log.warning( 'Field name "%s" is a reserved name ' '(ignoring)', field['name']) continue if not re.match('^[a-zA-Z][a-zA-Z0-9_]+$', field['name']): self.log.warning( 'Invalid name for custom field: "%s" ' '(ignoring)', field['name']) continue fields.append(field) fields.sort(key=lambda f: (f['order'], f['name'])) return fields
def _render_repository_index(self, context, all_repositories, order, desc): # Color scale for the age column timerange = custom_colorizer = None if self.color_scale: custom_colorizer = self.get_custom_colorizer() rm = RepositoryManager(self.env) repositories = [] for reponame, repoinfo in all_repositories.iteritems(): if not reponame or as_bool(repoinfo.get('hidden')): continue try: repos = rm.get_repository(reponame) except TracError as err: entry = (reponame, repoinfo, None, None, exception_to_unicode(err), None) else: if repos: if not repos.is_viewable(context.perm): continue try: youngest = repos.get_changeset(repos.youngest_rev) except NoSuchChangeset: youngest = None if self.color_scale and youngest: if not timerange: timerange = TimeRange(youngest.date) else: timerange.insert(youngest.date) raw_href = self._get_download_href(context.href, repos, None, None) entry = (reponame, repoinfo, repos, youngest, None, raw_href) else: entry = (reponame, repoinfo, None, None, u"\u2013", None) if entry[4] is not None: # Check permission in case of error root = Resource('repository', reponame).child('source', '/') if 'BROWSER_VIEW' not in context.perm(root): continue repositories.append(entry) # Ordering of repositories if order == 'date': def repo_order((reponame, repoinfo, repos, youngest, err, href)): return (youngest.date if youngest else to_datetime(0), embedded_numbers(reponame.lower())) elif order == 'author': def repo_order((reponame, repoinfo, repos, youngest, err, href)): return (youngest.author.lower() if youngest else '', embedded_numbers(reponame.lower())) else: def repo_order((reponame, repoinfo, repos, youngest, err, href)): return embedded_numbers(reponame.lower()) repositories = sorted(repositories, key=repo_order, reverse=desc) return {'repositories' : repositories, 'timerange': timerange, 'colorize_age': custom_colorizer}
def _render_repository_index(self, context, all_repositories, order, desc): # Color scale for the age column timerange = custom_colorizer = None if self.color_scale: custom_colorizer = self.get_custom_colorizer() rm = RepositoryManager(self.env) repositories = [] for reponame, repoinfo in all_repositories.iteritems(): if not reponame or as_bool(repoinfo.get("hidden")): continue try: repos = rm.get_repository(reponame) except TracError as err: entry = (reponame, repoinfo, None, None, exception_to_unicode(err), None) else: if repos: if not repos.is_viewable(context.perm): continue try: youngest = repos.get_changeset(repos.youngest_rev) except NoSuchChangeset: youngest = None if self.color_scale and youngest: if not timerange: timerange = TimeRange(youngest.date) else: timerange.insert(youngest.date) raw_href = self._get_download_href(context.href, repos, None, None) entry = (reponame, repoinfo, repos, youngest, None, raw_href) else: entry = (reponame, repoinfo, None, None, u"\u2013", None) if entry[4] is not None: # Check permission in case of error root = Resource("repository", reponame).child(self.realm, "/") if "BROWSER_VIEW" not in context.perm(root): continue repositories.append(entry) # Ordering of repositories if order == "date": def repo_order((reponame, repoinfo, repos, youngest, err, href)): return (youngest.date if youngest else to_datetime(0), embedded_numbers(reponame.lower())) elif order == "author": def repo_order((reponame, repoinfo, repos, youngest, err, href)): return (youngest.author.lower() if youngest else "", embedded_numbers(reponame.lower())) else: def repo_order((reponame, repoinfo, repos, youngest, err, href)): return embedded_numbers(reponame.lower()) repositories = sorted(repositories, key=repo_order, reverse=desc) return {"repositories": repositories, "timerange": timerange, "colorize_age": custom_colorizer}
def process_request(self, req): req.perm.require('BROWSER_VIEW') presel = req.args.get('preselected') if presel and (presel + '/').startswith(req.href.browser() + '/'): req.redirect(presel) path = req.args.get('path', '/') rev = req.args.get('rev', '') if rev.lower() in ('', 'head'): rev = None format = req.args.get('format') order = req.args.get('order', 'name').lower() desc = req.args.has_key('desc') xhr = req.get_header('X-Requested-With') == 'XMLHttpRequest' rm = RepositoryManager(self.env) all_repositories = rm.get_all_repositories() reponame, repos, path = rm.get_repository_by_path(path) # Repository index show_index = not reponame and path == '/' if show_index: if repos and (as_bool(all_repositories[''].get('hidden')) or not repos.is_viewable(req.perm)): repos = None if not repos and reponame: raise ResourceNotFound(_("Repository '%(repo)s' not found", repo=reponame)) if reponame and reponame != repos.reponame: # Redirect alias qs = req.query_string req.redirect(req.href.browser(repos.reponame or None, path) + ('?' + qs if qs else '')) reponame = repos.reponame if repos else None # Find node for the requested path/rev context = web_context(req) node = None display_rev = lambda rev: rev if repos: try: if rev: rev = repos.normalize_rev(rev) # If `rev` is `None`, we'll try to reuse `None` consistently, # as a special shortcut to the latest revision. rev_or_latest = rev or repos.youngest_rev node = get_existing_node(req, repos, path, rev_or_latest) except NoSuchChangeset, e: raise ResourceNotFound(e.message, _('Invalid changeset number')) context = context.child(repos.resource.child('source', path, version=rev_or_latest)) display_rev = repos.display_rev
def get_subscriptions(self): """Generates tuples of (distributor, sid, authenticated, email). Tuples are suitable for yielding from IAnnouncementSubscriber's subscriptions method. """ with self.env.db_query as db: cursor = db.cursor() cursor.execute(""" SELECT sid, authenticated, value FROM session_attribute WHERE name=%s """, (self._attr_name(),)) for result in cursor.fetchall(): dists, val = decode(result[2]) for dist in dists: if as_bool(val): authenticated = as_bool(result[1]) yield (dist, result[0], authenticated, None)
def getbool(self, key, default=''): """Return the value of the specified option as boolean. This method returns `True` if the option value is one of "yes", "true", "enabled", "on", or non-zero numbers, ignoring case. Otherwise `False` is returned. Valid default input is a string or a bool. Returns a bool. """ return as_bool(self.get(key, default))
def process_request(self, req): req.perm.require('BROWSER_VIEW') presel = req.args.get('preselected') if presel and (presel + '/').startswith(req.href.browser() + '/'): req.redirect(presel) path = req.args.get('path', '/') rev = req.args.get('rev', '') if rev in ('', 'HEAD'): rev = None order = req.args.get('order', 'name').lower() desc = req.args.has_key('desc') xhr = req.get_header('X-Requested-With') == 'XMLHttpRequest' rm = RepositoryManager(self.env) all_repositories = rm.get_all_repositories() reponame, repos, path = rm.get_repository_by_path(path) # Repository index show_index = not reponame and path == '/' if show_index: if repos and (as_bool(all_repositories[''].get('hidden')) or not repos.can_view(req.perm)): repos = None if not repos and reponame: raise ResourceNotFound(_("Repository '%(repo)s' not found", repo=reponame)) if reponame and reponame != repos.reponame: # Redirect alias qs = req.query_string req.redirect(req.href.browser(repos.reponame or None, path) + (qs and '?' + qs or '')) reponame = repos and repos.reponame or None # Find node for the requested path/rev context = Context.from_request(req) node = None display_rev = lambda rev: rev if repos: try: if rev: rev = repos.normalize_rev(rev) # If `rev` is `None`, we'll try to reuse `None` consistently, # as a special shortcut to the latest revision. rev_or_latest = rev or repos.youngest_rev node = get_existing_node(req, repos, path, rev_or_latest) except NoSuchChangeset, e: raise ResourceNotFound(e.message, _('Invalid changeset number')) context = context(repos.resource.child('source', path, version=rev_or_latest)) display_rev = repos.display_rev
def get_ticket_group_stats(self, ticket_ids): stat = TicketGroupStats(self.drilldown_label, self.sum_label) for group in self._get_groups(ticket_ids): stat.add_interval(title=group.get('label', group['name']), count=group.get('total', 0), qry_args=group.get('query_args', {}), css_class=group.get('css_class', group['name']), overall_completion=as_bool( group.get('overall_completion'))) stat.refresh_calcs() return stat
def get_ticket_group_stats(self, ticket_ids): stat = TicketGroupStats(self.drilldown_label, self.sum_label) for group in self._get_groups(ticket_ids): stat.add_interval( title=group.get('label', group['name']), count=group.get('total', 0), qry_args=group.get('query_args', {}), css_class=group.get('css_class', group['name']), overall_completion=as_bool(group.get('overall_completion'))) stat.refresh_calcs() return stat
def _get_repository_data_from_request(self, req, prefix=''): """Fill a dict with common repository data for create/fork/modify actions. """ directory = req.args.get(prefix + 'dir') if self.restrict_dir or not directory: directory = req.args.get(prefix + 'name') return {'name': req.args.get(prefix + 'name'), 'type': req.args.get(prefix + 'type'), 'dir': normalize_whitespace(directory), 'owner': req.args.get(prefix + 'owner', req.authname), 'inherit_readers': as_bool(req.args.get('inherit_readers'))}
def _extend_info(self, reponame, info, editable): """Extend repository info for rendering.""" info['name'] = reponame info['hidden'] = as_bool(info.get('hidden')) info['editable'] = editable if 'alias' not in info: if info.get('dir') is not None: info['prettydir'] = breakable_path(info['dir']) or '' try: repos = RepositoryManager(self.env).get_repository(reponame) except InvalidRepository, e: info['error'] = e except TracError: pass # Probably "unsupported connector"
def _extend_info(self, reponame, info, editable): """Extend repository info for rendering.""" info["name"] = reponame info["hidden"] = as_bool(info.get("hidden")) info["sync_per_request"] = as_bool(info.get("sync_per_request")) info["editable"] = editable if "alias" not in info: if info.get("dir") is not None: info["prettydir"] = breakable_path(info["dir"]) or "" try: repos = RepositoryManager(self.env).get_repository(reponame) except InvalidRepository as e: info["error"] = e except TracError: pass # Probably "unsupported connector" else: youngest_rev = repos.get_youngest_rev() info["rev"] = youngest_rev try: info["display_rev"] = repos.display_rev(youngest_rev) except NoSuchChangeset: pass return info
def _extend_info(self, reponame, info, editable): """Extend repository info for rendering.""" info['name'] = reponame info['hidden'] = as_bool(info.get('hidden')) info['sync_per_request'] = as_bool(info.get('sync_per_request')) info['editable'] = editable if 'alias' not in info: if info.get('dir') is not None: info['prettydir'] = breakable_path(info['dir']) or '' try: repos = RepositoryManager(self.env).get_repository(reponame) except InvalidRepository as e: info['error'] = e except TracError: pass # Probably "unsupported connector" else: youngest_rev = repos.get_youngest_rev() info['rev'] = youngest_rev try: info['display_rev'] = repos.display_rev(youngest_rev) except NoSuchChangeset: pass return info
def as_bool(self, key, default=None): """Return the value as a boolean. Return `default` if if an exception is raised while converting the value to a boolean. :param key: the name of the session attribute :keyword default: the value to return if the parameter is not specified or an exception occurs converting the value to a boolean. :since: 1.2 """ if key not in self: return default return as_bool(self[key], default)
def as_bool(self, name, default=None): """Return the value as a boolean. Return `default` if if an exception is raised while converting the value to a boolean. :param name: the name of the request parameter :keyword default: the value to return if the parameter is not specified or an exception occurs converting the value to a boolean. :since: 1.2 """ if name not in self: return default return as_bool(self.getfirst(name), default)
def validate_ticket(self, req, ticket): if not self.limit_sensitivity: return [] sensitive = 1 try: sensitive = ticket['sensitive'] except: pass if as_bool(sensitive): if req.authname is 'anonymous': return [(None, 'Sorry, you cannot create or update a sensitive ticket without at least logging in first')] if self.bypass_sensitive_view(ticket, req.authname): return [] req.perm(ticket.resource).require('SENSITIVE_VIEW') return []
def _do_set(self, reponame, key, value): if key not in self.repository_attrs: raise AdminCommandError(_('Invalid key "%(key)s"', key=key)) if key == 'dir': value = os.path.abspath(value) if key in ('hidden', 'sync_per_request'): value = '1' if as_bool(value) else None self.modify_repository(reponame, {key: value}) if not reponame: reponame = '(default)' if key == 'dir': printout(_('You should now run "repository resync %(name)s".', name=reponame)) elif key == 'type': printout(_('You may have to run "repository resync %(name)s".', name=reponame))
def _extend_info(self, reponame, info, editable): """Extend repository info for rendering.""" info['name'] = reponame if info.get('dir') is not None: info['prettydir'] = breakable_path(info['dir']) or '' info['hidden'] = as_bool(info.get('hidden')) info['editable'] = editable if not info.get('alias'): try: repos = RepositoryManager(self.env).get_repository(reponame) youngest_rev = repos.get_youngest_rev() info['rev'] = youngest_rev info['display_rev'] = repos.display_rev(youngest_rev) except Exception: pass return info
def _extend_info(self, reponame, info, editable): """Extend repository info for rendering.""" info["name"] = reponame if info.get("dir") is not None: info["prettydir"] = breakable_path(info["dir"]) or "" info["hidden"] = as_bool(info.get("hidden")) info["editable"] = editable if not info.get("alias"): try: repos = RepositoryManager(self.env).get_repository(reponame) youngest_rev = repos.get_youngest_rev() info["rev"] = youngest_rev info["display_rev"] = repos.display_rev(youngest_rev) except Exception: pass return info
def validate_ticket(self, req, ticket): if not self.limit_sensitivity: return [] sensitive = 1 try: sensitive = ticket['sensitive'] except: pass if as_bool(sensitive): if req.authname is 'anonymous': return [( None, 'Sorry, you cannot create or update a sensitive ticket without at least logging in first' )] if self.bypass_sensitive_view(ticket, req.authname): return [] req.perm(ticket.resource).require('SENSITIVE_VIEW') return []
def _render_list(self, req): """Render the list of available reports.""" sort = req.args.get('sort', 'report') asc = as_bool(req.args.get('asc', 1)) format = req.args.get('format') rows = self.env.db_query(""" SELECT id, title, description FROM report ORDER BY %s %s """ % ('title' if sort == 'title' else 'id', '' if asc else 'DESC')) rows = [(id, title, description) for id, title, description in rows if 'REPORT_VIEW' in req.perm('report', id)] if format == 'rss': data = {'rows': rows} return 'report_list.rss', data, 'application/rss+xml' elif format == 'csv': self._send_csv(req, ['report', 'title', 'description'], rows, mimetype='text/csv', filename='reports.csv') elif format == 'tab': self._send_csv(req, ['report', 'title', 'description'], rows, '\t', mimetype='text/tab-separated-values', filename='reports.tsv') def report_href(**kwargs): return req.href.report(sort=req.args.get('sort'), asc='1' if asc else '0', **kwargs) add_link(req, 'alternate', auth_link(req, report_href(format='rss')), _('RSS Feed'), 'application/rss+xml', 'rss') add_link(req, 'alternate', report_href(format='csv'), _('Comma-delimited Text'), 'text/plain') add_link(req, 'alternate', report_href(format='tab'), _('Tab-delimited Text'), 'text/plain') reports = [(id, title, description, 'REPORT_MODIFY' in req.perm('report', id), 'REPORT_DELETE' in req.perm('report', id)) for id, title, description in rows] data = {'reports': reports, 'sort': sort, 'asc': asc} return 'report_list.html', data, None
def _convert_value(self, value, option=None): """ Converts a config value into a string so that it can be used by Genshi without needing to convert or escape anything. """ if option is not None: option_type = option.__class__.__name__ if option_type == 'BoolOption': if as_bool(str(value)): return 'true' else: return 'false' elif (option_type == 'ListOption' or option_type == 'OrderedExtensionsOption' ) and type(value).__name__ == 'list': return unicode(option.sep).join(value) if value is None: return '' return unicode(value)
def parse_subscriber_config(rawsubscriptions): """Given a list of options from [notification-subscriber]""" required_attrs = { 'distributor': 'email', 'priority': 100, 'adverb': 'always', 'format': 'text/plain', } optional_attrs = {} known_attrs = required_attrs.copy() known_attrs.update(optional_attrs) byname = defaultdict(dict) for option, value in rawsubscriptions: parts = option.split('.', 1) name = parts[0] if len(parts) == 1: byname[name].update({'name': name, 'class': value.strip()}) else: attribute = parts[1] known = known_attrs.get(attribute) if known is None or isinstance(known, basestring): pass elif isinstance(known, int): value = int(value) elif isinstance(known, bool): value = as_bool(value) elif isinstance(known, list): value = to_list(value) byname[name][attribute] = value byclass = defaultdict(list) for name, attributes in byname.items(): for key, value in required_attrs.items(): attributes.setdefault(key, value) byclass[attributes['class']].append(attributes) for values in byclass.values(): values.sort(key=lambda value: (value['priority'], value['name'])) return byclass
def get_user_setting(self, sid): """Returns tuple of (value, authenticated).""" with self.env.db_query as db: cursor = db.cursor() cursor.execute(""" SELECT value, authenticated FROM session_attribute WHERE sid=%s AND name=%s """, (sid, self._attr_name())) row = cursor.fetchone() if row: pair = decode(row[0]) authenticated = as_bool(row[1]) else: pair = (self.default['dists'], self.default['value']) authenticated = False # We use None here so that Genshi templates check their checkboxes # properly and without confusion. return pair + (authenticated,)
def _render_repository_index(self, context, all_repositories, order, desc): # Color scale for the age column timerange = custom_colorizer = None if self.color_scale: custom_colorizer = self.get_custom_colorizer() rm = RepositoryManager(self.env) repositories = [] for reponame, repoinfo in all_repositories.iteritems(): if not reponame or as_bool(repoinfo.get('hidden')): continue try: repos = rm.get_repository(reponame) except TracError, err: entry = (reponame, repoinfo, None, None, exception_to_unicode(err), None) else: if repos: if not repos.is_viewable(context.perm): continue try: youngest = repos.get_changeset(repos.youngest_rev) except NoSuchChangeset: youngest = None if self.color_scale and youngest: if not timerange: timerange = TimeRange(youngest.date) else: timerange.insert(youngest.date) raw_href = self._get_download_href(context.href, repos, None, None) entry = (reponame, repoinfo, repos, youngest, None, raw_href) else: entry = (reponame, repoinfo, None, None, u"\u2013", None) if entry[4] is not None: # Check permission in case of error root = Resource('repository', reponame).child('source', '/') if 'BROWSER_VIEW' not in context.perm(root): continue repositories.append(entry)
def __init__(self): self.section = 'multiproject-files' # logic is a mapping from configuration_key to # tuple (default_value, value_getter, strip_value) _as_bool = lambda ignored_key, value: as_bool(value) self.logic = { 'default_downloads_directory': ('downloads', self._get_directory, True), 'sys_dav_root': ('/var/www/trac/webdav', self._get_abspath, True), 'url_dav_path': ('dav', self._get_relative_url, True), 'downloads_dir_customizable': ('True', _as_bool, True), } values = {} self.defaults = {} value = None options = {} try: items = Configuration.instance().config_parser.items(self.section) for item in items: options[item[0]] = item[1] except NoSectionError: options = {} for key, logic in self.logic.items(): default_val, handler, do_strip = logic self.defaults[key] = default_val if key not in options: value = handler(key, default_val) else: value = options[key].strip() if do_strip else options[key] value = handler(key, value) values[key] = value self.default_downloads_directory = values[ 'default_downloads_directory'] self.sys_dav_root = values['sys_dav_root'] self.url_dav_path = values['url_dav_path'] self.downloads_dir_customizable = values['downloads_dir_customizable']
def get_subscriptions(self, match): """Generates tuples of (distributor, sid, authenticated, email). `match` should is passed the string value of the setting and should return true or false depending on whether the subscription matches. Tuples are suitable for yielding from IAnnouncementSubscriber's subscriptions method. """ with self.env.db_query as db: cursor = db.cursor() cursor.execute(""" SELECT sid, authenticated, value FROM session_attribute WHERE name=%s """, (self._attr_name(),)) for result in cursor.fetchall(): dists, val = decode(result[2]) for dist in dists: if match(dist, val): authenticated = as_bool(result[1]) yield (dist, result[0], authenticated, None)
def parse_subscriber_config(rawsubscriptions): """Given a list of options from [notification-subscriber]""" required_attrs = { 'distributor': 'email', 'priority': 100, 'adverb': 'always', 'format': 'text/plain', } optional_attrs = {} known_attrs = required_attrs.copy() known_attrs.update(optional_attrs) byname = defaultdict(dict) for option, value in rawsubscriptions: parts = option.split('.') name = parts[0] if len(parts) == 1: byname[name]['name'] = name byname[name]['class'] = value.strip() else: attribute = parts[1] if attribute not in known_attrs.keys or \ isinstance(known_attrs[attribute], str): byname[name][attribute] = value elif isinstance(known_attrs[attribute], int): byname[name][attribute] = int(value) elif isinstance(known_attrs[attribute], bool): byname[name][attribute] = as_bool(value) elif isinstance(known_attrs[attribute], list): byname[name][attribute] = to_list(value) byclass = defaultdict(list) for name, attributes in byname.items(): for key, val in required_attrs.items(): attributes.setdefault(key, val) byclass[attributes['class']].append(attributes) return byclass
def __init__(self): self.section = 'multiproject-files' # logic is a mapping from configuration_key to # tuple (default_value, value_getter, strip_value) _as_bool = lambda ignored_key, value: as_bool(value) self.logic = { 'default_downloads_directory': ('downloads', self._get_directory, True), 'sys_dav_root': ('/var/www/trac/webdav', self._get_abspath, True), 'url_dav_path': ('dav', self._get_relative_url, True), 'downloads_dir_customizable': ('True', _as_bool, True), } values = {} self.defaults = {} value = None options = {} try: items = Configuration.instance().config_parser.items(self.section) for item in items: options[item[0]] = item[1] except NoSectionError: options = {} for key, logic in self.logic.items(): default_val, handler, do_strip = logic self.defaults[key] = default_val if key not in options: value = handler(key, default_val) else: value = options[key].strip() if do_strip else options[key] value = handler(key, value) values[key] = value self.default_downloads_directory = values['default_downloads_directory'] self.sys_dav_root = values['sys_dav_root'] self.url_dav_path = values['url_dav_path'] self.downloads_dir_customizable = values['downloads_dir_customizable']
def normalize(self, value): if value not in (True, False): value = as_bool(value) return self.dumps(value)
def process_request(self, req): presel = req.args.get("preselected") if presel and (presel + "/").startswith(req.href.browser() + "/"): req.redirect(presel) path = req.args.get("path", "/") rev = req.args.get("rev", "") if rev.lower() in ("", "head"): rev = None format = req.args.get("format") order = req.args.get("order", "name").lower() desc = "desc" in req.args rm = RepositoryManager(self.env) all_repositories = rm.get_all_repositories() reponame, repos, path = rm.get_repository_by_path(path) # Repository index show_index = not reponame and path == "/" if show_index: if repos and (as_bool(all_repositories[""].get("hidden")) or not repos.is_viewable(req.perm)): repos = None if not repos and reponame: raise ResourceNotFound(_("Repository '%(repo)s' not found", repo=reponame)) if reponame and reponame != repos.reponame: # Redirect alias qs = req.query_string req.redirect(req.href.browser(repos.reponame or None, path) + ("?" + qs if qs else "")) reponame = repos.reponame if repos else None # Find node for the requested path/rev context = web_context(req) node = None changeset = None display_rev = lambda rev: rev if repos: try: if rev: rev = repos.normalize_rev(rev) # If `rev` is `None`, we'll try to reuse `None` consistently, # as a special shortcut to the latest revision. rev_or_latest = rev or repos.youngest_rev node = get_existing_node(req, repos, path, rev_or_latest) except NoSuchChangeset as e: raise ResourceNotFound(e, _("Invalid changeset number")) if node: try: # use changeset instance to retrieve branches and tags changeset = repos.get_changeset(node.rev) except NoSuchChangeset: pass context = context.child(repos.resource.child(self.realm, path, version=rev_or_latest)) display_rev = repos.display_rev # Prepare template data path_links = get_path_links(req.href, reponame, path, rev, order, desc) repo_data = dir_data = file_data = None if show_index: repo_data = self._render_repository_index(context, all_repositories, order, desc) if node: if not node.is_viewable(req.perm): raise PermissionError("BROWSER_VIEW" if node.isdir else "FILE_VIEW", node.resource, self.env) if node.isdir: if format in ("zip",): # extension point here... self._render_zip(req, context, repos, node, rev) # not reached dir_data = self._render_dir(req, repos, node, rev, order, desc) elif node.isfile: file_data = self._render_file(req, context, repos, node, rev) if not repos and not (repo_data and repo_data["repositories"]): # If no viewable repositories, check permission instead of # repos.is_viewable() req.perm.require("BROWSER_VIEW") if show_index: raise ResourceNotFound(_("No viewable repositories")) else: raise ResourceNotFound(_("No node %(path)s", path=path)) quickjump_data = properties_data = None if node and not req.is_xhr: properties_data = self.render_properties("browser", context, node.get_properties()) quickjump_data = list(repos.get_quickjump_entries(rev)) data = { "context": context, "reponame": reponame, "repos": repos, "repoinfo": all_repositories.get(reponame or ""), "path": path, "rev": node and node.rev, "stickyrev": rev, "display_rev": display_rev, "changeset": changeset, "created_path": node and node.created_path, "created_rev": node and node.created_rev, "properties": properties_data, "path_links": path_links, "order": order, "desc": 1 if desc else None, "repo": repo_data, "dir": dir_data, "file": file_data, "quickjump_entries": quickjump_data, "wiki_format_messages": self.config["changeset"].getbool("wiki_format_messages"), "xhr": req.is_xhr, # Remove in 1.3.1 } if req.is_xhr: # render and return the content only return "dir_entries.html", data, None if dir_data or repo_data: add_script(req, "common/js/expand_dir.js") add_script(req, "common/js/keyboard_nav.js") # Links for contextual navigation if node: if node.isfile: prev_rev = repos.previous_rev(rev=node.created_rev, path=node.created_path) if prev_rev: href = req.href.browser(reponame, node.created_path, rev=prev_rev) add_link(req, "prev", href, _("Revision %(num)s", num=display_rev(prev_rev))) if rev is not None: add_link(req, "up", req.href.browser(reponame, node.created_path)) next_rev = repos.next_rev(rev=node.created_rev, path=node.created_path) if next_rev: href = req.href.browser(reponame, node.created_path, rev=next_rev) add_link(req, "next", href, _("Revision %(num)s", num=display_rev(next_rev))) prevnext_nav(req, _("Previous Revision"), _("Next Revision"), _("Latest Revision")) else: if path != "/": add_link(req, "up", path_links[-2]["href"], _("Parent directory")) add_ctxtnav( req, tag.a(_("Last Change"), href=req.href.changeset(node.created_rev, reponame, node.created_path)) ) if node.isfile: annotate = data["file"]["annotate"] if annotate: add_ctxtnav( req, _("Normal"), title=_("View file without annotations"), href=req.href.browser(reponame, node.created_path, rev=rev), ) if annotate != "blame": add_ctxtnav( req, _("Blame"), title=_( "Annotate each line with the last " "changed revision " "(this can be time consuming...)" ), href=req.href.browser(reponame, node.created_path, rev=rev, annotate="blame"), ) add_ctxtnav(req, _("Revision Log"), href=req.href.log(reponame, path, rev=rev)) path_url = repos.get_path_url(path, rev) if path_url: if path_url.startswith("//"): path_url = req.scheme + ":" + path_url add_ctxtnav(req, _("Repository URL"), href=path_url) add_stylesheet(req, "common/css/browser.css") return "browser.html", data, None
def expand_macro(self, formatter, name, content): args, kwargs = parse_args(content) format = kwargs.get("format", "compact") glob = kwargs.get("glob", "*") order = kwargs.get("order") desc = as_bool(kwargs.get("desc", 0)) rm = RepositoryManager(self.env) all_repos = dict(rdata for rdata in rm.get_all_repositories().items() if fnmatchcase(rdata[0], glob)) if format == "table": repo = self._render_repository_index(formatter.context, all_repos, order, desc) add_stylesheet(formatter.req, "common/css/browser.css") wiki_format_messages = self.config["changeset"].getbool("wiki_format_messages") data = { "repo": repo, "order": order, "desc": 1 if desc else None, "reponame": None, "path": "/", "stickyrev": None, "wiki_format_messages": wiki_format_messages, } from trac.web.chrome import Chrome return Chrome(self.env).render_template(formatter.req, "repository_index.html", data, None, fragment=True) def get_repository(reponame): try: return rm.get_repository(reponame) except TracError: return all_repos = [(reponame, get_repository(reponame)) for reponame in all_repos] all_repos = sorted( ( (reponame, repos) for reponame, repos in all_repos if repos and not as_bool(repos.params.get("hidden")) and repos.is_viewable(formatter.perm) ), reverse=desc, ) def repolink(reponame, repos): label = reponame or _("(default)") return Markup( tag.a( label, title=_("View repository %(repo)s", repo=label), href=formatter.href.browser(repos.reponame or None), ) ) if format == "list": return tag.dl( [ tag(tag.dt(repolink(reponame, repos)), tag.dd(repos.params.get("description"))) for reponame, repos in all_repos ] ) else: # compact return Markup(", ").join([repolink(reponame, repos) for reponame, repos in all_repos])
def get_ticket_group_stats(self, ticket_ids): total_cnt = len(ticket_ids) all_statuses = set(TicketSystem(self.env).get_all_status()) status_cnt = {} for s in all_statuses: status_cnt[s] = 0 if total_cnt: for status, count in self.env.db_query(""" SELECT status, count(status) FROM ticket WHERE id IN (%s) GROUP BY status """ % ",".join(str(x) for x in sorted(ticket_ids))): status_cnt[status] = count stat = TicketGroupStats(_('ticket status'), _('tickets')) remaining_statuses = set(all_statuses) groups = self._get_ticket_groups() catch_all_group = None # we need to go through the groups twice, so that the catch up group # doesn't need to be the last one in the sequence for group in groups: status_str = group['status'].strip() if status_str == '*': if catch_all_group: raise TracError(_( "'%(group1)s' and '%(group2)s' milestone groups " "both are declared to be \"catch-all\" groups. " "Please check your configuration.", group1=group['name'], group2=catch_all_group['name'])) catch_all_group = group else: group_statuses = set([s.strip() for s in status_str.split(',')]) \ & all_statuses if group_statuses - remaining_statuses: raise TracError(_( "'%(groupname)s' milestone group reused status " "'%(status)s' already taken by other groups. " "Please check your configuration.", groupname=group['name'], status=', '.join(group_statuses - remaining_statuses))) else: remaining_statuses -= group_statuses group['statuses'] = group_statuses if catch_all_group: catch_all_group['statuses'] = remaining_statuses for group in groups: group_cnt = 0 query_args = {} for s, cnt in status_cnt.iteritems(): if s in group['statuses']: group_cnt += cnt query_args.setdefault('status', []).append(s) for arg in [kv for kv in group.get('query_args', '').split(',') if '=' in kv]: k, v = [a.strip() for a in arg.split('=', 1)] query_args.setdefault(k, []).append(v) stat.add_interval(group.get('label', group['name']), group_cnt, query_args, group.get('css_class', group['name']), as_bool(group.get('overall_completion'))) stat.refresh_calcs() return stat