source: xmlrpcplugin/0.10/tracrpc/ticket.py

Last change on this file was 1950, checked in by Alec Thomas, 17 years ago

XmlRpcPlugin:

Send ticket notifications when. Thanks to stp for the patch. Closes #1069.

File size: 11.0 KB
Line 
1from trac.attachment import Attachment
2from trac.core import *
3from tracrpc.api import IXMLRPCHandler, expose_rpc
4from tracrpc.util import to_timestamp
5import trac.ticket.model as model
6import trac.ticket.query as query
7from trac.ticket.api import TicketSystem
8from trac.ticket.notification import TicketNotifyEmail
9
10import time
11import pydoc
12import xmlrpclib
13from StringIO import StringIO
14
15class TicketRPC(Component):
16    """ An interface to Trac's ticketing system. """
17
18    implements(IXMLRPCHandler)
19
20    # IXMLRPCHandler methods
21    def xmlrpc_namespace(self):
22        return 'ticket'
23
24    def xmlrpc_methods(self):
25        yield ('TICKET_VIEW', ((list,), (list, str)), self.query)
26        yield ('TICKET_VIEW', ((list, xmlrpclib.DateTime),), self.getRecentChanges)
27        yield ('TICKET_VIEW', ((list, int),), self.getAvailableActions)
28        yield ('TICKET_VIEW', ((list, int),), self.get)
29        yield ('TICKET_CREATE', ((int, str, str), (int, str, str, dict), (int, str, str, dict, bool)), self.create)
30        yield ('TICKET_APPEND', ((list, int, str), (list, int, str, dict), (list, int, str, dict, bool)), self.update)
31        yield ('TICKET_ADMIN', ((None, int),), self.delete)
32        yield ('TICKET_VIEW', ((dict, int), (dict, int, int)), self.changeLog)
33        yield ('TICKET_VIEW', ((list, int),), self.listAttachments)
34        yield ('TICKET_VIEW', ((xmlrpclib.Binary, int, str),), self.getAttachment)
35        yield ('TICKET_APPEND',
36               ((str, int, str, str, xmlrpclib.Binary, bool),
37                (str, int, str, str, xmlrpclib.Binary)),
38               self.putAttachment)
39        yield ('TICKET_ADMIN', ((bool, int, str),), self.deleteAttachment)
40        yield ('TICKET_VIEW', ((list,),), self.getTicketFields)
41
42    # Exported methods
43    def query(self, req, qstr='status!=closed'):
44        """ Perform a ticket query, returning a list of ticket ID's. """
45        q = query.Query.from_string(self.env, qstr)
46        out = []
47        for t in q.execute(req):
48            out.append(t['id'])
49        return out
50
51    def getRecentChanges(self, req, since):
52        """Returns a list of IDs of tickets that have changed since timestamp."""
53        since = to_timestamp(since)
54        db = self.env.get_db_cnx()
55        cursor = db.cursor()
56        cursor.execute('SELECT id FROM ticket'
57                       ' WHERE changetime >= %s', (since,))
58        result = []
59        for row in cursor:
60            result.append(int(row[0]))
61        return result
62
63    def getAvailableActions(self, req, id):
64        """Returns the actions that can be performed on the ticket."""
65        ticketSystem = TicketSystem(self.env)
66        t = model.Ticket(self.env, id)
67        return ticketSystem.get_available_actions(t, req.perm)
68
69    def get(self, req, id):
70        """ Fetch a ticket. Returns [id, time_created, time_changed, attributes]. """
71        t = model.Ticket(self.env, id)
72        return (t.id, t.time_created, t.time_changed, t.values)
73
74    def create(self, req, summary, description, attributes = {}, notify=False):
75        """ Create a new ticket, returning the ticket ID. """
76        t = model.Ticket(self.env)
77        t['status'] = 'new'
78        t['summary'] = summary
79        t['description'] = description
80        t['reporter'] = req.authname or 'anonymous'
81        for k, v in attributes.iteritems():
82            t[k] = v
83        t.insert()
84
85        if notify:
86            try:
87                tn = TicketNotifyEmail(self.env)
88                tn.notify(t, newticket=True)
89            except Exception, e:
90                self.log.exception("Failure sending notification on creation "
91                                   "of ticket #%s: %s" % (t.id, e))
92       
93        return t.id
94
95    def update(self, req, id, comment, attributes = {}, notify=False):
96        """ Update a ticket, returning the new ticket in the same form as getTicket(). """
97        now = int(time.time())
98
99        t = model.Ticket(self.env, id)
100        for k, v in attributes.iteritems():
101            t[k] = v
102        t.save_changes(req.authname or 'anonymous', comment)
103
104        if notify:
105            try:
106                tn = TicketNotifyEmail(self.env)
107                tn.notify(t, newticket=False, modtime=now)
108            except Exception, e:
109                self.log.exception("Failure sending notification on change of "
110                                   "ticket #%s: %s" % (t.id, e))
111
112        return self.get(req, t.id)
113
114    def delete(self, req, id):
115        """ Delete ticket with the given id. """
116        t = model.Ticket(self.env, id)
117        t.delete()
118
119    def changeLog(self, req, id, when=0):
120        t = model.Ticket(self.env, id)
121        return t.get_changelog(when)
122    # Use existing documentation from Ticket model
123    changeLog.__doc__ = pydoc.getdoc(model.Ticket.get_changelog)
124
125    def listAttachments(self, req, ticket):
126        """ Lists attachments for a given ticket. Returns (filename,
127        description, size, time, author) for each attachment."""
128        for t in Attachment.select(self.env, 'ticket', ticket):
129            yield (t.filename, t.description or '', t.size, t.time, t.author)
130
131    def getAttachment(self, req, ticket, filename):
132        """ returns the content of an attachment. """
133        attachment = Attachment(self.env, 'ticket', ticket, filename)
134        return xmlrpclib.Binary(attachment.open().read())
135
136    def putAttachment(self, req, ticket, filename, description, data, replace=True):
137        """ Add an attachment, optionally (and defaulting to) overwriting an
138        existing one. Returns filename."""
139        if not model.Ticket(self.env, ticket).exists:
140            raise TracError, 'Ticket "%s" does not exist' % ticket
141        if replace:
142            try:
143                attachment = Attachment(self.env, 'ticket', ticket, filename)
144                attachment.delete()
145            except TracError:
146                pass
147        attachment = Attachment(self.env, 'ticket', ticket)
148        attachment.author = req.authname or 'anonymous'
149        attachment.description = description
150        attachment.insert(filename, StringIO(data.data), len(data.data))
151        return attachment.filename
152
153    def deleteAttachment(self, req, ticket, filename):
154        """ Delete an attachment. """
155        if not model.Ticket(self.env, ticket).exists:
156            raise TracError('Ticket "%s" does not exists' % ticket)
157        attachment = Attachment(self.env, 'ticket', ticket, filename)
158        attachment.delete()
159        return True
160
161    def getTicketFields(self, req):
162        """ Return a list of all ticket fields fields. """
163        return TicketSystem(self.env).get_ticket_fields()
164
165
166def ticketModelFactory(cls, cls_attributes):
167    """ Return a class which exports an interface to trac.ticket.model.<cls>. """
168    class TicketModelImpl(Component):
169        implements(IXMLRPCHandler)
170
171        def xmlrpc_namespace(self):
172            return 'ticket.' + cls.__name__.lower()
173
174        def xmlrpc_methods(self):
175            yield ('TICKET_VIEW', ((list,),), self.getAll)
176            yield ('TICKET_VIEW', ((dict, str),), self.get)
177            yield ('TICKET_ADMIN', ((None, str,),), self.delete)
178            yield ('TICKET_ADMIN', ((None, str, dict),), self.create)
179            yield ('TICKET_ADMIN', ((None, str, dict),), self.update)
180
181        def getAll(self, req):
182            for i in cls.select(self.env):
183                yield i.name
184        getAll.__doc__ = """ Get a list of all ticket %s names. """ % cls.__name__.lower()
185
186        def get(self, req, name):
187            i = cls(self.env, name)
188            attributes= {}
189            for k, default in cls_attributes.iteritems():
190                v = getattr(i, k)
191                if v is None:
192                    v = default
193                attributes[k] = v
194            return attributes
195        get.__doc__ = """ Get a ticket %s. """ % cls.__name__.lower()
196
197        def delete(self, req, name):
198            cls(self.env, name).delete()
199        delete.__doc__ = """ Delete a ticket %s """ % cls.__name__.lower()
200
201        def create(self, req, name, attributes):
202            i = cls(self.env)
203            i.name = name
204            for k, v in attributes.iteritems():
205                setattr(i, k, v)
206            i.insert();
207        create.__doc__ = """ Create a new ticket %s with the given attributes. """ % cls.__name__.lower()
208
209        def update(self, req, name, attributes):
210            self._updateHelper(name, attributes).update()
211        update.__doc__ = """ Update ticket %s with the given attributes. """ % cls.__name__.lower()
212
213        def _updateHelper(self, name, attributes):
214            i = cls(self.env, name)
215            for k, v in attributes.iteritems():
216                setattr(i, k, v)
217            return i
218    TicketModelImpl.__doc__ = """ Interface to ticket %s objects. """ % cls.__name__.lower()
219    TicketModelImpl.__name__ = '%sRPC' % cls.__name__
220    return TicketModelImpl
221
222def ticketEnumFactory(cls):
223    """ Return a class which exports an interface to one of the Trac ticket abstract enum types. """
224    class AbstractEnumImpl(Component):
225        implements(IXMLRPCHandler)
226
227        def xmlrpc_namespace(self):
228            return 'ticket.' + cls.__name__.lower()
229
230        def xmlrpc_methods(self):
231            yield ('TICKET_VIEW', ((list,),), self.getAll)
232            yield ('TICKET_VIEW', ((str, str),), self.get)
233            yield ('TICKET_ADMIN', ((None, str,),), self.delete)
234            yield ('TICKET_ADMIN', ((None, str, str),), self.create)
235            yield ('TICKET_ADMIN', ((None, str, str),), self.update)
236
237        def getAll(self, req):
238            for i in cls.select(self.env):
239                yield i.name
240        getAll.__doc__ = """ Get a list of all ticket %s names. """ % cls.__name__.lower()
241
242        def get(self, req, name):
243            i = cls(self.env, name)
244            return i.value
245        get.__doc__ = """ Get a ticket %s. """ % cls.__name__.lower()
246
247        def delete(self, req, name):
248            cls(self.env, name).delete()
249        delete.__doc__ = """ Delete a ticket %s """ % cls.__name__.lower()
250
251        def create(self, req, name, value):
252            i = cls(self.env)
253            i.name = name
254            i.value = value
255            i.insert()
256        create.__doc__ = """ Create a new ticket %s with the given value. """ % cls.__name__.lower()
257
258        def update(self, req, name, value):
259            self._updateHelper(name, value).update()
260        update.__doc__ = """ Update ticket %s with the given value. """ % cls.__name__.lower()
261
262        def _updateHelper(self, name, value):
263            i = cls(self.env, name)
264            i.value = value
265            return i
266
267    AbstractEnumImpl.__doc__ = """ Interface to ticket %s. """ % cls.__name__.lower()
268    AbstractEnumImpl.__name__ = '%sRPC' % cls.__name__
269    return AbstractEnumImpl
270
271ticketModelFactory(model.Component, {'name': '', 'owner': '', 'description': ''})
272ticketModelFactory(model.Version, {'name': '', 'time': 0, 'description': ''})
273ticketModelFactory(model.Milestone, {'name': '', 'due': 0, 'completed': 0, 'description': ''})
274
275ticketEnumFactory(model.Type)
276ticketEnumFactory(model.Status)
277ticketEnumFactory(model.Resolution)
278ticketEnumFactory(model.Priority)
279ticketEnumFactory(model.Severity)
Note: See TracBrowser for help on using the repository browser.