def validate(self, relation): rls = RelationsSystem(self.env) existing_relations = rls._select_relations(relation.source, relation.type) if existing_relations: raise ValidationError( "%s can only have one %s" % (relation.source, self.render_relation_type(relation.type)))
def validate(self, relation): rls = RelationsSystem(self.env) existing_relations = rls._select_relations(relation.source, relation.type) if existing_relations: raise ValidationError( "%s can only have one %s" % ( relation.source, self.render_relation_type(relation.type) ))
def validate(self, relation): rls = RelationsSystem(self.env) existing_relations = rls._select_relations(resource_type=relation.type, destination=relation.destination) if existing_relations: raise ValidationError( tag_("Another resource is already related to %(destination)s " "with %(relation)s relation.", destination=tag.em(relation.destination), relation=tag.b(self.render_relation_type(relation.type))) )
def get_ticket_relations(self, ticket): grouped_relations = {} relsys = RelationsSystem(self.env) reltypes = relsys.get_relation_types() trs = TicketRelationsSpecifics(self.env) for r in relsys.get_relations(ticket): r['desthref'] = get_resource_url(self.env, r['destination'], self.env.href) r['destticket'] = trs._create_ticket_by_full_id(r['destination']) grouped_relations.setdefault(reltypes[r['type']], []).append(r) return grouped_relations
def remove_relations(self, req, rellist): relsys = RelationsSystem(self.env) for relid in rellist: relation = Relation.load_by_relation_id(self.env, relid) resource = ResourceIdSerializer.get_resource_by_id( relation.destination) if 'TICKET_MODIFY' in req.perm(resource): relsys.delete(relid) else: add_warning(req, _('Not enough permissions to remove relation "%s"' % relid))
def remove_relations(self, req, rellist): relsys = RelationsSystem(self.env) for relid in rellist: relation = Relation.load_by_relation_id(self.env, relid) resource = \ ResourceIdSerializer.get_resource_by_id(relation.destination) if 'TICKET_MODIFY' in req.perm(resource): relsys.delete(relid) else: add_warning(req, _('Insufficient permissions to remove ' 'relation "%(relation)s"', relation=relid))
def validate(self, relation): rls = RelationsSystem(self.env) existing_relations = \ rls._select_relations(resource_type=relation.type, destination=relation.destination) if existing_relations: raise ValidationError( tag_("Another resource is already related to %(destination)s " "with %(relation)s relation.", destination=tag.em(relation.destination), relation=tag.b(self.render_relation_type(relation.type))) )
def setUp(self, enabled=()): env = EnvironmentStub( default_data=True, enable=(['trac.*', 'multiproduct.*', 'bhrelations.*'] + list(enabled)) ) env.config.set('bhrelations', 'global_validators', 'NoSelfReferenceValidator,ExclusiveValidator,' 'BlockerValidator') env.config.set('bhrelations', 'duplicate_relation', 'duplicateof') config_name = RELATIONS_CONFIG_NAME env.config.set(config_name, 'dependency', ','.join([DEPENDS_ON, DEPENDENCY_OF])) env.config.set(config_name, 'dependency.validators', 'NoCycles,SingleProduct') env.config.set(config_name, 'dependson.blocks', 'true') env.config.set(config_name, 'parent_children', ','.join([PARENT, CHILD])) env.config.set(config_name, 'parent_children.validators', 'OneToMany,SingleProduct,NoCycles') env.config.set(config_name, 'children.label', 'Overridden') env.config.set(config_name, 'parent.copy_fields', 'summary, foo') env.config.set(config_name, 'parent.exclusive', 'true') env.config.set(config_name, 'multiproduct_relation', ','.join([MULTIPRODUCT_REL, MULTIPRODUCT_BACKREL])) env.config.set(config_name, 'oneway', REFERS_TO) env.config.set(config_name, 'duplicate', ','.join([DUPLICATE_OF, DUPLICATED_BY])) env.config.set(config_name, 'duplicate.validators', 'ReferencesOlder') env.config.set(config_name, 'duplicateof.label', 'is a duplicate of') env.config.set(config_name, 'duplicatedby.label', 'duplicates') env.config.set(config_name, 'blocker', ','.join([BLOCKED_BY, BLOCKS])) env.config.set(config_name, 'blockedby.blocks', 'true') self.global_env = env self._upgrade_mp(self.global_env) self._setup_test_log(self.global_env) self._load_product_from_data(self.global_env, self.default_product) self.env = ProductEnvironment(self.global_env, self.default_product) self.req = Mock(href=self.env.href, authname='anonymous', tz=utc, args=dict(action='dummy'), locale=locale_en, lc_time=locale_en, chrome={'warnings': []}) self.req.perm = MockPerm() self.relations_system = RelationsSystem(self.env) self._upgrade_env()
def post_process_request(self, req, template, data, content_type): if req.path_info.startswith('/ticket/'): ticket = data['ticket'] rls = RelationsSystem(self.env) try: resid = ResourceIdSerializer.get_resource_id_from_instance( self.env, ticket) except ValueError: resid = None if rls.duplicate_relation_type and resid is not None: duplicate_relations = \ rls._select_relations(resid, rls.duplicate_relation_type) if duplicate_relations: data['ticket_duplicate_of'] = \ duplicate_relations[0].destination return template, data, content_type
def pre_process(self, doc): resource_id = ':'.join([ doc.get('product', ''), doc.get('type', ''), doc.get('id')]) try: rls = RelationsSystem(self.env) relations = [] for relation in rls._select_relations(resource_id): relations.extend(self._format_relations(relation)) doc['relations'] = ','.join(relations) except self.env.db_exc.OperationalError: # If bhrelations and bhsearch are installed at the same time and # bhsearch is upgraded before bhrelations, table # bloodhound_relations will be missing, thus causing the # OperationalError. As this means that the relations do not # exist yet, just skip indexing them. self.log.debug("Not indexing relations for %s", resource_id)
def validate(self, relation): """If a path exists from relation's destination to its source, adding the relation will create a cycle. """ rls = RelationsSystem(self.env) if not rls.is_blocker(relation.type): relation = rls.get_reverted_relation(relation) if not relation or not rls.is_blocker(relation.type): return blockers = ','.join(b for b, is_blocker in rls._blockers.items() if is_blocker) path = self._find_path(relation.source, relation.destination, blockers) if path: cycle_str = map(self.get_resource_name, path) error = 'Cycle in ' '%s' ': %s' % (self.render_relation_type( relation.type), ' -> '.join(cycle_str)) error = ValidationError(error) error.failed_ids = path raise error
def validate(self, relation): """Prevents adding a cyclical blocker relation. """ rls = RelationsSystem(self.env) if not rls.is_blocker(relation.type): relation = rls.get_reverted_relation(relation) if not relation or not rls.is_blocker(relation.type): return blockers = ','.join(b for b, is_blocker in rls._blockers.items() if is_blocker) path = self._find_path(relation.source, relation.destination, blockers) if path: cycle_str = map(self.get_resource_name, path) error = 'Cycle in ''%s'': %s' % ( self.render_relation_type(relation.type), ' -> '.join(cycle_str)) error = ValidationError(error) error.failed_ids = path raise error
def validate(self, relation): """If a path of exclusive type exists between source and destination, adding a relation is not allowed. """ rls = RelationsSystem(self.env) source, destination = relation.source, relation.destination for exclusive_type in rls._exclusive: path = (self._find_path(source, destination, exclusive_type) or self._find_path(destination, source, exclusive_type)) if path: raise ValidationError( "Cannot add relation %s, source and destination " "are connected with %s relation." % ( self.render_relation_type(relation.type), self.render_relation_type(exclusive_type), ) ) if relation.type in rls._exclusive: d_ancestors = self._ancestors(destination, exclusive_type) d_ancestors.add(destination) s_descendants = self._descendants(source, exclusive_type) s_descendants.add(source) query = """ SELECT source, destination, type FROM bloodhound_relations WHERE (source in (%(s_ancestors)s) AND destination in (%(d_descendants)s)) OR (source in (%(d_descendants)s) AND destination in (%(s_ancestors)s)) """ % dict( s_ancestors=', '.join("'%s'" % n for n in d_ancestors), d_descendants=', '.join("'%s'" % n for n in s_descendants)) conflicting_relations = list(self.env.db_query(query)) if conflicting_relations: raise ValidationError( "Connecting %s and %s with relation %s " "would make the following relations invalid:\n" "%s" % ( source, destination, self.render_relation_type(relation.type), '\n'.join(map(str, conflicting_relations)) ) )
def setUp(self, enabled=()): env = EnvironmentStub( default_data=True, enable=(['trac.*', 'multiproduct.*', 'bhrelations.*'] + list(enabled))) env.config.set( 'bhrelations', 'global_validators', 'NoSelfReferenceValidator,ExclusiveValidator,' 'BlockerValidator') env.config.set('bhrelations', 'duplicate_relation', 'duplicateof') config_name = RelationsSystem.RELATIONS_CONFIG_NAME env.config.set(config_name, 'dependency', 'dependson,dependent') env.config.set(config_name, 'dependency.validators', 'NoCycles,SingleProduct') env.config.set(config_name, 'dependson.blocks', 'true') env.config.set(config_name, 'parent_children', 'parent,children') env.config.set(config_name, 'parent_children.validators', 'OneToMany,SingleProduct,NoCycles') env.config.set(config_name, 'children.label', 'Overridden') env.config.set(config_name, 'parent.copy_fields', 'summary, foo') env.config.set(config_name, 'parent.exclusive', 'true') env.config.set(config_name, 'multiproduct_relation', 'mprel,mpbackrel') env.config.set(config_name, 'oneway', 'refersto') env.config.set(config_name, 'duplicate', 'duplicateof,duplicatedby') env.config.set(config_name, 'duplicate.validators', 'ReferencesOlder') env.config.set(config_name, 'duplicateof.label', 'is a duplicate of') env.config.set(config_name, 'duplicatedby.label', 'duplicates') env.config.set(config_name, 'blocker', 'blockedby,blocks') env.config.set(config_name, 'blockedby.blocks', 'true') self.global_env = env self._upgrade_mp(self.global_env) self._setup_test_log(self.global_env) self._load_product_from_data(self.global_env, self.default_product) self.env = ProductEnvironment(self.global_env, self.default_product) self.req = Mock(href=self.env.href, authname='anonymous', tz=utc, args=dict(action='dummy'), locale=locale_en, lc_time=locale_en) self.req.perm = MockPerm() self.relations_system = RelationsSystem(self.env) self._upgrade_env()
class BaseRelationsTestCase(MultiproductTestCase): def setUp(self, enabled=()): env = EnvironmentStub( default_data=True, enable=(['trac.*', 'multiproduct.*', 'bhrelations.*'] + list(enabled)) ) env.config.set('bhrelations', 'global_validators', 'NoSelfReferenceValidator,ExclusiveValidator,' 'BlockerValidator') env.config.set('bhrelations', 'duplicate_relation', 'duplicateof') config_name = RELATIONS_CONFIG_NAME env.config.set(config_name, 'dependency', ','.join([DEPENDS_ON, DEPENDENCY_OF])) env.config.set(config_name, 'dependency.validators', 'NoCycles,SingleProduct') env.config.set(config_name, 'dependson.blocks', 'true') env.config.set(config_name, 'parent_children', ','.join([PARENT, CHILD])) env.config.set(config_name, 'parent_children.validators', 'OneToMany,SingleProduct,NoCycles') env.config.set(config_name, 'children.label', 'Overridden') env.config.set(config_name, 'parent.copy_fields', 'summary, foo') env.config.set(config_name, 'parent.exclusive', 'true') env.config.set(config_name, 'multiproduct_relation', ','.join([MULTIPRODUCT_REL, MULTIPRODUCT_BACKREL])) env.config.set(config_name, 'oneway', REFERS_TO) env.config.set(config_name, 'duplicate', ','.join([DUPLICATE_OF, DUPLICATED_BY])) env.config.set(config_name, 'duplicate.validators', 'ReferencesOlder') env.config.set(config_name, 'duplicateof.label', 'is a duplicate of') env.config.set(config_name, 'duplicatedby.label', 'duplicates') env.config.set(config_name, 'blocker', ','.join([BLOCKED_BY, BLOCKS])) env.config.set(config_name, 'blockedby.blocks', 'true') self.global_env = env self._upgrade_mp(self.global_env) self._setup_test_log(self.global_env) self._load_product_from_data(self.global_env, self.default_product) self.env = ProductEnvironment(self.global_env, self.default_product) self.req = Mock(href=self.env.href, authname='anonymous', tz=utc, args=dict(action='dummy'), locale=locale_en, lc_time=locale_en, chrome={'warnings': []}) self.req.perm = MockPerm() self.relations_system = RelationsSystem(self.env) self._upgrade_env() def tearDown(self): self.global_env.reset_db() def _upgrade_env(self): environment_setup = EnvironmentSetup(self.env) try: environment_setup.upgrade_environment(self.env.db_transaction) except self.env.db_exc.OperationalError: # table remains but database version is deleted pass @classmethod def _insert_ticket(cls, env, summary, **kw): """Helper for inserting a ticket into the database""" ticket = Ticket(env) ticket["summary"] = summary for k, v in kw.items(): ticket[k] = v return ticket.insert() def _insert_and_load_ticket(self, summary, **kw): return Ticket(self.env, self._insert_ticket(self.env, summary, **kw)) def _insert_and_load_ticket_with_env(self, env, summary, **kw): return Ticket(env, self._insert_ticket(env, summary, **kw)) def add_relation(self, source, reltype, destination, *args, **kwargs): return self.relations_system.add(source, destination, reltype, *args, **kwargs) def get_relations(self, ticket): return self.relations_system.get_relations(ticket) def delete_relation(self, relation): self.relations_system.delete(relation["relation_id"])
def process_request(self, req): tid = req.args.get('id') if not tid: raise TracError(_('No ticket id provided.')) try: ticket = Ticket(self.env, tid) except ValueError: raise TracError(_('Invalid ticket id.')) req.perm.require('TICKET_VIEW') relsys = RelationsSystem(self.env) data = { 'relation': {}, } if req.method == 'POST': # for modifying the relations TICKET_MODIFY is required for # both the source and the destination tickets req.perm.require('TICKET_MODIFY') if 'remove' in req.args: rellist = req.args.get('sel') if rellist: if isinstance(rellist, basestring): rellist = [rellist, ] self.remove_relations(req, rellist) elif 'add' in req.args: relation = dict( destination=req.args.get('dest_tid', ''), type=req.args.get('reltype', ''), comment=req.args.get('comment', ''), ) try: trs = TicketRelationsSpecifics(self.env) dest_ticket = trs.find_ticket(relation['destination']) except NoSuchTicketError: data['error'] = _('Invalid ticket ID.') else: req.perm.require('TICKET_MODIFY', Resource(dest_ticket.id)) try: dbrel = relsys.add(ticket, dest_ticket, relation['type'], relation['comment'], req.authname) except NoSuchTicketError: data['error'] = _('Invalid ticket ID.') except UnknownRelationType: data['error'] = _('Unknown relation type.') except ValidationError as ex: data['error'] = ex.message else: # Notify try: self.notify_relation_changed(dbrel) except Exception, e: self.log.error("Failure sending notification on" "creation of relation: %s", exception_to_unicode(e)) add_warning(req, _("The relation has been added, but an " "error occurred while sending" "notifications: " "%(message)s", message=to_unicode(e))) if 'error' in data: data['relation'] = relation else: raise TracError(_('Invalid operation.'))
def process_request(self, req): tid = req.args.get('id') if not tid: raise TracError(_('No ticket id provided.')) try: ticket = Ticket(self.env, tid) except ValueError: raise TracError(_('Invalid ticket id.')) req.perm.require('TICKET_VIEW') relsys = RelationsSystem(self.env) data = { 'relation': {}, } if req.method == 'POST': # for modifying the relations TICKET_MODIFY is required for # both the source and the destination tickets req.perm.require('TICKET_MODIFY') if 'remove' in req.args: rellist = req.args.get('sel') if rellist: if isinstance(rellist, basestring): rellist = [rellist, ] self.remove_relations(req, rellist) elif 'add' in req.args: relation = dict( destination=req.args.get('dest_tid', ''), type=req.args.get('reltype', ''), comment=req.args.get('comment', ''), ) try: trs = TicketRelationsSpecifics(self.env) dest_ticket = trs.find_ticket(relation['destination']) except NoSuchTicketError: data['error'] = _('Invalid ticket ID.') else: req.perm.require('TICKET_MODIFY', Resource(dest_ticket.id)) try: relsys.add(ticket, dest_ticket, relation['type'], relation['comment'], req.authname) except NoSuchTicketError: data['error'] = _('Invalid ticket ID.') except UnknownRelationType: data['error'] = _('Unknown relation type.') except ValidationError as ex: data['error'] = ex.message if 'error' in data: data['relation'] = relation else: raise TracError(_('Invalid operation.')) data.update({ 'ticket': ticket, 'reltypes': sorted(relsys.get_relation_types().iteritems(), key=lambda x: x[0]), 'relations': self.get_ticket_relations(ticket), }) return 'relations_manage.html', data, None
def process_request(self, req): tid = req.args.get('id') if not tid: raise TracError(_('No ticket id provided.')) try: ticket = Ticket(self.env, tid) except ValueError: raise TracError(_('Invalid ticket id.')) req.perm.require('TICKET_VIEW') relsys = RelationsSystem(self.env) data = { 'relation': {}, } if req.method == 'POST': # for modifying the relations TICKET_MODIFY is required for # both the source and the destination tickets req.perm.require('TICKET_MODIFY') if 'remove' in req.args: rellist = req.args.get('sel') if rellist: if isinstance(rellist, basestring): rellist = [ rellist, ] self.remove_relations(req, rellist) elif 'add' in req.args: relation = dict( destination=req.args.get('dest_tid', ''), type=req.args.get('reltype', ''), comment=req.args.get('comment', ''), ) try: trs = TicketRelationsSpecifics(self.env) dest_ticket = trs.find_ticket(relation['destination']) except NoSuchTicketError: data['error'] = _('Invalid ticket ID.') else: req.perm.require('TICKET_MODIFY', Resource(dest_ticket.id)) try: relsys.add(ticket, dest_ticket, relation['type'], relation['comment'], req.authname) except NoSuchTicketError: data['error'] = _('Invalid ticket ID.') except UnknownRelationType: data['error'] = _('Unknown relation type.') except ValidationError as ex: data['error'] = ex.message if 'error' in data: data['relation'] = relation else: raise TracError(_('Invalid operation.')) data.update({ 'ticket': ticket, 'reltypes': sorted(relsys.get_relation_types().iteritems(), key=lambda x: x[0]), 'relations': self.get_ticket_relations(ticket), }) return 'relations_manage.html', data, None
def render_relation_type(self, end): return RelationsSystem(self.env)._labels[end]
def process_request(self, req): tid = req.args.get('id') if not tid: raise TracError(_('No ticket id provided.')) try: ticket = Ticket(self.env, tid) except ValueError: raise TracError(_('Invalid ticket id.')) # For access to the relation management, TICKET_MODIFY is required. req.perm.require('TICKET_MODIFY') relsys = RelationsSystem(self.env) data = { 'relation': {}, } if req.method == 'POST': # for modifying the relations TICKET_MODIFY is required for # both the source and the destination tickets if 'remove' in req.args: rellist = req.args.get('sel') if rellist: if isinstance(rellist, basestring): rellist = [ rellist, ] self.remove_relations(req, rellist) elif 'add' in req.args: relation = dict( destination=req.args.get('dest_tid', ''), type=req.args.get('reltype', ''), comment=req.args.get('comment', ''), ) try: trs = TicketRelationsSpecifics(self.env) dest_ticket = trs.find_ticket(relation['destination']) except NoSuchTicketError: data['error'] = _('Invalid ticket ID.') else: req.perm.require('TICKET_MODIFY', Resource(dest_ticket.id)) try: dbrel = relsys.add(ticket, dest_ticket, relation['type'], relation['comment'], req.authname) except NoSuchTicketError: data['error'] = _('Invalid ticket ID.') except UnknownRelationType: data['error'] = _('Unknown relation type.') except ValidationError as ex: data['error'] = ex.message else: # Notify try: self.notify_relation_changed(dbrel) except Exception, e: self.log.error( "Failure sending notification on" "creation of relation: %s", exception_to_unicode(e)) add_warning( req, _( "The relation has been added, " "but an error occurred while " "sending notifications: " "%(message)s", message=to_unicode(e))) if 'error' in data: data['relation'] = relation else: raise TracError(_('Invalid operation.'))