Some Eclipse Foundation services are deprecated, or will be soon. Please ensure you've read this important communication.
View | Details | Raw Unified | Return to bug 223173 | Differences between
and this patch

Collapse All | Expand All

(-)tracrpc/ticket.py (-15 / +139 lines)
Lines 7-14 Link Here
7
import trac.ticket.query as query
7
import trac.ticket.query as query
8
from trac.ticket.api import TicketSystem
8
from trac.ticket.api import TicketSystem
9
from trac.ticket.notification import TicketNotifyEmail
9
from trac.ticket.notification import TicketNotifyEmail
10
from trac.ticket.web_ui import TicketModule
10
from trac.util.datefmt import utc
11
from trac.util.datefmt import utc
11
12
13
import genshi
14
12
from datetime import datetime
15
from datetime import datetime
13
import inspect
16
import inspect
14
import xmlrpclib
17
import xmlrpclib
Lines 27-35 Link Here
27
        yield ('TICKET_VIEW', ((list,), (list, str)), self.query)
30
        yield ('TICKET_VIEW', ((list,), (list, str)), self.query)
28
        yield ('TICKET_VIEW', ((list, xmlrpclib.DateTime),), self.getRecentChanges)
31
        yield ('TICKET_VIEW', ((list, xmlrpclib.DateTime),), self.getRecentChanges)
29
        yield ('TICKET_VIEW', ((list, int),), self.getAvailableActions)
32
        yield ('TICKET_VIEW', ((list, int),), self.getAvailableActions)
33
        yield ('TICKET_VIEW', ((list, int),), self.getActions)
30
        yield ('TICKET_VIEW', ((list, int),), self.get)
34
        yield ('TICKET_VIEW', ((list, int),), self.get)
31
        yield ('TICKET_CREATE', ((int, str, str), (int, str, str, dict), (int, str, str, dict, bool)), self.create)
35
        yield ('TICKET_CREATE', ((int, str, str), (int, str, str, dict), (int, str, str, dict, bool)), self.create)
32
        yield ('TICKET_ADMIN', ((list, int, str), (list, int, str, dict), (list, int, str, dict, bool)), self.update)
36
        yield ('TICKET_VIEW', ((list, int, str), (list, int, str, dict), (list, int, str, dict, bool)), self.update)
33
        yield ('TICKET_ADMIN', ((None, int),), self.delete)
37
        yield ('TICKET_ADMIN', ((None, int),), self.delete)
34
        yield ('TICKET_VIEW', ((dict, int), (dict, int, int)), self.changeLog)
38
        yield ('TICKET_VIEW', ((dict, int), (dict, int, int)), self.changeLog)
35
        yield ('TICKET_VIEW', ((list, int),), self.listAttachments)
39
        yield ('TICKET_VIEW', ((list, int),), self.listAttachments)
Lines 63-72 Link Here
63
        return result
67
        return result
64
68
65
    def getAvailableActions(self, req, id):
69
    def getAvailableActions(self, req, id):
66
        """Returns the actions that can be performed on the ticket."""
70
        """ Deprecated - will be removed. Replaced by `getActions()`. """
67
        ticketSystem = TicketSystem(self.env)
71
        self.log.warning("Rpc ticket.getAvailableActions is deprecated")
72
        return [action for action, inputs in self.getActions(req, id)]
73
74
    def getActions(self, req, id):
75
        """Returns the actions that can be performed on the ticket as a list of
76
        `[action, label, hint, [input_fields]]` elements, where `input_fields` is a list
77
        of `[name, value, [options]]` for any required action inputs."""
78
        ts = TicketSystem(self.env)
68
        t = model.Ticket(self.env, id)
79
        t = model.Ticket(self.env, id)
69
        return ticketSystem.get_available_actions(req, t)
80
        actions = []
81
        for action in ts.get_available_actions(req, t):
82
            fragment = genshi.builder.Fragment()
