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

« back to all changes in this revision

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

  • Committer: David Coles
  • Date: 2010-07-29 10:51:30 UTC
  • Revision ID: coles.david@gmail.com-20100729105130-el3ut0zwisvgvthu
Fix issue with CodeMirror being wider than the screen and scrolling.

Seems like width: 100% + padding/margin causes the actual width to be more 
than 100%. Though if the CodeMirror element just auto-calculate the width it's 
fine. Also fixed similar issue with the <textbox>.

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