class TestUtilsCommand(AgiloTestCase): """Testcase for command parser, used by Request Filter module at the momemt""" def setUp(self): """Creates the needed environment""" self.super() alias_mapping = AgiloConfig(self.env).ALIASES type_mapping = dict(zip(alias_mapping.values(), alias_mapping.keys())) self.cp = CommandParser(self.env, type_mapping, alias_mapping) def testCommandParser(self): """Tests the command parser with some command samples""" commands = { "'ticket':Key.TYPE": [ (self.cp._by_key, 'ticket', ':Key.TYPE'), (self.cp._by_key, Key.TYPE, '')], "'fields'@'options'(item[Key.NAME]==Key.TYPE)": [ (self.cp._by_key, 'fields', "@'options'(item[Key.NAME]==Key.TYPE)"), (self.cp._iterate, ('options', '(item[Key.NAME]==Key.TYPE)'), '')], "'allowed_links':Key.TYPE": [ (self.cp._by_key, 'allowed_links', ':Key.TYPE'), (self.cp._by_key, Key.TYPE, '')], "'available_types'": [(self.cp._by_key, 'available_types', '')], "'source'": [(self.cp._by_key, 'source', '')], "'target'": [(self.cp._by_key, 'target', '')], "'changes'@'fields'(item.has_key(Key.FIELDS)):Key.TYPE:'new'|'old'": [ (self.cp._by_key, 'changes', "@'fields'(item.has_key(Key.FIELDS)):Key.TYPE:'new'|'old'"), (self.cp._iterate, ('fields', '(item.has_key(Key.FIELDS))'), ":Key.TYPE:'new'|'old'"), (self.cp._by_key, Key.TYPE, ":'new'|'old'"), (self.cp._by_key, ('new', 'old'), '')], #Note that string key are cleaned up here! "'ticket'@'types'": [ (self.cp._by_key, 'ticket', "@'types'"), (self.cp._iterate, 'types', '') ], "'row_groups',0,1@'cell_groups',0@'value'(item['header']['title']=='Type')": [ (self.cp._by_key, 'row_groups', ",0,1@'cell_groups',0@'value'(item['header']['title']=='Type')"), (self.cp._by_pos, 0, ",1@'cell_groups',0@'value'(item['header']['title']=='Type')"), (self.cp._by_pos, 1, "@'cell_groups',0@'value'(item['header']['title']=='Type')"), (self.cp._iterate, 'cell_groups', ",0@'value'(item['header']['title']=='Type')"), (self.cp._by_pos, 0, "@'value'(item['header']['title']=='Type')"), (self.cp._iterate, ('value', "(item['header']['title']=='Type')"), '') ], "'allowed_links':'*'" : [ (self.cp._by_key, 'allowed_links', ":'*'"), (self.cp._by_key, '*', '') ], } # Iterate through commands and consume them for command in commands.keys(): #print "Command: %s" % command acc, key, command = self.cp._parse_command(command) while command: #print "\nAcc: %s\nKey: %s\nCommand: %s\n" % (acc, key, command) acc, key, command = self.cp._parse_command(command) # Test against the expected results for command, results in commands.items(): for res in results: self.assert_equals(self.cp._parse_command(command), res) command = res[2] # Substitute with remaining command
def config_reloaded(self): """Recreate mapping dictionaries when needed""" config = AgiloConfig(self.env) if config.ALIASES is not None: self._alias_to_type = dict( zip(config.ALIASES.values(), config.ALIASES.keys())) self.cp = CommandParser(self.env, config.ALIASES, self._alias_to_type)
def config_reloaded(self): """Recreate mapping dictionaries when needed""" config = AgiloConfig(self.env) if config.ALIASES is not None: self._alias_to_type = dict(zip(config.ALIASES.values(), config.ALIASES.keys())) self.cp = CommandParser(self.env, config.ALIASES, self._alias_to_type)
def setUp(self): """Creates the needed environment""" self.super() alias_mapping = AgiloConfig(self.env).ALIASES type_mapping = dict(zip(alias_mapping.values(), alias_mapping.keys())) self.cp = CommandParser(self.env, type_mapping, alias_mapping)
class TestUtilsCommand(AgiloTestCase): """Testcase for command parser, used by Request Filter module at the momemt""" def setUp(self): """Creates the needed environment""" self.super() alias_mapping = AgiloConfig(self.env).ALIASES type_mapping = dict(zip(alias_mapping.values(), alias_mapping.keys())) self.cp = CommandParser(self.env, type_mapping, alias_mapping) def testCommandParser(self): """Tests the command parser with some command samples""" commands = { "'ticket':Key.TYPE": [(self.cp._by_key, 'ticket', ':Key.TYPE'), (self.cp._by_key, Key.TYPE, '')], "'fields'@'options'(item[Key.NAME]==Key.TYPE)": [(self.cp._by_key, 'fields', "@'options'(item[Key.NAME]==Key.TYPE)"), (self.cp._iterate, ('options', '(item[Key.NAME]==Key.TYPE)'), '') ], "'allowed_links':Key.TYPE": [(self.cp._by_key, 'allowed_links', ':Key.TYPE'), (self.cp._by_key, Key.TYPE, '')], "'available_types'": [(self.cp._by_key, 'available_types', '')], "'source'": [(self.cp._by_key, 'source', '')], "'target'": [(self.cp._by_key, 'target', '')], "'changes'@'fields'(item.has_key(Key.FIELDS)):Key.TYPE:'new'|'old'": [(self.cp._by_key, 'changes', "@'fields'(item.has_key(Key.FIELDS)):Key.TYPE:'new'|'old'"), (self.cp._iterate, ('fields', '(item.has_key(Key.FIELDS))'), ":Key.TYPE:'new'|'old'"), (self.cp._by_key, Key.TYPE, ":'new'|'old'"), (self.cp._by_key, ('new', 'old'), '') ], #Note that string key are cleaned up here! "'ticket'@'types'": [(self.cp._by_key, 'ticket', "@'types'"), (self.cp._iterate, 'types', '')], "'row_groups',0,1@'cell_groups',0@'value'(item['header']['title']=='Type')": [(self.cp._by_key, 'row_groups', ",0,1@'cell_groups',0@'value'(item['header']['title']=='Type')"), (self.cp._by_pos, 0, ",1@'cell_groups',0@'value'(item['header']['title']=='Type')"), (self.cp._by_pos, 1, "@'cell_groups',0@'value'(item['header']['title']=='Type')"), (self.cp._iterate, 'cell_groups', ",0@'value'(item['header']['title']=='Type')"), (self.cp._by_pos, 0, "@'value'(item['header']['title']=='Type')"), (self.cp._iterate, ('value', "(item['header']['title']=='Type')"), '')], "'allowed_links':'*'": [(self.cp._by_key, 'allowed_links', ":'*'"), (self.cp._by_key, '*', '')], } # Iterate through commands and consume them for command in commands.keys(): #print "Command: %s" % command acc, key, command = self.cp._parse_command(command) while command: #print "\nAcc: %s\nKey: %s\nCommand: %s\n" % (acc, key, command) acc, key, command = self.cp._parse_command(command) # Test against the expected results for command, results in commands.items(): for res in results: self.assert_equals(self.cp._parse_command(command), res) command = res[2] # Substitute with remaining command
class CoreTemplateProvider(Component): implements(ITemplateProvider, IRequestFilter, IAgiloConfigChangeListener) # Condition Regular expression matching PROP = re.compile(r'(p)(\[)([\w\.]+|[0-9]+)(\])') # List with Type -> Alias conversion paths # ANdreaT: I want to write something more expressive and intuitive # than this mess. Something more like: # ticket:Key.TYPE # fileds@options(item[Key.NAME]==Key.TYPE) # allowed_links:Key.TYPE # available_types # source # target # changes@fileds(item.has_key(Key.FIELDS)):Key.TYPE:'new' # Where: # @, :, (), |, & are predicates: # @ => in the list # () => if condition is verified # : => accessing by key, with '*' iterate on all value of a dictionary # | => or # & => and ALIAS_KEYS = [ #"'ticket':Key.TYPE", "'fields'@'options'(isinstance(item, dict) and item[Key.NAME]==Key.TYPE)", #"'type_selection':'options'", "'changes'@'fields'(isinstance(item, dict) and item.has_key(Key.FIELDS)):Key.TYPE:'new'|'old'", "'available_types'", "'row_groups',0,1@'cell_groups',0@'value'(isinstance(item, dict) and item['header']['title']=='Type')", "'allowed_links':'*'", "'source'", "'ticket_types'@'type'", "'target'", # String get evaluated, because there may be variable and indexes ] # List with Alias -> Type values TYPE_KEYS = [Key.TYPE, 'field_type', 'source', 'target', 'ticket_types'] def __init__(self, *args, **kwargs): """Initialize the template provider for Agilo""" super(CoreTemplateProvider, self).__init__(*args, **kwargs) self._alias_to_type = {} self.config_reloaded() self.env.systeminfo.append(('Agilo', VersionChecker().agilo_version())) def config_reloaded(self): """Recreate mapping dictionaries when needed""" config = AgiloConfig(self.env) if config.ALIASES is not None: self._alias_to_type = dict(zip(config.ALIASES.values(), config.ALIASES.keys())) self.cp = CommandParser(self.env, config.ALIASES, self._alias_to_type) #============================================================================= # IRequestFilter methods #============================================================================= def pre_process_request(self, req, handler): """ Modifies the data of an HTTP request and substitutes type aliases with ticket type names. Always returns the request handler unchanged. """ # Performance measurement setattr(req, START_TIME, now()) #substitute aliases with ticket types in request arguments for typedef in self.TYPE_KEYS: if req.args.has_key(typedef): typedef_obj = req.args.get(typedef) if isinstance(typedef_obj, list): req.args[typedef] = list() # We need a new list for td in typedef_obj: if self._alias_to_type.has_key(td): req.args[typedef].append(self._alias_to_type[td]) else: req.args[typedef].append(td) elif self._alias_to_type.has_key(req.args[typedef]): # print "!!! Replacing %s with %s" % (req.args[typedef], # self._alias_to_type[req.args[typedef]]) req.args[typedef] = self._alias_to_type[req.args[typedef]] return handler def post_process_request(self, req, template, data, content_type): """ Modify the data of a request and substitutes ticket type names with alias names. Always returns the request template and content_type unchanged. """ if data is None: data = dict() self._substitute_ticket_types_with_aliases_in_request_arguments(req) self._substitute_ticket_types_with_aliases_in_genshi_data(data) config = AgiloConfig(self.env) data['agilo_ticket_types'] = config.ALIASES.items() data['create_perm'] = self.create_permissions(req) data['agilo_version'] = VersionChecker().agilo_version() self._inject_agilo_ui_for_this_request(req, data) self._inject_processing_time(req, data) return template, data, content_type #============================================================================= # ITemplateProvider methods #============================================================================= def get_templates_dirs(self): return [resource_filename('agilo', 'templates')] def get_htdocs_dirs(self): return [('agilo', resource_filename('agilo', 'htdocs'))] #============================================================================= # just private instance methods #============================================================================= def _inject_agilo_ui_for_this_request(self, req, data): # The idea is that all things here are purely for display purposes so # we don't have to bother for ajax requests. This saves us ~0.2 seconds # for every request because loading a backlog (even without the real # tickets does some db queries). if is_ajax(req): return add_stylesheet(req, 'agilo/stylesheet/agilo.css') config = AgiloConfig(self.env) self._remove_trac_stylesheet_for_this_request(req) # avoid circular imports from agilo.scrum.backlog.web_ui import BacklogModule # adds to data the info needed to visualize the Backlog list BacklogModule(self.env).send_backlog_list_data(req, data) add_script(req, 'agilo/js/sidebar.js') add_script(req, 'agilo/js/cookies.js') add_script(req, 'agilo/js/settings.js') add_script(req, 'agilo/js/collapse.js') add_script(req, 'agilo/js/ie-patches.js') def _inject_processing_time(self, req, data): if hasattr(req, START_TIME): duration = now() - getattr(req, START_TIME) duration_string = '%s.%s' % (duration.seconds, duration.microseconds) data['processing_time'] = duration_string def _substitute_ticket_types_with_aliases_in_request_arguments(self, req): if req.args.has_key(Key.TYPE): req_value = req.args[Key.TYPE] config = AgiloConfig(self.env) if isinstance(req_value, list): req.args[Key.TYPE] = list() for typedef in req_value: prefix = '' if typedef != None and typedef.startswith('!'): prefix = typedef[0] typedef = typedef[1:] if config.ALIASES.has_key(typedef): alias = config.ALIASES[typedef] req.args[Key.TYPE].append(prefix + alias) else: req.args[Key.TYPE].append(typedef) elif config.ALIASES.has_key(req_value): req.args[Key.TYPE] = config.ALIASES[req_value] def _substitute_ticket_types_with_aliases_in_genshi_data(self, data, aliases=None): if aliases is None: aliases = self.ALIAS_KEYS if data is not None and aliases is not None: for cmd in aliases: self.cp.replace(data, cmd) def _remove_trac_stylesheet_for_this_request(self, req): linkset = list(req.chrome.get('linkset')) links = req.chrome.get('links') if linkset is not None: for link in linkset: # We must only remove the 'main' trac.css. Syntax # highlighting/ pygments come with another css named # 'pygments/trac.css' so we must be careful only to remove # the right trac.css. if link.startswith('stylesheet:') and link.find('/css/trac.css') != -1: linkset.remove(link) break # remove the link if links is not None and links.get('stylesheet', None) is not None: for link in links['stylesheet']: if link.has_key('href') and link['href'].find('trac.css') != -1: links['stylesheet'].remove(link) break req.chrome['linkset'] = set(linkset) def get_permission_name_to_create(self, trac_type_name): permission_name = 'CREATE_%s' % trac_type_name.upper() if hasattr(Action, permission_name): permission_name = getattr(Action, permission_name) return permission_name def create_permissions(self, req): """ Returns a list of the permissions to create new ticket types for the given request object """ create_perms = list() # see what ticket types the user has create permissions for for t_type, alias in AgiloConfig(self.env).ALIASES.items(): permission_name = self.get_permission_name_to_create(t_type) debug(self, "Type: %s, Alias: %s Permission: %s" % \ (t_type, alias, permission_name)) if permission_name in req.perm: create_perms.append(t_type) debug(self, "%s has create permission for types: %s" % \ (req.authname, create_perms)) return create_perms
class CoreTemplateProvider(Component): implements(ITemplateProvider, IRequestFilter, IAgiloConfigChangeListener) # Condition Regular expression matching PROP = re.compile(r'(p)(\[)([\w\.]+|[0-9]+)(\])') # List with Type -> Alias conversion paths # ANdreaT: I want to write something more expressive and intuitive # than this mess. Something more like: # ticket:Key.TYPE # fileds@options(item[Key.NAME]==Key.TYPE) # allowed_links:Key.TYPE # available_types # source # target # changes@fileds(item.has_key(Key.FIELDS)):Key.TYPE:'new' # Where: # @, :, (), |, & are predicates: # @ => in the list # () => if condition is verified # : => accessing by key, with '*' iterate on all value of a dictionary # | => or # & => and ALIAS_KEYS = [ #"'ticket':Key.TYPE", "'fields'@'options'(isinstance(item, dict) and item[Key.NAME]==Key.TYPE)", #"'type_selection':'options'", "'changes'@'fields'(isinstance(item, dict) and item.has_key(Key.FIELDS)):Key.TYPE:'new'|'old'", "'available_types'", "'row_groups',0,1@'cell_groups',0@'value'(isinstance(item, dict) and item['header']['title']=='Type')", "'allowed_links':'*'", "'source'", "'ticket_types'@'type'", "'target'", # String get evaluated, because there may be variable and indexes ] # List with Alias -> Type values TYPE_KEYS = [Key.TYPE, 'field_type', 'source', 'target', 'ticket_types'] def __init__(self, *args, **kwargs): """Initialize the template provider for Agilo""" super(CoreTemplateProvider, self).__init__(*args, **kwargs) self._alias_to_type = {} self.config_reloaded() self.env.systeminfo.append(('Agilo', VersionChecker().agilo_version())) def config_reloaded(self): """Recreate mapping dictionaries when needed""" config = AgiloConfig(self.env) if config.ALIASES is not None: self._alias_to_type = dict( zip(config.ALIASES.values(), config.ALIASES.keys())) self.cp = CommandParser(self.env, config.ALIASES, self._alias_to_type) #============================================================================= # IRequestFilter methods #============================================================================= def pre_process_request(self, req, handler): """ Modifies the data of an HTTP request and substitutes type aliases with ticket type names. Always returns the request handler unchanged. """ # Performance measurement setattr(req, START_TIME, now()) #substitute aliases with ticket types in request arguments for typedef in self.TYPE_KEYS: if req.args.has_key(typedef): typedef_obj = req.args.get(typedef) if isinstance(typedef_obj, list): req.args[typedef] = list() # We need a new list for td in typedef_obj: if self._alias_to_type.has_key(td): req.args[typedef].append(self._alias_to_type[td]) else: req.args[typedef].append(td) elif self._alias_to_type.has_key(req.args[typedef]): # print "!!! Replacing %s with %s" % (req.args[typedef], # self._alias_to_type[req.args[typedef]]) req.args[typedef] = self._alias_to_type[req.args[typedef]] return handler def post_process_request(self, req, template, data, content_type): """ Modify the data of a request and substitutes ticket type names with alias names. Always returns the request template and content_type unchanged. """ if data is None: data = dict() self._substitute_ticket_types_with_aliases_in_request_arguments(req) self._substitute_ticket_types_with_aliases_in_genshi_data(data) config = AgiloConfig(self.env) data['agilo_ticket_types'] = config.ALIASES.items() data['create_perm'] = self.create_permissions(req) data['agilo_version'] = VersionChecker().agilo_version() self._inject_agilo_ui_for_this_request(req, data) self._inject_processing_time(req, data) return template, data, content_type #============================================================================= # ITemplateProvider methods #============================================================================= def get_templates_dirs(self): return [resource_filename('agilo', 'templates')] def get_htdocs_dirs(self): return [('agilo', resource_filename('agilo', 'htdocs'))] #============================================================================= # just private instance methods #============================================================================= def _inject_agilo_ui_for_this_request(self, req, data): # The idea is that all things here are purely for display purposes so # we don't have to bother for ajax requests. This saves us ~0.2 seconds # for every request because loading a backlog (even without the real # tickets does some db queries). if is_ajax(req): return add_stylesheet(req, 'agilo/stylesheet/agilo.css') config = AgiloConfig(self.env) self._remove_trac_stylesheet_for_this_request(req) # avoid circular imports from agilo.scrum.backlog.web_ui import BacklogModule # adds to data the info needed to visualize the Backlog list BacklogModule(self.env).send_backlog_list_data(req, data) add_script(req, 'agilo/js/sidebar.js') add_script(req, 'agilo/js/cookies.js') add_script(req, 'agilo/js/settings.js') add_script(req, 'agilo/js/collapse.js') add_script(req, 'agilo/js/ie-patches.js') def _inject_processing_time(self, req, data): if hasattr(req, START_TIME): duration = now() - getattr(req, START_TIME) duration_string = '%s.%s' % (duration.seconds, duration.microseconds) data['processing_time'] = duration_string def _substitute_ticket_types_with_aliases_in_request_arguments(self, req): if req.args.has_key(Key.TYPE): req_value = req.args[Key.TYPE] config = AgiloConfig(self.env) if isinstance(req_value, list): req.args[Key.TYPE] = list() for typedef in req_value: prefix = '' if typedef != None and typedef.startswith('!'): prefix = typedef[0] typedef = typedef[1:] if config.ALIASES.has_key(typedef): alias = config.ALIASES[typedef] req.args[Key.TYPE].append(prefix + alias) else: req.args[Key.TYPE].append(typedef) elif config.ALIASES.has_key(req_value): req.args[Key.TYPE] = config.ALIASES[req_value] def _substitute_ticket_types_with_aliases_in_genshi_data( self, data, aliases=None): if aliases is None: aliases = self.ALIAS_KEYS if data is not None and aliases is not None: for cmd in aliases: self.cp.replace(data, cmd) def _remove_trac_stylesheet_for_this_request(self, req): linkset = list(req.chrome.get('linkset')) links = req.chrome.get('links') if linkset is not None: for link in linkset: # We must only remove the 'main' trac.css. Syntax # highlighting/ pygments come with another css named # 'pygments/trac.css' so we must be careful only to remove # the right trac.css. if link.startswith( 'stylesheet:') and link.find('/css/trac.css') != -1: linkset.remove(link) break # remove the link if links is not None and links.get('stylesheet', None) is not None: for link in links['stylesheet']: if link.has_key( 'href') and link['href'].find('trac.css') != -1: links['stylesheet'].remove(link) break req.chrome['linkset'] = set(linkset) def get_permission_name_to_create(self, trac_type_name): permission_name = 'CREATE_%s' % trac_type_name.upper() if hasattr(Action, permission_name): permission_name = getattr(Action, permission_name) return permission_name def create_permissions(self, req): """ Returns a list of the permissions to create new ticket types for the given request object """ create_perms = list() # see what ticket types the user has create permissions for for t_type, alias in AgiloConfig(self.env).ALIASES.items(): permission_name = self.get_permission_name_to_create(t_type) debug(self, "Type: %s, Alias: %s Permission: %s" % \ (t_type, alias, permission_name)) if permission_name in req.perm: create_perms.append(t_type) debug(self, "%s has create permission for types: %s" % \ (req.authname, create_perms)) return create_perms