Example #1
0
 def _set_restrict_owner_option(self, new_value):
     env = self._testenv.get_trac_environment()
     agilo_config = AgiloConfig(env)
     old_value = agilo_config.get_bool('restrict_owner', 'ticket')
     agilo_config.change_option('restrict_owner', new_value, 
                                section='ticket', save=True)
     return old_value
Example #2
0
 def initialize_agilo(self, project_name, db_url, svn_repo, demo=False):
     try:
         self.do_initenv('%s %s %s %s' % (project_name,
                                          db_url, 'svn', 
                                          svn_repo or 'somewhere'))
         # Now add agilo and the template path
         env = Environment(self.envname)
         ac = AgiloConfig(env)
         if not svn_repo:
             # remove the fake from the config
             ac.change_option('repository_dir', '', 'trac')
         # sets the restric_owner option
         ac.change_option('restrict_owner',
                          'true',
                          'ticket')
         # this is also saving the config
         ac.enable_agilo()
         # update wiki
         wiki = WikiPage(env, name='WikiStart')
         wiki.text = agilo_wiki
         wiki.save('admin', 'Updated to Agilo', '127.0.0.1')
         # reset the env
         self.env_set(envname=self.envname, env=env)
         # Now initialize Agilo
         self.do_upgrade('upgrade --no-backup')
         # Now create the demo if needed
         if demo:
             try:
                 from create_demo_data import _create_demo_data
                 _create_demo_data(env)
             except ImportError, e:
                 env.log.error(exception_to_unicode(e))
     except:
         pass
Example #3
0
 def initialize_agilo(self, project_name, db_url, svn_repo, demo=False):
     try:
         self.do_initenv(
             '%s %s %s %s' %
             (project_name, db_url, 'svn', svn_repo or 'somewhere'))
         # Now add agilo and the template path
         env = Environment(self.envname)
         ac = AgiloConfig(env)
         if not svn_repo:
             # remove the fake from the config
             ac.change_option('repository_dir', '', 'trac')
         # sets the restric_owner option
         ac.change_option('restrict_owner', 'true', 'ticket')
         # this is also saving the config
         ac.enable_agilo()
         # update wiki
         wiki = WikiPage(env, name='WikiStart')
         wiki.text = agilo_wiki
         wiki.save('admin', 'Updated to Agilo', '127.0.0.1')
         # reset the env
         self.env_set(envname=self.envname, env=env)
         # Now initialize Agilo
         self.do_upgrade('upgrade --no-backup')
         # Now create the demo if needed
         if demo:
             try:
                 from create_demo_data import _create_demo_data
                 _create_demo_data(env)
             except ImportError, e:
                 env.log.error(exception_to_unicode(e))
     except:
         pass
Example #4
0
 def _set_default_type(self, new_value):
     agilo_config = AgiloConfig(self.env)
     old_value = agilo_config.get('default_type', 'ticket')
     agilo_config.change_option('default_type',
                                new_value,
                                section='ticket',
                                save=True)
     return old_value
 def set_sprint_can_start_or_end_on_weekends(self):
     # The sprint must be running only for exactly one day, otherwise 
     # confirm commitment is not possible anymore (and the total capacity
     # might be different)
     env = self.testenv.get_trac_environment()
     config = AgiloConfig(env)
     config.change_option('sprints_can_start_or_end_on_weekends', True, section='agilo-general')
     config.save()
Example #6
0
 def test_doesnt_overwrite_custom_favicons(self):
     custom_favicon = 'fnord'
     project_section = AgiloConfig(self.env).get_section('project')
     project_section.change_option('icon', custom_favicon)
     self.assert_equals(custom_favicon, project_section.get('icon'))
     
     initialize_config(self.env, {})
     project_section = AgiloConfig(self.env).get_section('project')
     self.assert_equals(custom_favicon, project_section.get('icon'))
Example #7
0
    def test_doesnt_overwrite_custom_favicons(self):
        custom_favicon = 'fnord'
        project_section = AgiloConfig(self.env).get_section('project')
        project_section.change_option('icon', custom_favicon)
        self.assert_equals(custom_favicon, project_section.get('icon'))

        initialize_config(self.env, {})
        project_section = AgiloConfig(self.env).get_section('project')
        self.assert_equals(custom_favicon, project_section.get('icon'))
Example #8
0
 def _set_restrict_owner_option(self, new_value):
     env = self._testenv.get_trac_environment()
     agilo_config = AgiloConfig(env)
     old_value = agilo_config.get_bool('restrict_owner', 'ticket')
     agilo_config.change_option('restrict_owner',
                                new_value,
                                section='ticket',
                                save=True)
     return old_value
Example #9
0
 def _create_custom_ticket_type(self, type_name, field_names):
     custom_type = TicketType(self.env)
     custom_type.name = type_name
     custom_type.insert()
     
     config = AgiloConfig(self.env)
     config.change_option(type_name, field_names, section=AgiloConfig.AGILO_TYPES)
     config.reload()
     self.assert_true(type_name in config.get_available_types())
Example #10
0
 def setUp(self):
     self.super()
     links = AgiloConfig(self.env).get_section(AgiloConfig.AGILO_LINKS)
     links.change_option(LinkOption.ALLOW, 'story-task, bug-task, bug-story')
     
     # Creates tickets
     self.t1 = self.teh.create_ticket(Type.USER_STORY, props={Key.SUMMARY: u"User Story"})
     self.t2 = self.teh.create_ticket(Type.TASK, props={Key.SUMMARY: u"First Task"})
     self.t3 = self.teh.create_ticket(Type.TASK, props={Key.SUMMARY: u"Second Task"})
     self.linksearch = LinksSearchModule(self.env)
Example #11
0
 def test_reload_config(self):
     """Tests the reload of the config"""
     types = AgiloConfig(self.env).get_section(AgiloConfig.AGILO_TYPES)
     
     # Add a ticket type
     types.change_option('my_type', ', '.join([Key.PRIORITY, Key.COMPONENT]))
     types.change_option('my_type.alias', 'My Type')
     types.save()
     self.assert_true('my_type' in AgiloConfig(self.env).ALIASES, 
                      "Type not found in aliases, AgiloConfig not reloaded?")
 def set_sprint_can_start_or_end_on_weekends(self):
     # The sprint must be running only for exactly one day, otherwise
     # confirm commitment is not possible anymore (and the total capacity
     # might be different)
     env = self.testenv.get_trac_environment()
     config = AgiloConfig(env)
     config.change_option('sprints_can_start_or_end_on_weekends',
                          True,
                          section='agilo-general')
     config.save()
