~azzar1/unity/add-show-desktop-key

« back to all changes in this revision

Viewing changes to ivle/webapp/base/rest.py

  • Committer: William Grant
  • Date: 2009-05-31 01:34:26 UTC
  • mto: (1281.1.8 aufsless)
  • mto: This revision was merged to the branch mainline in revision 1300.
  • Revision ID: grantw@unimelb.edu.au-20090531013426-ys8aifjtbn8cis7i
ivle.makeuser.make_jail creates an appropriate /tmp.

Show diffs side-by-side

added added

removed removed

Lines of Context:
17
17
 
18
18
# Author: Matt Giuca, Will Grant, Nick Chadwick
19
19
 
 
20
import os
20
21
import cgi
21
 
import functools
 
22
import urlparse
22
23
import inspect
23
 
import os
24
 
import urlparse
25
 
 
26
 
try:
27
 
    import json
28
 
except ImportError:
29
 
    import simplejson as json
30
 
 
 
24
 
 
25
import cjson
31
26
import genshi.template
32
27
 
33
28
from ivle.webapp.base.views import BaseView
34
 
from ivle.webapp.base.xhtml import GenshiLoaderMixin
35
29
from ivle.webapp.errors import BadRequest, MethodNotAllowed, Unauthorized
36
30
 
37
31
class RESTView(BaseView):
41
35
    """
42
36
    content_type = "application/octet-stream"
43
37
 
 
38
    def __init__(self, req, *args, **kwargs):
 
39
        for key in kwargs:
 
40
            setattr(self, key, kwargs[key])
 
41
 
44
42
    def render(self, req):
45
43
        raise NotImplementedError()
46
44
 
61
59
        if not hasattr(op, '_rest_api_permission'):
62
60
            raise Unauthorized()
63
61
 
64
 
        if (op._rest_api_permission not in
65
 
            self.get_permissions(req.user, req.config)):
 
62
        if op._rest_api_permission not in self.get_permissions(req.user):
66
63
            raise Unauthorized()
67
64
    
68
65
    def convert_bool(self, value):
78
75
            raise MethodNotAllowed(allowed=self._allowed_methods)
79
76
 
80
77
        if req.method == 'GET':
81
 
            qargs = dict(cgi.parse_qsl(
82
 
                urlparse.urlparse(req.unparsed_uri).query,
83
 
                keep_blank_values=1))
84
 
            if 'ivle.op' in qargs:
85
 
                outjson = self._named_operation(req, qargs, readonly=True)
86
 
            else:
87
 
                self.authorize_method(req, self.GET)
88
 
                outjson = self.GET(req)
 
78
            self.authorize_method(req, self.GET)
 
79
            outjson = self.GET(req)
89
80
        # Since PATCH isn't yet an official HTTP method, we allow users to
90
81
        # turn a PUT into a PATCH by supplying a special header.
91
82
        elif req.method == 'PATCH' or (req.method == 'PUT' and
93
84
              req.headers_in['X-IVLE-Patch-Semantics'].lower() == 'yes'):
94
85
            self.authorize_method(req, self.PATCH)
95
86
            try:
96
 
                input = json.loads(req.read())
97
 
            except ValueError:
 
87
                input = cjson.decode(req.read())
 
88
            except cjson.DecodeError:
98
89
                raise BadRequest('Invalid JSON data')
99
90
            outjson = self.PATCH(req, input)
100
91
        elif req.method == 'PUT':
101
92
            self.authorize_method(req, self.PUT)
102
93
            try:
103
 
                input = json.loads(req.read())
104
 
            except ValueError:
 
94
                input = cjson.decode(req.read())
 
95
            except cjson.DecodeError:
105
96
                raise BadRequest('Invalid JSON data')
106
97
            outjson = self.PUT(req, input)
107
98
        # POST implies named operation.
109
100
            # TODO: Check Content-Type and implement multipart/form-data.
110
101
            data = req.read()
111
102
            opargs = dict(cgi.parse_qsl(data, keep_blank_values=1))
112
 
            outjson = self._named_operation(req, opargs)
 
103
            try:
 
104
                opname = opargs['ivle.op']
 
105
                del opargs['ivle.op']
 
106
            except KeyError:
 
107
                raise BadRequest('No named operation specified.')
 
108
 
 
109
            try:
 
110
                op = getattr(self, opname)
 
111
            except AttributeError:
 
112
                raise BadRequest('Invalid named operation.')
 
113
 
 
114
            if not hasattr(op, '_rest_api_callable') or \
 
115
               not op._rest_api_callable:
 
116
                raise BadRequest('Invalid named operation.')
 
117
 
 
118
            self.authorize_method(req, op)
 
119
 
 
120
            # Find any missing arguments, except for the first two (self, req)
 
121
            (args, vaargs, varkw, defaults) = inspect.getargspec(op)
 
122
            args = args[2:]
 
123
 
 
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):],
 
127
            # we are OK.
 
128
            unspec = set(args) - set(opargs.keys())
 
129
            if unspec and not defaults:
 
130
                raise BadRequest('Missing arguments: ' + ', '.join(unspec))
 
131
 
 
132
            unspec = [k for k in unspec if k not in args[-len(defaults):]]
 
133
 
 
134
            if unspec:
 
135
                raise BadRequest('Missing arguments: ' + ', '.join(unspec))
 
136
 
 
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))
 
142
 
 
143
            outjson = op(req, **opargs)
113
144
 
114
145
        req.content_type = self.content_type
115
146
        self.write_json(req, outjson)
117
148
    #This is a separate function to allow additional data to be passed through
118
149
    def write_json(self, req, outjson):
119
150
        if outjson is not None:
120
 
            req.write(json.dumps(outjson))
 
151
            req.write(cjson.encode(outjson))
121
152
            req.write("\n")
122
153
 
123
 
    def _named_operation(self, req, opargs, readonly=False):
124
 
        try:
125
 
            opname = opargs['ivle.op']
126
 
            del opargs['ivle.op']
127
 
        except KeyError:
128
 
            raise BadRequest('No named operation specified.')
129
 
 
130
 
        try:
131
 
            op = getattr(self, opname)
132
 
        except AttributeError:
133
 
            raise BadRequest('Invalid named operation.')
134
 
 
135
 
        if not hasattr(op, '_rest_api_callable') or \
136
 
           not op._rest_api_callable:
137
 
            raise BadRequest('Invalid named operation.')
138
 
 
139
 
        if readonly and op._rest_api_write_operation:
140
 
            raise BadRequest('POST required for write operation.')
141
 
 
142
 
        self.authorize_method(req, op)
143
 
 
144
 
        # Find any missing arguments, except for the first two (self, req)
145
 
        (args, vaargs, varkw, defaults) = inspect.getargspec(op)
146
 
        args = args[2:]
147
 
 
148
 
        # To find missing arguments, we eliminate the provided arguments
149
 
        # from the set of remaining function signature arguments. If the
150
 
        # remaining signature arguments are in the args[-len(defaults):],
151
 
        # we are OK.
152
 
        unspec = set(args) - set(opargs.keys())
153
 
        if unspec and not defaults:
154
 
            raise BadRequest('Missing arguments: ' + ', '.join(unspec))
155
 
 
156
 
        unspec = [k for k in unspec if k not in args[-len(defaults):]]
157
 
 
158
 
        if unspec:
159
 
            raise BadRequest('Missing arguments: ' + ', '.join(unspec))
160
 
 
161
 
        # We have extra arguments if the are no match args in the function
162
 
        # signature, AND there is no **.
163
 
        extra = set(opargs.keys()) - set(args)
164
 
        if extra and not varkw:
165
 
            raise BadRequest('Extra arguments: ' + ', '.join(extra))
166
 
 
167
 
        return op(req, **opargs)
168
 
 
169
 
 
170
 
class XHTMLRESTView(GenshiLoaderMixin, JSONRESTView):
 
154
 
 
155
class XHTMLRESTView(JSONRESTView):
171
156
    """A special type of RESTView which takes enhances the standard JSON
