Commit | Line | Data |
---|---|---|
c638d827 CR |
1 | # |
2 | # Copyright (C) 2007 Stefan Seefeld | |
3 | # All rights reserved. | |
4 | # For license terms see the file COPYING.txt. | |
5 | # | |
6 | ||
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 | |
14 | ||
15 | def translate(value): | |
16 | """Translate value to becomes valid for XMLRPC transmission.""" | |
17 | ||
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 | |
28 | ||
29 | ||
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.""" | |
33 | ||
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 | |
57 | ||
58 | return props | |
59 | ||
60 | class RoundupInstance: | |
61 | """The RoundupInstance provides the interface accessible through | |
62 | the Python XMLRPC mapping.""" | |
63 | ||
64 | def __init__(self, db, actions, translator): | |
65 | ||
66 | self.db = db | |
67 | self.actions = actions | |
68 | self.translator = translator | |
69 | ||
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 | |
77 | ||
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 | |
88 | ||
89 | def filter(self, classname, search_matches, filterspec, | |
90 | sort=[], group=[]): | |
91 | cl = self.db.getclass(classname) | |
92 | result = cl.filter(search_matches, filterspec, sort=sort, group=group) | |
93 | return result | |
94 | ||
95 | def display(self, designator, *properties): | |
96 | classname, itemid = hyperdb.splitDesignator(designator) | |
97 | cl = self.db.getclass(classname) | |
98 | props = properties and list(properties) or cl.properties.keys() | |
99 | props.sort() | |
100 | for p in props: | |
101 | if not self.db.security.hasPermission('View', self.db.getuid(), | |
102 | classname, p, itemid): | |
103 | raise Unauthorised('Permission to view %s of %s denied'% | |
104 | (p, designator)) | |
105 | result = [(prop, cl.get(itemid, prop)) for prop in props] | |
106 | return dict(result) | |
107 | ||
108 | def create(self, classname, *args): | |
109 | ||
110 | if not self.db.security.hasPermission('Create', self.db.getuid(), classname): | |
111 | raise Unauthorised('Permission to create %s denied'%classname) | |
112 | ||
113 | cl = self.db.getclass(classname) | |
114 | ||
115 | # convert types | |
116 | props = props_from_args(self.db, cl, args) | |
117 | ||
118 | # check for the key property | |
119 | key = cl.getkey() | |
120 | if key and not props.has_key(key): | |
121 | raise UsageError, 'you must provide the "%s" property.'%key | |
122 | ||
123 | for key in props: | |
124 | if not self.db.security.hasPermission('Create', self.db.getuid(), | |
125 | classname, property=key): | |
126 | raise Unauthorised('Permission to create %s.%s denied'%(classname, key)) | |
127 | ||
128 | # do the actual create | |
129 | try: | |
130 | result = cl.create(**props) | |
131 | self.db.commit() | |
132 | except (TypeError, IndexError, ValueError), message: | |
133 | raise UsageError, message | |
134 | return result | |
135 | ||
136 | def set(self, designator, *args): | |
137 | ||
138 | classname, itemid = hyperdb.splitDesignator(designator) | |
139 | cl = self.db.getclass(classname) | |
140 | props = props_from_args(self.db, cl, args, itemid) # convert types | |
141 | for p in props.iterkeys(): | |
142 | if not self.db.security.hasPermission('Edit', self.db.getuid(), | |
143 | classname, p, itemid): | |
144 | raise Unauthorised('Permission to edit %s of %s denied'% | |
145 | (p, designator)) | |
146 | try: | |
147 | result = cl.set(itemid, **props) | |
148 | self.db.commit() | |
149 | except (TypeError, IndexError, ValueError), message: | |
150 | raise UsageError, message | |
151 | return result | |
152 | ||
153 | ||
154 | builtin_actions = {'retire': actions.Retire} | |
155 | ||
156 | def action(self, name, *args): | |
157 | """Execute a named action.""" | |
158 | ||
159 | if name in self.actions: | |
160 | action_type = self.actions[name] | |
161 | elif name in self.builtin_actions: | |
162 | action_type = self.builtin_actions[name] | |
163 | else: | |
164 | raise Exception('action "%s" is not supported %s' % (name, ','.join(self.actions.keys()))) | |
165 | action = action_type(self.db, self.translator) | |
166 | return action.execute(*args) | |
167 | ||
168 | ||
169 | class RoundupDispatcher(SimpleXMLRPCDispatcher): | |
170 | """RoundupDispatcher bridges from cgi.client to RoundupInstance. | |
171 | It expects user authentication to be done.""" | |
172 | ||
173 | def __init__(self, db, actions, translator, | |
174 | allow_none=False, encoding=None): | |
175 | ||
176 | try: | |
177 | # python2.5 and beyond | |
178 | SimpleXMLRPCDispatcher.__init__(self, allow_none, encoding) | |
179 | except TypeError: | |
180 | # python2.4 | |
181 | SimpleXMLRPCDispatcher.__init__(self) | |
182 | self.register_instance(RoundupInstance(db, actions, translator)) | |
183 | ||
184 | ||
185 | def dispatch(self, input): | |
186 | return self._marshaled_dispatch(input) | |
187 | ||
188 | def _dispatch(self, method, params): | |
189 | ||
190 | retn = SimpleXMLRPCDispatcher._dispatch(self, method, params) | |
191 | retn = translate(retn) | |
192 | return retn | |
193 |