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

« back to all changes in this revision

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

ivle.webapp.base.test:
 - Test named operations on JSONRESTView.
 - Check for MethodNotAllowed where appropriate, not BadRequest.

ivle.webapp.base.views#JSONRESTView:
 - Add named operation support.
 - Raise MethodNotAllowed instead of BadRequest for bad methods.

ivle.webapp.base.views#named_operation: Add. Decorator to export named ops.
ivle.webapp.errors: Add MethodNotAllowed (405).

Show diffs side-by-side

added added

removed removed

Lines of Context:
18
18
# Author: Matt Giuca, Will Grant
19
19
 
20
20
import inspect
 
21
import cgi
21
22
import os.path
22
23
 
23
24
import cjson
25
26
 
26
27
import ivle.conf
27
28
import ivle.util
28
 
 
29
 
from ivle.webapp.errors import BadRequest
 
29
from ivle.webapp.errors import BadRequest, MethodNotAllowed
30
30
 
31
31
class BaseView(object):
32
32
    """
65
65
    def render(self, req):
66
66
        if req.method == 'GET':
67
67
            outjson = self.GET(req)
 
68
        # Since PATCH isn't yet an official HTTP method, we allow users to
 
69
        # turn a PUT into a PATCH by supplying a special header.
68
70
        elif req.method == 'PATCH' or (req.method == 'PUT' and
69
71
              'X-IVLE-Patch-Semantics' in req.headers_in and
70
72
              req.headers_in['X-IVLE-Patch-Semantics'].lower() == 'yes'):
71
73
            outjson = self.PATCH(req, cjson.decode(req.read()))
72
74
        elif req.method == 'PUT':
73
75
            outjson = self.PUT(req, cjson.decode(req.read()))
 
76
        # POST implies named operation.
 
77
        elif req.method == 'POST':
 
78
            # TODO: Check Content-Type and implement multipart/form-data.
 
79
            opargs = dict(cgi.parse_qsl(req.read()))
 
80
            try:
 
81
                opname = opargs['ivle.op']
 
82
                del opargs['ivle.op']
 
83
            except KeyError:
 
84
                raise BadRequest('No named operation specified.')
 
85
 
 
86
            try:
 
87
                op = getattr(self, opname)
 
88
            except AttributeError:
 
89
                raise BadRequest('Invalid named operation.')
 
90
 
 
91
            if not hasattr(op, '_rest_api_callable') or \
 
92
               not op._rest_api_callable:
 
93
                raise BadRequest('Invalid named operation.')
 
94
 
 
95
            # Find any missing arguments, except for the first one (self).
 
96
            argspec = inspect.getargspec(op)
 
97
            missing = frozenset(argspec[0][1:]) - frozenset(opargs.keys())
 
98
            if missing:
 
99
                raise BadRequest('Missing arguments: ' + ', '.join(missing))
 
100
 
 
101
            outjson = op(**opargs)
74
102
        else:
75
 
            raise BadRequest
 
103
            raise MethodNotAllowed()
 
104
 
76
105
        req.content_type = self.content_type
77
106
        if outjson is not None:
78
107
            req.write(cjson.encode(outjson))
79
108
            req.write("\n")
80
 
            
 
109
 
 
110
def named_operation(meth):
 
111
    '''Declare a function to be accessible to HTTP users via the REST API.
 
112
    '''
 
113
    meth._rest_api_callable = True
 
114
    return meth
 
115
 
81
116
class XHTMLView(BaseView):
82
117
    """
83
118
    A view which provides a base class for views which need to return XHTML