Example #13
0
 def list_save_view(self, req, cat, page):
     """Stores general preferences for agilo"""
     use_days = (req.args.get('use_days') == 'True')
     # sets the days option
     config = AgiloConfig(self.env)
     # REFACT: Actually we could use config.use_days = use_days but then
     # we don't have any control when the config is actually written...
     config.change_option(Key.USE_DAYS, use_days, 'agilo-general', save=False)
     # Save only once
     self.config.save()
     req.redirect(req.href.admin(cat, page))
Example #14
0
    def _create_custom_ticket_type(self, type_name, field_names):
        custom_type = TicketType(self.env)
        custom_type.name = type_name
        custom_type.insert()

        config = AgiloConfig(self.env)
        config.change_option(type_name,
                             field_names,
                             section=AgiloConfig.AGILO_TYPES)
        config.reload()
        self.assert_true(type_name in config.get_available_types())
Example #15
0
    def setUp(self):
        self.super()
        self.tester.login_as(Usernames.admin)
        

        components = AgiloConfig(self.env).get_section('components')
        components.change_option("tracrpc.*", 'enabled')
        components.save()
        
        self.ticket1_id = self.tester.create_new_agilo_task('foo', 'abc')
        self.ticket2_id = self.tester.create_new_agilo_task('bar', 'def')
Example #16
0
 def testCanDisableSprintStartDateNormalization(self):
     config = AgiloConfig(self.env).get_section(AgiloConfig.AGILO_GENERAL)
     option_name = 'sprints_can_start_or_end_on_weekends'
     self.assert_false(config.get_bool(option_name))
     config.change_option(option_name, True, save=True)
     self.assert_true(AgiloConfig(self.env).sprints_can_start_or_end_on_weekends)
     
     start = datetime(2009, 05, 30, tzinfo=utc)
     sprint = self.teh.create_sprint('Foo', start=start)
     self.assert_equals(start, sprint.start)
     self.assert_equals(utc, sprint.start.tzinfo)
Example #17
0
    def testCanDisableSprintStartDateNormalization(self):
        config = AgiloConfig(self.env).get_section(AgiloConfig.AGILO_GENERAL)
        option_name = 'sprints_can_start_or_end_on_weekends'
        self.assert_false(config.get_bool(option_name))
        config.change_option(option_name, True, save=True)
        self.assert_true(
            AgiloConfig(self.env).sprints_can_start_or_end_on_weekends)

        start = datetime(2009, 05, 30, tzinfo=utc)
        sprint = self.teh.create_sprint('Foo', start=start)
        self.assert_equals(start, sprint.start)
        self.assert_equals(utc, sprint.start.tzinfo)
Example #18
0
 def agilo_was_installed(self):
     try:
         from acct_mgr.web_ui import EmailVerificationModule
     except ImportError:
         return
     if not self.env.is_component_enabled(EmailVerificationModule):
         return
     
     components = AgiloConfig(self.env).get_section('components')
     if self.was_email_verification_enabled_explicitely(components):
         return
     components.change_option(self.name_emailverificationmodule(), 'disabled')
     components.save()
Example #19
0
    def agilo_was_installed(self):
        try:
            from acct_mgr.web_ui import EmailVerificationModule
        except ImportError:
            return
        if not self.env.is_component_enabled(EmailVerificationModule):
            return

        components = AgiloConfig(self.env).get_section('components')
        if self.was_email_verification_enabled_explicitely(components):
            return
        components.change_option(self.name_emailverificationmodule(),
                                 'disabled')
        components.save()
Example #20
0
 def list_save_view(self, req, cat, page):
     """Stores general preferences for agilo"""
     use_days = (req.args.get('use_days') == 'True')
     # sets the days option
     config = AgiloConfig(self.env)
     # REFACT: Actually we could use config.use_days = use_days but then
     # we don't have any control when the config is actually written...
     config.change_option(Key.USE_DAYS,
                          use_days,
                          'agilo-general',
                          save=False)
     # Save only once
     self.config.save()
     req.redirect(req.href.admin(cat, page))
Example #21
0
    def setUp(self):
        self.super()
        links = AgiloConfig(self.env).get_section(AgiloConfig.AGILO_LINKS)
        links.change_option(LinkOption.ALLOW,
                            'story-task, bug-task, bug-story')

        # Creates tickets
        self.t1 = self.teh.create_ticket(Type.USER_STORY,
                                         props={Key.SUMMARY: u"User Story"})
        self.t2 = self.teh.create_ticket(Type.TASK,
                                         props={Key.SUMMARY: u"First Task"})
        self.t3 = self.teh.create_ticket(Type.TASK,
                                         props={Key.SUMMARY: u"Second Task"})
        self.linksearch = LinksSearchModule(self.env)
 def runTest(self):
     env = self._testenv.get_trac_environment()
     config = AgiloConfig(env).get_section(AgiloConfig.AGILO_LINKS)
     option_name = '%s.calculate' % Type.REQUIREMENT
     configured_properties = config.get_list(option_name)
     broken_definition = 'sum:get_outgoing.blubber'
     configured_properties.append(broken_definition)
     config.change_option(option_name, ', '.join(configured_properties))
     config.save()
     self._tester.login_as(Usernames.admin)
     page_url = self._tester.url + '/admin/agilo/types/%s' % Type.REQUIREMENT
     tc.go(page_url)
     tc.code(200)
     
     html = tc.show()
     assert "sum:get_outgoing.rd_points|type=story|story_priority=Mandatory" in html
     assert 'blubber' not in html