83
            for controller in ts.action_controllers:
84
                if action in controller.actions.keys():
85
                    (label, control, hint) = controller.render_ticket_action_control(req, t, action)
86
                    fragment += control
87
            controls = []
88
            for elem in fragment.children:
89
                if not isinstance(elem, genshi.builder.Element):
90
                    continue
91
                if elem.tag == 'input':
92
                    controls.append((elem.attrib.get('name'),
93
                                    elem.attrib.get('value'), []))
94
                elif elem.tag == 'select':
95
                    value = ''
96
                    options = []
97
                    for opt in elem.children:
98
                        if not (opt.tag == 'option' and opt.children):
99
                            continue
100
                        option = opt.children[0]
101
                        options.append(option)
102
                        if opt.attrib.get('selected'):
103
                            value = option
104
                    controls.append((elem.attrib.get('name'),
105
                                    value, options))
106
            actions.append((action, label, hint, controls))
107
        self.log.debug('Rpc ticket.getActions for ticket %d, user %s: %s' % (
108
                    id, req.authname, repr(actions)))
109
        return actions
70
110
71
    def get(self, req, id):
111
    def get(self, req, id):
72
        """ Fetch a ticket. Returns [id, time_created, time_changed, attributes]. """
112
        """ Fetch a ticket. Returns [id, time_created, time_changed, attributes]. """
Lines 77-90 Link Here
77
    def create(self, req, summary, description, attributes = {}, notify=False):
117
    def create(self, req, summary, description, attributes = {}, notify=False):
78
        """ Create a new ticket, returning the ticket ID. """
118
        """ Create a new ticket, returning the ticket ID. """
79
        t = model.Ticket(self.env)
119
        t = model.Ticket(self.env)
80
        t['status'] = 'new'
81
        t['summary'] = summary
120
        t['summary'] = summary
82
        t['description'] = description
121
        t['description'] = description
83
        t['reporter'] = req.authname or 'anonymous'
122
        t['reporter'] = req.authname or 'anonymous'
84
        for k, v in attributes.iteritems():
123
        for k, v in attributes.iteritems():
85
            t[k] = v
124
            t[k] = v
125
        t['status'] = 'new'
126
        t['resolution'] = ''
86
        t.insert()
127
        t.insert()
87
128
        # Call ticket change listeners
129
        ts = TicketSystem(self.env)
130
        for listener in ts.change_listeners:
131
            listener.ticket_created(t)
88
        if notify:
132
        if notify:
89
            try:
133
            try:
90
                tn = TicketNotifyEmail(self.env)
134
                tn = TicketNotifyEmail(self.env)
Lines 92-109 Link Here
92
            except Exception, e:
136
            except Exception, e:
93
                self.log.exception("Failure sending notification on creation "
137
                self.log.exception("Failure sending notification on creation "
94
                                   "of ticket #%s: %s" % (t.id, e))
138
                                   "of ticket #%s: %s" % (t.id, e))
95
		
96
        return t.id
139
        return t.id
97
140
98
    def update(self, req, id, comment, attributes = {}, notify=False):
141
    def update(self, req, id, comment, attributes = {}, notify=False):
99
        """ Update a ticket, returning the new ticket in the same form as getTicket(). """
142
        """ Update a ticket, returning the new ticket in the same form as
143
        getTicket(). Requires a valid 'action' in attributes to support workflow. """
100
        now = datetime.now(utc)
144
        now = datetime.now(utc)
101
102
        t = model.Ticket(self.env, id)
145
        t = model.Ticket(self.env, id)
103
        for k, v in attributes.iteritems():
146
        if not 'action' in attributes:
104
            t[k] = v
147
            # FIXME: Old, non-restricted update - remove soon!
105
        t.save_changes(req.authname or 'anonymous', comment)
148
            self.log.warning("Rpc ticket.update for ticket %d by user %s " \
106
149
                    "has no workflow 'action'." % (id, req.authname))
