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 try :
38 key, value = arg.split('=', 1)
39 except ValueError :
40 raise UsageError, 'argument "%s" not propname=value'%arg
41 if isinstance (key, unicode) :
42 try :
43 key = key.encode ('ascii')
44 except UnicodeEncodeError:
45 raise UsageError, 'argument %r is no valid ascii keyword'%key
46 if isinstance (value, unicode) :
47 value = value.encode ('utf-8')
48 if value:
49 try:
50 props[key] = hyperdb.rawToHyperdb(db, cl, itemid,
51 key, value)
52 except hyperdb.HyperdbValueError, message:
53 raise UsageError, message
54 else:
55 props[key] = None
57 return props
59 class RoundupInstance:
60 """The RoundupInstance provides the interface accessible through
61 the Python XMLRPC mapping."""
63 def __init__(self, db, actions, translator):
65 self.db = db
66 self.actions = actions
67 self.translator = translator
69 def schema(self):
70 s = {}
71 for c in self.db.classes:
72 cls = self.db.classes[c]
73 props = [(n,repr(v)) for n,v in cls.properties.items()]
74 s[c] = props
75 return s
77 def list(self, classname, propname=None):
78 cl = self.db.getclass(classname)
79 if not propname:
80 propname = cl.labelprop()
81 result = [cl.get(itemid, propname)
82 for itemid in cl.list()
83 if self.db.security.hasPermission('View', self.db.getuid(),
84 classname, propname, itemid)
85 ]
86 return result
88 def filter(self, classname, search_matches, filterspec,
89 sort=[], group=[]):
90 cl = self.db.getclass(classname)
91 result = cl.filter(search_matches, filterspec, sort=sort, group=group)
92 return result
94 def display(self, designator, *properties):
95 classname, itemid = hyperdb.splitDesignator(designator)
96 cl = self.db.getclass(classname)
97 props = properties and list(properties) or cl.properties.keys()
98 props.sort()
99 for p in props:
100 if not self.db.security.hasPermission('View', self.db.getuid(),
101 classname, p, itemid):
102 raise Unauthorised('Permission to view %s of %s denied'%
103 (p, designator))
104 result = [(prop, cl.get(itemid, prop)) for prop in props]
105 return dict(result)
107 def create(self, classname, *args):
109 if not self.db.security.hasPermission('Create', self.db.getuid(), classname):
110 raise Unauthorised('Permission to create %s denied'%classname)
112 cl = self.db.getclass(classname)
114 # convert types
115 props = props_from_args(self.db, cl, args)
117 # check for the key property
118 key = cl.getkey()
119 if key and not props.has_key(key):
120 raise UsageError, 'you must provide the "%s" property.'%key
122 for key in props:
123 if not self.db.security.hasPermission('Create', self.db.getuid(),
124 classname, property=key):
125 raise Unauthorised('Permission to create %s.%s denied'%(classname, key))
127 # do the actual create
128 try:
129 result = cl.create(**props)
130 self.db.commit()
131 except (TypeError, IndexError, ValueError), message:
132 raise UsageError, message
133 return result
135 def set(self, designator, *args):
137 classname, itemid = hyperdb.splitDesignator(designator)
138 cl = self.db.getclass(classname)
139 props = props_from_args(self.db, cl, args, itemid) # convert types
140 for p in props.iterkeys():
141 if not self.db.security.hasPermission('Edit', self.db.getuid(),
142 classname, p, itemid):
143 raise Unauthorised('Permission to edit %s of %s denied'%
144 (p, designator))
145 try:
146 result = cl.set(itemid, **props)
147 self.db.commit()
148 except (TypeError, IndexError, ValueError), message:
149 raise UsageError, message
150 return result
153 builtin_actions = {'retire': actions.Retire}
155 def action(self, name, *args):
156 """Execute a named action."""
158 if name in self.actions:
159 action_type = self.actions[name]
160 elif name in self.builtin_actions:
161 action_type = self.builtin_actions[name]
162 else:
163 raise Exception('action "%s" is not supported %s' % (name, ','.join(self.actions.keys())))
164 action = action_type(self.db, self.translator)
165 return action.execute(*args)
168 class RoundupDispatcher(SimpleXMLRPCDispatcher):
169 """RoundupDispatcher bridges from cgi.client to RoundupInstance.
170 It expects user authentication to be done."""
172 def __init__(self, db, actions, translator,
173 allow_none=False, encoding=None):
175 try:
176 # python2.5 and beyond
177 SimpleXMLRPCDispatcher.__init__(self, allow_none, encoding)
178 except TypeError:
179 # python2.4
180 SimpleXMLRPCDispatcher.__init__(self)
181 self.register_instance(RoundupInstance(db, actions, translator))
184 def dispatch(self, input):
185 return self._marshaled_dispatch(input)
187 def _dispatch(self, method, params):
189 retn = SimpleXMLRPCDispatcher._dispatch(self, method, params)
190 retn = translate(retn)
191 return retn