| 1 | from trac.core import * |
|---|
| 2 | from tractags.api import ITaggingSystemProvider, TaggingSystem, sorted, set |
|---|
| 3 | from trac.ticket.query import Query |
|---|
| 4 | from trac.ticket import model |
|---|
| 5 | import re |
|---|
| 6 | |
|---|
| 7 | class TicketTaggingSystem(TaggingSystem): |
|---|
| 8 | _keyword_split = re.compile(r'''[\w.-]+''') |
|---|
| 9 | |
|---|
| 10 | def __init__(self, env): |
|---|
| 11 | self.env = env |
|---|
| 12 | |
|---|
| 13 | def _ticket_tags(self, ticket): |
|---|
| 14 | return set(self._keyword_split.findall(ticket['keywords'])) |
|---|
| 15 | |
|---|
| 16 | def walk_tagged_names(self, names, tags, predicate): |
|---|
| 17 | db = self.env.get_db_cnx() |
|---|
| 18 | cursor = db.cursor() |
|---|
| 19 | tags = set(tags) |
|---|
| 20 | names = set(names) |
|---|
| 21 | args = [] |
|---|
| 22 | sql = "SELECT id, keywords FROM ticket WHERE keywords IS NOT NULL AND keywords != ''" |
|---|
| 23 | if names: |
|---|
| 24 | sql += " AND id IN (" + ', '.join(['%s' for n in names]) + ")" |
|---|
| 25 | args += [unicode(n) for n in names] |
|---|
| 26 | if tags: |
|---|
| 27 | sql += " AND (" + ' OR '.join(["keywords LIKE %s" for t in tags]) + ")" |
|---|
| 28 | args += ['%' + t + '%' for t in tags] |
|---|
| 29 | sql += " ORDER BY id" |
|---|
| 30 | cursor.execute(sql, args) |
|---|
| 31 | for id, ttags in cursor: |
|---|
| 32 | ticket_tags = set(self._keyword_split.findall(ttags)) |
|---|
| 33 | if not tags or ticket_tags.intersection(tags): |
|---|
| 34 | if predicate(id, ticket_tags): |
|---|
| 35 | yield (id, ticket_tags) |
|---|
| 36 | |
|---|
| 37 | def get_name_tags(self, name): |
|---|
| 38 | db = self.env.get_db_cnx() |
|---|
| 39 | cursor = db.cursor() |
|---|
| 40 | cursor.execute("SELECT keywords FROM ticket WHERE id=%s AND keywords IS NOT NULL AND keywords != ''", (name,)) |
|---|
| 41 | for row in cursor: |
|---|
| 42 | return set(self._keyword_split.findall(row[0])) |
|---|
| 43 | |
|---|
| 44 | def add_tags(self, req, name, tags): |
|---|
| 45 | ticket = model.Ticket(self.env, name) |
|---|
| 46 | ticket_tags = self._ticket_tags(ticket) |
|---|
| 47 | ticket_tags.update(tags) |
|---|
| 48 | ticket['keywords'] = ' '.join(sorted(ticket_tags)) |
|---|
| 49 | ticket.save_changes(req.authname, None) |
|---|
| 50 | |
|---|
| 51 | def replace_tags(self, req, name, tags): |
|---|
| 52 | ticket = model.Ticket(self.env, name) |
|---|
| 53 | ticket['keywords'] = ' '.join(sorted(tags)) |
|---|
| 54 | ticket.save_changes(req.authname, None) |
|---|
| 55 | |
|---|
| 56 | def remove_tags(self, req, name, tags): |
|---|
| 57 | ticket = model.Ticket(self.env, name) |
|---|
| 58 | ticket_tags = self._ticket_tags(ticket) |
|---|
| 59 | ticket_tags.symmetric_difference_update(tags) |
|---|
| 60 | ticket['keywords'] = ' '.join(sorted(ticket_tags)) |
|---|
| 61 | ticket.save_changes(req.authname, None) |
|---|
| 62 | |
|---|
| 63 | def remove_all_tags(self, req, name): |
|---|
| 64 | ticket = model.Ticket(self.env, name) |
|---|
| 65 | ticket['keywords'] = '' |
|---|
| 66 | ticket.save_changes(req.authname, None) |
|---|
| 67 | |
|---|
| 68 | def name_details(self, name): |
|---|
| 69 | ticket = model.Ticket(self.env, name) |
|---|
| 70 | href = self.env.href.ticket(name) |
|---|
| 71 | from trac.wiki.formatter import wiki_to_oneliner |
|---|
| 72 | return (href, wiki_to_oneliner('#%s' % name, self.env), |
|---|
| 73 | ticket.exists and ticket['summary'] or '') |
|---|
| 74 | |
|---|
| 75 | class TicketTags(Component): |
|---|
| 76 | """ Export a ticket tag interface, using the "keywords" field of tickets. """ |
|---|
| 77 | implements(ITaggingSystemProvider) |
|---|
| 78 | |
|---|
| 79 | def get_tagspaces_provided(self): |
|---|
| 80 | yield 'ticket' |
|---|
| 81 | |
|---|
| 82 | def get_tagging_system(self, tagspace): |
|---|
| 83 | return TicketTaggingSystem(self.env) |
|---|
| 84 | |
|---|