150
            for k, v in attributes.iteritems():
151
                t[k] = v
152
            t.save_changes(req.authname, comment)
153
        else:
154
            ts = TicketSystem(self.env)
155
            tm = TicketModule(self.env)
156
            action = attributes.get('action')
157
            avail_actions = ts.get_available_actions(req, t)
158
            if not action in avail_actions:
159
                raise TracError("Rpc: Ticket %d by %s " \
160
                        "invalid action '%s'" % (id, req.authname, action))
161
            controllers = list(tm._get_action_controllers(req, t, action))
162
            all_fields = [field['name'] for field in ts.get_ticket_fields()]
163
            for k, v in attributes.iteritems():
164
                if k in all_fields and k not in ['status', 'resolution']:
165
                    t[k] = v
166
            # TicketModule reads req.args - need to move things there...
167
            req.args.update(attributes)
168
            req.args['comment'] = comment
169
            req.args['ts'] = str(t.time_changed) # collision hack...
170
            changes, problems = tm.get_ticket_changes(req, t, action)
171
            for warning in problems:
172
                req.add_warning("Rpc ticket.update: %(warning)s",
173
                                    warning=warning)
174
            valid = problems and False or tm._validate_ticket(req, t)
175
            if not valid:
176
                raise TracError(
177
                    " ".join([warning for warning in req.chrome['warnings']]))
178
            else:
179
                tm._apply_ticket_changes(t, changes)
180
                self.log.debug("Rpc ticket.update save: %s" % repr(t.values))
181
                t.save_changes(req.authname, comment)
182
                # Apply workflow side-effects
183
                for controller in controllers:
184
                    controller.apply_action_side_effects(req, t, action)
185
                # Call ticket change listeners
186
                for listener in ts.change_listeners:
187
                    listener.ticket_changed(t, comment, req.authname, t._old)
107
        if notify:
188
        if notify:
108
            try:
189
            try:
109
                tn = TicketNotifyEmail(self.env)
190
                tn = TicketNotifyEmail(self.env)
Lines 111-123 Link Here
111
            except Exception, e:
192
            except Exception, e:
112
                self.log.exception("Failure sending notification on change of "
193
                self.log.exception("Failure sending notification on change of "
113
                                   "ticket #%s: %s" % (t.id, e))
194
                                   "ticket #%s: %s" % (t.id, e))
114
115
        return self.get(req, t.id)
195
        return self.get(req, t.id)
116
196
117
    def delete(self, req, id):
197
    def delete(self, req, id):
118
        """ Delete ticket with the given id. """
198
        """ Delete ticket with the given id. """
119
        t = model.Ticket(self.env, id)
199
        t = model.Ticket(self.env, id)
120
        t.delete()
200
        t.delete()
201
        ts = TicketSystem(self.env)
202
        # Call ticket change listeners
203
        for listener in ts.change_listeners:
204
            listener.ticket_deleted(t)
121
205
122
    def changeLog(self, req, id, when=0):
206
    def changeLog(self, req, id, when=0):
123
        t = model.Ticket(self.env, id)
207
        t = model.Ticket(self.env, id)
Lines 167-172 Link Here
167
        """ Return a list of all ticket fields fields. """
251
        """ Return a list of all ticket fields fields. """
168
        return TicketSystem(self.env).get_ticket_fields()
252
        return TicketSystem(self.env).get_ticket_fields()
169
253
254
class StatusRPC(Component):
255
    """ An interface to Trac ticket status objects.
256
    Note: Status is defined workflows, and all methods except getAll()
257
    are deprecated no-op methods - these will be removed later. """
258
259
    implements(IXMLRPCHandler)
260
261
    # IXMLRPCHandler methods
262
    def xmlrpc_namespace(self):
263
        return 'ticket.status'
264
265
    def xmlrpc_methods(self):
