Exemple #1
0
    def render_admin_panel(self, req, cat, page, version):
        req.perm.require("TAGS_ADMIN")
        data = {}
        tag_system = TagSystem(self.env)
        if req.method == "POST":
            # Replace Tag
            allow_delete = req.args.get("allow_delete")
            new_tag = req.args.get("tag_new_name").strip()
            new_tag = not new_tag == u"" and new_tag or None
            if not (allow_delete or new_tag):
                data["error"] = _(
                    """Selected current tag(s) and either
                                  new tag or delete approval are required"""
                )
            else:
                comment = req.args.get("comment", u"")
                old_tags = req.args.get("tag_name")
                if old_tags:
                    # Provide list regardless of single or multiple selection.
                    old_tags = isinstance(old_tags, list) and old_tags or [old_tags]
                    tag_system.replace_tag(req, old_tags, new_tag, comment, allow_delete)
                data["selected"] = new_tag

        all_tags = sorted(tag_system.get_all_tags(req, "-dummy"))
        data["tags"] = all_tags
        try:
            Chrome(self.env).add_textarea_grips(req)
        except AttributeError:
            # Element modifiers unavailable before Trac 0.12, skip gracefully.
            pass
        return "admin_tag_change.html", data
Exemple #2
0
    def _format_tagged(self, formatter, ns, target, label, fullmatch=None):
        """Tag and tag query expression link formatter."""

        def unquote(text):
            """Strip all matching pairs of outer quotes from string."""
            while re.match(WikiParser.QUOTED_STRING, text):
                # Remove outer whitespace after stripped quotation too.
                text = text[1:-1].strip()
            return text
 
        label = label and unquote(label.strip()) or ''
        target = unquote(target.strip())
        tag_res = Resource('tag', target)
        if 'TAGS_VIEW' in formatter.perm(tag_res):
            context = formatter.context
            href = get_resource_url(self.env, tag_res, context.href)
            tag_sys = TagSystem(self.env)
            # Tag exists or tags query yields at least one match.
            if target in tag_sys.get_all_tags(formatter.req) or \
                    [(res, tags) for res, tags in
                     tag_sys.query(formatter.req, target)]:
                if label:
                    return tag.a(label, href=href)
                return render_resource_link(self.env, context, tag_res)
            else:
                return tag.a(label+'?', href=href, class_='missing tags',
                             rel='nofollow')
        else:
            return tag.span(label, class_='forbidden tags',
                            title=_("no permission to view tags"))
Exemple #3
0
    def render_admin_panel(self, req, cat, page, version):
        req.perm.require('TAGS_ADMIN')
        data = {}
        tag_system = TagSystem(self.env)
        if req.method == 'POST':
            # Replace Tag
            allow_delete = req.args.get('allow_delete')
            new_tag = req.args.get('tag_new_name').strip()
            new_tag = not new_tag == u'' and new_tag or None
            if not (allow_delete or new_tag):
                data['error'] = _("""Selected current tag(s) and either
                                  new tag or delete approval are required""")
            else:
                comment = req.args.get('comment', u'')
                old_tags = req.args.get('tag_name')
                if old_tags:
                    # Provide list regardless of single or multiple selection.
                    old_tags = isinstance(old_tags, list) and old_tags or \
                               [old_tags]
                    tag_system.replace_tag(req, old_tags, new_tag, comment,
                                           allow_delete)
                data['selected'] = new_tag

        all_tags = sorted(tag_system.get_all_tags(req, '-dummy'))
        data['tags'] = all_tags
        try:
            Chrome(self.env).add_textarea_grips(req)
        except AttributeError:
            # Element modifiers unavailable before Trac 0.12, skip gracefully.
            pass
        return 'admin_tag_change.html', data
Exemple #4
0
    def render_admin_panel(self, req, cat, page, version):
        req.perm.require('TAGS_ADMIN')

        realms = [p.get_taggable_realm() for p in self.tag_providers
                  if (not hasattr(p, 'check_permission') or \
                      p.check_permission(req.perm, 'view'))]
        # Check request for enabled filters, or use default.
        if [r for r in realms if r in req.args] == []:
            for realm in realms:
                req.args[realm] = 'on'
        checked_realms = [r for r in realms if r in req.args]
        data = dict(checked_realms=checked_realms,
                    tag_realms=list(
                        dict(name=realm, checked=realm in checked_realms)
                        for realm in realms))

        tag_system = TagSystem(self.env)
        if req.method == 'POST':
            # Replace Tag
            allow_delete = req.args.get('allow_delete')
            new_tag = req.args.get('tag_new_name').strip()
            new_tag = not new_tag == u'' and new_tag or None
            if not (allow_delete or new_tag):
                data['error'] = _("Selected current tag(s) and either "
                                  "new tag or delete approval are required")
            else:
                comment = req.args.get('comment', u'')
                old_tags = req.args.get('tag_name')
                if old_tags:
                    # Provide list regardless of single or multiple selection.
                    old_tags = isinstance(old_tags, list) and old_tags or \
                               [old_tags]
                    tag_system.replace_tag(req,
                                           old_tags,
                                           new_tag,
                                           comment,
                                           allow_delete,
                                           filter=checked_realms)
                data['selected'] = new_tag

        query = ' or '.join(['realm:%s' % r for r in checked_realms])
        all_tags = sorted(tag_system.get_all_tags(req, query))
        data['tags'] = all_tags
        try:
            Chrome(self.env).add_textarea_grips(req)
        except AttributeError:
            # Element modifiers unavailable before Trac 0.12, skip gracefully.
            pass
        return 'admin_tag_change.html', data
Exemple #5
0
    def render_admin_panel(self, req, cat, page, version):
        req.perm.require('TAGS_ADMIN')

        tag_system = TagSystem(self.env)
        all_realms = tag_system.get_taggable_realms(req.perm)
        # Check request for enabled filters, or use default.
        if not [r for r in all_realms if r in req.args]:
            for realm in all_realms:
                req.args[realm] = 'on'
        checked_realms = [r for r in all_realms if r in req.args]
        data = dict(checked_realms=checked_realms,
                    tag_realms=list(
                        dict(name=realm, checked=realm in checked_realms)
                        for realm in all_realms))

        if req.method == 'POST':
            # Replace Tag
            allow_delete = req.args.get('allow_delete')
            new_tag = req.args.get('tag_new_name').strip()
            new_tag = not new_tag == u'' and new_tag or None
            if not (allow_delete or new_tag):
                add_warning(
                    req,
                    _("Selected current tag(s) and either "
                      "new tag or delete approval are required"))
            else:
                comment = req.args.get('comment', u'')
                old_tags = req.args.getlist('tag_name')
                if old_tags:
                    tag_system.replace_tag(req,
                                           old_tags,
                                           new_tag,
                                           comment,
                                           allow_delete,
                                           filter=checked_realms)
                data['selected'] = new_tag
            req.redirect(req.href.admin('tags', 'replace'))

        query = ' or '.join('realm:%s' % r for r in checked_realms)
        all_tags = sorted(tag_system.get_all_tags(req, query))
        data['tags'] = all_tags
        chrome = Chrome(self.env)
        chrome.add_textarea_grips(req)
        if hasattr(chrome, 'jenv'):
            return 'admin_tag_change.html', data, None
        else:
            return 'admin_tag_change.html', data
Exemple #6
0
    def render_admin_panel(self, req, cat, page, version):
        req.perm.require('TAGS_ADMIN')

        tag_system = TagSystem(self.env)
        all_realms = tag_system.get_taggable_realms(req.perm)
        # Check request for enabled filters, or use default.
        if not [r for r in all_realms if r in req.args]:
            for realm in all_realms:
                req.args[realm] = 'on'
        checked_realms = [r for r in all_realms if r in req.args]
        data = dict(checked_realms=checked_realms,
                    tag_realms=list(dict(name=realm,
                                         checked=realm in checked_realms)
                                    for realm in all_realms))

        if req.method == 'POST':
            # Replace Tag
            allow_delete = req.args.get('allow_delete')
            new_tag = req.args.get('tag_new_name').strip()
            new_tag = not new_tag == u'' and new_tag or None
            if not (allow_delete or new_tag):
                data['error'] = _("Selected current tag(s) and either "
                                  "new tag or delete approval are required")
            else:
                comment = req.args.get('comment', u'')
                old_tags = req.args.get('tag_name')
                if old_tags:
                    # Provide list regardless of single or multiple selection.
                    old_tags = isinstance(old_tags, list) and old_tags or \
                               [old_tags]
                    tag_system.replace_tag(req, old_tags, new_tag, comment,
                                           allow_delete, filter=checked_realms)
                data['selected'] = new_tag

        query = ' or '.join(['realm:%s' % r for r in checked_realms])
        all_tags = sorted(tag_system.get_all_tags(req, query))
        data['tags'] = all_tags
        try:
            Chrome(self.env).add_textarea_grips(req)
        except AttributeError:
            # Element modifiers unavailable before Trac 0.12, skip gracefully.
            pass
        return 'admin_tag_change.html', data
