| 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[\w.@-]+''') |
|---|
| 9 | |
|---|
| 10 | # These builtin fields can be used as tag sources |
|---|
| 11 | _builtin_fields = ('component', 'severity', 'priority', 'owner', |
|---|
| 12 | 'reporter', 'cc', 'version', 'milestone', 'status', |
|---|
| 13 | 'resolution', 'summary', 'description', 'keywords') |
|---|
| 14 | |
|---|
| 15 | def __init__(self, env): |
|---|
| 16 | self.env = env |
|---|
| 17 | self.fields = [x.strip() for x in self.env.config.get('tags.ticket', |
|---|
| 18 | 'fields', 'keywords').split(',') if x.strip() |
|---|
| 19 | and x.strip() in self._builtin_fields] |
|---|
| 20 | self.custom_fields = [x.strip() for x in self.env.config.get('tags.ticket', |
|---|
| 21 | 'custom_fields', '').split(',') if x.strip()] |
|---|
| 22 | |
|---|
| 23 | def _ticket_tags(self, ticket): |
|---|
| 24 | return set(self._keyword_split.findall(' '.join([ticket[f] for f in self.fields]))) |
|---|
| 25 | |
|---|
| 26 | def walk_tagged_names(self, names, tags, predicate): |
|---|
| 27 | db = self.env.get_db_cnx() |
|---|
| 28 | cursor = db.cursor() |
|---|
| 29 | tags = set(tags) |
|---|
| 30 | names = set(names) |
|---|
| 31 | args = [] |
|---|
| 32 | sql = "SELECT id, %s, %s AS allfields FROM ticket" % (','.join(self.fields), |
|---|
| 33 | '||'.join(["COALESCE(%s, '')" % f for f in self.fields])) |
|---|
| 34 | constraints = [] |
|---|
| 35 | if names: |
|---|
| 36 | constraints.append("id IN (" + ', '.join(['%s' for n in names]) + ")") |
|---|
| 37 | args += [unicode(n) for n in names] |
|---|
| 38 | if tags: |
|---|
| 39 | constraints.append("(" + ' OR '.join(["allfields LIKE %s" for t in tags]) + ")") |
|---|
| 40 | args += ['%' + t + '%' for t in tags] |
|---|
| 41 | if constraints: |
|---|
| 42 | sql += " WHERE " + " AND ".join(constraints) |
|---|
| 43 | sql += " ORDER BY id" |
|---|
| 44 | cursor.execute(sql, args) |
|---|
| 45 | for row in cursor: |
|---|
| 46 | id, ttags = row[0], ' '.join([f for f in row[1:-1] if f]) |
|---|
| 47 | ticket_tags = set(self._keyword_split.findall(ttags)) |
|---|
| 48 | if not tags or ticket_tags.intersection(tags): |
|---|
| 49 | if predicate(id, ticket_tags): |
|---|
| 50 | yield (id, ticket_tags) |
|---|
| 51 | |
|---|
| 52 | def get_name_tags(self, name): |
|---|
| 53 | db = self.env.get_db_cnx() |
|---|
| 54 | cursor = db.cursor() |
|---|
| 55 | cursor.execute("SELECT keywords FROM ticket WHERE id=%s AND keywords IS NOT NULL AND keywords != ''", (name,)) |
|---|
| 56 | for row in cursor: |
|---|
| 57 | return set(self._keyword_split.findall(row[0])) |
|---|
| 58 | |
|---|
| 59 | def add_tags(self, req, name, tags): |
|---|
| 60 | ticket = model.Ticket(self.env, name) |
|---|
| 61 | ticket_tags = self._ticket_tags(ticket) |
|---|
| 62 | ticket_tags.update(tags) |
|---|
| 63 | ticket['keywords'] = ' '.join(sorted(ticket_tags)) |
|---|
| 64 | ticket.save_changes(req.authname, None) |
|---|
| 65 | |
|---|
| 66 | def replace_tags(self, req, name, tags): |
|---|
| 67 | ticket = model.Ticket(self.env, name) |
|---|
| 68 | ticket['keywords'] = ' '.join(sorted(tags)) |
|---|
| 69 | ticket.save_changes(req.authname, None) |
|---|
| 70 | |
|---|
| 71 | def remove_tags(self, req, name, tags): |
|---|
| 72 | ticket = model.Ticket(self.env, name) |
|---|
| 73 | ticket_tags = self._ticket_tags(ticket) |
|---|
| 74 | ticket_tags.symmetric_difference_update(tags) |
|---|
| 75 | ticket['keywords'] = ' '.join(sorted(ticket_tags)) |
|---|
| 76 | ticket.save_changes(req.authname, None) |
|---|
| 77 | |
|---|
| 78 | def remove_all_tags(self, req, name): |
|---|
| 79 | ticket = model.Ticket(self.env, name) |
|---|
| 80 | ticket['keywords'] = '' |
|---|
| 81 | ticket.save_changes(req.authname, None) |
|---|
| 82 | |
|---|
| 83 | def name_details(self, name): |
|---|
| 84 | ticket = model.Ticket(self.env, name) |
|---|
| 85 | href = self.env.href.ticket(name) |
|---|
| 86 | from trac.wiki.formatter import wiki_to_oneliner |
|---|
| 87 | summary = ticket['summary'] or u'' |
|---|
| 88 | return (href, wiki_to_oneliner('#%s' % name, self.env), |
|---|
| 89 | ticket.exists and summary) |
|---|
| 90 | |
|---|
| 91 | class TicketTags(Component): |
|---|
| 92 | """ Export a ticket tag interface, using the "keywords" field of tickets. """ |
|---|
| 93 | implements(ITaggingSystemProvider) |
|---|
| 94 | |
|---|
| 95 | def get_tagspaces_provided(self): |
|---|
| 96 | yield 'ticket' |
|---|
| 97 | |
|---|
| 98 | def get_tagging_system(self, tagspace): |
|---|
| 99 | return TicketTaggingSystem(self.env) |
|---|
| 100 | |
|---|