Code

45552872576c75a94f200567e1ca2f5d961d6d1b
[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 SimpleXMLRPCServer import *
13 def translate(value):
14     """Translate value to becomes valid for XMLRPC transmission."""
16     if isinstance(value, (Date, Range, Interval)):
17         return repr(value)
18     elif type(value) is list:
19         return [translate(v) for v in value]
20     elif type(value) is tuple:
21         return tuple([translate(v) for v in value])
22     elif type(value) is dict:
23         return dict([[translate(k), translate(value[k])] for k in value])
24     else:
25         return value
28 def props_from_args(db, cl, args, itemid=None):
29     """Construct a list of properties from the given arguments,
30     and return them after validation."""
32     props = {}
33     for arg in args:
34         if arg.find('=') == -1:
35             raise UsageError, 'argument "%s" not propname=value'%arg
36         l = arg.split('=')
37         if len(l) < 2:
38             raise UsageError, 'argument "%s" not propname=value'%arg
39         key, value = l[0], '='.join(l[1:])
40         if value:
41             try:
42                 props[key] = hyperdb.rawToHyperdb(db, cl, itemid,
43                                                   key, value)
44             except hyperdb.HyperdbValueError, message:
45                 raise UsageError, message
46         else:
47             props[key] = None
49     return props
51 class RoundupInstance:
52     """The RoundupInstance provides the interface accessible through
53     the Python XMLRPC mapping."""
55     def __init__(self, db, translator):
57         self.db = db
58         self.userid = db.getuid()
59         self.translator = translator
61     def list(self, classname, propname=None):
62         cl = self.db.getclass(classname)
63         if not propname:
64             propname = cl.labelprop()
65         result = [cl.get(itemid, propname)
66                   for itemid in cl.list()
67                   if self.db.security.hasPermission('View', self.db.getuid(),
68                                                     classname, propname, itemid)
69                   ]
70         return result
72     def filter(self, classname, search_matches, filterspec,
73                sort=[], group=[]):
74         cl = self.db.getclass(classname)
75         result = cl.filter(search_matches, filterspec, sort=sort, group=group)
76         return result
78     def display(self, designator, *properties):
79         classname, itemid = hyperdb.splitDesignator(designator)
80         cl = self.db.getclass(classname)
81         props = properties and list(properties) or cl.properties.keys()
82         props.sort()
83         for p in props:
84             if not self.db.security.hasPermission('View', self.db.getuid(),
85                                                   classname, p, itemid):
86                 raise Unauthorised('Permission to view %s of %s denied'%
87                                    (p, designator))
88             result = [(prop, cl.get(itemid, prop)) for prop in props]
89         return dict(result)
91     def create(self, classname, *args):
92         if not self.db.security.hasPermission('Create', self.db.getuid(), classname):
93             raise Unauthorised('Permission to create %s denied'%classname)
95         cl = self.db.getclass(classname)
97         # convert types
98         props = props_from_args(self.db, cl, args)
100         # check for the key property
101         key = cl.getkey()
102         if key and not props.has_key(key):
103             raise UsageError, 'you must provide the "%s" property.'%key
105         # do the actual create
106         try:
107             result = cl.create(**props)
108         except (TypeError, IndexError, ValueError), message:
109             raise UsageError, message
110         return result
112     def set(self, designator, *args):
114         classname, itemid = hyperdb.splitDesignator(designator)
115         cl = self.db.getclass(classname)
116         props = props_from_args(self.db, cl, args, itemid) # convert types
117         for p in props.iterkeys():
118             if not self.db.security.hasPermission('Edit', self.db.getuid(),
119                                                   classname, p, itemid):
120                 raise Unauthorised('Permission to edit %s of %s denied'%
121                                    (p, designator))
122         try:
123             return cl.set(itemid, **props)
124         except (TypeError, IndexError, ValueError), message:
125             raise UsageError, message
128 class RoundupDispatcher(SimpleXMLRPCDispatcher):
129     """RoundupDispatcher bridges from cgi.client to RoundupInstance.
130     It expects user authentication to be done."""
132     def __init__(self, db, userid, translator,
133                  allow_none=False, encoding=None):
135         SimpleXMLRPCDispatcher.__init__(self, allow_none, encoding)
136         self.register_instance(RoundupInstance(db, userid, translator))
137                  
139     def dispatch(self, input):
140         return self._marshaled_dispatch(input)
142     def _dispatch(self, method, params):
144         retn = SimpleXMLRPCDispatcher._dispatch(self, method, params)
145         retn = translate(retn)
146         return retn
147