18
18
# Author: Matt Giuca, Will Grant, Nick Chadwick
26
27
import genshi.template
28
29
from ivle.webapp.base.views import BaseView
30
from ivle.webapp.base.xhtml import GenshiLoaderMixin
29
31
from ivle.webapp.errors import BadRequest, MethodNotAllowed, Unauthorized
31
33
class RESTView(BaseView):
55
57
if not hasattr(op, '_rest_api_permission'):
56
58
raise Unauthorized()
58
if op._rest_api_permission not in self.get_permissions(req.user):
60
if (op._rest_api_permission not in
61
self.get_permissions(req.user, req.config)):
59
62
raise Unauthorized()
61
64
def convert_bool(self, value):
71
74
raise MethodNotAllowed(allowed=self._allowed_methods)
73
76
if req.method == 'GET':
74
self.authorize_method(req, self.GET)
75
outjson = self.GET(req)
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)
76
85
# Since PATCH isn't yet an official HTTP method, we allow users to
77
86
# turn a PUT into a PATCH by supplying a special header.
78
87
elif req.method == 'PATCH' or (req.method == 'PUT' and
96
105
# TODO: Check Content-Type and implement multipart/form-data.
98
107
opargs = dict(cgi.parse_qsl(data, keep_blank_values=1))
100
opname = opargs['ivle.op']
101
del opargs['ivle.op']
103
raise BadRequest('No named operation specified.')
106
op = getattr(self, opname)
107
except AttributeError:
108
raise BadRequest('Invalid named operation.')
110
if not hasattr(op, '_rest_api_callable') or \
111
not op._rest_api_callable:
112
raise BadRequest('Invalid named operation.')
114
self.authorize_method(req, op)
116
# Find any missing arguments, except for the first two (self, req)
117
(args, vaargs, varkw, defaults) = inspect.getargspec(op)
120
# To find missing arguments, we eliminate the provided arguments
121
# from the set of remaining function signature arguments. If the
122
# remaining signature arguments are in the args[-len(defaults):],
124
unspec = set(args) - set(opargs.keys())
125
if unspec and not defaults:
126
raise BadRequest('Missing arguments: ' + ', '.join(unspec))
128
unspec = [k for k in unspec if k not in args[-len(defaults):]]
131
raise BadRequest('Missing arguments: ' + ', '.join(unspec))
133
# We have extra arguments if the are no match args in the function
134
# signature, AND there is no **.
135
extra = set(opargs.keys()) - set(args)
136
if extra and not varkw:
137
raise BadRequest('Extra arguments: ' + ', '.join(extra))
139
outjson = op(req, **opargs)
108
outjson = self._named_operation(req, opargs)
141
110
req.content_type = self.content_type
142
111
self.write_json(req, outjson)
147
116
req.write(cjson.encode(outjson))
151
class XHTMLRESTView(JSONRESTView):
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):
152
167
"""A special type of RESTView which takes enhances the standard JSON
153
168
with genshi XHTML functions.
164
179
rest_template = os.path.join(os.path.dirname(
165
180
inspect.getmodule(self).__file__), self.template)
166
loader = genshi.template.TemplateLoader(".", auto_reload=True)
167
tmpl = loader.load(rest_template)
181
tmpl = self._loader.load(rest_template)
169
183
return tmpl.generate(self.ctx).render('xhtml', doctype='xhtml')
174
188
req.write(cjson.encode(outjson))
177
class named_operation(object):
191
class _named_operation(object):
178
192
'''Declare a function to be accessible to HTTP users via the REST API.
180
def __init__(self, permission):
194
def __init__(self, write_operation, permission):
195
self.write_operation = write_operation
181
196
self.permission = permission
183
198
def __call__(self, func):
184
199
func._rest_api_callable = True
200
func._rest_api_write_operation = self.write_operation
185
201
func._rest_api_permission = self.permission
204
write_operation = functools.partial(_named_operation, True)
205
read_operation = functools.partial(_named_operation, False)
188
207
class require_permission(object):
189
208
'''Declare the permission required for use of a method via the REST API.