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

« back to all changes in this revision

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

ivle.webapp.console.service: Port www/apps/consoleservice to new framework.
    consoleservice now lives under /console/service, and actions are taken
    in the "ivle.op" query argument, not as the last segment of the path.
    Otherwise the interface is identical.
www/apps/console/console.js: Update to new consoleservice interface.

Show diffs side-by-side

added added

removed removed

Lines of Context:
17
17
 
18
18
# Author: Matt Giuca, Will Grant
19
19
 
 
20
import inspect
 
21
import cgi
 
22
import os.path
 
23
 
 
24
import cjson
 
25
import genshi.template
 
26
 
 
27
import ivle.conf
 
28
import ivle.util
 
29
from ivle.webapp.errors import BadRequest, MethodNotAllowed
 
30
 
20
31
class BaseView(object):
21
32
    """
22
33
    Abstract base class for all view objects.
23
34
    """
24
 
    def render(self, req):
25
 
        raise NotImplementedError()
26
 
 
27
 
    def get_permissions(self, user):
28
 
        return self.context.get_permissions(user)
29
 
 
30
 
    def authorize(self, req):
31
 
        self.perms = self.get_permissions(req.user)
32
 
 
33
 
        return self.permission is None or self.permission in self.perms
 
35
    def __init__(self, req, **kwargs):
 
36
        pass
 
37
    def render(self, req):
 
38
        pass
 
39
 
 
40
class RESTView(BaseView):
 
41
    """
 
42
    A view which provides a RESTful interface. The content type is
 
43
    unspecified (see JSONRESTView for a specific content type).
 
44
    """
 
45
    content_type = "application/octet-stream"
 
46
 
 
47
    def __init__(self, req, *args, **kwargs):
 
48
        pass
 
49
 
 
50
    def render(self, req):
 
51
        if req.method == 'GET':
 
52
            outstr = self.GET(req)
 
53
        # XXX PATCH hack
 
54
        if req.method == 'PUT':
 
55
            outstr = self.PATCH(req, req.read())
 
56
        req.content_type = self.content_type
 
57
        req.write(outstr)
 
58
 
 
59
class JSONRESTView(RESTView):
 
60
    """
 
61
    A special case of RESTView which deals entirely in JSON.
 
62
    """
 
63
    content_type = "application/json"
 
64
 
 
65
    _allowed_methods = property(
 
66
        lambda self: [m for m in ('GET', 'PUT', 'PATCH')
 
67
                      if hasattr(self, m)] + ['POST'])
 
68
 
 
69
    def render(self, req):
 
70
        if req.method not in self._allowed_methods:
 
71
            raise MethodNotAllowed(allowed=self._allowed_methods)
 
72
 
 
73
        if req.method == 'GET':
 
74
            outjson = self.GET(req)
 
75
        # Since PATCH isn't yet an official HTTP method, we allow users to
 
76
        # turn a PUT into a PATCH by supplying a special header.
 
77
        elif req.method == 'PATCH' or (req.method == 'PUT' and
 
78
              'X-IVLE-Patch-Semantics' in req.headers_in and
 
79
              req.headers_in['X-IVLE-Patch-Semantics'].lower() == 'yes'):
 
80
            outjson = self.PATCH(req, cjson.decode(req.read()))
 
81
        elif req.method == 'PUT':
 
82
            outjson = self.PUT(req, cjson.decode(req.read()))
 
83
        # POST implies named operation.
 
84
        elif req.method == 'POST':
 
85
            # TODO: Check Content-Type and implement multipart/form-data.
 
86
            opargs = dict(cgi.parse_qsl(req.read()))
 
87
            try:
 
88
                opname = opargs['ivle.op']
 
89
                del opargs['ivle.op']
 
90
            except KeyError:
 
91
                raise BadRequest('No named operation specified.')
 
92
 
 
93
            try:
 
94
                op = getattr(self, opname)
 
95
            except AttributeError:
 
96
                raise BadRequest('Invalid named operation.')
 
97
 
 
98
            if not hasattr(op, '_rest_api_callable') or \
 
99
               not op._rest_api_callable:
 
100
                raise BadRequest('Invalid named operation.')
 
101
 
 
102
            # Find any missing arguments, except for the first two (self, req)
 
103
            (args, vaargs, varkw, defaults) = inspect.getargspec(op)
 
104
            args = args[2:]
 
105
 
 
106
            # To find missing arguments, we eliminate the provided arguments
 
