source: tagsplugin/trunk/tractags/model.py

Last change on this file was 18144, checked in by Cinc-th, 2 years ago

TagsPlugin: fixed a string/bytes issue. Added more classifiers to setup.py.

Closes #13994

  • Property svn:eol-style set to native
File size: 6.7 KB
RevLine 
[12069]1#!/usr/bin/env python
2# -*- coding: utf-8 -*-
3#
[13730]4# Copyright (C) 2014 Jun Omae <jun66j5@gmail.com>
[13392]5# Copyright (C) 2012,2013 Steffen Hoffmann <hoff.st@web.de>
[12069]6#
7# This software is licensed as described in the file COPYING, which
8# you should have received as part of this distribution.
[12078]9
[13428]10from datetime import datetime
[14950]11from itertools import groupby
[13428]12
[12078]13from trac.resource import Resource
[14950]14from trac.util.datefmt import to_datetime, to_utimestamp, utc
[13428]15from trac.util.text import to_unicode
[12078]16
[13721]17from tractags.util import split_into_tags
[12078]18
[13428]19
[12078]20# Public functions (not yet)
21
22
23# Utility functions
24
[15559]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    """
[13430]30    args = [resource.realm, to_unicode(resource.id)]
31    sql = ''
32    if tags:
33        args += list(tags)
[15559]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)))
[13430]45
[15558]46
[13854]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])
[15559]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)))]
[13854]57    # Timeline events query.
58    return [(to_datetime(row[0]), row[1], row[2], row[3], row[4], row[5])
[15559]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)))]
[13428]63
[15558]64
[13427]65def tag_frequency(env, realm, filter=None, db=None):
[13392]66    """Return tags and numbers of their occurrence."""
[13427]67    if filter:
[15559]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]
[13392]74
[15558]75
[15559]76def tag_resource(env, resource, old_id=None, author='anonymous', tags=None,
[13724]77                 log=False, when=None):
[13428]78    """Save tags and tag changes for a Trac resource.
[12078]79
80    This function combines delete, reparent and set actions now, but it could
81    possibly be still a bit more efficient.
82    """
[15559]83    tags = tags or []
[13724]84    if when is None:
85        when = datetime.now(utc)
86    if isinstance(when, datetime):
87        when = to_utimestamp(when)
[12078]88
[13429]89    if old_id:
[15559]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)))
[12078]101    else:
[13430]102        # Calculate effective tag changes.
[15559]103        old_tags = set(resource_tags(env, resource))
[13430]104        tags = set(tags)
105        remove = old_tags - tags
[15559]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))),))
[12078]129
[15558]130
[12390]131def tagged_resources(env, perm_check, perm, realm, tags=None, filter=None,
132                     db=None):
[12078]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"""
[12390]142    if filter:
143        sql += ''.join([" AND %s" % f for f in filter])
[12078]144    if tags:
[12390]145        sql += " AND tags.tag IN (%s)" % ','.join(['%s' for tag in tags])
[12078]146        args += tags
147    sql += " ORDER by name"
148
[15559]149    # Inline permission check for efficiency.
[12078]150    resources = {}
[15559]151    for name, in env.db_query(sql, args):
[12078]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.
[15559]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)),
[18144]165            [realm] + list(resources.keys())), lambda row: row[0]):
[12078]166        resource = resources[name]
167        yield resource, set([tag[1] for tag in tags])
168
[15558]169
[15559]170def resource_tags(env, resource, when=None):
[12078]171    """Return all tags for a Trac resource by realm and ID."""
[13721]172    id = to_unicode(resource.id)
173    if when is None:
[15559]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
[13721]179    else:
180        if isinstance(when, datetime):
181            when = to_utimestamp(when)
[15559]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):
[13721]188                yield tag
Note: See TracBrowser for help on using the repository browser.