Example #1
0
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
Example #2
0
 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)
Example #3
0
 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)
Example #4
0
 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)
Example #5
0
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
Example #6
0
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
Example #7
0
 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)
Example #8
0
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