source: tagsplugin/tags/0.7/tractags/model.py

Last change on this file was 13730, checked in by Steffen Hoffmann, 10 years ago

TagsPlugin: Reworked wiki link resolver code, refs #3624, #5523 and #9630.

Newly added permission and resource existance awareness mimics wiki link
behavior for tickets and wiki pages.

Unit tests have been refactored too, partially with more reasonable test cases.

  • Property svn:eol-style set to native
File size: 6.7 KB
Line 
1#!/usr/bin/env python
2# -*- coding: utf-8 -*-
3#
4# Copyright (C) 2014 Jun Omae <jun66j5@gmail.com>
5# Copyright (C) 2012,2013 Steffen Hoffmann <hoff.st@web.de>
6#
7# This software is licensed as described in the file COPYING, which
8# you should have received as part of this distribution.
9
10from datetime import datetime
11
12from trac.resource import Resource
13from trac.util.compat import groupby, set
14from trac.util.datefmt import utc
15from trac.util.text import to_unicode
16
17from tractags.compat import to_datetime, to_utimestamp
18from tractags.util import split_into_tags
19
20
21# Public functions (not yet)
22
23
24# Utility functions
25
26def delete_tags(env, resource, tags=None, db=None):
27    """Delete tags and tag changes for a Trac resource."""
28    do_commit = db is None or False
29    db = _get_db(env, db)
30    cursor = db.cursor()
31    args = [resource.realm, to_unicode(resource.id)]
32    sql = ''
33    if tags:
34        args += list(tags)
35        sql += " AND tags.tag IN (%s)" % ','.join(['%s' for tag in tags])
36    cursor.execute("""
37        DELETE FROM tags
38         WHERE tagspace=%%s
39           AND name=%%s%s
40    """ % sql, args)
41    if do_commit:
42        # Call outside of another db transaction means resource destruction,
43        # so purge change records too.
44        cursor.execute("""
45            DELETE FROM tags_change
46             WHERE tagspace=%s
47               AND name=%s
48        """, (resource.realm, to_unicode(resource.id)))
49        db.commit()
50
51def tag_changes(env, resource):
52    """Return tag history for a Trac resource."""
53    db = _get_db(env)
54    cursor = db.cursor()
55    cursor.execute("""
56        SELECT time,author,oldtags,newtags
57          FROM tags_change
58         WHERE tagspace=%s
59           AND name=%s
60         ORDER BY time DESC
61    """, (resource.realm, to_unicode(resource.id)))
62    return [(to_datetime(row[0]), row[1], row[2], row[3])
63            for row in cursor]
64
65def tag_frequency(env, realm, filter=None, db=None):
66    """Return tags and numbers of their occurrence."""
67    if filter:
68        sql = ''.join([" AND %s" % f for f in filter])
69    db = _get_db(env, db)
70    cursor = db.cursor()
71    cursor.execute("""
72        SELECT tag,count(tag)
73          FROM tags
74         WHERE tagspace=%%s%s
75         GROUP BY tag
76    """ % (filter and sql or ''), (realm,))
77    for row in cursor:
78        yield (row[0], row[1])
79
80def tag_resource(env, resource, old_id=None, author='anonymous', tags=[],
81                 log=False, when=None):
82    """Save tags and tag changes for a Trac resource.
83
84    This function combines delete, reparent and set actions now, but it could
85    possibly be still a bit more efficient.
86    """
87    db = _get_db(env)
88    cursor = db.cursor()
89    if when is None:
90        when = datetime.now(utc)
91    if isinstance(when, datetime):
92        when = to_utimestamp(when)
93
94    if old_id:
95        cursor.execute("""
96            UPDATE tags
97               SET name=%s
98             WHERE tagspace=%s
99               AND name=%s
100        """, (to_unicode(resource.id), resource.realm, to_unicode(old_id)))
101        cursor.execute("""
102            UPDATE tags_change
103               SET name=%s
104             WHERE tagspace=%s
105               AND name=%s
106        """, (to_unicode(resource.id), resource.realm, to_unicode(old_id)))
107    else:
108        # Calculate effective tag changes.
109        old_tags = set(resource_tags(env, resource, db=db))
110        tags = set(tags)
111        remove = old_tags - tags
112        if remove:
113            if tags:
114                delete_tags(env, resource, remove, db=db)
115            else:
116                # Delete all resource's tags - simplified transaction.
117                delete_tags(env, resource, db=db)
118        add = tags - old_tags
119        if add:
120            cursor.executemany("""
121                INSERT INTO tags
122                       (tagspace, name, tag)
123                VALUES (%s,%s,%s)
124            """, [(resource.realm, to_unicode(resource.id), tag)
125                  for tag in add])
126        if log:
127            cursor.execute("""
128                INSERT INTO tags_change
129                       (tagspace, name, time, author, oldtags, newtags)
130                VALUES (%s,%s,%s,%s,%s,%s)
131            """, (resource.realm, to_unicode(resource.id), when, author,
132                  u' '.join(sorted(map(to_unicode, old_tags))),
133                  u' '.join(sorted(map(to_unicode, tags))),))
134    db.commit()
135
136def tagged_resources(env, perm_check, perm, realm, tags=None, filter=None,
137                     db=None):
138    """Return Trac resources including their associated tags.
139
140    This is currently known to be a major performance hog.
141    """
142    db = _get_db(env, db)
143    cursor = db.cursor()
144
145    args = [realm]
146    sql = """
147        SELECT DISTINCT name
148          FROM tags
149         WHERE tagspace=%s"""
150    if filter:
151        sql += ''.join([" AND %s" % f for f in filter])
152    if tags:
153        sql += " AND tags.tag IN (%s)" % ','.join(['%s' for tag in tags])
154        args += tags
155    sql += " ORDER by name"
156    cursor.execute(sql, args)
157
158    # Inlined permission check for efficiency.
159    resources = {}
160    for name, in cursor:
161        resource = Resource(realm, name)
162        if perm_check(perm(resource), 'view'):
163            resources[resource.id] = resource
164    if not resources:
165        return
166
167    args = [realm] + resources.keys()
168    # DEVEL: Is this going to be excruciatingly slow?
169    #        The explicite resource ID list might even grow beyond some limit.
170    sql = """
171        SELECT DISTINCT name, tag
172          FROM tags
173         WHERE tagspace=%%s
174           AND name IN (%s)
175         ORDER BY name
176    """ % ', '.join(['%s' for resource in resources])
177    cursor.execute(sql, args)
178
179    for name, tags in groupby(cursor, lambda row: row[0]):
180        resource = resources[name]
181        yield resource, set([tag[1] for tag in tags])
182
183def resource_tags(env, resource, db=None, when=None):
184    """Return all tags for a Trac resource by realm and ID."""
185    db = _get_db(env, db)
186    cursor = db.cursor()
187    id = to_unicode(resource.id)
188    if when is None:
189        cursor.execute("""
190            SELECT tag
191              FROM tags
192             WHERE tagspace=%s
193               AND name=%s
194        """, (resource.realm, id))
195        for row in cursor:
196            yield row[0]
197    else:
198        if isinstance(when, datetime):
199            when = to_utimestamp(when)
200        cursor.execute("SELECT newtags FROM tags_change "
201                       "WHERE tagspace=%s AND name=%s AND time<=%s "
202                       "ORDER BY time DESC LIMIT 1",
203                       (resource.realm, id, when))
204        row = cursor.fetchone()
205        if row and row[0]:
206            for tag in split_into_tags(row[0]):
207                yield tag
208
209# Internal functions
210
211def _get_db(env, db=None):
212    return db or env.get_db_cnx()
Note: See TracBrowser for help on using the repository browser.