Example #23
0
    def test_handles_types_with_dashes(self):
        from_type = 'with-dashes'
        to_type = 'bug'
        custom_type = TicketType(self.env)
        custom_type.name = from_type
        custom_type.insert()

        config = AgiloConfig(self.env)
        config.change_option(from_type, "", section=AgiloConfig.AGILO_TYPES)
        config.reload()
        self.assert_true(from_type in config.get_available_types())

        section = config.get_section(AgiloConfig.AGILO_LINKS)
        allowed_links = section.get_list('allow')
        allowed_links.append('%s-%s' % (from_type, to_type))
        section.change_option('allow', ', '.join(allowed_links), save=True)
        self.links_configuration = LinksConfiguration(self.env)
        self.assert_equals(self.links_configuration.get_alloweds(from_type)[0].dest_type, to_type)
 def setUp(self):
     self.super()
     config = AgiloConfig(self.env)
     # Adding properties for multiple calculated property testing
     # The problem is that at this point the linkConfiguration as been
     # already initialized so we will need to do it again manually
     config.change_option('actual_time', 'text', 
                          section=AgiloConfig.TICKET_CUSTOM)
     config.change_option(Type.TASK, 
                          'sprint, remaining_time, actual_time, estimated_time, owner, drp_resources',
                          section=AgiloConfig.AGILO_TYPES)
     config.change_option('story.calculate', 
                          'total_remaining_time=sum:get_outgoing.remaining_time, total_actual_time=sum:get_outgoing.actual_time',
                          section=AgiloConfig.AGILO_LINKS)
     config.save()
     
     self.assert_true(config.is_agilo_enabled)
     self.assert_true('actual_time' in config.get_list(Type.TASK, section=AgiloConfig.AGILO_TYPES))
     self.assert_true('actual_time' in config.get_fields_for_type().get(Type.TASK))
     self.assert_equals(config.get('story.calculate', section=AgiloConfig.AGILO_LINKS), 
                      'total_remaining_time=sum:get_outgoing.remaining_time, total_actual_time=sum:get_outgoing.actual_time')
     self._reset_links_configuration()
     # Creates tickets
     self.t1 = self.teh.create_ticket(Type.USER_STORY)
     self.t2 = self.teh.create_ticket(Type.TASK, 
                                      props={Key.REMAINING_TIME: u'20', 
                                             Key.RESOURCES: u'Tim, Tom'})
     self.t3 = self.teh.create_ticket(Type.TASK, 
                                      props={Key.REMAINING_TIME: u'10', 
                                             Key.RESOURCES: u'Tim, Tom'})
     # Now actual_time should be a valid field for Task...
     self.assert_not_none(self.t2.get_field('actual_time'))
 def set_cascade_delete_story_task(self):
     env = self.testenv.get_trac_environment()
     config = AgiloConfig(env)
     config.change_option('delete', 'task-story', section='agilo-links')
     config.save()
Example #26
0
class TypesAdminPanel(AgiloAdminPanel):
    """
    Administration panel for different ticket types and their fields.
    Needs to get imported in agilo/admin/__init__.py in order to appear
    on the web interface.
    """
    
    _type = 'types'
    _label = ('Types', _('Types'))

    def __init__(self):
        self.agilo_config = AgiloConfig(self.env)

    def _get_field_labels(self):
        """Returns a dictionary of fields and their labels."""
        labels = {}
        for f_name, label in self.agilo_config.LABELS.items():
            if f_name not in MANDATORY_FIELDS:
                labels[f_name] = label
        return labels

    def _get_fields(self, type_name=None):
        """
        If type_name is not set, return a dictionary of ticket types 
        and the corresponding fields as a list. If a type_name gets 
        passed, return a list of fields corresponding to this ticket 
        type.
        """
        fields = self.agilo_config.get_available_types(with_fields=True)
        if type_name:
            fields = fields.get(type_name)
        return fields
    
    def detail_save_view(self, req, cat, page, ticket_type):
        """Save the detail panel view"""
        # The types will be stored in lowercase and the space is not a
        # valid character for the config file key
        ticket_type = normalize_ticket_type(ticket_type)
        
        alias = req.args.get(Key.ALIAS)
        if alias:
            self.agilo_config.change_option('%s.%s' % (ticket_type, Key.ALIAS), 
                                            alias, 
                                            section=AgiloConfig.AGILO_TYPES,
                                            save=False)
        # save fields as string or comma-separated list of values
        # FIXME: (AT) after going crazy, it came out that some types are not
        # saved because there is no specific field assigned and the config 
        # won't store the property in the trac.ini. So the agilo type will also
        # not be loaded, even if the alias would be set.
        fields = req.args.get(Key.FIELDS, '') 
        # We have to save strings not lists
        if isinstance(fields, list):
            fields = ', '.join(fields)
        self.agilo_config.change_option(ticket_type, 
                                        fields,
                                        section=AgiloConfig.AGILO_TYPES,
                                        save=False)
        calc = []
        for res, func in zip(req.args.getlist('result'), 
                             req.args.getlist('function')):
            if res and func:
                configstring = u'%s=%s' % (res.strip(), func.strip())
                parsed = parse_calculated_field(configstring)
                if parsed == None:
                    msg = u"Wrong format for calculated property '%s'"
                    raise TracError(_(msg) % res)
                calc.append(configstring)
        calc = ','.join(calc)
        if calc:
            self.agilo_config.change_option('%s.%s' % (ticket_type, 
                                                       LinkOption.CALCULATE), 
                                            calc,
                                            section=AgiloConfig.AGILO_LINKS,
                                            save=False)
        self.agilo_config.save()
        
        # on 0.12 we need to reset the ticket fields explicitely as the synchronization 
        # is not done with the trac.ini anymore
        if AgiloTicketSystem(self.env).is_trac_012():
            AgiloTicketSystem(self.env).reset_ticket_fields()
        return req.redirect(req.href.admin(cat, page))
    
    def detail_view(self, req, cat, page, ticket_type):
        # All keys are saved lower-cased, but this is not done 
        # automatically for retrieval
        calc_prop = self.agilo_config.get_list('%s.%s' % (ticket_type, 
                                                          LinkOption.CALCULATE),
                                               section=AgiloConfig.AGILO_LINKS)
        calculated_properties = []
        if len(calc_prop) > 0:
            for definition in calc_prop:
                parts = definition.split('=', 1)
                if len(parts) == 2:
                    property_name, formula = parts
                    calculated_properties.append((property_name.strip(), 
                                                  formula.strip()))
                else:
                    message = u"Ignoring broken definition for " \
                              "calculated property: %s" % definition
                    warning(self, message)
        data = {
            'calculate' : calculated_properties,
            'view': 'detail',
            'type': ticket_type,
            'alias' : self.agilo_config.ALIASES.get(ticket_type, ''),
            'type_fields' : self._get_fields(ticket_type),
            'labels' : self._get_field_labels(),
        }
        return 'agilo_admin_types.html', data
    
    def list_view(self, req, cat, page):
        data = {
            'view': 'list',
            'fields': self._get_fields(),
            'aliases' : self.agilo_config.ALIASES,
            'labels' : self._get_field_labels(),
        }
        return 'agilo_admin_types.html', data