Exemple #7
0
class TicketTagProviderTestCase(unittest.TestCase):

    def setUp(self):
        self.env = EnvironmentStub(default_data=True,
                                   enable=['trac.*', 'tractags.*'])
        self.env.path = tempfile.mkdtemp()
        self.perms = PermissionSystem(self.env)

        self.db = self.env.get_db_cnx()
        setup = TagSetup(self.env)
        # Current tractags schema is setup with enabled component anyway.
        #   Revert these changes for getting default permissions inserted.
        self._revert_tractags_schema_init()
        setup.upgrade_environment(self.db)

        self.provider = TicketTagProvider(self.env)
        self.realm = 'ticket'
        self.tag_sys = TagSystem(self.env)
        self.tags = ['tag1', 'tag2']

        cursor = self.db.cursor()
        # Populate tables with initial test data.
        self._create_ticket(self.tags)

        # Mock an anonymous request.
        self.anon_req = Mock()
        self.anon_req.perm = PermissionCache(self.env)

        self.req = Mock(authname='editor')
        self.req.authname = 'editor'
        self.req.perm = PermissionCache(self.env, username='******')

    def tearDown(self):
        self.db.close()
        # Really close db connections.
        self.env.shutdown()
        shutil.rmtree(self.env.path)

    # Helpers

    def _create_ticket(self, tags, **kwargs):
        ticket = Ticket(self.env)
        ticket['keywords'] = u' '.join(sorted(map(to_unicode, tags)))
        ticket['summary'] = 'summary'
        ticket['reporter'] = 'admin'
        for name, value in kwargs.iteritems():
            ticket[name] = value
        ticket.insert()
        return ticket

    def _revert_tractags_schema_init(self):
        cursor = self.db.cursor()
        cursor.execute("DROP TABLE IF EXISTS tags")
        cursor.execute("DROP TABLE IF EXISTS tags_change")
        cursor.execute("DELETE FROM system WHERE name='tags_version'")
        cursor.execute("DELETE FROM permission WHERE action %s"
                       % self.db.like(), ('TAGS_%',))

    def _tags(self):
        tags = {}
        cursor = self.db.cursor()
        cursor.execute("SELECT name,tag FROM tags")
        for name, tag in cursor:
            if name in tags:
                tags[name].add(tag)
            else:
                tags[name] = set([tag])
        return tags

    # Tests

    def test_get_tagged_resources(self):
        # No tags, no restrictions, all resources.
        self.assertEquals(
            [r for r in
             self.provider.get_tagged_resources(self.req, None)][0][1],
            set(self.tags))
        # Force fine-grained perm-check check for all tags, not just the one
        # from query.
        self.provider.fast_permcheck = False
        self.assertEquals(
            [r for r in
             self.provider.get_tagged_resources(self.req,
                                                set(self.tags[:1]))][0][1],
            set(self.tags))

    def test_get_tags(self):
        resource = Resource('ticket', 2)
        self.assertRaises(ResourceNotFound, self.provider.get_resource_tags,
                          self.req, resource)
        self._create_ticket(self.tags)
        self.assertEquals(
            [tag for tag in
             self.provider.get_resource_tags(self.req, resource)], self.tags)
        #ignore_closed_tickets

    def test_set_tags(self):
        tags = ['tag3']
        ticket = Ticket(self.env, 1)
        ticket['keywords'] = tags[0]
        # Tags get updated by TicketChangeListener method.
        ticket.save_changes(self.req.authname)
        self.assertEquals(self.tag_sys.get_all_tags(self.req).keys(), tags)

    def test_remove_tags(self):
        resource = Resource('ticket', 1)
        # Anonymous lacks required permissions.
        self.assertRaises(PermissionError, self.provider.remove_resource_tags,
                          self.anon_req, resource)
        # Shouldn't raise an error with appropriate permission.
        self.provider.remove_resource_tags(self.req, resource, 'comment')
        ticket = Ticket(self.env, 1)
        self.assertEquals(ticket['keywords'], '')

    def test_describe_tagged_resource(self):
        resource = Resource('ticket', 1)
        self.assertEquals(
            self.provider.describe_tagged_resource(self.req, resource),
            'defect: summary')

    def test_create_ticket_by_anonymous(self):
        ticket = self._create_ticket(self.tags, reporter='anonymous')
        tags = self.provider.get_resource_tags(self.req, ticket.resource)
        self.assertEquals(tags, set(self.tags))

    def test_update_ticket_by_anonymous(self):
        ticket = self._create_ticket([])
        tags = self.provider.get_resource_tags(self.req, ticket.resource)
        self.assertEquals(tags, set([]))

        ticket['keywords'] = ', '.join(self.tags)
        ticket.save_changes('anonymous', comment='Adding keywords')
        tags = self.provider.get_resource_tags(self.req, ticket.resource)
        self.assertEquals(tags, set(self.tags))
Exemple #8
0
    def expand_macro(self, formatter, name, content):
        env = self.env
        req = formatter.req
        args, kw = parse_args(content)

        # Use macro arguments (most likely wiki macro calls).
        realms = 'realm' in kw and kw['realm'].split('|') or []
        tag_system = TagSystem(env)
        all_realms = [p.get_taggable_realm() for p in tag_system.tag_providers]
        self.all_realms = all_realms
        self.realms = realms

        if name == 'TagCloud':
            args.append(' or '.join(['realm:%s' % r for r in realms]))
            all_tags = tag_system.get_all_tags(req, ' '.join(args))
            mincount = 'mincount' in kw and kw['mincount'] or None
            return self.render_cloud(req,
                                     all_tags,
                                     caseless_sort=self.caseless_sort,
                                     mincount=mincount)
        elif name == 'ListTagged':
            if _OBSOLETE_ARGS_RE.search(content):
                data = {'warning': 'obsolete_args'}
            else:
                data = {'warning': None}
            context = formatter.context
            # Use TagsQuery arguments (most likely wiki macro calls).
            cols = 'cols' in kw and kw['cols'] or self.default_cols
            format = 'format' in kw and kw['format'] or self.default_format
            query = args and args[0].strip() or None
            if query and not realms:
                # First read query arguments (most likely a web-UI call).
                for realm in all_realms:
                    if re.search('(^|\W)realm:%s(\W|$)' % (realm), query):
                        realms = realms and realms.append(realm) or [realm]
            if not realms:
                # Apply ListTagged defaults to macro call w/o realm.
                realms = list(set(all_realms) - set(self.exclude_realms))
            if not realms:
                return ''
            else:
                self.query = query
                self.realms = realms
            query = '(%s) (%s)' % (query or '', ' or '.join(
                ['realm:%s' % (r) for r in realms]))
            env.log.debug('LISTTAGGED_QUERY: ' + query)
            query_result = tag_system.query(req, query)
            if not query_result:
                return ''

            def _link(resource):
                if resource.realm == 'tag':
                    # Keep realm selection in tag links.
                    return builder.a(resource.id,
                                     href=self.get_href(req, tag=resource))
                elif resource.realm == 'ticket':
                    # Return resource link including ticket status dependend
                    #   class to allow for common Trac ticket link style.
                    ticket = Ticket(env, resource.id)
                    return builder.a('#%s' % ticket.id,
                                     class_=ticket['status'],
                                     href=formatter.href.ticket(ticket.id),
                                     title=shorten_line(ticket['summary']))
                return render_resource_link(env, context, resource, 'compact')

            if format == 'table':
                cols = [
                    col for col in cols.split('|')
                    if col in self.supported_cols
                ]
                # Use available translations from Trac core.
                try:
                    labels = TicketSystem(env).get_ticket_field_labels()
                    labels['id'] = _('Id')
                except AttributeError:
                    # Trac 0.11 neither has the attribute nor uses i18n.
                    labels = {'id': 'Id', 'description': 'Description'}
                labels['realm'] = _('Realm')
                labels['tags'] = _('Tags')
                headers = [{'label': labels.get(col)} for col in cols]
                data.update({'cols': cols, 'headers': headers})

            results = sorted(query_result, key=lambda r: \
                             embedded_numbers(to_unicode(r[0].id)))
            results = self._paginate(req, results)
            rows = []
            for resource, tags in results:
                desc = tag_system.describe_tagged_resource(req, resource)
                tags = sorted(tags)
                if tags:
                    rendered_tags = [
                        _link(Resource('tag', tag)) for tag in tags
                    ]
                    if 'oldlist' == format:
                        resource_link = _link(resource)
                    else:
                        desc = desc or \
                               get_resource_description(env, resource,
                                                        context=context)
                        resource_link = builder.a(desc,
                                                  href=get_resource_url(
                                                      env, resource,
                                                      context.href))
                        if 'table' == format:
                            cells = []
                            for col in cols:
                                if col == 'id':
                                    cells.append(_link(resource))
                                # Don't duplicate links to resource in both.
                                elif col == 'description' and 'id' in cols:
                                    cells.append(desc)
                                elif col == 'description':
                                    cells.append(resource_link)
                                elif col == 'realm':
                                    cells.append(resource.realm)
                                elif col == 'tags':
                                    cells.append(
                                        builder([(tag, ' ')
                                                 for tag in rendered_tags]))
                            rows.append({'cells': cells})
                            continue
                rows.append({
                    'desc': desc,
                    'rendered_tags': None,
                    'resource_link': _link(resource)
                })
            data.update({
                'format': format,
                'paginator': results,
                'results': rows,
                'tags_url': req.href('tags')
            })

            # Work around a bug in trac/templates/layout.html, that causes a
            # TypeError for the wiki macro call, if we use add_link() alone.
            add_stylesheet(req, 'common/css/search.css')

            return Chrome(env).render_template(req, 'listtagged_results.html',
                                               data, 'text/html', True)
