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
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
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
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()
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 _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())
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 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 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))
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')
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)
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)
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()
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
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()
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_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
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()
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()
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)
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'))
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()
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