18
18
# Author: Matt Giuca, Will Grant, Nick Chadwick
27
26
import genshi.template
29
28
from ivle.webapp.base.views import BaseView
30
from ivle.webapp.base.xhtml import GenshiLoaderMixin
31
29
from ivle.webapp.errors import BadRequest, MethodNotAllowed, Unauthorized
33
31
class RESTView(BaseView):
38
36
content_type = "application/octet-stream"
38
def __init__(self, req, *args, **kwargs):
40
setattr(self, key, kwargs[key])
40
42
def render(self, req):
41
43
raise NotImplementedError()
57
59
if not hasattr(op, '_rest_api_permission'):
58
60
raise Unauthorized()
60
if (op._rest_api_permission not in
61
self.get_permissions(req.user, req.config)):
62
if op._rest_api_permission not in self.get_permissions(req.user):
62
63
raise Unauthorized()
64
65
def convert_bool(self, value):
74
75
raise MethodNotAllowed(allowed=self._allowed_methods)
76
77
if req.method == 'GET':
77
qargs = dict(cgi.parse_qsl(
78
urlparse.urlparse(req.unparsed_uri).query,
80
if 'ivle.op' in qargs:
81
outjson = self._named_operation(req, qargs, readonly=True)
83
self.authorize_method(req, self.GET)
84
outjson = self.GET(req)
78
self.authorize_method(req, self.GET)
79
outjson = self.GET(req)
85
80
# Since PATCH isn't yet an official HTTP method, we allow users to
86
81
# turn a PUT into a PATCH by supplying a special header.
87
82
elif req.method == 'PATCH' or (req.method == 'PUT' and
105
100
# TODO: Check Content-Type and implement multipart/form-data.
106
101
data = req.read()
107
102
opargs = dict(cgi.parse_qsl(data, keep_blank_values=1))
108
outjson = self._named_operation(req, opargs)
104
opname = opargs['ivle.op']
105
del opargs['ivle.op']
107
raise BadRequest('No named operation specified.')
110
op = getattr(self, opname)
111
except AttributeError:
112
raise BadRequest('Invalid named operation.')
114
if not hasattr(op, '_rest_api_callable') or \
115
not op._rest_api_callable:
116
raise BadRequest('Invalid named operation.')
118
self.authorize_method(req, op)
120
# Find any missing arguments, except for the first two (self, req)
121
(args, vaargs, varkw, defaults) = inspect.getargspec(op)
124
# To find missing arguments, we eliminate the provided arguments
125
# from the set of remaining function signature arguments. If the
126
# remaining signature arguments are in the args[-len(defaults):],
128
unspec = set(args) - set(opargs.keys())
129
if unspec and not defaults:
130
raise BadRequest('Missing arguments: ' + ', '.join(unspec))
132
unspec = [k for k in unspec if k not in args[-len(defaults):]]
135
raise BadRequest('Missing arguments: ' + ', '.join(unspec))
137
# We have extra arguments if the are no match args in the function
138
# signature, AND there is no **.
139
extra = set(opargs.keys()) - set(args)
140
if extra and not varkw:
141
raise BadRequest('Extra arguments: ' + ', '.join(extra))
143
outjson = op(req, **opargs)
110
145
req.content_type = self.content_type
111
146
self.write_json(req, outjson)
116
151
req.write(cjson.encode(outjson))
119
def _named_operation(self, req, opargs, readonly=False):
121
opname = opargs['ivle.op']
122
del opargs['ivle.op']
124
raise BadRequest('No named operation specified.')
127
op = getattr(self, opname)
128
except AttributeError:
129
raise BadRequest('Invalid named operation.')
131
if not hasattr(op, '_rest_api_callable') or \
132
not op._rest_api_callable:
133
raise BadRequest('Invalid named operation.')
135
if readonly and op._rest_api_write_operation:
136
raise BadRequest('POST required for write operation.')
138
self.authorize_method(req, op)
140
# Find any missing arguments, except for the first two (self, req)
141
(args, vaargs, varkw, defaults) = inspect.getargspec(op)
144
# To find missing arguments, we eliminate the provided arguments
145
# from the set of remaining function signature arguments. If the
146
# remaining signature arguments are in the args[-len(defaults):],
148
unspec = set(args) - set(opargs.keys())
149
if unspec and not defaults:
150
raise BadRequest('Missing arguments: ' + ', '.join(unspec))
152
unspec = [k for k in unspec if k not in args[-len(defaults):]]
155
raise BadRequest('Missing arguments: ' + ', '.join(unspec))
157
# We have extra arguments if the are no match args in the function
158
# signature, AND there is no **.
159
extra = set(opargs.keys()) - set(args)
160
if extra and not varkw:
161
raise BadRequest('Extra arguments: ' + ', '.join(extra))
163
return op(req, **opargs)
166
class XHTMLRESTView(GenshiLoaderMixin, JSONRESTView):
155
class XHTMLRESTView(JSONRESTView):
167
156
"""A special type of RESTView which takes enhances the standard JSON
168
157
with genshi XHTML functions.
173
162
ctx = genshi.template.Context()
164
def __init__(self, req, *args, **kwargs):
166
setattr(self, key, kwargs[key])
175
168
def render_fragment(self):
176
169
if self.template is None:
177
170
raise NotImplementedError()
179
172
rest_template = os.path.join(os.path.dirname(
180
173
inspect.getmodule(self).__file__), self.template)
181
tmpl = self._loader.load(rest_template)
174
loader = genshi.template.TemplateLoader(".", auto_reload=True)
175
tmpl = loader.load(rest_template)
183
177
return tmpl.generate(self.ctx).render('xhtml', doctype='xhtml')
188
182
req.write(cjson.encode(outjson))
191
class _named_operation(object):
185
class named_operation(object):
192
186
'''Declare a function to be accessible to HTTP users via the REST API.
194
def __init__(self, write_operation, permission):
195
self.write_operation = write_operation
188
def __init__(self, permission):
196
189
self.permission = permission
198
191
def __call__(self, func):
199
192
func._rest_api_callable = True
200
func._rest_api_write_operation = self.write_operation
201
193
func._rest_api_permission = self.permission
204
write_operation = functools.partial(_named_operation, True)
205
read_operation = functools.partial(_named_operation, False)
207
196
class require_permission(object):
208
197
'''Declare the permission required for use of a method via the REST API.