Exemple #9
0
    def expand_macro(self, formatter, name, content, realms=[]):
        """Evaluate macro call and render results.

        Calls from web-UI come with pre-processed realm selection.
        """
        env = self.env
        req = formatter.req
        tag_system = TagSystem(env)

        all_realms = tag_system.get_taggable_realms()
        if not all_realms:
            # Tag providers are required, no result without at least one.
            return ''
        args, kw = parse_args(content)

        query = args and args[0].strip() or None
        if not realms:
            # Check macro arguments for realms (typical wiki macro call).
            realms = 'realm' in kw and kw['realm'].split('|') or []
        if query:
            # Add realms from query expression.
            realms.extend(query_realms(query, all_realms))
            # Remove redundant realm selection for performance.
            if set(realms) == all_realms:
                query = re.sub('(^|\W)realm:\S+(\W|$)', ' ', query).strip()
        if name == 'TagCloud':
            # Set implicit 'all tagged realms' as default.
            if not realms:
                realms = all_realms
            if query:
                all_tags = Counter()
                # Require per resource query including view permission checks.
                for resource, tags in tag_system.query(req, query):
                    all_tags.update(tags)
            else:
                # Allow faster per tag query, side steps permission checks.
                all_tags = tag_system.get_all_tags(req, realms=realms)
            mincount = 'mincount' in kw and kw['mincount'] or None
            return self.render_cloud(req, all_tags,
                                     caseless_sort=self.caseless_sort,
                                     mincount=mincount, realms=realms)
        elif name == 'ListTagged':
            if content and _OBSOLETE_ARGS_RE.search(content):
                data = {'warning': 'obsolete_args'}
            else:
                data = {'warning': None}
            context = formatter.context
            # Use TagsQuery arguments (most likely wiki macro calls).
            cols = 'cols' in kw and kw['cols'] or self.default_cols
            format = 'format' in kw and kw['format'] or self.default_format
            if not realms:
                # Apply ListTagged defaults to macro call w/o realm.
                realms = list(set(all_realms)-set(self.exclude_realms))
                if not realms:
                    return ''
            query = '(%s) (%s)' % (query or '', ' or '.join(['realm:%s' % (r)
                                                             for r in realms]))
            query_result = tag_system.query(req, query)
            excludes = [exc.strip()
                        for exc in kw.get('exclude', '' ).split(':')
                        if exc.strip()]
            if excludes and query_result:
                filtered_result = [(resource, tags)
                                   for resource, tags in query_result
                                   if not any(fnmatchcase(resource.id, exc)
                                              for exc in excludes)]
                query_result = filtered_result
            if not query_result:
                return ''

            def _link(resource):
                if resource.realm == 'tag':
                    # Keep realm selection in tag links.
                    return builder.a(resource.id,
                                     href=self.get_href(req, realms,
                                                        tag=resource))
                elif resource.realm == 'ticket':
                    # Return resource link including ticket status dependend
                    #   class to allow for common Trac ticket link style.
                    ticket = Ticket(env, resource.id)
                    return builder.a('#%s' % ticket.id,
                                     class_=ticket['status'],
                                     href=formatter.href.ticket(ticket.id),
                                     title=shorten_line(ticket['summary']))
                return render_resource_link(env, context, resource, 'compact')

            if format == 'table':
                cols = [col for col in cols.split('|')
                        if col in self.supported_cols]
                # Use available translations from Trac core.
                try:
                    labels = TicketSystem(env).get_ticket_field_labels()
                    labels['id'] = _('Id')
                except AttributeError:
                    # Trac 0.11 neither has the attribute nor uses i18n.
                    labels = {'id': 'Id', 'description': 'Description'}
                labels['realm'] = _('Realm')
                labels['tags'] = _('Tags')
                headers = [{'label': labels.get(col)}
                           for col in cols]
                data.update({'cols': cols,
                             'headers': headers})

            results = sorted(query_result, key=lambda r: \
                             embedded_numbers(to_unicode(r[0].id)))
            results = self._paginate(req, results, realms)
            rows = []
            for resource, tags in results:
                desc = tag_system.describe_tagged_resource(req, resource)
                tags = sorted(tags)
                wiki_desc = format_to_oneliner(env, context, desc)
                if tags:
                    rendered_tags = [_link(Resource('tag', tag))
                                     for tag in tags]
                    if 'oldlist' == format:
                        resource_link = _link(resource)
                    else:
                        resource_link = builder.a(wiki_desc,
                                                  href=get_resource_url(
                                                  env, resource, context.href))
                        if 'table' == format:
                            cells = []
                            for col in cols:
                                if col == 'id':
                                    cells.append(_link(resource))
                                # Don't duplicate links to resource in both.
                                elif col == 'description' and 'id' in cols:
                                    cells.append(wiki_desc)
                                elif col == 'description':
                                    cells.append(resource_link)
                                elif col == 'realm':
                                    cells.append(resource.realm)
                                elif col == 'tags':
                                    cells.append(
                                        builder([(tag, ' ')
                                                 for tag in rendered_tags]))
                            rows.append({'cells': cells})
                            continue
                rows.append({'desc': wiki_desc,
                             'rendered_tags': None,
                             'resource_link': _link(resource)})
            data.update({'format': format,
                         'paginator': results,
                         'results': rows,
                         'tags_url': req.href('tags')})

            # Work around a bug in trac/templates/layout.html, that causes a
            # TypeError for the wiki macro call, if we use add_link() alone.
            add_stylesheet(req, 'common/css/search.css')

            return Chrome(env).render_template(
                req, 'listtagged_results.html', data, 'text/html', True)
Exemple #10
0
class WikiTagProviderTestCase(unittest.TestCase):

    def setUp(self):
        self.env = EnvironmentStub(default_data=True,
                                   enable=['trac.*', 'tractags.*'])
        self.env.path = tempfile.mkdtemp()
        setup = TagSetup(self.env)
        # Current tractags schema is partially setup with enabled component.
        #   Revert these changes for getting a clean setup.
        self._revert_tractags_schema_init()
        setup.upgrade_environment()

        self.perms = PermissionSystem(self.env)
        self.tag_s = TagSystem(self.env)
        self.tag_wp = WikiTagProvider(self.env)

        # Populate table with initial test data.
        self.env.db_transaction("""
            INSERT INTO tags (tagspace, name, tag)
            VALUES ('wiki', 'WikiStart', 'tag1')
            """)

        self.req = Mock(authname='editor')
        # Mock an anonymous request.
        self.req.perm = PermissionCache(self.env)
        self.realm = 'wiki'
        self.tags = ['tag1']

    def tearDown(self):
        self.env.shutdown()
        shutil.rmtree(self.env.path)

    # Helpers

    def _revert_tractags_schema_init(self):
        _revert_tractags_schema_init(self.env)

    # Tests

    def test_get_tags(self):
        resource = Resource('wiki', 'WikiStart')
        self.assertEquals([tag for tag in
                           self.tag_wp.get_resource_tags(self.req, resource)],
                          self.tags)

    def test_exclude_template_tags(self):
        # Populate table with more test data.
        self.env.db_transaction("""
            INSERT INTO tags (tagspace, name, tag)
            VALUES ('wiki', 'PageTemplates/Template', 'tag2')
            """)
        tags = ['tag1', 'tag2']
        self.assertEquals(self.tag_s.get_all_tags(self.req).keys(), self.tags)
        self.env.config.set('tags', 'query_exclude_wiki_templates', False)
        self.assertEquals(self.tag_s.get_all_tags(self.req).keys(), tags)

    def test_set_tags_no_perms(self):
        resource = Resource('wiki', 'TaggedPage')
        self.assertRaises(PermissionError, self.tag_wp.set_resource_tags,
                          self.req, resource, self.tags)

    def test_set_tags(self):
        resource = Resource('wiki', 'TaggedPage')
        self.req.perm = PermissionCache(self.env, username='******')
        # Shouldn't raise an error with appropriate permission.
        self.tag_wp.set_resource_tags(self.req, resource, self.tags)
        self.tag_wp.set_resource_tags(self.req, resource, ['tag2'])
        # Check change records.
        rows = self.env.db_query("""
            SELECT author,oldtags,newtags FROM tags_change
            WHERE tagspace=%s AND name=%s
            ORDER by time DESC
            """, ('wiki', 'TaggedPage'))
        self.assertEqual(rows[0], ('editor', 'tag1', 'tag2'))
        self.assertEqual(rows[1], ('editor', '', 'tag1'))
