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

« back to all changes in this revision

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

Split up ivle.webapp.base.views into ivle.webapp.base.{rest,xhtml}, as it was
getting rather large and messy.

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
 
 
31
20
class BaseView(object):
32
21
    """
33
22
    Abstract base class for all view objects.
36
25
        pass
37
26
    def render(self, req):
38
27
        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)