107
            # from the set of remaining function signature arguments. If the
 
108
            # remaining signature arguments are in the args[-len(defaults):],
 
109
            # we are OK.
 
110
            unspec = set(args) - set(opargs.keys())
 
111
            if unspec and not defaults:
 
112
                raise BadRequest('Missing arguments: ' + ','.join(unspec))
 
113
 
 
114
            unspec = [k for k in unspec if k not in args[-len(defaults):]]
 
115
 
 
116
            if unspec:
 
117
                raise BadRequest('Missing arguments: ' + ','.join(unspec))
 
118
 
 
119
            # We have extra arguments if the are no match args in the function
 
120
            # signature, AND there is no **.
 
121
            extra = set(opargs.keys()) - set(args)
 
122
            if extra and not varkw:
 
123
                raise BadRequest('Extra arguments: ' + ', '.join(extra))
 
124
 
 
125
            outjson = op(req, **opargs)
 
126
        else:
 
127
            raise AssertionError('Unknown method somehow got through.')
 
128
 
 
129
        req.content_type = self.content_type
 
130
        if outjson is not None:
 
131
            req.write(cjson.encode(outjson))
 
132
            req.write("\n")
 
133
 
 
134
def named_operation(meth):
 
135
    '''Declare a function to be accessible to HTTP users via the REST API.
 
136
    '''
 
137
    meth._rest_api_callable = True
 
138
    return meth
 
139
 
 
140
class XHTMLView(BaseView):
 
141
    """
 
142
    A view which provides a base class for views which need to return XHTML
 
143
    It is expected that apps which use this view will be written using Genshi
 
144
    templates.
 
145
    """
 
146
    def __init__(self, req, **kwargs):
 
147
        for key in kwargs:
 
148
          setattr(self, key, kwargs[key])
 
149
        
 
150
    def render(self, req):
 
151
        req.content_type = 'text/html' # TODO: Detect application/xhtml+xml
 
152
        ctx = genshi.template.Context()
 
153
        self.populate(req, ctx)
 
154
        self.populate_headings(req, ctx)
 
155
        
 
156
        ctx['app_styles'] = req.styles
 
157
        ctx['scripts'] = req.scripts
 
158
        ctx['scripts_init'] = req.scripts_init
 
159
        app_template = os.path.join(os.path.dirname(
 
160
                        inspect.getmodule(self).__file__), self.app_template) 
 
161
        req.write_html_head_foot = False
 
162
        loader = genshi.template.TemplateLoader(".", auto_reload=True)
 
163
        tmpl = loader.load(app_template)
 
164
        app = tmpl.generate(ctx)
 
165
        ctx['app_template'] = app
 
166
        tmpl = loader.load(os.path.join(os.path.dirname(__file__), 
 
167
                                                        'ivle-headings.html'))
 
168
        req.write(tmpl.generate(ctx).render('xhtml', doctype='xhtml'))
 
169
 
 
170
    def populate_headings(self, req, ctx):
 
171
        ctx['favicon'] = None
 
172
        ctx['root_dir'] = ivle.conf.root_dir
 
173
        ctx['public_host'] = ivle.conf.public_host
 
174
        ctx['write_javascript_settings'] = req.write_javascript_settings
 
175
        if req.user:
 
176
            ctx['login'] = req.user.login
 
177
            ctx['logged_in'] = True
 
178
            ctx['nick'] = req.user.nick
 
179
        else:
 
180
            ctx['login'] = None
 
181
        ctx['publicmode'] = req.publicmode
 
182
        ctx['apps_in_tabs'] = []
 
183
        for urlname in ivle.conf.apps.apps_in_tabs:
 
184
            new_app = {}
 
185
            app = ivle.conf.apps.app_url[urlname]
 
186
            new_app['this_app'] = urlname == self.appname
 
187
            if app.icon:
 
188
                new_app['has_icon'] = True
 
189
                icon_dir = ivle.conf.apps.app_icon_dir
 
190
                icon_url = ivle.util.make_path(os.path.join(icon_dir, app.icon))
 
191
                new_app['icon_url'] = icon_url
 
192
                if new_app['this_app']:
 
193
                    ctx['favicon'] = icon_url
 
194
            else:
 
195
                new_app['has_icon'] = False
 
196
            new_app['path'] = ivle.util.make_path(urlname)
 
197
            new_app['desc'] = app.desc
 
198
            new_app['name'] = app.name
 
199
            ctx['apps_in_tabs'].append(new_app)