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
RevLine 
[808]1from trac.attachment import Attachment
[218]2from trac.core import *
[229]3from tracrpc.api import IXMLRPCHandler, expose_rpc
[1188]4from tracrpc.util import to_timestamp
[218]5import trac.ticket.model as model
6import trac.ticket.query as query
[1188]7from trac.ticket.api import TicketSystem
[1950]8from trac.ticket.notification import TicketNotifyEmail
[808]9
[1950]10import time
[219]11import pydoc
[225]12import xmlrpclib
[820]13from StringIO import StringIO
[218]14
[229]15class TicketRPC(Component):
[224]16    """ An interface to Trac's ticketing system. """
17
[229]18    implements(IXMLRPCHandler)
19
[219]20    # IXMLRPCHandler methods
[224]21    def xmlrpc_namespace(self):
22        return 'ticket'
[219]23
[229]24    def xmlrpc_methods(self):
25        yield ('TICKET_VIEW', ((list,), (list, str)), self.query)
[1188]26        yield ('TICKET_VIEW', ((list, xmlrpclib.DateTime),), self.getRecentChanges)
27        yield ('TICKET_VIEW', ((list, int),), self.getAvailableActions)
[229]28        yield ('TICKET_VIEW', ((list, int),), self.get)
[1950]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)
[229]31        yield ('TICKET_ADMIN', ((None, int),), self.delete)
32        yield ('TICKET_VIEW', ((dict, int), (dict, int, int)), self.changeLog)
[808]33        yield ('TICKET_VIEW', ((list, int),), self.listAttachments)
34        yield ('TICKET_VIEW', ((xmlrpclib.Binary, int, str),), self.getAttachment)
[820]35        yield ('TICKET_APPEND',
[1154]36               ((str, int, str, str, xmlrpclib.Binary, bool),
37                (str, int, str, str, xmlrpclib.Binary)),
[820]38               self.putAttachment)
39        yield ('TICKET_ADMIN', ((bool, int, str),), self.deleteAttachment)
[1278]40        yield ('TICKET_VIEW', ((list,),), self.getTicketFields)
[229]41
[225]42    # Exported methods
[1070]43    def query(self, req, qstr='status!=closed'):
[226]44        """ Perform a ticket query, returning a list of ticket ID's. """
[218]45        q = query.Query.from_string(self.env, qstr)
46        out = []
[1070]47        for t in q.execute(req):
[226]48            out.append(t['id'])
[218]49        return out
50
[1188]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
[808]69    def get(self, req, id):
[225]70        """ Fetch a ticket. Returns [id, time_created, time_changed, attributes]. """
[218]71        t = model.Ticket(self.env, id)
72        return (t.id, t.time_created, t.time_changed, t.values)
73
[1950]74    def create(self, req, summary, description, attributes = {}, notify=False):
[225]75        """ Create a new ticket, returning the ticket ID. """
[218]76        t = model.Ticket(self.env)
[808]77        t['status'] = 'new'
[218]78        t['summary'] = summary
79        t['description'] = description
[826]80        t['reporter'] = req.authname or 'anonymous'
[225]81        for k, v in attributes.iteritems():
[220]82            t[k] = v
[218]83        t.insert()
[1950]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       
[225]93        return t.id
[218]94
[1950]95    def update(self, req, id, comment, attributes = {}, notify=False):
[224]96        """ Update a ticket, returning the new ticket in the same form as getTicket(). """
[1950]97        now = int(time.time())
98
[219]99        t = model.Ticket(self.env, id)
[225]100        for k, v in attributes.iteritems():
[220]101            t[k] = v
[826]102        t.save_changes(req.authname or 'anonymous', comment)
[1950]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
[820]112        return self.get(req, t.id)
[219]113
[808]114    def delete(self, req, id):
[219]115        """ Delete ticket with the given id. """
116        t = model.Ticket(self.env, id)
117        t.delete()
118
[820]119    def changeLog(self, req, id, when=0):
[1732]120        t = model.Ticket(self.env, id)
121        return t.get_changelog(when)
[219]122    # Use existing documentation from Ticket model
[225]123    changeLog.__doc__ = pydoc.getdoc(model.Ticket.get_changelog)
124
[808]125    def listAttachments(self, req, ticket):
[1154]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)
[225]130
[808]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
[1154]136    def putAttachment(self, req, ticket, filename, description, data, replace=True):
[820]137        """ Add an attachment, optionally (and defaulting to) overwriting an
138        existing one. Returns filename."""
139        if not model.Ticket(self.env, ticket).exists:
[808]140            raise TracError, 'Ticket "%s" does not exist' % ticket
[820]141        if replace:
142            try:
143                attachment = Attachment(self.env, 'ticket', ticket, filename)
144                attachment.delete()
145            except TracError:
146                pass
[808]147        attachment = Attachment(self.env, 'ticket', ticket)
[820]148        attachment.author = req.authname or 'anonymous'
[1154]149        attachment.description = description
[808]150        attachment.insert(filename, StringIO(data.data), len(data.data))
[820]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()
[808]159        return True
160
[1278]161    def getTicketFields(self, req):
162        """ Return a list of all ticket fields fields. """
163        return TicketSystem(self.env).get_ticket_fields()
[808]164
[1278]165
[225]166def ticketModelFactory(cls, cls_attributes):
167    """ Return a class which exports an interface to trac.ticket.model.<cls>. """
[229]168    class TicketModelImpl(Component):
169        implements(IXMLRPCHandler)
170
[225]171        def xmlrpc_namespace(self):
172            return 'ticket.' + cls.__name__.lower()
173
[229]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
[808]181        def getAll(self, req):
[225]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
[808]186        def get(self, req, name):
[225]187            i = cls(self.env, name)
188            attributes= {}
[1070]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
[820]194            return attributes
[225]195        get.__doc__ = """ Get a ticket %s. """ % cls.__name__.lower()
196
[808]197        def delete(self, req, name):
[225]198            cls(self.env, name).delete()
199        delete.__doc__ = """ Delete a ticket %s """ % cls.__name__.lower()
200
[808]201        def create(self, req, name, attributes):
[821]202            i = cls(self.env)
203            i.name = name
204            for k, v in attributes.iteritems():
205                setattr(i, k, v)
206            i.insert();
[225]207        create.__doc__ = """ Create a new ticket %s with the given attributes. """ % cls.__name__.lower()
208
[808]209        def update(self, req, name, attributes):
[225]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):
[821]214            i = cls(self.env, name)
[225]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. """
[229]224    class AbstractEnumImpl(Component):
225        implements(IXMLRPCHandler)
226
[225]227        def xmlrpc_namespace(self):
228            return 'ticket.' + cls.__name__.lower()
229
[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
[808]237        def getAll(self, req):
[225]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
[808]242        def get(self, req, name):
[225]243            i = cls(self.env, name)
244            return i.value
245        get.__doc__ = """ Get a ticket %s. """ % cls.__name__.lower()
246
[808]247        def delete(self, req, name):
[225]248            cls(self.env, name).delete()
249        delete.__doc__ = """ Delete a ticket %s """ % cls.__name__.lower()
250
[808]251        def create(self, req, name, value):
[820]252            i = cls(self.env)
[821]253            i.name = name
[820]254            i.value = value
255            i.insert()
[225]256        create.__doc__ = """ Create a new ticket %s with the given value. """ % cls.__name__.lower()
257
[808]258        def update(self, req, name, value):
[225]259            self._updateHelper(name, value).update()
260        update.__doc__ = """ Update ticket %s with the given value. """ % cls.__name__.lower()
261
[821]262        def _updateHelper(self, name, value):
[820]263            i = cls(self.env, name)
[225]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
[1070]271ticketModelFactory(model.Component, {'name': '', 'owner': '', 'description': ''})
272ticketModelFactory(model.Version, {'name': '', 'time': 0, 'description': ''})
273ticketModelFactory(model.Milestone, {'name': '', 'due': 0, 'completed': 0, 'description': ''})
[225]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.