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