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):
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."""
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))
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