Exemple #11
0
class TagWikiSyntaxProvider(Component):
    """[opt] Provides tag:<expr> links.

    This extends TracLinks via WikiFormatting to point at tag queries or
    at specific tags.
    """

    implements(IWikiSyntaxProvider)

    def __init__(self):
        self.tag_system = TagSystem(self.env)

    # IWikiSyntaxProvider methods

    def get_wiki_syntax(self):
        """Additional syntax for quoted tags or tag expression."""
        tag_expr = (r"(%s)" % (WikiParser.QUOTED_STRING))

        # Simple (tag|tagged):link syntax
        yield (
            r'''(?P<qualifier>tag(?:ged)?):(?P<tag_expr>%s)''' % tag_expr,
            lambda f, ns, match: self._format_tagged(
                f, match.group('qualifier'), match.group('tag_expr'), '%s:%s' %
                (match.group('qualifier'), match.group('tag_expr'))))

        # [(tag|tagged):link with label]
        yield (
            r'''\[tag(?:ged)?:'''
            r'''(?P<ltag_expr>%s)\s*(?P<tag_title>[^\]]+)?\]''' % tag_expr,
            lambda f, ns, match: self._format_tagged(
                f, 'tag', match.group('ltag_expr'), match.group('tag_title')))

    def get_link_resolvers(self):
        return [('tag', self._format_tagged), ('tagged', self._format_tagged)]

    def _format_tagged(self, formatter, ns, target, label, fullmatch=None):
        """Tag and tag query expression link formatter."""
        def unquote(text):
            """Strip all matching pairs of outer quotes from string."""
            while re.match(WikiParser.QUOTED_STRING, text):
                # Remove outer whitespace after stripped quotation too.
                text = text[1:-1].strip()
            return text

        label = label and unquote(label.strip()) or ''
        target = unquote(target.strip())

        query = target
        # Pop realms from query expression.
        all_realms = self.tag_system.get_taggable_realms(formatter.perm)
        realms = query_realms(target, all_realms)
        if realms:
            kwargs = dict((realm, 'on') for realm in realms)
            target = re.sub('(^|\W)realm:\S+(\W|$)', ' ', target).strip()
        else:
            kwargs = {}

        tag_res = Resource('tag', target)
        if 'TAGS_VIEW' not in formatter.perm(tag_res):
            return tag.span(label,
                            class_='forbidden tags',
                            title=_("no permission to view tags"))

        context = formatter.context
        href = self.tag_system.get_resource_url(tag_res, context.href, kwargs)
        if all_realms and (target in self.tag_system.get_all_tags(
                formatter.req) or not iter_is_empty(
                    self.tag_system.query(formatter.req, query))):
            # At least one tag provider is available and tag exists or
            # tags query yields at least one match.
            if label:
                return tag.a(label, href=href)
            return render_resource_link(self.env, context, tag_res)

        return tag.a(label + '?',
                     href=href,
                     class_='missing tags',
                     rel='nofollow')
Exemple #12
0
class WikiTagProviderTestCase(unittest.TestCase):

    def setUp(self):
        self.env = EnvironmentStub(default_data=True,
                                   path=tempfile.mkdtemp(),
                                   enable=['trac.*', 'tractags.*'])
        setup = TagSetup(self.env)
        # Current tractags schema is partially setup with enabled component.
        #   Revert these changes for getting a clean setup.
        self._revert_tractags_schema_init()
        setup.upgrade_environment()

        self.perms = PermissionSystem(self.env)
        self.tag_s = TagSystem(self.env)
        self.tag_wp = WikiTagProvider(self.env)

        # Populate table with initial test data.
        self.env.db_transaction("""
            INSERT INTO tags (tagspace, name, tag)
            VALUES ('wiki', 'WikiStart', 'tag1')
            """)

        self.realm = 'wiki'
        self.tags = ['tag1']

    def tearDown(self):
        self.env.shutdown()
        shutil.rmtree(self.env.path)

    # Helpers

    def _revert_tractags_schema_init(self):
        _revert_tractags_schema_init(self.env)

    # Tests

    def test_get_tags(self):
        resource = Resource('wiki', 'WikiStart')
        req = MockRequest(self.env, authname='editor')
        self.assertEquals([tag for tag in
                           self.tag_wp.get_resource_tags(req, resource)],
                          self.tags)

    def test_exclude_template_tags(self):
        # Populate table with more test data.
        req = MockRequest(self.env, authname='editor')
        self.env.db_transaction("""
            INSERT INTO tags (tagspace, name, tag)
            VALUES ('wiki', 'PageTemplates/Template', 'tag2')
            """)
        tags = ['tag1', 'tag2']
        self.assertEquals(self.tag_s.get_all_tags(req).keys(), self.tags)
        self.env.config.set('tags', 'query_exclude_wiki_templates', False)
        self.assertEquals(self.tag_s.get_all_tags(req).keys(), tags)

    def test_set_tags_no_perms(self):
        resource = Resource('wiki', 'TaggedPage')
        req = MockRequest(self.env, authname='anonymous')
        self.assertRaises(PermissionError, self.tag_wp.set_resource_tags,
                          req, resource, self.tags)

    def test_set_tags(self):
        resource = Resource('wiki', 'TaggedPage')
        req = MockRequest(self.env, authname='editor')
        # Shouldn't raise an error with appropriate permission.
        self.tag_wp.set_resource_tags(req, resource, self.tags)
        self.tag_wp.set_resource_tags(req, resource, ['tag2'])
        # Check change records.
        rows = self.env.db_query("""
            SELECT author,oldtags,newtags FROM tags_change
            WHERE tagspace=%s AND name=%s
            ORDER by time DESC
            """, ('wiki', 'TaggedPage'))
        self.assertEqual(rows[0], ('editor', 'tag1', 'tag2'))
        self.assertEqual(rows[1], ('editor', '', 'tag1'))