Example #27
0
def set_config_value(env, name, section, new_value):
    agilo_config = AgiloConfig(env)
    old_value = agilo_config.get(name, section)
    agilo_config.change_option(name, new_value, section=section, save=True)
    return old_value
Example #28
0
 def sprints_can_start_and_end_anytime(self):
     config = AgiloConfig(self.env)
     config.change_option('sprints_can_start_or_end_on_weekends',
                          True,
                          section='agilo-general')
     config.save()
Example #29
0
class AgiloConfigTest(AgiloTestCase):
    
    def setUp(self):
        self.super()
        self.trac_config = self.env.config
        self.config = AgiloConfig(self.env)
    
    # --------------------------------------------------------------------------
    # Agilo-specific configuration
    
    def test_days_are_fetched_correctly_from_config(self):
        """Regression test: Check that AgiloConfig uses the right configuration
        section and that use_days is really a bool, not a string."""
        self.trac_config.set('agilo-general', Key.USE_DAYS, False)
        self.assert_false(self.config.use_days)
        
        self.trac_config.set('agilo-general', Key.USE_DAYS, True)
        self.config.reload()
        self.assert_true(self.config.use_days)
    
    def test_can_enable_agilo_ui(self):
        self.config.enable_agilo_ui(save=True)
        self.assert_true(self.config.is_agilo_ui_enabled)
        self.config.disable_agilo_ui(save=True)
        self.assert_none(self.config.get('templates_dir', 'inherit'))
        self.assert_false(self.config.is_agilo_ui_enabled)
    
    def test_can_enable_agilo(self):
        self.config.enable_agilo()
        self.assert_true(self.config.is_agilo_enabled)
        self.config.disable_agilo()
        self.assert_false(self.config.is_agilo_enabled)
    
    def test_can_disable_agilo_ui(self):
        self.assert_true(self.config.is_agilo_ui_enabled)
        self.config.disable_agilo_ui(save=True)
        self.assert_false(self.config.is_agilo_ui_enabled)
    
    def _set_template_dir(self, config, dirname):
        config.change_option('templates_dir', dirname, 'inherit', save=True)
    
    def test_configuration_detects_outdated_template_path(self):
        self.assert_true(self.config.is_agilo_enabled)
        self.assert_true(self.config.is_agilo_ui_enabled)
        
        current_dir = '/usr/share/agilo-0.42-r12345.egg/templates'
        self._set_template_dir(self.config, '')
        self.assert_false(self.config.is_agilo_ui_enabled)
        self.assert_true(self.config._is_template_dir_outdated(current_dir))
        
        self._set_template_dir(self.config, '/my/user/configured/template')
        self.assert_false(self.config._is_template_dir_outdated(current_dir))
        
        self._set_template_dir(self.config, current_dir.replace('12345', '54321'))
        self.assert_true(self.config._is_template_dir_outdated(current_dir))
    
    def test_knows_when_filtered_burndown_is_enabled(self):
        self.assert_false(self.config.is_filtered_burndown_enabled())
        self.config.change_option('should_reload_burndown_on_filter_change_when_filtering_by_component', True, section=AgiloConfig.AGILO_GENERAL)
        self.assert_false(self.config.is_filtered_burndown_enabled())
        self.config.change_option('backlog_filter_attribute', 'component', section=AgiloConfig.AGILO_GENERAL)
        self.assert_true(self.config.is_filtered_burndown_enabled())
    
    # --------------------------------------------------------------------------
    # modify low-level configuration
    
    def test_can_remove_whole_sections(self):
        section = self.config.get_section('fnord')
        section.change_option('foo', 'bar')
        self.assert_true('fnord' in self.trac_config.sections())
        self.assert_equals('bar', section.get('foo'))
        section.remove()
        self.assert_not_equals('bar', section.get('foo'))
    
    def test_can_remove_sections_without_getting_it_first(self):
        section = self.config.get_section('fnord')
        section.change_option('foo', 'bar')
        self.assert_true('fnord' in self.trac_config.sections())
        self.assert_equals('bar', section.get('foo'))
        self.config.remove(section='fnord')
        self.assert_false(self.config.get_section('fnord').has_option('foo'))
        self.assert_not_equals('bar', section.get('foo'))
    
    def test_config_knows_if_an_option_is_set(self):
        self.assert_false(self.config.has_option('foo', section='fnord'))
        self.config.change_option('foo', 'bar', section='fnord')
        self.assert_true(self.config.has_option('foo', section='fnord'))
    
    def test_config_reloads_on_change(self):
        self.config.change_option('%s.%s' % (Type.BUG, Key.ALIAS),
                                  'Bugone', section=AgiloConfig.AGILO_TYPES,
                                  save=True)
        self.assert_equals('Bugone', self.config.ALIASES.get(Type.BUG))
    
    def test_config_reloads_links_configuration_on_change(self):
        self.assert_contains('story', LinksConfiguration(self.env).get_allowed_destination_types('requirement'))
        self.config.change_option('allow',
                                  '', section=AgiloConfig.AGILO_LINKS,
                                  save=True)
        self.assert_not_contains('story', LinksConfiguration(self.env).get_allowed_destination_types('requirement'))
    
    def test_config_writing_key_with_capitals(self):
        my_section = self.config.get_section('my-section')
        my_section.change_option('TestMe', 'This is a test', save=True)
        # Test that it is stored
        self.assert_equals('This is a test', 
                         self.env.config.get('my-section', 'TestMe'))
        # Test that is case insensitive
        self.assert_equals('This is a test', 
                         self.env.config.get('my-section', 'testme'))
        self.assert_equals('This is a test', 
                         self.env.config.get('my-section', 'TESTME'))
    
    def test_config_is_normalizing(self):
        my_section = self.config.get_section('my-section')
        my_section.change_option('TestMe', 'This is a test', save=True)
        # check that in reality only the lowecased version is saved in the
        # config file trac.ini
        options = self.config.get_options('my-section')
        self.assert_true('testme' in options,
                        "TestMe not found in: %s" % options)
        self.assert_false('TestMe' in options,
                        "TestMe found in: %s" % options)
    
    def test_config_not_updating_case_sensitive(self):
        my_section = self.config.get_section('my-section')
        my_section.change_option('TestMe', 'This is a test', save=True)
        # Test that it is asymmetric
        # Using set will not set the option as it is case insensitive and is
        # not stored because testme already exists in the trac.ini
        my_section.set_option('TESTME', 'This is another test', save=True)
        self.assert_not_equals('This is another test', 
                            self.env.config.get('my-section', 'TESTME'))
        self.assert_equals('This is a test', 
                         self.env.config.get('my-section', 'TESTME'))
        options = self.config.get_options('my-section')
        self.assert_true('testme' in options,
                         'TestMe not found in: %s' % options)
        self.assert_false('TestMe' in options,
                          'TestMe found in: %s' % options)
    
    def test_config_is_case_insensitive_and_overwrites(self):
        my_section = self.config.get_section('my-section')
        my_section.change_option('TestMe', 'This is a test', save=True)
        # Now change the option and check that also the old key, that is the
        # same actually changed
        my_section.change_option('TESTME', 'This is another test', save=True)
        self.assert_equals('This is another test', 
                         self.env.config.get('my-section', 'TESTME'))
        self.assert_not_equals('This is a test', 
                            self.env.config.get('my-section', 'testme'))
        # Now check what it is stored
        options = self.config.get_options('my-section')
        self.assert_true('testme' in options, 'testme not found in: %s' % options)
        self.assert_false('TESTME' in options, 'TESTME found in: %s' % options)
        # Test it is in the AgiloWrapper also after reload
        self.config.reload()
        self.assert_equals('This is another test', 
                         self.config.get('TestMe', 'my-section'))
    
    def test_config_stores_none_as_empty_string(self):
        my_section = self.config.get_section('my-section')
        my_section.set_option('test', 'This is a test', save=True)
        self.assert_equals('This is a test', 
                         self.env.config.get('my-section', 'test'))
        # Now change the option and check that also the old key, that is the
        # same actually changed
        my_section.change_option('test', None, save=True)
        self.assert_not_equals('This is a test', 
                               self.env.config.get('my-section', 'test'))
        self.assert_equals('', self.env.config.get('my-section', 'test'))
        # Check real config
        self.env.config.set('my-section', 'test', None)
        self.env.config.save()
        self.assert_equals('', self.env.config.get('my-section', 'test'))
    
    def test_dont_strip_non_string_values(self):
        self.assert_true(self.config.get('foo', default=True, section='trac'))
    
    def test_sets_default_agilo_logo_on_new_install(self):
        # should be set by the initialization so let's check it
        agilo_logo_src = 'agilo/images/default_logo.png'
        self.assert_equals(agilo_logo_src, self.env.config.get('header_logo', 'src'))
    
    def test_do_not_sets_default_logo_if_changed(self):
        test_src = 'my_logo'
        agilo_config = AgiloConfig(self.env)
        header_logo = agilo_config.get_section('header_logo')
        header_logo.change_option('src', test_src)
        self.assert_equals(test_src, header_logo.get('src'))
        set_default_agilo_logo(agilo_config)
        self.assert_equals(test_src, header_logo.get('src'))
    
    def test_sets_agilo_favicon_on_new_install(self):
        agilo_favicon = 'agilo/images/favicon.ico'
        self.assert_equals(agilo_favicon, self.env.config.get('project', 'icon'))
    
    def test_doesnt_overwrite_custom_favicons(self):
        custom_favicon = 'fnord'
        project_section = AgiloConfig(self.env).get_section('project')
        project_section.change_option('icon', custom_favicon)
        self.assert_equals(custom_favicon, project_section.get('icon'))
        
        initialize_config(self.env, {})
        project_section = AgiloConfig(self.env).get_section('project')
        self.assert_equals(custom_favicon, project_section.get('icon'))
 def _restore_old_configuration(self):
     env = self.testenv.get_trac_environment()
     config = AgiloConfig(env)
     for (section, property), value in self._old_configuration.items():
         config.change_option(property, value, section)
     config.save()
