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 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)
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
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
class LinksAdminPanel(AgiloAdminPanel): """Administration panel for links. Needs to get imported in agilo/admin/__init__.py in order to appear on the web interface.""" _type = 'links' _label = ('Links', _('Links')) def __init__(self): self.config = AgiloConfig(self.env) self.links = self.config.get_section(AgiloConfig.AGILO_LINKS) self.allowed_links = self._get_allowed_links() def _get_allowed_links(self): """Returns the dictionary containing the allowed links pairs""" links_configuration = LinksConfiguration(self.env) return dict([(l, list(links_configuration.extract_types(l))) for l in self.links.get_list(LinkOption.ALLOW)]) def _get_delete_pairs(self): """Returns the dictionary containing the cascade delete pairs""" links_configuration = LinksConfiguration(self.env) return dict([(l, list(links_configuration.extract_types(l))) for l in self.links.get_list(LinkOption.DELETE)]) def detail_view(self, req, cat, page, link): links_configuration = LinksConfiguration(self.env) (source, target) = links_configuration.extract_types(link) copy_fields = [f.strip() for f in self.links.get('%s.%s.%s' % \ (source, target, LinkOption.COPY), default='').split(',')] show_fields = [f.strip() for f in self.links.get('%s.%s.%s' % \ (source, target, LinkOption.SHOW), default='').split(',')] ticket_system = AgiloTicketSystem(self.env) # dict of name->label for all core and custom fields labels = dict([(f['name'], f['label']) for f in ticket_system.get_ticket_fields()]) cascade_delete = source + '-' + target in self._get_delete_pairs() data = { 'view': 'detail', 'link': link, 'source': source, 'target': target, 'source_fields': self.config.TYPES[source], 'target_fields': self.config.TYPES[target], 'labels': labels, 'copy_fields': copy_fields, 'show_fields': show_fields, 'cascade_delete': cascade_delete } return 'agilo_admin_links.html', data def detail_save_view(self, req, cat, page, link): links_configuration = LinksConfiguration(self.env) (source, target) = links_configuration.extract_types(link) fields = req.args.get('copy_fields', []) if type(fields) != type([]): fields = [fields] # set copy options for this link self.links.change_option( '%s.%s.%s' % (source, target, LinkOption.COPY), ', '.join(fields)) fields = req.args.get('show_fields', []) if type(fields) != type([]): fields = [fields] # set show options for this link self.links.change_option( '%s.%s.%s' % (source, target, LinkOption.SHOW), ', '.join(fields)) cascade_delete = req.args.get('cascade_delete') delete_pairs = self._get_delete_pairs() if cascade_delete and source + '-' + target not in delete_pairs: delete_pairs[source + '-' + target] = (source, target) self.links.change_option(LinkOption.DELETE, ', '.join(delete_pairs.keys())) elif not cascade_delete and source + '-' + target in self._get_delete_pairs( ): del delete_pairs[source + '-' + target] self.links.change_option(LinkOption.DELETE, ', '.join(delete_pairs.keys())) # saved it, redirect back to admin view self.links.save() req.redirect(req.href.admin(cat, page)) def list_view(self, req, cat, page): data = { 'view': 'list', 'allowed_links': self._get_allowed_links(), 'available_types': self.config.get_available_types(strict=True), } return 'agilo_admin_links.html', data def list_save_view(self, req, cat, page): source = req.args.get('source') target = req.args.get('target') if req.args.get('add') and source and target: if (source, target) in self.allowed_links: # link already exists, redirect to it req.redirect( req.href.admin(cat, page, '%s-%s' % (source, target))) # Set, save because there is the redirect self.links.change_option(LinkOption.ALLOW, '%s, %s-%s' % \ (self.links.get(LinkOption.ALLOW, default=''), source, target), save=True) return req.redirect( req.href.admin(cat, page, '%s-%s' % (source, target))) # Remove components if req.args.get('remove'): sel = req.args.get('sel') if not sel: raise TracError(_('No link selected')) if not isinstance(sel, list): sel = [sel] # delete selection from allowed links for s in sel: del self.allowed_links[s] # write new value back to config, and save self.links.change_option(LinkOption.ALLOW, ', '.join(self.allowed_links.keys()), save=True) return req.redirect(req.href.admin(cat, page))
class LinksAdminPanel(AgiloAdminPanel): """Administration panel for links. Needs to get imported in agilo/admin/__init__.py in order to appear on the web interface.""" _type = 'links' _label = ('Links', _('Links')) def __init__(self): self.config = AgiloConfig(self.env) self.links = self.config.get_section(AgiloConfig.AGILO_LINKS) self.allowed_links = self._get_allowed_links() def _get_allowed_links(self): """Returns the dictionary containing the allowed links pairs""" links_configuration = LinksConfiguration(self.env) return dict([(l, list(links_configuration.extract_types(l))) for l in self.links.get_list(LinkOption.ALLOW)]) def _get_delete_pairs(self): """Returns the dictionary containing the cascade delete pairs""" links_configuration = LinksConfiguration(self.env) return dict([(l, list(links_configuration.extract_types(l))) for l in self.links.get_list(LinkOption.DELETE)]) def detail_view(self, req, cat, page, link): links_configuration = LinksConfiguration(self.env) (source, target) = links_configuration.extract_types(link) copy_fields = [f.strip() for f in self.links.get('%s.%s.%s' % \ (source, target, LinkOption.COPY), default='').split(',')] show_fields = [f.strip() for f in self.links.get('%s.%s.%s' % \ (source, target, LinkOption.SHOW), default='').split(',')] ticket_system = AgiloTicketSystem(self.env) # dict of name->label for all core and custom fields labels = dict([(f['name'], f['label']) for f in ticket_system.get_ticket_fields()]) cascade_delete = source+'-'+target in self._get_delete_pairs() data = { 'view': 'detail', 'link': link, 'source' : source, 'target' : target, 'source_fields' : self.config.TYPES[source], 'target_fields' : self.config.TYPES[target], 'labels' : labels, 'copy_fields' : copy_fields, 'show_fields' : show_fields, 'cascade_delete': cascade_delete } return 'agilo_admin_links.html', data def detail_save_view(self, req, cat, page, link): links_configuration = LinksConfiguration(self.env) (source, target) = links_configuration.extract_types(link) fields = req.args.get('copy_fields', []) if type(fields) != type([]): fields = [fields] # set copy options for this link self.links.change_option('%s.%s.%s' % (source, target, LinkOption.COPY), ', '.join(fields)) fields = req.args.get('show_fields', []) if type(fields) != type([]): fields = [fields] # set show options for this link self.links.change_option('%s.%s.%s' % (source, target, LinkOption.SHOW), ', '.join(fields)) cascade_delete = req.args.get('cascade_delete') delete_pairs = self._get_delete_pairs() if cascade_delete and source+'-'+target not in delete_pairs: delete_pairs[source+'-'+target] = (source, target) self.links.change_option(LinkOption.DELETE, ', '.join(delete_pairs.keys())) elif not cascade_delete and source+'-'+target in self._get_delete_pairs(): del delete_pairs[source+'-'+target] self.links.change_option(LinkOption.DELETE, ', '.join(delete_pairs.keys())) # saved it, redirect back to admin view self.links.save() req.redirect(req.href.admin(cat, page)) def list_view(self, req, cat, page): data = { 'view': 'list', 'allowed_links': self._get_allowed_links(), 'available_types' : self.config.get_available_types(strict=True), } return 'agilo_admin_links.html', data def list_save_view(self, req, cat, page): source = req.args.get('source') target = req.args.get('target') if req.args.get('add') and source and target: if (source, target) in self.allowed_links: # link already exists, redirect to it req.redirect(req.href.admin(cat, page, '%s-%s' % (source, target))) # Set, save because there is the redirect self.links.change_option(LinkOption.ALLOW, '%s, %s-%s' % \ (self.links.get(LinkOption.ALLOW, default=''), source, target), save=True) return req.redirect(req.href.admin(cat, page, '%s-%s' % (source, target))) # Remove components if req.args.get('remove'): sel = req.args.get('sel') if not sel: raise TracError(_('No link selected')) if not isinstance(sel, list): sel = [sel] # delete selection from allowed links for s in sel: del self.allowed_links[s] # write new value back to config, and save self.links.change_option(LinkOption.ALLOW, ', '.join(self.allowed_links.keys()), save=True) return req.redirect(req.href.admin(cat, page))