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

Last change on this file was 16943, checked in by Ryan J Ollos, 6 years ago

TracTags 0.10dev: Make compatible with Trac 1.3.3dev

Fixes #13316.

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