Exemple #13
0
class WikiTagProviderTestCase(unittest.TestCase):
    def setUp(self):
        self.env = EnvironmentStub(default_data=True,
                                   enable=['trac.*', 'tractags.*'])
        self.env.path = tempfile.mkdtemp()
        self.perms = PermissionSystem(self.env)

        self.tag_s = TagSystem(self.env)
        self.tag_wp = WikiTagProvider(self.env)

        self.db = self.env.get_db_cnx()
        setup = TagSetup(self.env)
        # Current tractags schema is partially setup with enabled component.
        #   Revert these changes for getting a clean setup.
        self._revert_tractags_schema_init()
        setup.upgrade_environment(self.db)

        cursor = self.db.cursor()
        # Populate table with initial test data.
        cursor.execute("""
            INSERT INTO tags
                   (tagspace, name, tag)
            VALUES ('wiki', 'WikiStart', 'tag1')
        """)

        self.req = Mock()
        # Mock an anonymous request.
        self.req.perm = PermissionCache(self.env)
        self.realm = 'wiki'
        self.tags = ['tag1']

    def tearDown(self):
        self.db.close()
        # Really close db connections.
        self.env.shutdown()
        shutil.rmtree(self.env.path)

    # Helpers

    def _revert_tractags_schema_init(self):
        cursor = self.db.cursor()
        cursor.execute("DROP TABLE IF EXISTS tags")
        cursor.execute("DELETE FROM system WHERE name='tags_version'")
        cursor.execute(
            "DELETE FROM permission WHERE action %s" % self.db.like(),
            ('TAGS_%', ))

    # Tests

    def test_get_tags(self):
        resource = Resource('wiki', 'WikiStart')
        self.assertEquals(
            [tag for tag in self.tag_wp.get_resource_tags(self.req, resource)],
            self.tags)

    def test_exclude_template_tags(self):
        cursor = self.db.cursor()
        # Populate table with more test data.
        cursor.execute("""
            INSERT INTO tags
                   (tagspace, name, tag)
            VALUES ('wiki', 'PageTemplates/Template', 'tag2')
        """)
        tags = ['tag1', 'tag2']
        # Previous workaround for regression in query parser still works,
        #   but the query without argument works again as well.
        self.assertEquals(
            self.tag_s.get_all_tags(self.req, '-invalid').keys(), self.tags)
        self.assertEquals(
            self.tag_s.get_all_tags(self.req, '').keys(), self.tags)
        self.env.config.set('tags', 'query_exclude_wiki_templates', False)
        self.assertEquals(
            self.tag_s.get_all_tags(self.req, '-invalid').keys(), tags)
        self.assertEquals(self.tag_s.get_all_tags(self.req, '').keys(), tags)

    def test_set_tags_no_perms(self):
        resource = Resource('wiki', 'TaggedPage')
        self.assertRaises(PermissionError, self.tag_wp.set_resource_tags,
                          self.req, resource, self.tags)

    def test_set_tags(self):
        resource = Resource('wiki', 'TaggedPage')
        self.req.perm = PermissionCache(self.env, username='******')
        # Shouldn't raise an error with appropriate permission.
        self.tag_wp.set_resource_tags(self.req, resource, self.tags)
Exemple #14
0
    def expand_macro(self, formatter, name, content, realms=[]):
        """Evaluate macro call and render results.

        Calls from web-UI come with pre-processed realm selection.
        """
        env = self.env
        req = formatter.req
        tag_system = TagSystem(env)

        all_realms = tag_system.get_taggable_realms()
        if not all_realms:
            # Tag providers are required, no result without at least one.
            return ''
        args, kw = parse_args(content)

        query = args and args[0].strip() or None
        if not realms:
            # Check macro arguments for realms (typical wiki macro call).
            realms = 'realm' in kw and kw['realm'].split('|') or []
        if query:
            # Add realms from query expression.
            realms.extend(query_realms(query, all_realms))
            # Remove redundant realm selection for performance.
            if set(realms) == all_realms:
                query = re.sub('(^|\W)realm:\S+(\W|$)', ' ', query).strip()
        if name == 'TagCloud':
            # Set implicit 'all tagged realms' as default.
            if not realms:
                realms = all_realms
            if query:
                all_tags = Counter()
                # Require per resource query including view permission checks.
                for resource, tags in tag_system.query(req, query):
                    all_tags.update(tags)
            else:
                # Allow faster per tag query, side steps permission checks.
                all_tags = tag_system.get_all_tags(req, realms=realms)
            mincount = 'mincount' in kw and kw['mincount'] or None
            return self.render_cloud(req,
                                     all_tags,
                                     caseless_sort=self.caseless_sort,
                                     mincount=mincount,
                                     realms=realms)
        elif name == 'ListTagged':
            if content and _OBSOLETE_ARGS_RE.search(content):
                data = {'warning': 'obsolete_args'}
            else:
                data = {'warning': None}
            context = formatter.context
            # Use TagsQuery arguments (most likely wiki macro calls).
            cols = 'cols' in kw and kw['cols'] or self.default_cols
            format = 'format' in kw and kw['format'] or self.default_format
            if not realms:
                # Apply ListTagged defaults to macro call w/o realm.
                realms = list(set(all_realms) - set(self.exclude_realms))
                if not realms:
                    return ''
            query = '(%s) (%s)' % (query or '', ' or '.join(
                ['realm:%s' % (r) for r in realms]))
            query_result = tag_system.query(req, query)
            excludes = [
                exc.strip() for exc in kw.get('exclude', '').split(':')
                if exc.strip()
            ]
            if excludes and query_result:
                filtered_result = [(resource, tags)
                                   for resource, tags in query_result
                                   if not any(
                                       fnmatchcase(resource.id, exc)
                                       for exc in excludes)]
                query_result = filtered_result
            if not query_result:
                return ''

            def _link(resource):
                if resource.realm == 'tag':
                    # Keep realm selection in tag links.
                    return builder.a(resource.id,
                                     href=self.get_href(req,
                                                        realms,
                                                        tag=resource))
                elif resource.realm == 'ticket':
                    # Return resource link including ticket status dependend
                    #   class to allow for common Trac ticket link style.
                    ticket = Ticket(env, resource.id)
                    return builder.a('#%s' % ticket.id,
                                     class_=ticket['status'],
                                     href=formatter.href.ticket(ticket.id),
                                     title=shorten_line(ticket['summary']))
                return render_resource_link(env, context, resource, 'compact')

            if format == 'table':
                cols = [
                    col for col in cols.split('|')
                    if col in self.supported_cols
                ]
                # Use available translations from Trac core.
                try:
                    labels = TicketSystem(env).get_ticket_field_labels()
                    labels['id'] = _('Id')
                except AttributeError:
                    # Trac 0.11 neither has the attribute nor uses i18n.
                    labels = {'id': 'Id', 'description': 'Description'}
                labels['realm'] = _('Realm')
                labels['tags'] = _('Tags')
                headers = [{'label': labels.get(col)} for col in cols]
                data.update({'cols': cols, 'headers': headers})

            try:
                results = sorted(
                    query_result,
                    key=lambda r: embedded_numbers(to_unicode(r[0].id)))
            except (InvalidQuery, InvalidTagRealm), e:
                return system_message(_("ListTagged macro error"), e)
            results = self._paginate(req, results, realms)
            rows = []
            for resource, tags in results:
                desc = tag_system.describe_tagged_resource(req, resource)
                tags = sorted(tags)
                wiki_desc = format_to_oneliner(env, context, desc)
                if tags:
                    rendered_tags = [
                        _link(Resource('tag', tag)) for tag in tags
                    ]
                    if 'oldlist' == format:
                        resource_link = _link(resource)
                    else:
                        resource_link = builder.a(wiki_desc,
                                                  href=get_resource_url(
                                                      env, resource,
                                                      context.href))
                        if 'table' == format:
                            cells = []
                            for col in cols:
                                if col == 'id':
                                    cells.append(_link(resource))
                                # Don't duplicate links to resource in both.
                                elif col == 'description' and 'id' in cols:
                                    cells.append(wiki_desc)
                                elif col == 'description':
                                    cells.append(resource_link)
                                elif col == 'realm':
                                    cells.append(resource.realm)
                                elif col == 'tags':
                                    cells.append(
                                        builder([(tag, ' ')
                                                 for tag in rendered_tags]))
                            rows.append({'cells': cells})
                            continue
                rows.append({
                    'desc': wiki_desc,
                    'rendered_tags': None,
                    'resource_link': _link(resource)
                })
            data.update({
                'format': format,
                'paginator': results,
                'results': rows,
                'tags_url': req.href('tags')
            })

            # Work around a bug in trac/templates/layout.html, that causes a
            # TypeError for the wiki macro call, if we use add_link() alone.
            add_stylesheet(req, 'common/css/search.css')

            return Chrome(env).render_template(req, 'listtagged_results.html',
                                               data, 'text/html', True)