Example #31
0
 def _set_configuration(self, section, property, value):
     env = self.testenv.get_trac_environment()
     config = AgiloConfig(env)
     self._old_configuration[(section, property)] = value
     config.change_option(property, value, section, save=True)
Example #32
0
 def sprints_can_start_and_end_anytime(self):
     config = AgiloConfig(self.env)
     config.change_option('sprints_can_start_or_end_on_weekends', True, section='agilo-general')
     config.save()
Example #33
0
class AgiloConfigTest(AgiloTestCase):
    def setUp(self):
        self.super()
        self.trac_config = self.env.config
        self.config = AgiloConfig(self.env)

    # --------------------------------------------------------------------------
    # Agilo-specific configuration

    def test_days_are_fetched_correctly_from_config(self):
        """Regression test: Check that AgiloConfig uses the right configuration
        section and that use_days is really a bool, not a string."""
        self.trac_config.set('agilo-general', Key.USE_DAYS, False)
        self.assert_false(self.config.use_days)

        self.trac_config.set('agilo-general', Key.USE_DAYS, True)
        self.config.reload()
        self.assert_true(self.config.use_days)

    def test_can_enable_agilo_ui(self):
        self.config.enable_agilo_ui(save=True)
        self.assert_true(self.config.is_agilo_ui_enabled)
        self.config.disable_agilo_ui(save=True)
        self.assert_none(self.config.get('templates_dir', 'inherit'))
        self.assert_false(self.config.is_agilo_ui_enabled)

    def test_can_enable_agilo(self):
        self.config.enable_agilo()
        self.assert_true(self.config.is_agilo_enabled)
        self.config.disable_agilo()
        self.assert_false(self.config.is_agilo_enabled)

    def test_can_disable_agilo_ui(self):
        self.assert_true(self.config.is_agilo_ui_enabled)
        self.config.disable_agilo_ui(save=True)
        self.assert_false(self.config.is_agilo_ui_enabled)

    def _set_template_dir(self, config, dirname):
        config.change_option('templates_dir', dirname, 'inherit', save=True)

    def test_configuration_detects_outdated_template_path(self):
        self.assert_true(self.config.is_agilo_enabled)
        self.assert_true(self.config.is_agilo_ui_enabled)

        current_dir = '/usr/share/agilo-0.42-r12345.egg/templates'
        self._set_template_dir(self.config, '')
        self.assert_false(self.config.is_agilo_ui_enabled)
        self.assert_true(self.config._is_template_dir_outdated(current_dir))

        self._set_template_dir(self.config, '/my/user/configured/template')
        self.assert_false(self.config._is_template_dir_outdated(current_dir))

        self._set_template_dir(self.config,
                               current_dir.replace('12345', '54321'))
        self.assert_true(self.config._is_template_dir_outdated(current_dir))

    def test_knows_when_filtered_burndown_is_enabled(self):
        self.assert_false(self.config.is_filtered_burndown_enabled())
        self.config.change_option(
            'should_reload_burndown_on_filter_change_when_filtering_by_component',
            True,
            section=AgiloConfig.AGILO_GENERAL)
        self.assert_false(self.config.is_filtered_burndown_enabled())
        self.config.change_option('backlog_filter_attribute',
                                  'component',
                                  section=AgiloConfig.AGILO_GENERAL)
        self.assert_true(self.config.is_filtered_burndown_enabled())

    # --------------------------------------------------------------------------
    # modify low-level configuration

    def test_can_remove_whole_sections(self):
        section = self.config.get_section('fnord')
        section.change_option('foo', 'bar')
        self.assert_true('fnord' in self.trac_config.sections())
        self.assert_equals('bar', section.get('foo'))
        section.remove()
        self.assert_not_equals('bar', section.get('foo'))

    def test_can_remove_sections_without_getting_it_first(self):
        section = self.config.get_section('fnord')
        section.change_option('foo', 'bar')
        self.assert_true('fnord' in self.trac_config.sections())
        self.assert_equals('bar', section.get('foo'))
        self.config.remove(section='fnord')
        self.assert_false(self.config.get_section('fnord').has_option('foo'))
        self.assert_not_equals('bar', section.get('foo'))

    def test_config_knows_if_an_option_is_set(self):
        self.assert_false(self.config.has_option('foo', section='fnord'))
        self.config.change_option('foo', 'bar', section='fnord')
        self.assert_true(self.config.has_option('foo', section='fnord'))

    def test_config_reloads_on_change(self):
        self.config.change_option('%s.%s' % (Type.BUG, Key.ALIAS),
                                  'Bugone',
                                  section=AgiloConfig.AGILO_TYPES,
                                  save=True)
        self.assert_equals('Bugone', self.config.ALIASES.get(Type.BUG))

    def test_config_reloads_links_configuration_on_change(self):
        self.assert_contains(
            'story',
            LinksConfiguration(
                self.env).get_allowed_destination_types('requirement'))
        self.config.change_option('allow',
                                  '',
                                  section=AgiloConfig.AGILO_LINKS,
                                  save=True)
        self.assert_not_contains(
            'story',
            LinksConfiguration(
                self.env).get_allowed_destination_types('requirement'))

    def test_config_writing_key_with_capitals(self):
        my_section = self.config.get_section('my-section')
        my_section.change_option('TestMe', 'This is a test', save=True)
        # Test that it is stored
        self.assert_equals('This is a test',
                           self.env.config.get('my-section', 'TestMe'))
        # Test that is case insensitive
        self.assert_equals('This is a test',
                           self.env.config.get('my-section', 'testme'))
        self.assert_equals('This is a test',
                           self.env.config.get('my-section', 'TESTME'))

    def test_config_is_normalizing(self):
        my_section = self.config.get_section('my-section')
        my_section.change_option('TestMe', 'This is a test', save=True)
        # check that in reality only the lowecased version is saved in the
        # config file trac.ini
        options = self.config.get_options('my-section')
        self.assert_true('testme' in options,
                         "TestMe not found in: %s" % options)
        self.assert_false('TestMe' in options, "TestMe found in: %s" % options)

    def test_config_not_updating_case_sensitive(self):
        my_section = self.config.get_section('my-section')
        my_section.change_option('TestMe', 'This is a test', save=True)
        # Test that it is asymmetric
        # Using set will not set the option as it is case insensitive and is
        # not stored because testme already exists in the trac.ini
        my_section.set_option('TESTME', 'This is another test', save=True)
        self.assert_not_equals('This is another test',
                               self.env.config.get('my-section', 'TESTME'))
        self.assert_equals('This is a test',
                           self.env.config.get('my-section', 'TESTME'))
        options = self.config.get_options('my-section')
        self.assert_true('testme' in options,
                         'TestMe not found in: %s' % options)
        self.assert_false('TestMe' in options, 'TestMe found in: %s' % options)

    def test_config_is_case_insensitive_and_overwrites(self):
        my_section = self.config.get_section('my-section')
        my_section.change_option('TestMe', 'This is a test', save=True)
        # Now change the option and check that also the old key, that is the
        # same actually changed
        my_section.change_option('TESTME', 'This is another test', save=True)
        self.assert_equals('This is another test',
                           self.env.config.get('my-section', 'TESTME'))
        self.assert_not_equals('This is a test',
                               self.env.config.get('my-section', 'testme'))
        # Now check what it is stored
        options = self.config.get_options('my-section')
        self.assert_true('testme' in options,
                         'testme not found in: %s' % options)
        self.assert_false('TESTME' in options, 'TESTME found in: %s' % options)
        # Test it is in the AgiloWrapper also after reload
        self.config.reload()
        self.assert_equals('This is another test',
                           self.config.get('TestMe', 'my-section'))

    def test_config_stores_none_as_empty_string(self):
        my_section = self.config.get_section('my-section')
        my_section.set_option('test', 'This is a test', save=True)
        self.assert_equals('This is a test',
                           self.env.config.get('my-section', 'test'))
        # Now change the option and check that also the old key, that is the
        # same actually changed
        my_section.change_option('test', None, save=True)
        self.assert_not_equals('This is a test',
                               self.env.config.get('my-section', 'test'))
        self.assert_equals('', self.env.config.get('my-section', 'test'))
        # Check real config
        self.env.config.set('my-section', 'test', None)
        self.env.config.save()
        self.assert_equals('', self.env.config.get('my-section', 'test'))

    def test_dont_strip_non_string_values(self):
        self.assert_true(self.config.get('foo', default=True, section='trac'))

    def test_sets_default_agilo_logo_on_new_install(self):
        # should be set by the initialization so let's check it
        agilo_logo_src = 'agilo/images/default_logo.png'
        self.assert_equals(agilo_logo_src,
                           self.env.config.get('header_logo', 'src'))

    def test_do_not_sets_default_logo_if_changed(self):
        test_src = 'my_logo'
        agilo_config = AgiloConfig(self.env)
        header_logo = agilo_config.get_section('header_logo')
        header_logo.change_option('src', test_src)
        self.assert_equals(test_src, header_logo.get('src'))
        set_default_agilo_logo(agilo_config)
        self.assert_equals(test_src, header_logo.get('src'))

    def test_sets_agilo_favicon_on_new_install(self):
        agilo_favicon = 'agilo/images/favicon.ico'
        self.assert_equals(agilo_favicon,
                           self.env.config.get('project', 'icon'))

    def test_doesnt_overwrite_custom_favicons(self):
        custom_favicon = 'fnord'
        project_section = AgiloConfig(self.env).get_section('project')
        project_section.change_option('icon', custom_favicon)
        self.assert_equals(custom_favicon, project_section.get('icon'))

        initialize_config(self.env, {})
        project_section = AgiloConfig(self.env).get_section('project')
        self.assert_equals(custom_favicon, project_section.get('icon'))
