Code

86c8f992b207e443bacbd101311b16c3b209bae2
[roundup.git] / roundup / xmlrpc.py
1 #
2 # Copyright (C) 2007 Stefan Seefeld
3 # All rights reserved.
4 # For license terms see the file COPYING.txt.
5 #
7 from roundup import hyperdb
8 from roundup.cgi.exceptions import *
9 from roundup.exceptions import UsageError
10 from roundup.date import Date, Range, Interval
11 from roundup import actions
12 from SimpleXMLRPCServer import *
14 def translate(value):
15     """Translate value to becomes valid for XMLRPC transmission."""
17     if isinstance(value, (Date, Range, Interval)):
18         return repr(value)
19     elif type(value) is list:
20         return [translate(v) for v in value]
21     elif type(value) is tuple:
22         return tuple([translate(v) for v in value])
23     elif type(value) is dict:
24         return dict([[translate(k), translate(value[k])] for k in value])
25     else:
26         return value
29 def props_from_args(db, cl, args, itemid=None):
30     """Construct a list of properties from the given arguments,
31     and return them after validation."""
33     props = {}
34     for arg in args:
35         if arg.find('=') == -1:
36             raise UsageError, 'argument "%s" not propname=value'%arg
37         l = arg.split('=')
38         if len(l) < 2:
39             raise UsageError, 'argument "%s" not propname=value'%arg
40         key, value = l[0], '='.join(l[1:])
41         if value:
42             try:
43                 props[key] = hyperdb.rawToHyperdb(db, cl, itemid,
44                                                   key, value)
45             except hyperdb.HyperdbValueError, message:
46                 raise UsageError, message
47         else:
48             props[key] = None
50     return props
52 class RoundupInstance:
53     """The RoundupInstance provides the interface accessible through
54     the Python XMLRPC mapping."""
56     def __init__(self, db, actions, translator):
58         self.db = db
59         self.actions = actions
60         self.translator = translator
62     def schema(self):
63         s = {}
64         for c in self.db.classes:
65             cls = self.db.classes[c]
66             props = [(n,repr(v)) for n,v in cls.properties.items()]
67             s[c] = props
68         return s
70     def list(self, classname, propname=None):
71         cl = self.db.getclass(classname)
72         if not propname:
73             propname = cl.labelprop()
74         result = [cl.get(itemid, propname)
75                   for itemid in cl.list()
76                   if self.db.security.hasPermission('View', self.db.getuid(),
77                                                     classname, propname, itemid)
78                   ]
79         return result
81     def filter(self, classname, search_matches, filterspec,
82                sort=[], group=[]):
83         cl = self.db.getclass(classname)
84         result = cl.filter(search_matches, filterspec, sort=sort, group=group)
85         return result
87     def display(self, designator, *properties):
88         classname, itemid = hyperdb.splitDesignator(designator)
89         cl = self.db.getclass(classname)
90         props = properties and list(properties) or cl.properties.keys()
91         props.sort()
92         for p in props:
93             if not self.db.security.hasPermission('View', self.db.getuid(),
94                                                   classname, p, itemid):
95                 raise Unauthorised('Permission to view %s of %s denied'%
96                                    (p, designator))
97             result = [(prop, cl.get(itemid, prop)) for prop in props]
98         return dict(result)
100     def create(self, classname, *args):
101         
102         if not self.db.security.hasPermission('Create', self.db.getuid(), classname):
103             raise Unauthorised('Permission to create %s denied'%classname)
105         cl = self.db.getclass(classname)
107         # convert types
108         props = props_from_args(self.db, cl, args)
110         # check for the key property
111         key = cl.getkey()
112         if key and not props.has_key(key):
113             raise UsageError, 'you must provide the "%s" property.'%key
115         for key in props:
116             if not self.db.security.hasPermission('Edit', self.db.getuid(), classname,
117                                                   property=key):
118                 raise Unauthorised('Permission to set %s.%s denied'%(classname, key))
120         # do the actual create
121         try:
122             result = cl.create(**props)
123             self.db.commit()
124         except (TypeError, IndexError, ValueError), message:
125             raise UsageError, message
126         return result
128     def set(self, designator, *args):
130         classname, itemid = hyperdb.splitDesignator(designator)
131         cl = self.db.getclass(classname)
132         props = props_from_args(self.db, cl, args, itemid) # convert types
133         for p in props.iterkeys():
134             if not self.db.security.hasPermission('Edit', self.db.getuid(),
135                                                   classname, p, itemid):
136                 raise Unauthorised('Permission to edit %s of %s denied'%
137                                    (p, designator))
138         try:
139             result = cl.set(itemid, **props)
140             self.db.commit()
141         except (TypeError, IndexError, ValueError), message:
142             raise UsageError, message
143         return result
146     builtin_actions = {'retire': actions.Retire}
148     def action(self, name, *args):
149         """Execute a named action."""
150         
151         if name in self.actions:
152             action_type = self.actions[name]
153         elif name in self.builtin_actions:
154             action_type = self.builtin_actions[name]
155         else:
156             raise Exception('action "%s" is not supported %s' % (name, ','.join(self.actions.keys())))
157         action = action_type(self.db, self.translator)
158         return action.execute(*args)
161 class RoundupDispatcher(SimpleXMLRPCDispatcher):
162     """RoundupDispatcher bridges from cgi.client to RoundupInstance.
163     It expects user authentication to be done."""
165     def __init__(self, db, actions, translator,
166                  allow_none=False, encoding=None):
168         try:
169             # python2.5 and beyond
170             SimpleXMLRPCDispatcher.__init__(self, allow_none, encoding)
171         except TypeError:
172             # python2.4
173             SimpleXMLRPCDispatcher.__init__(self)
174         self.register_instance(RoundupInstance(db, actions, translator))
175                  
177     def dispatch(self, input):
178         return self._marshaled_dispatch(input)
180     def _dispatch(self, method, params):
182         retn = SimpleXMLRPCDispatcher._dispatch(self, method, params)
183         retn = translate(retn)
184         return retn
185