source: tagsplugin/tags/0.6/tractags/api.py

Last change on this file was 7383, checked in by Michael Renzmann, 14 years ago

Revert r7372 up to and including r7377. These should have been applied to
trunk rather than to a tagged version. Sorry for any confusion this may
have caused.

File size: 9.5 KB
Line 
1# -*- coding: utf-8 -*-
2#
3# Copyright (C) 2006 Alec Thomas <alec@swapoff.org>
4#
5# This software is licensed as described in the file COPYING, which
6# you should have received as part of this distribution.
7#
8
9import re
10from trac.core import *
11from trac.resource import Resource
12from tractags.query import *
13from trac.perm import IPermissionRequestor, PermissionError
14from trac.web.chrome import add_warning
15from trac.wiki.model import WikiPage
16from trac.util.text import to_unicode
17from trac.util.compat import set, groupby
18from trac.resource import IResourceManager, get_resource_url, \
19    get_resource_description
20from genshi import Markup
21from genshi.builder import tag as tag_
22
23
24class InvalidTagRealm(TracError):
25    pass
26
27
28class ITagProvider(Interface):
29    def get_taggable_realm():
30        """Return the realm this provider supports tags on."""
31
32    def get_tagged_resources(req, tags=None):
33        """Return a sequence of resources and *all* their tags.
34
35        :param tags: If provided, return only those resources with the given
36                     tags.
37
38        :rtype: Sequence of (resource, tags) tuples.
39        """
40
41    def get_resource_tags(req, resource):
42        """Get tags for a Resource object."""
43
44    def set_resource_tags(req, resource, tags):
45        """Set tags for a resource."""
46
47    def remove_resource_tags(req, resource):
48        """Remove all tags from a resource."""
49
50
51class DefaultTagProvider(Component):
52    """An abstract base tag provider that stores tags in the database.
53
54    Use this if you need storage for your tags. Simply set the class variable
55    `realm` and optionally `check_permission()`.
56
57    See tractags.wiki.WikiTagProvider for an example.
58    """
59
60    implements(ITagProvider)
61
62    abstract = True
63
64    # Resource realm this provider manages tags for. Set this.
65    realm = None
66
67    # Public methods
68    def check_permission(self, perm, operation):
69        """Delegate function for checking permissions.
70
71        Override to implement custom permissions. Defaults to TAGS_VIEW and
72        TAGS_MODIFY.
73        """
74        map = {'view': 'TAGS_VIEW', 'modify': 'TAGS_MODIFY'}
75        return map[operation] in perm('tag')
76
77    # ITagProvider methods
78    def get_taggable_realm(self):
79        return self.realm
80
81    def get_tagged_resources(self, req, tags):
82        if not self.check_permission(req.perm, 'view'):
83            return
84        db = self.env.get_db_cnx()
85        cursor = db.cursor()
86        args = [self.realm]
87        sql = 'SELECT DISTINCT name FROM tags WHERE tagspace=%s'
88        if tags:
89            sql += ' AND tags.tag IN (%s)' % ', '.join(['%s' for t in tags])
90            args += tags
91        sql += ' ORDER by name'
92        cursor.execute(sql, args)
93
94        resources = {}
95        for name, in cursor:
96            resource = Resource(self.realm, name)
97            if self.check_permission(req.perm(resource), 'view'):
98                resources[resource.id] = resource
99
100        if not resources:
101          return
102
103        args = [self.realm] + list(resources)
104        # XXX Is this going to be excruciatingly slow?
105        sql = 'SELECT DISTINCT name, tag FROM tags WHERE tagspace=%%s AND ' \
106              'name IN (%s) ORDER BY name' % ', '.join(['%s' for _ in resources])
107        cursor.execute(sql, args)
108
109        for name, tags in groupby(cursor, lambda row: row[0]):
110            resource = resources[name]
111            yield resource, set([tag[1] for tag in tags])
112
113    def get_resource_tags(self, req, resource):
114        assert resource.realm == self.realm
115        if not self.check_permission(req.perm(resource), 'view'):
116            return
117        db = self.env.get_db_cnx()
118        cursor = db.cursor()
119        cursor.execute('SELECT tag FROM tags WHERE tagspace=%s AND name=%s',
120                       (self.realm, resource.id))
121        for row in cursor:
122            yield row[0]
123
124    def set_resource_tags(self, req, resource, tags):
125        assert resource.realm == self.realm
126        if not self.check_permission(req.perm(resource), 'modify'):
127            raise PermissionError(resource=resource, env=self.env)
128        db = self.env.get_db_cnx()
129        try:
130            cursor = db.cursor()
131            cursor.execute('DELETE FROM tags WHERE tagspace=%s AND name=%s',
132                           (self.realm, resource.id))
133            for tag in tags:
134                cursor.execute('INSERT INTO tags (tagspace, name, tag) '
135                               'VALUES (%s, %s, %s)',
136                               (self.realm, resource.id, tag))
137            db.commit()
138        except:
139            db.rollback()
140            raise
141
142    def remove_resource_tags(self, req, resource):
143        assert resource.realm == self.realm
144        if not self.check_permission(req.perm(resource), 'modify'):
145            raise PermissionError(resource=resource, env=self.env)
146        db = self.env.get_db_cnx()
147        try:
148            cursor = db.cursor()
149            cursor.execute('DELETE FROM tags WHERE tagspace=%s AND name=%s',
150                           (self.realm, resource.id))
151            db.commit()
152        except:
153            db.rollback()
154            raise
155
156
157class TagSystem(Component):
158    """Tagging system for Trac."""
159
160    implements(IPermissionRequestor, IResourceManager)
161
162    tag_providers = ExtensionPoint(ITagProvider)
163
164    # Internal variables
165    _tag_split = re.compile('[,\s]+')
166    _realm_provider_map = None
167
168    # Public methods
169    def query(self, req, query='', attribute_handlers=None):
170        """Return a sequence of (resource, tags) tuples matching a query.
171
172        Query syntax is described in tractags.query.
173
174        :param attribute_handlers: Register additional query attribute
175                                   handlers. See Query documentation for more
176                                   information.
177        """
178        def realm_handler(_, node, context):
179            return query.match(node, [context.realm])
180
181        all_attribute_handlers = {
182            'realm': realm_handler,
183            }
184        all_attribute_handlers.update(attribute_handlers or {})
185        if re.search(r'(expression|tagspace|tagspaces|operation|showheadings'
186                     '|expression)=', query):
187            message = Markup('You seem to be using an old Tag query. '
188                             'Try using the <a href="%s">new syntax</a> in your '
189                             '<strong>ListTagged</strong> macro.',
190                             req.href('tags'))
191            add_warning(req, message)
192        query = Query(query, attribute_handlers=all_attribute_handlers)
193
194        query_tags = set(query.terms())
195        for provider in self.tag_providers:
196            for resource, tags in provider.get_tagged_resources(req, query_tags):
197                if query(tags, context=resource):
198                    yield resource, tags
199
200    def get_tags(self, req, resource):
201        """Get tags for resource."""
202        return set(self._get_provider(resource.realm) \
203                   .get_resource_tags(req, resource))
204
205    def set_tags(self, req, resource, tags):
206        """Set tags on a resource.
207
208        Existing tags are replaced.
209        """
210        return self._get_provider(resource.realm) \
211            .set_resource_tags(req, resource, set(tags))
212
213    def add_tags(self, req, resource, tags):
214        """Add to existing tags on a resource."""
215        tags = set(tags)
216        tags.update(self.get_tags(req, resource))
217        self.set_tags(req, resource, tags)
218
219    def delete_tags(self, req, resource, tags=None):
220        """Delete tags on a resource.
221
222        If tags is None, remove all tags on the resource.
223        """
224        provider = self._get_provider(resource.realm)
225        if tags is None:
226            provider.remove_resource_tags(req, resource)
227        else:
228            tags = set(tags)
229            current_tags = provider.get_resource_tags(req, resource)
230            current_tags.remove(tags)
231            provider.set_resource_tags(req, resource, tags)
232
233    def split_into_tags(self, text):
234        """Split plain text into tags."""
235        return set([t.strip() for t in self._tag_split.split(text) if t.strip()])
236
237    # IPermissionRequestor methods
238    def get_permission_actions(self):
239        return ['TAGS_VIEW', 'TAGS_MODIFY']
240
241    # IResourceManager methods
242    def get_resource_realms(self):
243        yield 'tag'
244
245    def get_resource_url(self, resource, href, **kwargs):
246        page = WikiPage(self.env, resource.id)
247        if page.exists:
248            return get_resource_url(self.env, page.resource, href, **kwargs)
249        return href("tags/'%s'" % unicode(resource.id).replace("'", "\\'"))
250
251    def get_resource_description(self, resource, format='default', context=None,
252                                 **kwargs):
253        page = WikiPage(self.env, resource.id)
254        if page.exists:
255            return get_resource_description(self.env, page.resource, format,
256                                            **kwargs)
257        rid = to_unicode(resource.id)
258        if format in ('compact', 'default'):
259            return rid
260        else:
261            return u'tag:%s' % rid
262
263    # Internal methods
264    def _populate_provider_map(self):
265        if self._realm_provider_map is None:
266            self._realm_provider_map = {}
267            for provider in self.tag_providers:
268                self._realm_provider_map[provider.get_taggable_realm()] = \
269                    provider
270
271    def _get_provider(self, realm):
272        self._populate_provider_map()
273        try:
274            return self._realm_provider_map[realm]
275        except KeyError:
276            raise InvalidTagRealm('Tags are not supported on the "%s" realm' % realm)
Note: See TracBrowser for help on using the repository browser.