Example #34
0
class CustomFields(Component):
    """ These methods should be part of TicketSystem API/Data Model.
    Adds update_custom_field and delete_custom_field methods.
    (The get_custom_fields is already part of the API - just redirect here,
     and add option to only get one named field back.)
    """
    def __init__(self, *args, **kwargs):
        """Initialize the component and set a TracConfig"""
        self.ticket_custom = \
            AgiloConfig(self.env).get_section(AgiloConfig.TICKET_CUSTOM)
        
    def get_custom_fields(self, field_name=None):
        """
        Returns the custom fields from TicketSystem component.
        Use a field name to find a specific custom field only
        """
        if not field_name:    # return full list
            return AgiloTicketSystem(self.env).get_custom_fields()
        else:                  # only return specific item with cfname
            all = AgiloTicketSystem(self.env).get_custom_fields()
            for item in all:
                if item[Key.NAME] == field_name:
                    return item
            return None        # item not found
    
    def _store_all_options_for_custom_field(self, customfield):
        added_keys = list()
        changed = False
        for key in customfield:
            if key == Key.NAME:
                continue
            elif key == Key.TYPE:
                config_key = customfield[Key.NAME]
            else:
                config_key = '%s.%s' % (customfield[Key.NAME], key)
            value = customfield[key]
            if isinstance(value, list):
                value = '|'.join(value)
            if value not in ['', None]:
                changed = True
                self.ticket_custom.change_option(config_key, value, save=False)
                added_keys.append(key)
        if changed:
            self._remove_old_keys(customfield[Key.NAME], added_keys)
            self.ticket_custom.save()
    
    def _del_custom_field_value(self, customfield, prop=None):
        """Deletes a property from a custom field"""
        if not prop:
            self.ticket_custom.remove_option(customfield[Key.NAME])
        else:
            self.ticket_custom.remove_option('%s.%s' % (customfield[Key.NAME], prop))
    
    def _validate_input(self, customfield, create):
        """Checks the input values and raises a TracError if severe problems
        are detected."""
        # Name, Type are required
        if not (customfield.get(Key.NAME) and customfield.get(Key.TYPE)):
            raise TracError("Custom field needs at least a name and type.")
        
        # Use lowercase custom fieldnames only
        f_name = unicode(customfield[Key.NAME]).lower()
        # Only alphanumeric characters (and [-_]) allowed for custom fieldname
        if re.search('^[a-z0-9-_]+$', f_name) == None:
            raise TracError("Only alphanumeric characters allowed for custom field name (a-z or 0-9 or -_).")
        # If Create, check that field does not already exist
        if create and self.ticket_custom.get(f_name):
            raise TracError("Can not create as field already exists.")
        
        # Check that it is a valid field type
        f_type = customfield[Key.TYPE]
        if not f_type in ('text', 'checkbox', 'select', 'radio', 'textarea'):
            raise TracError("%s is not a valid field type" % f_type)
        
        if (Key.ORDER in customfield) and (not str(customfield.get(Key.ORDER)).isdigit()):
            raise TracError("%s is not a valid number for %s" % (customfield.get(Key.ORDER), Key.ORDER))
        
        customfield[Key.NAME] = f_name
    
    def update_custom_field(self, customfield, create=False):
        """
        Update or create a new custom field (if requested).
        customfield is a dictionary with the following possible keys:
            name = name of field (alphanumeric only)
            type = text|checkbox|select|radio|textarea
            label = label description
            value = default value for field content
            options = options for select and radio types (list, leave first empty for optional)
            cols = number of columns for text area
            rows = number of rows for text area
            order = specify sort order for field
        """
        
        self._validate_input(customfield, create)
        f_type = customfield[Key.TYPE]
        if f_type == 'textarea':
            def set_default_value(key, default):
                if (key not in customfield) or \
                        (not unicode(customfield[key]).isdigit()):
                    customfield[key] = unicode(default)
            # dwt: why is this called twice?
            set_default_value(Key.COLS, 60)
            set_default_value(Key.COLS, 5)
        
        if create:
            number_of_custom_fields = len(self.get_custom_fields())
            # We assume that the currently added custom field is not present in 
            # the return value of get_custom_fields and we start counting from 0
            customfield[Key.ORDER] = str(number_of_custom_fields)
        
        self._store_all_options_for_custom_field(customfield)
        AgiloTicketSystem(self.env).reset_ticket_fields()
        # TODO: Check that you can change the type from select to something different
        # and the options are gone afterwards
    
    
    def _set_custom_field_value(self, customfield, prop=None):
        """Sets a value in the custom fields for a given property key"""
        config_key = value = None
        if prop:
            value = customfield.get(prop)
            if isinstance(value, list):
                value = '|'.join(value)
            config_key = '%s.%s' % (customfield[Key.NAME], prop)
        else:
            # Used to set the type
            config_key = customfield[Key.NAME]
            value = customfield[Key.TYPE]
        self.ticket_custom.change_option(config_key, value)
    
    def _remove_old_keys(self, fieldname, added_keys):
        for key in (Key.VALUE, Key.OPTIONS, Key.COLS, Key.ROWS):
            if key not in added_keys:
                self.ticket_custom.remove_option('%s.%s' % (fieldname, key), 
                                                 save=False)
    
    def delete_custom_field(self, field_name):
        """Deletes a custom field"""
        if not self.ticket_custom.get(field_name):
            return # Nothing to do here - cannot find field
        # Need to redo the order of fields that are after the field to be deleted
        order_to_delete = self.ticket_custom.get_int('%s.%s' % (field_name, Key.ORDER))
        cfs = self.get_custom_fields()
        for field in cfs:
            if field[Key.ORDER] > order_to_delete:
                field[Key.ORDER] -= 1
                self._set_custom_field_value(field, Key.ORDER)
            elif field[Key.NAME] == field_name:
                # Remove any data for the custom field (covering all bases)
                self._del_custom_field_value(field)
        # Save settings
        self.ticket_custom.save()
        AgiloTicketSystem(self.env).reset_ticket_fields()
