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

Last change on this file was 16054, checked in by Jun Omae, 7 years ago

0.9dev: fix unit tests with Trac 1.0 on Python 2.5

  • 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 __future__ import with_statement
11
12from datetime import datetime
13from itertools import groupby
14
15from trac.resource import Resource
16from trac.util.datefmt import to_datetime, to_utimestamp, utc
17from trac.util.text import to_unicode
18
19from tractags.util import split_into_tags
20
21
22# Public functions (not yet)
23
24
25# Utility functions
26
27def delete_tags(env, resource, tags=None, purge=False):
28    """Delete tags and tag changes for a Trac resource.
29
30    :param purge: if `True`, delete the change history.
31    """
32    args = [resource.realm, to_unicode(resource.id)]
33    sql = ''
34    if tags:
35        args += list(tags)
36        sql += " AND tags.tag IN (%s)" % ','.join(['%s'] * len(tags))
37    with env.db_transaction as db:
38        db("""DELETE FROM tags
39              WHERE tagspace=%%s AND name=%%s%s
40              """ % sql, args)
41        if purge:
42            # Call outside of another db transaction means resource destruction,
43            # so purge change records too.
44            db("""DELETE FROM tags_change
45                  WHERE tagspace=%s AND name=%s
46                  """, (resource.realm, to_unicode(resource.id)))
47
48
49def tag_changes(env, resource, start=None, stop=None):
50    """Return tag history for one or all tagged Trac resources."""
51    if resource:
52        # Resource changelog events query.
53        return [(to_datetime(row[0]), row[1], row[2], row[3])
54                for row in env.db_query("""
55                    SELECT time,author,oldtags,newtags FROM tags_change
56                    WHERE tagspace=%s AND name=%s
57                    ORDER BY time DESC
58                    """, (resource.realm, to_unicode(resource.id)))]
59    # Timeline events query.
60    return [(to_datetime(row[0]), row[1], row[2], row[3], row[4], row[5])
61            for row in env.db_query("""
62                SELECT time,author,tagspace,name,oldtags,newtags
63                FROM tags_change WHERE time>%s AND time<%s
64                """, (to_utimestamp(start), to_utimestamp(stop)))]
65
66
67def tag_frequency(env, realm, filter=None, db=None):
68    """Return tags and numbers of their occurrence."""
69    if filter:
70        sql = ''.join(" AND %s" % f for f in filter)
71    for row in env.db_query("""
72            SELECT tag,count(tag) FROM tags
73            WHERE tagspace=%%s%s GROUP BY tag
74            """ % (filter and sql or ''), (realm,)):
75        yield row[0], row[1]
76
77
78def tag_resource(env, resource, old_id=None, author='anonymous', tags=None,
79                 log=False, when=None):
80    """Save tags and tag changes for a Trac resource.
81
82    This function combines delete, reparent and set actions now, but it could
83    possibly be still a bit more efficient.
84    """
85    tags = tags or []
86    if when is None:
87        when = datetime.now(utc)
88    if isinstance(when, datetime):
89        when = to_utimestamp(when)
90
91    if old_id:
92        with env.db_transaction as db:
93            db("""
94               UPDATE tags SET name=%s
95               WHERE tagspace=%s AND name=%s
96               """, (to_unicode(resource.id), resource.realm,
97                     to_unicode(old_id)))
98            db("""
99               UPDATE tags_change SET name=%s
100               WHERE tagspace=%s AND name=%s
101               """, (to_unicode(resource.id), resource.realm,
102                     to_unicode(old_id)))
103    else:
104        # Calculate effective tag changes.
105        old_tags = set(resource_tags(env, resource))
106        tags = set(tags)
107        remove = old_tags - tags
108        with env.db_transaction as db:
109            if remove:
110                if tags:
111                    delete_tags(env, resource, remove)
112                else:
113                    # Delete all resource's tags - simplified transaction.
114                    delete_tags(env, resource)
115            add = tags - old_tags
116            if add:
117                db.executemany("""
118                    INSERT INTO tags (tagspace, name, tag)
119                    VALUES (%s,%s,%s)
120                    """, [(resource.realm, to_unicode(resource.id), tag)
121                          for tag in add])
122            if log:
123                db("""
124                  INSERT INTO tags_change
125                   (tagspace, name, time, author, oldtags, newtags)
126                  VALUES (%s,%s,%s,%s,%s,%s)
127                  """, (resource.realm, to_unicode(resource.id),
128                        when, author,
129                        u' '.join(sorted(map(to_unicode, old_tags))),
130                        u' '.join(sorted(map(to_unicode, tags))),))
131
132
133def tagged_resources(env, perm_check, perm, realm, tags=None, filter=None,
134                     db=None):
135    """Return Trac resources including their associated tags.
136
137    This is currently known to be a major performance hog.
138    """
139    args = [realm]
140    sql = """
141        SELECT DISTINCT name
142          FROM tags
143         WHERE tagspace=%s"""
144    if filter:
145        sql += ''.join([" AND %s" % f for f in filter])
146    if tags:
147        sql += " AND tags.tag IN (%s)" % ','.join(['%s' for tag in tags])
148        args += tags
149    sql += " ORDER by name"
150
151    # Inline permission check for efficiency.
152    resources = {}
153    for name, in env.db_query(sql, args):
154        resource = Resource(realm, name)
155        if perm_check(perm(resource), 'view'):
156            resources[resource.id] = resource
157    if not resources:
158        return
159
160    # DEVEL: Is this going to be excruciatingly slow?
161    #        The explicite resource ID list might even grow beyond some limit.
162    for name, tags in groupby(env.db_query("""
163            SELECT DISTINCT name, tag FROM tags
164            WHERE tagspace=%%s AND name IN (%s)
165            ORDER BY name
166            """ % ', '.join(['%s'] * len(resources)),
167            [realm] + resources.keys()), lambda row: row[0]):
168        resource = resources[name]
169        yield resource, set([tag[1] for tag in tags])
170
171
172def resource_tags(env, resource, when=None):
173    """Return all tags for a Trac resource by realm and ID."""
174    id = to_unicode(resource.id)
175    if when is None:
176        for tag, in env.db_query("""
177                SELECT tag FROM tags
178                WHERE tagspace=%s AND name=%s
179                """, (resource.realm, id)):
180            yield tag
181    else:
182        if isinstance(when, datetime):
183            when = to_utimestamp(when)
184        for newtags, in env.db_query("""
185                SELECT newtags FROM tags_change
186                WHERE tagspace=%s AND name=%s AND time<=%s
187                ORDER BY time DESC LIMIT 1
188                """, (resource.realm, id, when)):
189            for tag in split_into_tags(newtags):
190                yield tag
Note: See TracBrowser for help on using the repository browser.