15
15
# along with this program; if not, write to the Free Software
16
16
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
18
# Author: Matt Giuca, Will Grant
18
# Author: Matt Giuca, Will Grant, Nick Chadwick
26
import genshi.template
25
28
from ivle.webapp.base.views import BaseView
26
from ivle.webapp.errors import BadRequest, MethodNotAllowed
29
from ivle.webapp.base.xhtml import GenshiLoaderMixin
30
from ivle.webapp.errors import BadRequest, MethodNotAllowed, Unauthorized
28
32
class RESTView(BaseView):
33
37
content_type = "application/octet-stream"
35
def __init__(self, req, *args, **kwargs):
37
setattr(self, key, kwargs[key])
39
39
def render(self, req):
40
40
raise NotImplementedError()
49
49
lambda self: [m for m in ('GET', 'PUT', 'PATCH')
50
50
if hasattr(self, m)] + ['POST'])
52
def authorize(self, req):
53
return True # Real authz performed in render().
55
def authorize_method(self, req, op):
56
if not hasattr(op, '_rest_api_permission'):
59
if (op._rest_api_permission not in
60
self.get_permissions(req.user, req.config)):
63
def convert_bool(self, value):
64
if value in ('True', 'true', True):
66
elif value in ('False', 'false', False):
52
71
def render(self, req):
53
72
if req.method not in self._allowed_methods:
54
73
raise MethodNotAllowed(allowed=self._allowed_methods)
56
75
if req.method == 'GET':
76
self.authorize_method(req, self.GET)
57
77
outjson = self.GET(req)
58
78
# Since PATCH isn't yet an official HTTP method, we allow users to
59
79
# turn a PUT into a PATCH by supplying a special header.
60
80
elif req.method == 'PATCH' or (req.method == 'PUT' and
61
81
'X-IVLE-Patch-Semantics' in req.headers_in and
62
82
req.headers_in['X-IVLE-Patch-Semantics'].lower() == 'yes'):
83
self.authorize_method(req, self.PATCH)
64
85
input = cjson.decode(req.read())
65
86
except cjson.DecodeError:
66
87
raise BadRequest('Invalid JSON data')
67
88
outjson = self.PATCH(req, input)
68
89
elif req.method == 'PUT':
90
self.authorize_method(req, self.PUT)
70
92
input = cjson.decode(req.read())
71
93
except cjson.DecodeError:
74
96
# POST implies named operation.
75
97
elif req.method == 'POST':
76
98
# TODO: Check Content-Type and implement multipart/form-data.
77
opargs = dict(cgi.parse_qsl(req.read()))
100
opargs = dict(cgi.parse_qsl(data, keep_blank_values=1))
79
102
opname = opargs['ivle.op']
80
103
del opargs['ivle.op']
90
113
not op._rest_api_callable:
91
114
raise BadRequest('Invalid named operation.')
116
self.authorize_method(req, op)
93
118
# Find any missing arguments, except for the first two (self, req)
94
119
(args, vaargs, varkw, defaults) = inspect.getargspec(op)
116
141
outjson = op(req, **opargs)
118
143
req.content_type = self.content_type
144
self.write_json(req, outjson)
146
#This is a separate function to allow additional data to be passed through
147
def write_json(self, req, outjson):
119
148
if outjson is not None:
120
149
req.write(cjson.encode(outjson))
123
def named_operation(meth):
153
class XHTMLRESTView(GenshiLoaderMixin, JSONRESTView):
154
"""A special type of RESTView which takes enhances the standard JSON
155
with genshi XHTML functions.
157
XHTMLRESTViews should have a template, which is rendered using their
158
context. This is returned in the JSON as 'html'"""
160
ctx = genshi.template.Context()
162
def render_fragment(self):
163
if self.template is None:
164
raise NotImplementedError()
166
rest_template = os.path.join(os.path.dirname(
167
inspect.getmodule(self).__file__), self.template)
168
tmpl = self._loader.load(rest_template)
170
return tmpl.generate(self.ctx).render('xhtml', doctype='xhtml')
172
# This renders the template and adds it to the json
173
def write_json(self, req, outjson):
174
outjson["html"] = self.render_fragment()
175
req.write(cjson.encode(outjson))
178
class named_operation(object):
124
179
'''Declare a function to be accessible to HTTP users via the REST API.
126
meth._rest_api_callable = True
181
def __init__(self, permission):
182
self.permission = permission
184
def __call__(self, func):
185
func._rest_api_callable = True
186
func._rest_api_permission = self.permission
189
class require_permission(object):
190
'''Declare the permission required for use of a method via the REST API.
192
def __init__(self, permission):
193
self.permission = permission
195
def __call__(self, func):
196
func._rest_api_permission = self.permission