Exemple #15
0
class TicketTagProviderTestCase(unittest.TestCase):
    def setUp(self):
        self.env = EnvironmentStub(default_data=True,
                                   enable=['trac.*', 'tractags.*'])
        self.env.path = tempfile.mkdtemp()
        self.perms = PermissionSystem(self.env)

        self.db = self.env.get_db_cnx()
        setup = TagSetup(self.env)
        # Current tractags schema is setup with enabled component anyway.
        #   Revert these changes for getting default permissions inserted.
        self._revert_tractags_schema_init()
        setup.upgrade_environment(self.db)

        self.provider = TicketTagProvider(self.env)
        self.realm = 'ticket'
        self.tag_sys = TagSystem(self.env)
        self.tags = ['tag1']

        cursor = self.db.cursor()
        # Populate table with initial test data, not synced with tickets yet.
        cursor.execute("""
            INSERT INTO tags
                   (tagspace, name, tag)
            VALUES ('ticket', '1', 'deleted')""")
        self.realm = 'ticket'
        self._create_ticket(self.tags)

        self.req = Mock()
        # Mock an anonymous request.
        self.req.perm = PermissionCache(self.env)

    def tearDown(self):
        self.db.close()
        # Really close db connections.
        self.env.shutdown()
        shutil.rmtree(self.env.path)

    # Helpers

    def _create_ticket(self, tags):
        ticket = Ticket(self.env)
        ticket['keywords'] = u' '.join(sorted(map(to_unicode, tags)))
        ticket['summary'] = 'summary'
        ticket.insert()

    def _revert_tractags_schema_init(self):
        cursor = self.db.cursor()
        cursor.execute("DROP TABLE IF EXISTS tags")
        cursor.execute("DELETE FROM system WHERE name='tags_version'")
        cursor.execute(
            "DELETE FROM permission WHERE action %s" % self.db.like(),
            ('TAGS_%', ))

    def _tags(self):
        tags = {}
        cursor = self.db.cursor()
        cursor.execute("SELECT name,tag FROM tags")
        for name, tag in cursor.fetchall():
            if name in tags:
                tags[name].add(tag)
            else:
                tags[name] = set([tag])
        return tags

    # Tests

    def test_get_tags(self):
        resource = Resource('ticket', 2)
        self.assertRaises(ResourceNotFound, self.provider.get_resource_tags,
                          self.req, resource)
        self._create_ticket(self.tags)
        self.assertEquals([
            tag for tag in self.provider.get_resource_tags(self.req, resource)
        ], self.tags)
        #ignore_closed_tickets

    def test_set_tags(self):
        resource = Resource('ticket', 1)
        self.tags = ['tag2']
        # Anonymous lacks required permissions.
        self.assertRaises(PermissionError, self.provider.set_resource_tags,
                          self.req, resource, self.tags)
        self.req.authname = 'user'
        self.req.perm = PermissionCache(self.env, username='******')
        # Shouldn't raise an error with appropriate permission.
        self.provider.set_resource_tags(self.req, resource, self.tags)
        self.assertEquals(
            self.tag_sys.get_all_tags(self.req, '').keys(), self.tags)

    def test_remove_tags(self):
        resource = Resource('ticket', 1)
        # Anonymous lacks required permissions.
        self.assertRaises(PermissionError, self.provider.remove_resource_tags,
                          self.req, resource)
        self.req.authname = 'user'
        self.req.perm = PermissionCache(self.env, username='******')
        # Shouldn't raise an error with appropriate permission.
        self.provider.remove_resource_tags(self.req, resource, 'comment')
        tkt = Ticket(self.env, 1)
        self.assertEquals(tkt['keywords'], '')

    def test_describe_tagged_resource(self):
        resource = Resource('ticket', 1)
        self.assertEquals(
            self.provider.describe_tagged_resource(self.req, resource),
            'defect: summary')
Exemple #16
0
class WikiTagProviderTestCase(unittest.TestCase):

    def setUp(self):
        self.env = EnvironmentStub(default_data=True,
                                   enable=['trac.*', 'tractags.*'])
        self.env.path = tempfile.mkdtemp()
        self.db = self.env.get_db_cnx()
        setup = TagSetup(self.env)
        # Current tractags schema is partially setup with enabled component.
        #   Revert these changes for getting a clean setup.
        self._revert_tractags_schema_init()
        setup.upgrade_environment(self.db)

        self.perms = PermissionSystem(self.env)
        self.tag_s = TagSystem(self.env)
        self.tag_wp = WikiTagProvider(self.env)

        cursor = self.db.cursor()
        # Populate table with initial test data.
        cursor.execute("""
            INSERT INTO tags
                   (tagspace, name, tag)
            VALUES ('wiki', 'WikiStart', 'tag1')
        """)

        self.req = Mock(authname='editor')
        # Mock an anonymous request.
        self.req.perm = PermissionCache(self.env)
        self.realm = 'wiki'
        self.tags = ['tag1']

    def tearDown(self):
        self.db.close()
        # Really close db connections.
        self.env.shutdown()
        shutil.rmtree(self.env.path)

    # Helpers

    def _revert_tractags_schema_init(self):
        cursor = self.db.cursor()
        cursor.execute("DROP TABLE IF EXISTS tags")
        cursor.execute("DROP TABLE IF EXISTS tags_change")
        cursor.execute("DELETE FROM system WHERE name='tags_version'")
        cursor.execute("DELETE FROM permission WHERE action %s"
                       % self.db.like(), ('TAGS_%',))

    # Tests

    def test_get_tags(self):
        resource = Resource('wiki', 'WikiStart')
        self.assertEquals([tag for tag in
                           self.tag_wp.get_resource_tags(self.req, resource)],
                          self.tags)

    def test_exclude_template_tags(self):
        cursor = self.db.cursor()
        # Populate table with more test data.
        cursor.execute("""
            INSERT INTO tags
                   (tagspace, name, tag)
            VALUES ('wiki', 'PageTemplates/Template', 'tag2')
        """)
        tags = ['tag1', 'tag2']
        self.assertEquals(self.tag_s.get_all_tags(self.req).keys(), self.tags)
        self.env.config.set('tags', 'query_exclude_wiki_templates', False)
        self.assertEquals(self.tag_s.get_all_tags(self.req).keys(), tags)

    def test_set_tags_no_perms(self):
        resource = Resource('wiki', 'TaggedPage')
        self.assertRaises(PermissionError, self.tag_wp.set_resource_tags,
                          self.req, resource, self.tags)

    def test_set_tags(self):
        resource = Resource('wiki', 'TaggedPage')
        self.req.perm = PermissionCache(self.env, username='******')
        # Shouldn't raise an error with appropriate permission.
        self.tag_wp.set_resource_tags(self.req, resource, self.tags)
        self.tag_wp.set_resource_tags(self.req, resource, ['tag2'])
        cursor = self.db.cursor()
        # Check change records.
        cursor.execute("""
            SELECT *
              FROM tags_change
             WHERE tagspace=%s
               AND name=%s
             ORDER by time DESC
        """, ('wiki', 'TaggedPage'))
        row = cursor.fetchone()
        self.assertEqual([v for v in row[-3:]], ['editor', 'tag1', 'tag2'])
        row = cursor.fetchone()
        self.assertEqual([v for v in row[-3:]], ['editor', '', 'tag1'])
Exemple #17
0
    def expand_macro(self, formatter, name, content):
        env = self.env
        req = formatter.req
        args, kw = parse_args(content)

        # Use macro arguments (most likely wiki macro calls).
        realms = 'realm' in kw and kw['realm'].split('|') or []
        tag_system = TagSystem(env)
        all_realms = [p.get_taggable_realm()
                      for p in tag_system.tag_providers]
        self.all_realms = all_realms
        self.realms = realms

        if name == 'TagCloud':
            args.append(' or '.join(['realm:%s' % r for r in realms]))
            all_tags = tag_system.get_all_tags(req, ' '.join(args))
            mincount = 'mincount' in kw and kw['mincount'] or None
            return self.render_cloud(req, all_tags,
                                     caseless_sort=self.caseless_sort,
                                     mincount=mincount)
        elif name == 'ListTagged':
            if _OBSOLETE_ARGS_RE.search(content):
                data = {'warning': 'obsolete_args'}
            else:
                data = {'warning': None}
            context=formatter.context
            # Use TagsQuery arguments (most likely wiki macro calls).
            cols = 'cols' in kw and kw['cols'] or self.default_cols
            format = 'format' in kw and kw['format'] or self.default_format
            query = args and args[0].strip() or None
            if query and not realms:
                # First read query arguments (most likely a web-UI call).
                for realm in all_realms:
                    if re.search('(^|\W)realm:%s(\W|$)' % (realm), query):
                        realms = realms and realms.append(realm) or [realm]
            if not realms:
                # Apply ListTagged defaults to macro call w/o realm.
                realms = list(set(all_realms)-set(self.exclude_realms))
            if not realms:
                return ''
            else:
                self.query = query
                self.realms = realms
            query = '(%s) (%s)' % (query or '', ' or '.join(['realm:%s' % (r)
                                                             for r in realms]))
            env.log.debug('LISTTAGGED_QUERY: ' + query)
            query_result = tag_system.query(req, query)
            if not query_result:
                return ''

            def _link(resource):
                if resource.realm == 'tag':
                    # Keep realm selection in tag links.
                    return builder.a(resource.id,
                                     href=self.get_href(req, tag=resource))
                elif resource.realm == 'ticket':
                    # Return resource link including ticket status dependend
                    #   class to allow for common Trac ticket link style.
                    ticket = Ticket(env, resource.id)
                    return builder.a('#%s' % ticket.id,
                                     class_=ticket['status'],
                                     href=formatter.href.ticket(ticket.id),
                                     title=shorten_line(ticket['summary']))
                return render_resource_link(env, context, resource, 'compact')

            if format == 'table':
                cols = [col for col in cols.split('|')
                        if col in self.supported_cols]
                # Use available translations from Trac core.
                try:
                    labels = TicketSystem(env).get_ticket_field_labels()
                    labels['id'] = _('Id')
                except AttributeError:
                    # Trac 0.11 neither has the attribute nor uses i18n.
                    labels = {'id': 'Id', 'description': 'Description'}
                labels['realm'] = _('Realm')
                labels['tags'] = _('Tags')
                headers = [{'label': labels.get(col)}
                           for col in cols]
                data.update({'cols': cols,
                             'headers': headers})

            results = sorted(query_result, key=lambda r: \
                             embedded_numbers(to_unicode(r[0].id)))
            results = self._paginate(req, results)
            rows = []
            for resource, tags in results:
                desc = tag_system.describe_tagged_resource(req, resource)
                tags = sorted(tags)
                if tags:
                    rendered_tags = [_link(Resource('tag', tag))
                                     for tag in tags]
                    if 'oldlist' == format:
                        resource_link = _link(resource)
                    else:
                        desc = desc or \
                               get_resource_description(env, resource,
                                                        context=context)
                        resource_link = builder.a(desc, href=get_resource_url(
                                                  env, resource, context.href))
                        if 'table' == format:
                            cells = []
                            for col in cols:
                                if col == 'id':
                                    cells.append(_link(resource))
                                # Don't duplicate links to resource in both.
                                elif col == 'description' and 'id' in cols:
                                    cells.append(desc)
                                elif col == 'description':
                                    cells.append(resource_link)
                                elif col == 'realm':
                                    cells.append(resource.realm)
                                elif col == 'tags':
                                    cells.append(
                                        builder([(tag, ' ')
                                                 for tag in rendered_tags]))
                            rows.append({'cells': cells})
                            continue
                rows.append({'desc': desc,
                             'rendered_tags': None,
                             'resource_link': _link(resource)})
            data.update({'format': format,
                         'paginator': results,
                         'results': rows,
                         'tags_url': req.href('tags')})

            # Work around a bug in trac/templates/layout.html, that causes a
            # TypeError for the wiki macro call, if we use add_link() alone.
            add_stylesheet(req, 'common/css/search.css')

            return Chrome(env).render_template(
                req, 'listtagged_results.html', data, 'text/html', True)
