| 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 | |
|---|
| 9 | import re |
|---|
| 10 | from trac.core import * |
|---|
| 11 | from tractags.api import TagSystem, ITagProvider |
|---|
| 12 | from trac.ticket.model import Ticket |
|---|
| 13 | from trac.util.text import to_unicode |
|---|
| 14 | from trac.util.compat import set, sorted |
|---|
| 15 | from trac.config import * |
|---|
| 16 | from trac.resource import Resource |
|---|
| 17 | |
|---|
| 18 | |
|---|
| 19 | class TicketTagProvider(Component): |
|---|
| 20 | """A tag provider using ticket fields as sources of tags. |
|---|
| 21 | |
|---|
| 22 | Currently does NOT support custom fields. |
|---|
| 23 | """ |
|---|
| 24 | implements(ITagProvider) |
|---|
| 25 | |
|---|
| 26 | fields = ListOption('tags', 'ticket_fields', 'keywords', |
|---|
| 27 | doc='List of ticket fields to expose as tags.') |
|---|
| 28 | |
|---|
| 29 | ignore_closed_tickets = BoolOption('tags', 'ignore_closed_tickets', True, |
|---|
| 30 | 'Do not collect tags from closed tickets.') |
|---|
| 31 | |
|---|
| 32 | # custom_fields = ListOption('tags', 'custom_ticket_fields', |
|---|
| 33 | # doc='List of custom ticket fields to expose as tags.') |
|---|
| 34 | |
|---|
| 35 | # ITagProvider methods |
|---|
| 36 | def get_taggable_realm(self): |
|---|
| 37 | return 'ticket' |
|---|
| 38 | |
|---|
| 39 | def get_tagged_resources(self, req, tags): |
|---|
| 40 | if 'TICKET_VIEW' not in req.perm: |
|---|
| 41 | return |
|---|
| 42 | |
|---|
| 43 | split_into_tags = TagSystem(self.env).split_into_tags |
|---|
| 44 | db = self.env.get_db_cnx() |
|---|
| 45 | cursor = db.cursor() |
|---|
| 46 | args = [] |
|---|
| 47 | ignore = '' |
|---|
| 48 | if self.ignore_closed_tickets: |
|---|
| 49 | ignore = " WHERE status != 'closed'" |
|---|
| 50 | sql = "SELECT * FROM (SELECT id, %s, %s AS fields FROM ticket%s) s" % ( |
|---|
| 51 | ','.join(self.fields), |
|---|
| 52 | '||'.join(["COALESCE(%s, '')" % f for f in self.fields]), |
|---|
| 53 | ignore) |
|---|
| 54 | constraints = [] |
|---|
| 55 | if tags: |
|---|
| 56 | constraints.append( |
|---|
| 57 | "(" + ' OR '.join(["fields LIKE %s" for t in tags]) + ")") |
|---|
| 58 | args += ['%' + t + '%' for t in tags] |
|---|
| 59 | else: |
|---|
| 60 | constraints.append("fields != ''") |
|---|
| 61 | |
|---|
| 62 | if constraints: |
|---|
| 63 | sql += " WHERE " + " AND ".join(constraints) |
|---|
| 64 | sql += " ORDER BY id" |
|---|
| 65 | self.env.log.debug(sql) |
|---|
| 66 | cursor.execute(sql, args) |
|---|
| 67 | for row in cursor: |
|---|
| 68 | id, ttags = row[0], ' '.join([f for f in row[1:-1] if f]) |
|---|
| 69 | perm = req.perm('ticket', id) |
|---|
| 70 | if 'TICKET_VIEW' not in perm or 'TAGS_VIEW' not in perm: |
|---|
| 71 | continue |
|---|
| 72 | ticket_tags = split_into_tags(ttags) |
|---|
| 73 | tags = set([to_unicode(x) for x in tags]) |
|---|
| 74 | if (not tags or ticket_tags.intersection(tags)): |
|---|
| 75 | yield Resource('ticket', id), ticket_tags |
|---|
| 76 | |
|---|
| 77 | |
|---|
| 78 | def get_resource_tags(self, req, resource): |
|---|
| 79 | if 'TICKET_VIEW' not in req.perm(resource): |
|---|
| 80 | return |
|---|
| 81 | ticket = Ticket(self.env, resource.id) |
|---|
| 82 | return self._ticket_tags(ticket) |
|---|
| 83 | |
|---|
| 84 | def set_resource_tags(self, req, resource, tags): |
|---|
| 85 | req.perm.require('TICKET_MODIFY', resource) |
|---|
| 86 | split_into_tags = TagSystem(self.env).split_into_tags |
|---|
| 87 | ticket = Ticket(self.env, resource.id) |
|---|
| 88 | all = self._ticket_tags(ticket) |
|---|
| 89 | keywords = split_into_tags(ticket['keywords']) |
|---|
| 90 | tags.difference_update(all.difference(keywords)) |
|---|
| 91 | ticket['keywords'] = u' '.join(sorted(map(to_unicode, tags))) |
|---|
| 92 | ticket.save_changes(req.username, u'') |
|---|
| 93 | |
|---|
| 94 | def remove_resource_tags(self, req, resource): |
|---|
| 95 | req.perm.require('TICKET_MODIFY', resource) |
|---|
| 96 | ticket = Ticket(self.env, resource.id) |
|---|
| 97 | ticket['keywords'] = u'' |
|---|
| 98 | ticket.save_changes(req.username, u'') |
|---|
| 99 | |
|---|
| 100 | # Private methods |
|---|
| 101 | def _ticket_tags(self, ticket): |
|---|
| 102 | split_into_tags = TagSystem(self.env).split_into_tags |
|---|
| 103 | return split_into_tags( |
|---|
| 104 | ' '.join(filter(None, [ticket[f] for f in self.fields]))) |
|---|