266
        yield ('TICKET_VIEW', ((list,),), self.getAll)
267
        yield ('TICKET_VIEW', ((dict, str),), self.get)
268
        yield ('TICKET_ADMIN', ((None, str,),), self.delete)
269
        yield ('TICKET_ADMIN', ((None, str, dict),), self.create)
270
        yield ('TICKET_ADMIN', ((None, str, dict),), self.update)
271
272
    def getAll(self, req):
273
        """ Returns all ticket states described by active workflow. """
274
        return TicketSystem(self.env).get_all_status()
275
    
276
    def get(self, req, name):
277
        """ Deprecated no-op method. Do not use. """
278
        # FIXME: Remove
279
        return '0'
280
281
    def delete(self, req, name):
282
        """ Deprecated no-op method. Do not use. """
283
        # FIXME: Remove
284
        return 0
285
286
    def create(self, req, name, attributes):
287
        """ Deprecated no-op method. Do not use. """
288
        # FIXME: Remove
289
        return 0
290
291
    def update(self, req, name, attributes):
292
        """ Deprecated no-op method. Do not use. """
293
        # FIXME: Remove
294
        return 0
170
295
171
def ticketModelFactory(cls, cls_attributes):
296
def ticketModelFactory(cls, cls_attributes):
172
    """ Return a class which exports an interface to trac.ticket.model.<cls>. """
297
    """ Return a class which exports an interface to trac.ticket.model.<cls>. """
Lines 283-289 Link Here
283
ticketModelFactory(model.Milestone, {'name': '', 'due': 0, 'completed': 0, 'description': ''})
408
ticketModelFactory(model.Milestone, {'name': '', 'due': 0, 'completed': 0, 'description': ''})
284
409
285
ticketEnumFactory(model.Type)
410
ticketEnumFactory(model.Type)
286
ticketEnumFactory(model.Status)
287
ticketEnumFactory(model.Resolution)
411
ticketEnumFactory(model.Resolution)
288
ticketEnumFactory(model.Priority)
412
ticketEnumFactory(model.Priority)
289
ticketEnumFactory(model.Severity)
413
ticketEnumFactory(model.Severity)
(-).project (+18 lines)
Line 0 Link Here
1
<?xml version="1.0" encoding="UTF-8"?>
2
<projectDescription>
3
	<name>xmlrpcplugin</name>
4
	<comment></comment>
5
	<projects>
6
		<project>trac-0.11</project>
7
	</projects>
8
	<buildSpec>
9
		<buildCommand>
10
			<name>org.python.pydev.PyDevBuilder</name>
11
			<arguments>
12
			</arguments>
13
		</buildCommand>
14
	</buildSpec>
15
	<natures>
16
		<nature>org.python.pydev.pythonNature</nature>
17
	</natures>
18
</projectDescription>
(-).pydevproject (+10 lines)
Line 0 Link Here
1
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
2
<?eclipse-pydev version="1.0"?>
3
4
<pydev_project>
5
<pydev_property name="org.python.pydev.PYTHON_PROJECT_VERSION">python 2.4</pydev_property>
6
<pydev_pathproperty name="org.python.pydev.PROJECT_SOURCE_PATH">
7
<path>/xmlrpcplugin</path>
8
</pydev_pathproperty>
9
<pydev_property name="org.python.pydev.PYTHON_PROJECT_INTERPRETER">Default</pydev_property>
10
</pydev_project>
(-)setup.py (-1 / +1 lines)
Lines 4-10 Link Here
4
4
5
setup(
5
setup(
6
    name='TracXMLRPC',
6
    name='TracXMLRPC',
7
    version='1.0.0',
7
    version='1.0.1',
8
    license='BSD',
8
    license='BSD',
9
    author='Alec Thomas',
9
    author='Alec Thomas',
10
    author_email='alec@swapoff.org',
10
    author_email='alec@swapoff.org',

Return to bug 223173