Exemple #18
0
class TicketTagProviderTestCase(unittest.TestCase):
    def setUp(self):
        self.env = EnvironmentStub(default_data=True,
                                   enable=['trac.*', 'tractags.*'])
        self.env.path = tempfile.mkdtemp()
        self.perms = PermissionSystem(self.env)

        setup = TagSetup(self.env)
        # Current tractags schema is setup with enabled component anyway.
        #   Revert these changes for getting default permissions inserted.
        self._revert_tractags_schema_init()
        setup.upgrade_environment()

        self.provider = TicketTagProvider(self.env)
        self.realm = 'ticket'
        self.tag_sys = TagSystem(self.env)
        self.tags = ['tag1', 'tag2']

        # Populate tables with initial test data.
        self._create_ticket(self.tags)

    def tearDown(self):
        self.env.shutdown()
        shutil.rmtree(self.env.path)

    # Helpers

    def _create_ticket(self, tags, **kwargs):
        ticket = Ticket(self.env)
        ticket['keywords'] = u' '.join(sorted(map(to_unicode, tags)))
        ticket['summary'] = 'summary'
        ticket['reporter'] = 'admin'
        for name, value in kwargs.iteritems():
            ticket[name] = value
        ticket.insert()
        return ticket

    def _revert_tractags_schema_init(self):
        with self.env.db_transaction as db:
            db("DROP TABLE IF EXISTS tags")
            db("DROP TABLE IF EXISTS tags_change")
            db("DELETE FROM system WHERE name='tags_version'")
            db("DELETE FROM permission WHERE action %s" % db.like(),
               ('TAGS_%', ))

    def _tags(self):
        tags = {}
        for name, tag in self.env.db_query("""
                SELECT name,tag FROM tags
                """):
            if name in tags:
                tags[name].add(tag)
            else:
                tags[name] = set([tag])
        return tags

    # Tests

    def test_get_tagged_resources(self):
        # No tags, no restrictions, all resources.
        req = MockRequest(self.env, authname='editor')
        self.assertEquals(
            [r for r in self.provider.get_tagged_resources(req, None)][0][1],
            set(self.tags))
        # Force fine-grained perm-check check for all tags, not just the one
        # from query.
        self.provider.fast_permcheck = False
        self.assertEquals([
            r for r in self.provider.get_tagged_resources(
                req, set(self.tags[:1]))
        ][0][1], set(self.tags))

    def test_get_tags(self):
        req = MockRequest(self.env, authname='editor')
        resource = Resource('ticket', 2)
        self.assertRaises(ResourceNotFound, self.provider.get_resource_tags,
                          req, resource)
        self._create_ticket(self.tags)
        self.assertEquals(
            [tag for tag in self.provider.get_resource_tags(req, resource)],
            self.tags)

    def test_set_tags(self):
        req = MockRequest(self.env, authname='editor')
        tags = ['tag3']
        ticket = Ticket(self.env, 1)
        ticket['keywords'] = tags[0]
        # Tags get updated by TicketChangeListener method.
        ticket.save_changes(req.authname)
        self.assertEquals(self.tag_sys.get_all_tags(req).keys(), tags)

    def test_remove_tags(self):
        req = MockRequest(self.env, authname='editor')
        anon_req = MockRequest(self.env, authname='anonymous')
        resource = Resource('ticket', 1)
        # Anonymous lacks required permissions.
        self.assertRaises(PermissionError, self.provider.remove_resource_tags,
                          anon_req, resource)
        # Shouldn't raise an error with appropriate permission.
        self.provider.remove_resource_tags(req, resource, 'comment')
        ticket = Ticket(self.env, 1)
        self.assertEquals(ticket['keywords'], '')

    def test_describe_tagged_resource(self):
        req = MockRequest(self.env, authname='editor')
        resource = Resource('ticket', 1)
        self.assertEquals(
            self.provider.describe_tagged_resource(req, resource),
            'defect: summary')

    def test_create_ticket_by_anonymous(self):
        req = MockRequest(self.env, authname='editor')
        ticket = self._create_ticket(self.tags, reporter='anonymous')
        tags = self.provider.get_resource_tags(req, ticket.resource)
        self.assertEquals(tags, set(self.tags))

    def test_update_ticket_by_anonymous(self):
        req = MockRequest(self.env, authname='editor')
        ticket = self._create_ticket([])
        tags = self.provider.get_resource_tags(req, ticket.resource)
        self.assertEquals(tags, set([]))

        ticket['keywords'] = ', '.join(self.tags)
        ticket.save_changes('anonymous', comment='Adding keywords')
        tags = self.provider.get_resource_tags(req, ticket.resource)
        self.assertEquals(tags, set(self.tags))
Exemple #19
0
class TagWikiSyntaxProvider(Component):
    """[opt] Provides tag:<expr> links.

    This extends TracLinks via WikiFormatting to point at tag queries or
    at specific tags.
    """

    implements(IWikiSyntaxProvider)

    def __init__(self):
        self.tag_system = TagSystem(self.env)

    # IWikiSyntaxProvider methods

    def get_wiki_syntax(self):
        """Additional syntax for quoted tags or tag expression."""
        tag_expr = (
            r"(%s)" % (WikiParser.QUOTED_STRING)
            )

        # Simple (tag|tagged):link syntax
        yield (r'''(?P<qualifier>tag(?:ged)?):(?P<tag_expr>%s)''' % tag_expr,
               lambda f, ns, match: self._format_tagged(
                   f, match.group('qualifier'), match.group('tag_expr'),
                   '%s:%s' % (match.group('qualifier'),
                              match.group('tag_expr'))))

        # [(tag|tagged):link with label]
        yield (r'''\[tag(?:ged)?:'''
               r'''(?P<ltag_expr>%s)\s*(?P<tag_title>[^\]]+)?\]''' % tag_expr,
               lambda f, ns, match: self._format_tagged(f, 'tag',
                                    match.group('ltag_expr'),
                                    match.group('tag_title')))

    def get_link_resolvers(self):
        return [('tag', self._format_tagged),
                ('tagged', self._format_tagged)]

    def _format_tagged(self, formatter, ns, target, label, fullmatch=None):
        """Tag and tag query expression link formatter."""

        def unquote(text):
            """Strip all matching pairs of outer quotes from string."""
            while re.match(WikiParser.QUOTED_STRING, text):
                # Remove outer whitespace after stripped quotation too.
                text = text[1:-1].strip()
            return text

        label = label and unquote(label.strip()) or ''
        target = unquote(target.strip())

        query = target
        # Pop realms from query expression.
        all_realms = self.tag_system.get_taggable_realms(formatter.perm)
        realms = query_realms(target, all_realms)
        if realms:
            kwargs = dict((realm, 'on') for realm in realms)
            target = re.sub('(^|\W)realm:\S+(\W|$)', ' ', target).strip()
        else:
            kwargs = {}

        tag_res = Resource('tag', target)
        if 'TAGS_VIEW' not in formatter.perm(tag_res):
            return tag.span(label, class_='forbidden tags',
                            title=_("no permission to view tags"))

        context = formatter.context
        href = self.tag_system.get_resource_url(tag_res, context.href, kwargs)
        if all_realms and (
                target in self.tag_system.get_all_tags(formatter.req) or
                not iter_is_empty(self.tag_system.query(formatter.req,
                                                        query))):
            # At least one tag provider is available and tag exists or
            # tags query yields at least one match.
            if label:
                return tag.a(label, href=href)
            return render_resource_link(self.env, context, tag_res)

        return tag.a(label+'?', href=href, class_='missing tags',
                     rel='nofollow')
