root/xmlrpcplugin/0.10/tracrpc/ticket.py

Revision 1950, 11.0 kB (checked in by athomas, 2 years ago)

XmlRpcPlugin:

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

Line 
1 from trac.attachment import Attachment
2 from trac.core import *
3 from tracrpc.api import IXMLRPCHandler, expose_rpc
4 from tracrpc.util import to_timestamp
5 import trac.ticket.model as model
6 import trac.ticket.query as query
7 from trac.ticket.api import TicketSystem
8 from trac.ticket.notification import TicketNotifyEmail
9
10 import time
11 import pydoc
12 import xmlrpclib
13 from StringIO import StringIO
14
15 class 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
166 def 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
222 def 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
271 ticketModelFactory(model.Component, {'name': '', 'owner': '', 'description': ''})
272 ticketModelFactory(model.Version, {'name': '', 'time': 0, 'description': ''})
273 ticketModelFactory(model.Milestone, {'name': '', 'due': 0, 'completed': 0, 'description': ''})
274
275 ticketEnumFactory(model.Type)
276 ticketEnumFactory(model.Status)
277 ticketEnumFactory(model.Resolution)
278 ticketEnumFactory(model.Priority)
279 ticketEnumFactory(model.Severity)
Note: See TracBrowser for help on using the browser.