172
157
    with genshi XHTML functions.
173
158
    
176
161
    template = None
177
162
    ctx = genshi.template.Context()
178
163
 
 
164
    def __init__(self, req, *args, **kwargs):
 
165
        for key in kwargs:
 
166
            setattr(self, key, kwargs[key])
 
167
    
179
168
    def render_fragment(self):
180
169
        if self.template is None:
181
170
            raise NotImplementedError()
182
171
 
183
172
        rest_template = os.path.join(os.path.dirname(
184
173
                inspect.getmodule(self).__file__), self.template)
185
 
        tmpl = self._loader.load(rest_template)
 
174
        loader = genshi.template.TemplateLoader(".", auto_reload=True)
 
175
        tmpl = loader.load(rest_template)
186
176
 
187
177
        return tmpl.generate(self.ctx).render('xhtml', doctype='xhtml')
188
178
    
189
179
    # This renders the template and adds it to the json
190
180
    def write_json(self, req, outjson):
191
181
        outjson["html"] = self.render_fragment()
192
 
        req.write(json.dumps(outjson))
 
182
        req.write(cjson.encode(outjson))
193
183
        req.write("\n")
194
184
 
195
 
class _named_operation(object):
 
185
class named_operation(object):
196
186
    '''Declare a function to be accessible to HTTP users via the REST API.
197
187
    '''
198
 
    def __init__(self, write_operation, permission):
199
 
        self.write_operation = write_operation
 
188
    def __init__(self, permission):
200
189
        self.permission = permission
201
190
 
202
191
    def __call__(self, func):
203
192
        func._rest_api_callable = True
204
 
        func._rest_api_write_operation = self.write_operation
205
193
        func._rest_api_permission = self.permission
206
194
        return func
207
195
 
208
 
write_operation = functools.partial(_named_operation, True)
209
 
read_operation = functools.partial(_named_operation, False)
210
 
 
211
196
class require_permission(object):
212
197
    '''Declare the permission required for use of a method via the REST API.
213
198
    '''