Exemple #20
0
class TagRPC(Component):
    """[extra] RPC interface for the tag system.

    Access Trac resource tagging system through methods provided by
    [https://trac-hacks.org/wiki/XmlRpcPlugin XmlRpcPlugin].
    """

    implements(IXMLRPCHandler)

    def __init__(self):
        self.tag_system = TagSystem(self.env)

    # IXMLRPCHandler methods

    def xmlrpc_namespace(self):
        return 'tags'

    def xmlrpc_methods(self):
        yield (None, ((list, str),), self.splitIntoTags)
        yield ('TAGS_VIEW', ((list,),), self.getTaggableRealms)
        yield ('TAGS_VIEW', ((dict,), (dict, list)), self.getAllTags)
        yield ('TAGS_VIEW', ((list, str, str),), self.getTags)
        yield ('TAGS_VIEW', ((list, str),), self.query)
        yield ('TAGS_MODIFY', ((list, str, str, list),
                               (list, str, str, list, str)), self.addTags)
        yield ('TAGS_MODIFY', ((list, str, str, list),
                               (list, str, str, list, str)), self.setTags)

    # Exported functions and TagSystem methods

    def addTags(self, req, realm, id, tags, comment=u''):
        """Add the supplied list of tags to a taggable Trac resource.

        Returns the updated list of resource tags.
        """
        resource = Resource(realm, id)
        # Replicate TagSystem.add_tags() method due to xmlrpclib issue.
        tags = set(tags)
        tags.update(self._get_tags(req, resource))
        self.tag_system.set_tags(req, resource, tags, comment)
        return self._get_tags(req, resource)

    def getAllTags(self, req, realms=[]):
        """Returns a dict of all tags as keys and occurrences as values.

        If a realm list is supplied, only tags from these taggable realms
        are shown.
        """
        # Type conversion needed for content transfer of Counter object.
        return dict(self.tag_system.get_all_tags(req, realms))

    def getTaggableRealms(self, req):
        """Returns the list of taggable Trac realms."""
        return list(self.tag_system.get_taggable_realms())

    def getTags(self, req, realm, id):
        """Returns the list of tags for a Trac resource."""
        return self._get_tags(req, Resource(realm, id))

    def query(self, req, query_str):
        """Returns a list of tagged Trac resources, whose tags match the
        supplied tag query expression.
        """
        # Type conversion needed for content transfer of Python set objects.
        return [(resource.realm, resource.id, list(tags))
                for resource, tags in self.tag_system.query(req, query_str)]

    def setTags(self, req, realm, id, tags, comment=u''):
        """Replace tags for a Trac resource with the supplied list of tags.

        Returns the updated list of resource tags.
        """
        resource = Resource(realm, id)
        self._get_tags(req, resource) # Trac resource exists?
        self.tag_system.set_tags(req, resource, tags, comment)
        return self._get_tags(req, resource)

    def splitIntoTags(self, req, tag_str):
        """Returns a list of tags from a string.

        Comma, whitespace and combinations of these characters are recognized
        as delimiter, that get stripped from the output.
        """
        return split_into_tags(tag_str)

    # Private methods

    def _get_tags(self, req, resource):
        if not resource_exists(self.env, resource):
            raise ResourceNotFound('Resource "%r" does not exists' % resource)
        # Workaround for ServiceException when calling TagSystem.get_tags().
        provider = [p for p in self.tag_system.tag_providers
                    if p.get_taggable_realm() == resource.realm][0]
        # Type conversion needed for content transfer of Python set objects.
        return list(provider.get_resource_tags(req, resource))
Exemple #21
0
class TicketTagProviderTestCase(unittest.TestCase):

    def setUp(self):
        self.env = EnvironmentStub(default_data=True,
                                   enable=['trac.*', 'tractags.*'])
        self.env.path = tempfile.mkdtemp()
        self.perms = PermissionSystem(self.env)

        self.db = self.env.get_db_cnx()
        setup = TagSetup(self.env)
        # Current tractags schema is setup with enabled component anyway.
        #   Revert these changes for getting default permissions inserted.
        self._revert_tractags_schema_init()
        setup.upgrade_environment(self.db)

        self.provider = TicketTagProvider(self.env)
        self.realm = 'ticket'
        self.tag_sys = TagSystem(self.env)
        self.tags = ['tag1']

        cursor = self.db.cursor()
        # Populate table with initial test data, not synced with tickets yet.
        cursor.execute("""
            INSERT INTO tags
                   (tagspace, name, tag)
            VALUES ('ticket', '1', 'deleted')""")
        self.realm = 'ticket'
        self._create_ticket(self.tags)

        self.req = Mock()
        # Mock an anonymous request.
        self.req.perm = PermissionCache(self.env)

    def tearDown(self):
        self.db.close()
        # Really close db connections.
        self.env.shutdown()
        shutil.rmtree(self.env.path)

    # Helpers

    def _create_ticket(self, tags):
        ticket = Ticket(self.env)
        ticket['keywords'] = u' '.join(sorted(map(to_unicode, tags)))
        ticket['summary'] = 'summary'
        ticket.insert()

    def _revert_tractags_schema_init(self):
        cursor = self.db.cursor()
        cursor.execute("DROP TABLE IF EXISTS tags")
        cursor.execute("DELETE FROM system WHERE name='tags_version'")
        cursor.execute("DELETE FROM permission WHERE action %s"
                       % self.db.like(), ('TAGS_%',))

    def _tags(self):
        tags = {}
        cursor = self.db.cursor()
        cursor.execute("SELECT name,tag FROM tags")
        for name, tag in cursor.fetchall():
            if name in tags:
                tags[name].add(tag)
            else:
                tags[name] = set([tag])
        return tags

    # Tests

    def test_get_tags(self):
        resource = Resource('ticket', 2)
        self.assertRaises(ResourceNotFound, self.provider.get_resource_tags,
                          self.req, resource)
        self._create_ticket(self.tags)
        self.assertEquals(
            [tag for tag in
             self.provider.get_resource_tags(self.req, resource)], self.tags)
        #ignore_closed_tickets

    def test_set_tags(self):
        resource = Resource('ticket', 1)
        self.tags = ['tag2']
        # Anonymous lacks required permissions.
        self.assertRaises(PermissionError, self.provider.set_resource_tags,
                          self.req, resource, self.tags)
        self.req.authname = 'user'
        self.req.perm = PermissionCache(self.env, username='******')
        # Shouldn't raise an error with appropriate permission.
        self.provider.set_resource_tags(self.req, resource, self.tags)
        self.assertEquals(self.tag_sys.get_all_tags(self.req, '').keys(),
                          self.tags)

    def test_remove_tags(self):
        resource = Resource('ticket', 1)
        # Anonymous lacks required permissions.
        self.assertRaises(PermissionError, self.provider.remove_resource_tags,
                          self.req, resource)
        self.req.authname = 'user'
        self.req.perm = PermissionCache(self.env, username='******')
        # Shouldn't raise an error with appropriate permission.
        self.provider.remove_resource_tags(self.req, resource, 'comment')
        tkt = Ticket(self.env, 1)
        self.assertEquals(tkt['keywords'], '')

    def test_describe_tagged_resource(self):
        resource = Resource('ticket', 1)
        self.assertEquals(
            self.provider.describe_tagged_resource(self.req, resource),
            'defect: summary')