Code

PGP support is again working (pyme API has changed significantly) and we
[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 *
13 from xmlrpclib import Binary
15 def translate(value):
16     """Translate value to becomes valid for XMLRPC transmission."""
18     if isinstance(value, (Date, Range, Interval)):
19         return repr(value)
20     elif type(value) is list:
21         return [translate(v) for v in value]
22     elif type(value) is tuple:
23         return tuple([translate(v) for v in value])
24     elif type(value) is dict:
25         return dict([[translate(k), translate(value[k])] for k in value])
26     else:
27         return value
30 def props_from_args(db, cl, args, itemid=None):
31     """Construct a list of properties from the given arguments,
32     and return them after validation."""
34     props = {}
35     for arg in args:
36         if isinstance(arg, Binary):
37             arg = arg.data
38         try :
39             key, value = arg.split('=', 1)
40         except ValueError :
41             raise UsageError, 'argument "%s" not propname=value'%arg
42         if isinstance(key, unicode):
43             try:
44                 key = key.encode ('ascii')
45             except UnicodeEncodeError:
46                 raise UsageError, 'argument %r is no valid ascii keyword'%key
47         if isinstance(value, unicode):
48             value = value.encode('utf-8')
49         if value:
50             try:
51                 props[key] = hyperdb.rawToHyperdb(db, cl, itemid,
52                                                   key, value)
53             except hyperdb.HyperdbValueError, message:
54                 raise UsageError, message
55         else:
56             props[key] = None
58     return props
60 class RoundupInstance:
61     """The RoundupInstance provides the interface accessible through
62     the Python XMLRPC mapping."""
64     def __init__(self, db, actions, translator):
66         self.db = db
67         self.actions = actions
68         self.translator = translator
70     def schema(self):
71         s = {}
72         for c in self.db.classes:
73             cls = self.db.classes[c]
74             props = [(n,repr(v)) for n,v in cls.properties.items()]
75             s[c] = props
76         return s
78     def list(self, classname, propname=None):
79         cl = self.db.getclass(classname)
80         if not propname:
81             propname = cl.labelprop()
82         result = [cl.get(itemid, propname)
83                   for itemid in cl.list()
84                   if self.db.security.hasPermission('View', self.db.getuid(),
85                                                     classname, propname, itemid)
86                   ]
87         return result
89     def filter(self, classname, search_matches, filterspec,
90                sort=[], group=[]):
91         cl = self.db.getclass(classname)
92         uid = self.db.getuid()
93         security = self.db.security
94         filterspec = security.filterFilterspec (uid, classname, filterspec)
95         sort = security.filterSortspec (uid, classname, sort)
96         group = security.filterSortspec (uid, classname, group)
97         result = cl.filter(search_matches, filterspec, sort=sort, group=group)
98         check = security.hasPermission
99         x = [id for id in result if check('View', uid, classname, itemid=id)]
100         return x
102     def lookup(self, classname, key):
103         cl = self.db.getclass(classname)
104         uid = self.db.getuid()
105         prop = cl.getkey()
106         check = self.db.security.hasSearchPermission
107         if not check(uid, classname, 'id') or not check(uid, classname, prop):
108             raise Unauthorised('Permission to search %s denied'%classname)
109         return cl.lookup(key)
111     def display(self, designator, *properties):
112         classname, itemid = hyperdb.splitDesignator(designator)
113         cl = self.db.getclass(classname)
114         props = properties and list(properties) or cl.properties.keys()
115         props.sort()
116         for p in props:
117             if not self.db.security.hasPermission('View', self.db.getuid(),
118                                                   classname, p, itemid):
119                 raise Unauthorised('Permission to view %s of %s denied'%
120                                    (p, designator))
121             result = [(prop, cl.get(itemid, prop)) for prop in props]
122         return dict(result)
124     def create(self, classname, *args):
125         
126         if not self.db.security.hasPermission('Create', self.db.getuid(), classname):
127             raise Unauthorised('Permission to create %s denied'%classname)
129         cl = self.db.getclass(classname)
131         # convert types
132         props = props_from_args(self.db, cl, args)
134         # check for the key property
135         key = cl.getkey()
136         if key and not props.has_key(key):
137             raise UsageError, 'you must provide the "%s" property.'%key
139         for key in props:
140             if not self.db.security.hasPermission('Create', self.db.getuid(),
141                 classname, property=key):
142                 raise Unauthorised('Permission to create %s.%s denied'%(classname, key))
144         # do the actual create
145         try:
146             result = cl.create(**props)
147             self.db.commit()
148         except (TypeError, IndexError, ValueError), message:
149             raise UsageError, message
150         return result
152     def set(self, designator, *args):
154         classname, itemid = hyperdb.splitDesignator(designator)
155         cl = self.db.getclass(classname)
156         props = props_from_args(self.db, cl, args, itemid) # convert types
157         for p in props.iterkeys():
158             if not self.db.security.hasPermission('Edit', self.db.getuid(),
159                                                   classname, p, itemid):
160                 raise Unauthorised('Permission to edit %s of %s denied'%
161                                    (p, designator))
162         try:
163             result = cl.set(itemid, **props)
164             self.db.commit()
165         except (TypeError, IndexError, ValueError), message:
166             raise UsageError, message
167         return result
170     builtin_actions = {'retire': actions.Retire}
172     def action(self, name, *args):
173         """Execute a named action."""
174         
175         if name in self.actions:
176             action_type = self.actions[name]
177         elif name in self.builtin_actions:
178             action_type = self.builtin_actions[name]
179         else:
180             raise Exception('action "%s" is not supported %s' % (name, ','.join(self.actions.keys())))
181         action = action_type(self.db, self.translator)
182         return action.execute(*args)
185 class RoundupDispatcher(SimpleXMLRPCDispatcher):
186     """RoundupDispatcher bridges from cgi.client to RoundupInstance.
187     It expects user authentication to be done."""
189     def __init__(self, db, actions, translator,
190                  allow_none=False, encoding=None):
192         try:
193             # python2.5 and beyond
194             SimpleXMLRPCDispatcher.__init__(self, allow_none, encoding)
195         except TypeError:
196             # python2.4
197             SimpleXMLRPCDispatcher.__init__(self)
198         self.register_instance(RoundupInstance(db, actions, translator))
199                  
201     def dispatch(self, input):
202         return self._marshaled_dispatch(input)
204     def _dispatch(self, method, params):
206         retn = SimpleXMLRPCDispatcher._dispatch(self, method, params)
207         retn = translate(retn)
208         return retn
209