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.errors import BadRequest, MethodNotAllowed, Unauthorized
28
31
class RESTView(BaseView):
49
52
lambda self: [m for m in ('GET', 'PUT', 'PATCH')
50
53
if hasattr(self, m)] + ['POST'])
55
def authorize(self, req):
56
return True # Real authz performed in render().
58
def authorize_method(self, req, op):
59
if not hasattr(op, '_rest_api_permission'):
62
if op._rest_api_permission not in self.get_permissions(req.user):
65
def convert_bool(self, value):
66
if value in ('True', 'true', True):
68
elif value in ('False', 'false', False):
52
73
def render(self, req):
53
74
if req.method not in self._allowed_methods:
54
75
raise MethodNotAllowed(allowed=self._allowed_methods)
56
77
if req.method == 'GET':
78
self.authorize_method(req, self.GET)
57
79
outjson = self.GET(req)
58
80
# Since PATCH isn't yet an official HTTP method, we allow users to
59
81
# turn a PUT into a PATCH by supplying a special header.
60
82
elif req.method == 'PATCH' or (req.method == 'PUT' and
61
83
'X-IVLE-Patch-Semantics' in req.headers_in and
62
84
req.headers_in['X-IVLE-Patch-Semantics'].lower() == 'yes'):
85
self.authorize_method(req, self.PATCH)
64
87
input = cjson.decode(req.read())
65
88
except cjson.DecodeError:
66
89
raise BadRequest('Invalid JSON data')
67
90
outjson = self.PATCH(req, input)
68
91
elif req.method == 'PUT':
92
self.authorize_method(req, self.PUT)
70
94
input = cjson.decode(req.read())
71
95
except cjson.DecodeError:
90
115
not op._rest_api_callable:
91
116
raise BadRequest('Invalid named operation.')
118
self.authorize_method(req, op)
93
120
# Find any missing arguments, except for the first two (self, req)
94
121
(args, vaargs, varkw, defaults) = inspect.getargspec(op)
116
143
outjson = op(req, **opargs)
118
145
req.content_type = self.content_type
146
self.write_json(req, outjson)
148
#This is a separate function to allow additional data to be passed through
149
def write_json(self, req, outjson):
119
150
if outjson is not None:
120
151
req.write(cjson.encode(outjson))
123
def named_operation(meth):
155
class XHTMLRESTView(JSONRESTView):
156
"""A special type of RESTView which takes enhances the standard JSON
157
with genshi XHTML functions.
159
XHTMLRESTViews should have a template, which is rendered using their
160
context. This is returned in the JSON as 'html'"""
162
ctx = genshi.template.Context()
164
def __init__(self, req, *args, **kwargs):
166
setattr(self, key, kwargs[key])
168
def render_fragment(self):
169
if self.template is None:
170
raise NotImplementedError()
172
rest_template = os.path.join(os.path.dirname(
173
inspect.getmodule(self).__file__), self.template)
174
loader = genshi.template.TemplateLoader(".", auto_reload=True)
175
tmpl = loader.load(rest_template)
177
return tmpl.generate(self.ctx).render('xhtml', doctype='xhtml')
179
# This renders the template and adds it to the json
180
def write_json(self, req, outjson):
181
outjson["html"] = self.render_fragment()
182
req.write(cjson.encode(outjson))
185
class named_operation(object):
124
186
'''Declare a function to be accessible to HTTP users via the REST API.
126
meth._rest_api_callable = True
188
def __init__(self, permission):
189
self.permission = permission
191
def __call__(self, func):
192
func._rest_api_callable = True
193
func._rest_api_permission = self.permission
196
class require_permission(object):
197
'''Declare the permission required for use of a method via the REST API.
199
def __init__(self, permission):
200
self.permission = permission
202
def __call__(self, func):
203
func._rest_api_permission = self.permission