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

« back to all changes in this revision

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

  • Committer: mattgiuca
  • Date: 2007-12-21 04:31:48 UTC
  • Revision ID: svn-v3-trunk0:2b9c9e99-6f39-0410-b283-7f802c844ae2:trunk:123
setup.py: install creates a symlink from python pointing to python2.5.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# IVLE - Informatics Virtual Learning Environment
2
 
# Copyright (C) 2007-2009 The University of Melbourne
3
 
#
4
 
# This program is free software; you can redistribute it and/or modify
5
 
# it under the terms of the GNU General Public License as published by
6
 
# the Free Software Foundation; either version 2 of the License, or
7
 
# (at your option) any later version.
8
 
#
9
 
# This program is distributed in the hope that it will be useful,
10
 
# but WITHOUT ANY WARRANTY; without even the implied warranty of
11
 
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
 
# GNU General Public License for more details.
13
 
#
14
 
# You should have received a copy of the GNU General Public License
15
 
# along with this program; if not, write to the Free Software
16
 
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
17
 
 
18
 
# Author: Matt Giuca, Will Grant, Nick Chadwick
19
 
 
20
 
import cgi
21
 
import functools
22
 
import inspect
23
 
import os
24
 
import urlparse
25
 
 
26
 
try:
27
 
    import json
28
 
except ImportError:
29
 
    import simplejson as json
30
 
 
31
 
import genshi.template
32
 
 
33
 
from ivle.webapp.base.views import BaseView
34
 
from ivle.webapp.base.xhtml import GenshiLoaderMixin
35
 
from ivle.webapp.errors import BadRequest, MethodNotAllowed, Unauthorized
36
 
 
37
 
class RESTView(BaseView):
38
 
    """
39
 
    A view which provides a RESTful interface. The content type is
40
 
    unspecified (see JSONRESTView for a specific content type).
41
 
    """
42
 
    content_type = "application/octet-stream"
43
 
 
44
 
    def render(self, req):
45
 
        raise NotImplementedError()
46
 
 
47
 
class JSONRESTView(RESTView):
48
 
    """
49
 
    A special case of RESTView which deals entirely in JSON.
50
 
    """
51
 
    content_type = "application/json"
52
 
 
53
 
    _allowed_methods = property(
54
 
        lambda self: [m for m in ('GET', 'PUT', 'PATCH')
55
 
                      if hasattr(self, m)] + ['POST'])
56
 
 
57
 
    def authorize(self, req):
58
 
        return True # Real authz performed in render().
59
 
 
60
 
    def authorize_method(self, req, op):
61
 
        if not hasattr(op, '_rest_api_permission'):
62
 
            raise Unauthorized()
63
 
 
64
 
        if (op._rest_api_permission not in
65
 
            self.get_permissions(req.user, req.config)):
66
 
            raise Unauthorized()
67
 
    
68
 
    def convert_bool(self, value):
69
 
        if value in ('True', 'true', True):
70
 
            return True
71
 
        elif value in ('False', 'false', False):
72
 
            return False
73
 
        else:
74
 
            raise BadRequest()
75
 
 
76
 
    def render(self, req):
77
 
        if req.method not in self._allowed_methods:
78
 
            raise MethodNotAllowed(allowed=self._allowed_methods)
79
 
 
80
 
        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)
89
 
        # Since PATCH isn't yet an official HTTP method, we allow users to
90
 
        # turn a PUT into a PATCH by supplying a special header.
91
 
        elif req.method == 'PATCH' or (req.method == 'PUT' and
92
 
              'X-IVLE-Patch-Semantics' in req.headers_in and
93
 
              req.headers_in['X-IVLE-Patch-Semantics'].lower() == 'yes'):
94
 
            self.authorize_method(req, self.PATCH)
95
 
            try:
96
 
                input = json.loads(req.read())
97
 
            except ValueError:
98
 
                raise BadRequest('Invalid JSON data')
99
 
            outjson = self.PATCH(req, input)
100
 
        elif req.method == 'PUT':
101
 
            self.authorize_method(req, self.PUT)
102
 
            try:
103
 
                input = json.loads(req.read())
104
 
            except ValueError:
105
 
                raise BadRequest('Invalid JSON data')
106
 
            outjson = self.PUT(req, input)
107
 
        # POST implies named operation.
108
 
        elif req.method == 'POST':
109
 
            # TODO: Check Content-Type and implement multipart/form-data.
110
 
            data = req.read()
111
 
            opargs = dict(cgi.parse_qsl(data, keep_blank_values=1))
112
 
            outjson = self._named_operation(req, opargs)
113
 
 
114
 
        req.content_type = self.content_type
115
 
        self.write_json(req, outjson)
116
 
 
117
 
    #This is a separate function to allow additional data to be passed through
118
 
    def write_json(self, req, outjson):
119
 
        if outjson is not None:
120
 
            req.write(json.dumps(outjson))
121
 
            req.write("\n")
122
 
 
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):
171
 
    """A special type of RESTView which takes enhances the standard JSON
172
 
    with genshi XHTML functions.
173
 
    
174
 
    XHTMLRESTViews should have a template, which is rendered using their
175
 
    context. This is returned in the JSON as 'html'"""
176
 
    template = None
177
 
    ctx = genshi.template.Context()
178
 
 
179
 
    def render_fragment(self):
180
 
        if self.template is None:
181
 
            raise NotImplementedError()
182
 
 
183
 
        rest_template = os.path.join(os.path.dirname(
184
 
                inspect.getmodule(self).__file__), self.template)
185
 
        tmpl = self._loader.load(rest_template)
186
 
 
187
 
        return tmpl.generate(self.ctx).render('xhtml', doctype='xhtml')
188
 
    
189
 
    # This renders the template and adds it to the json
190
 
    def write_json(self, req, outjson):
191
 
        outjson["html"] = self.render_fragment()
192
 
        req.write(json.dumps(outjson))
193
 
        req.write("\n")
194
 
 
195
 
class _named_operation(object):
196
 
    '''Declare a function to be accessible to HTTP users via the REST API.
197
 
    '''
198
 
    def __init__(self, write_operation, permission):
199
 
        self.write_operation = write_operation
200
 
        self.permission = permission
201
 
 
202
 
    def __call__(self, func):
203
 
        func._rest_api_callable = True
204
 
        func._rest_api_write_operation = self.write_operation
205
 
        func._rest_api_permission = self.permission
206
 
        return func
207
 
 
208
 
write_operation = functools.partial(_named_operation, True)
209
 
read_operation = functools.partial(_named_operation, False)
210
 
 
211
 
class require_permission(object):
212
 
    '''Declare the permission required for use of a method via the REST API.
213
 
    '''
214
 
    def __init__(self, permission):
215
 
        self.permission = permission
216
 
 
217
 
    def __call__(self, func):
218
 
        func._rest_api_permission = self.permission
219
 
        return func