Example #35
0
 def _set_default_type(self, new_value):
     agilo_config = AgiloConfig(self.env)
     old_value = agilo_config.get('default_type', 'ticket')
     agilo_config.change_option('default_type', new_value, 
                                section='ticket', save=True)
     return old_value
Example #36
0
def set_config_value(env, name, section, new_value):
    agilo_config = AgiloConfig(env)
    old_value = agilo_config.get(name, section)
    agilo_config.change_option(name, new_value, section=section, save=True)
    return old_value
Example #37
0
class TypesAdminPanel(AgiloAdminPanel):
    """
    Administration panel for different ticket types and their fields.
    Needs to get imported in agilo/admin/__init__.py in order to appear
    on the web interface.
    """

    _type = 'types'
    _label = ('Types', _('Types'))

    def __init__(self):
        self.agilo_config = AgiloConfig(self.env)

    def _get_field_labels(self):
        """Returns a dictionary of fields and their labels."""
        labels = {}
        for f_name, label in self.agilo_config.LABELS.items():
            if f_name not in MANDATORY_FIELDS:
                labels[f_name] = label
        return labels

    def _get_fields(self, type_name=None):
        """
        If type_name is not set, return a dictionary of ticket types 
        and the corresponding fields as a list. If a type_name gets 
        passed, return a list of fields corresponding to this ticket 
        type.
        """
        fields = self.agilo_config.get_available_types(with_fields=True)
        if type_name:
            fields = fields.get(type_name)
        return fields

    def detail_save_view(self, req, cat, page, ticket_type):
        """Save the detail panel view"""
        # The types will be stored in lowercase and the space is not a
        # valid character for the config file key
        ticket_type = normalize_ticket_type(ticket_type)

        alias = req.args.get(Key.ALIAS)
        if alias:
            self.agilo_config.change_option('%s.%s' % (ticket_type, Key.ALIAS),
                                            alias,
                                            section=AgiloConfig.AGILO_TYPES,
                                            save=False)
        # save fields as string or comma-separated list of values
        # FIXME: (AT) after going crazy, it came out that some types are not
        # saved because there is no specific field assigned and the config
        # won't store the property in the trac.ini. So the agilo type will also
        # not be loaded, even if the alias would be set.
        fields = req.args.get(Key.FIELDS, '')
        # We have to save strings not lists
        if isinstance(fields, list):
            fields = ', '.join(fields)
        self.agilo_config.change_option(ticket_type,
                                        fields,
                                        section=AgiloConfig.AGILO_TYPES,
                                        save=False)
        calc = []
        for res, func in zip(req.args.getlist('result'),
                             req.args.getlist('function')):
            if res and func:
                configstring = u'%s=%s' % (res.strip(), func.strip())
                parsed = parse_calculated_field(configstring)
                if parsed == None:
                    msg = u"Wrong format for calculated property '%s'"
                    raise TracError(_(msg) % res)
                calc.append(configstring)
        calc = ','.join(calc)
        if calc:
            self.agilo_config.change_option(
                '%s.%s' % (ticket_type, LinkOption.CALCULATE),
                calc,
                section=AgiloConfig.AGILO_LINKS,
                save=False)
        self.agilo_config.save()

        # on 0.12 we need to reset the ticket fields explicitely as the synchronization
        # is not done with the trac.ini anymore
        if AgiloTicketSystem(self.env).is_trac_012():
            AgiloTicketSystem(self.env).reset_ticket_fields()
        return req.redirect(req.href.admin(cat, page))

    def detail_view(self, req, cat, page, ticket_type):
        # All keys are saved lower-cased, but this is not done
        # automatically for retrieval
        calc_prop = self.agilo_config.get_list(
            '%s.%s' % (ticket_type, LinkOption.CALCULATE),
            section=AgiloConfig.AGILO_LINKS)
        calculated_properties = []
        if len(calc_prop) > 0:
            for definition in calc_prop:
                parts = definition.split('=', 1)
                if len(parts) == 2:
                    property_name, formula = parts
                    calculated_properties.append(
                        (property_name.strip(), formula.strip()))
                else:
                    message = u"Ignoring broken definition for " \
                              "calculated property: %s" % definition
                    warning(self, message)
        data = {
            'calculate': calculated_properties,
            'view': 'detail',
            'type': ticket_type,
            'alias': self.agilo_config.ALIASES.get(ticket_type, ''),
            'type_fields': self._get_fields(ticket_type),
            'labels': self._get_field_labels(),
        }
        return 'agilo_admin_types.html', data

    def list_view(self, req, cat, page):
        data = {
            'view': 'list',
            'fields': self._get_fields(),
            'aliases': self.agilo_config.ALIASES,
            'labels': self._get_field_labels(),
        }
        return 'agilo_admin_types.html', data
 def set_cascade_delete_story_task(self):
     env = self.testenv.get_trac_environment()
     config = AgiloConfig(env)
     config.change_option('delete', 'task-story', section='agilo-links')
     config.save()
Example #39
0
 def _restore_old_configuration(self):
     env = self.testenv.get_trac_environment()
     config = AgiloConfig(env)
     for (section, property), value in self._old_configuration.items():
         config.change_option(property, value, section)
     config.save()
 def _set_configuration(self, section, property, value):
     env = self.testenv.get_trac_environment()
     config = AgiloConfig(env)
     self._old_configuration[(section, property)] = value
     config.change_option(property, value, section, save=True)