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

1099.1.34 by William Grant
Split up ivle.webapp.base.views into ivle.webapp.base.{rest,xhtml}, as it was
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: Nick Chadwick
19
20
import inspect
21
import os.path
1099.1.120 by William Grant
Move the login machinery to the new framework.
22
import urllib
1099.1.34 by William Grant
Split up ivle.webapp.base.views into ivle.webapp.base.{rest,xhtml}, as it was
23
24
import genshi.template
25
1099.1.60 by William Grant
Add support in XHTMLView for plugin styles and scripts.
26
from ivle.webapp.media import media_url
1092.1.24 by William Grant
Convince the main XHTML template to use versioned styles/scripts, and move the
27
from ivle.webapp.core import Plugin as CorePlugin
1099.1.34 by William Grant
Split up ivle.webapp.base.views into ivle.webapp.base.{rest,xhtml}, as it was
28
from ivle.webapp.base.views import BaseView
1099.1.115 by William Grant
Add tabs to the new framework. Move the app icons into the apps themselves.
29
from ivle.webapp.base.plugins import ViewPlugin, OverlayPlugin
1099.1.96 by William Grant
Add an XHTMLUnauthorizedView which redirects unauthenticated users to the
30
from ivle.webapp.errors import HTTPError, Unauthorized
1294.2.116 by William Grant
Merge from object-publishing.
31
from ivle.webapp.publisher import NoPath
1294.2.90 by William Grant
Produce breadcrumb menus.
32
from ivle.webapp.breadcrumbs import Breadcrumber
1099.1.34 by William Grant
Split up ivle.webapp.base.views into ivle.webapp.base.{rest,xhtml}, as it was
33
1720 by William Grant
Share one TemplateLoader between every instance of every view, so we cache EVERYTHING.
34
35
class GenshiLoaderMixin(object):
36
    """Mixin for classes which need to render Genshi templates.
37
38
    A TemplateLoader is shared between all instances, so templates are
39
    cached across multiple instances and therefore also requests.
40
    """
41
    _loader = None
42
43
    def __init__(self, *args, **kwargs):
44
        super(GenshiLoaderMixin, self).__init__(*args, **kwargs)
45
46
        # We use a single loader for all views, so we can cache the
47
        # parsed templates. auto_reload is convenient and has a minimal
48
        # performance penalty, so we'll leave it on.
49
        if GenshiLoaderMixin._loader is None:
50
            GenshiLoaderMixin._loader = genshi.template.TemplateLoader(
51
                ".", auto_reload=True,
52
                max_cache_size=100)
53
54
55
class XHTMLView(GenshiLoaderMixin, BaseView):
1099.1.34 by William Grant
Split up ivle.webapp.base.views into ivle.webapp.base.{rest,xhtml}, as it was
56
    """
57
    A view which provides a base class for views which need to return XHTML
58
    It is expected that apps which use this view will be written using Genshi
59
    templates.
60
    """
1099.1.60 by William Grant
Add support in XHTMLView for plugin styles and scripts.
61
62
    template = 'template.html'
1099.1.129 by William Grant
Allow XHTML views to specify that they cannot have overlays.
63
    allow_overlays = True
1294.2.127 by William Grant
Allow views to specify text for their breadcrumb.
64
    breadcrumb_text = None
1294.2.115 by William Grant
Don't share XHTMLView's attributes between instances. oops.
65
66
    def __init__(self, *args, **kwargs):
67
        super(XHTMLView, self).__init__(*args, **kwargs)
68
69
        self.overlay_blacklist = []
70
71
        self.plugin_scripts = {}
72
        self.plugin_styles = {}
73
        self.scripts_init = []
74
75
        self.extra_breadcrumbs = []
76
        self.overlay_blacklist = []
1099.1.60 by William Grant
Add support in XHTMLView for plugin styles and scripts.
77
1294.2.108 by William Grant
Display breadcrumbs on error pages too.
78
    def get_context_ancestry(self, req):
1294.2.118 by William Grant
Merge from object-publishing.
79
        return req.publisher.get_ancestors(self.context)
1294.2.108 by William Grant
Display breadcrumbs on error pages too.
80
1148 by William Grant
Allow XHTMLView subclasses to apply filters to the generated app XML stream.
81
    def filter(self, stream, ctx):
82
        return stream
83
1099.1.34 by William Grant
Split up ivle.webapp.base.views into ivle.webapp.base.{rest,xhtml}, as it was
84
    def render(self, req):
85
        req.content_type = 'text/html' # TODO: Detect application/xhtml+xml
1099.1.38 by William Grant
ivle.webapp.base.xhtml.XHTMLView: Split the global and view contexts.
86
87
        # View template
88
        viewctx = genshi.template.Context()
89
        self.populate(req, viewctx)
90
91
        # The template is found in the directory of the module containing the
92
        # view.
1099.1.34 by William Grant
Split up ivle.webapp.base.views into ivle.webapp.base.{rest,xhtml}, as it was
93
        app_template = os.path.join(os.path.dirname(
1099.1.35 by William Grant
ivle.webapp.base.xhtml#XHTMLView: Rename app_template to template (the things
94
                        inspect.getmodule(self).__file__), self.template) 
1706 by William Grant
Use a single (XHTMLView._loader, a class (not instance) variable) genshi TemplateLoader, so we can cache parsed templates.
95
        tmpl = self._loader.load(app_template)
1148 by William Grant
Allow XHTMLView subclasses to apply filters to the generated app XML stream.
96
        app = self.filter(tmpl.generate(viewctx), viewctx)
1099.1.38 by William Grant
ivle.webapp.base.xhtml.XHTMLView: Split the global and view contexts.
97
1282.1.2 by William Grant
Remove use of req.{styles,scripts{,_init}} from XHTMLView.
98
        view_scripts = []
1099.1.60 by William Grant
Add support in XHTMLView for plugin styles and scripts.
99
        for plugin in self.plugin_scripts:
100
            for path in self.plugin_scripts[plugin]:
1282.1.2 by William Grant
Remove use of req.{styles,scripts{,_init}} from XHTMLView.
101
                view_scripts.append(media_url(req, plugin, path))
1099.1.60 by William Grant
Add support in XHTMLView for plugin styles and scripts.
102
1282.1.2 by William Grant
Remove use of req.{styles,scripts{,_init}} from XHTMLView.
103
        view_styles = []
1099.1.60 by William Grant
Add support in XHTMLView for plugin styles and scripts.
104
        for plugin in self.plugin_styles:
105
            for path in self.plugin_styles[plugin]:
1282.1.2 by William Grant
Remove use of req.{styles,scripts{,_init}} from XHTMLView.
106
                view_styles.append(media_url(req, plugin, path))
1099.1.60 by William Grant
Add support in XHTMLView for plugin styles and scripts.
107
1099.1.38 by William Grant
ivle.webapp.base.xhtml.XHTMLView: Split the global and view contexts.
108
        # Global template
109
        ctx = genshi.template.Context()
1282.1.2 by William Grant
Remove use of req.{styles,scripts{,_init}} from XHTMLView.
110
111
        overlay_bits = self.render_overlays(req) if req.user else [[]]*4
112
        ctx['overlays'] = overlay_bits[0]
1092.1.24 by William Grant
Convince the main XHTML template to use versioned styles/scripts, and move the
113
114
        ctx['styles'] = [media_url(req, CorePlugin, 'ivle.css')]
1282.1.2 by William Grant
Remove use of req.{styles,scripts{,_init}} from XHTMLView.
115
        ctx['styles'] += view_styles
116
        ctx['styles'] += overlay_bits[1]
1092.1.24 by William Grant
Convince the main XHTML template to use versioned styles/scripts, and move the
117
118
        ctx['scripts'] = [media_url(req, CorePlugin, path) for path in
1092.1.28 by William Grant
Only load tos.js on /+tos.
119
                           ('util.js', 'json2.js', 'md5.js')]
1160 by William Grant
Swap the version and +external in media URLs, and use media_url for jQuery.
120
        ctx['scripts'].append(media_url(req, '+external/jquery', 'jquery.js'))
1282.1.2 by William Grant
Remove use of req.{styles,scripts{,_init}} from XHTMLView.
121
        ctx['scripts'] += view_scripts
122
        ctx['scripts'] += overlay_bits[2]
1092.1.24 by William Grant
Convince the main XHTML template to use versioned styles/scripts, and move the
123
1282.1.2 by William Grant
Remove use of req.{styles,scripts{,_init}} from XHTMLView.
124
        ctx['scripts_init'] = self.scripts_init + overlay_bits[3]
1099.1.34 by William Grant
Split up ivle.webapp.base.views into ivle.webapp.base.{rest,xhtml}, as it was
125
        ctx['app_template'] = app
1100.1.9 by Matt Giuca
The IVLE logo is now an <img> element, rather than a CSS background.
126
        ctx['title_img'] = media_url(req, CorePlugin,
1294.2.74 by William Grant
Replace the title image in the root template with the breadcrumb.
127
                                     "images/chrome/root-breadcrumb.png")
1294.2.79 by William Grant
Don't crash if the ancestry can't be calculated; assume none instead.
128
        try:
1465.1.1 by William Grant
Allow breadcrumbs to specify additional crumbs to surround them.
129
            ancestry = self.get_context_ancestry(req)
1294.2.79 by William Grant
Don't crash if the ancestry can't be calculated; assume none instead.
130
        except NoPath:
1465.1.1 by William Grant
Allow breadcrumbs to specify additional crumbs to surround them.
131
            ancestry = []
132
133
        crumber = Breadcrumber(req)
134
135
        ctx['breadcrumbs'] = []
1585 by William Grant
Disable breadcrumbs in public mode.
136
        if not req.publicmode:
137
            for ancestor in ancestry:
138
                crumb = crumber.crumb(ancestor)
139
                if crumb is None:
140
                    continue
141
142
                if hasattr(crumb, 'extra_breadcrumbs_before'):
143
                    ctx['breadcrumbs'].extend(crumb.extra_breadcrumbs_before)
144
                ctx['breadcrumbs'].append(crumb)
145
                if hasattr(crumb, 'extra_breadcrumbs_after'):
146
                    ctx['breadcrumbs'].extend(crumb.extra_breadcrumbs_after)
147
148
            # If the view has specified text for a breadcrumb, add one.
149
            if self.breadcrumb_text:
150
                ctx['breadcrumbs'].append(ViewBreadcrumb(req, self))
151
152
            # Allow the view to add its own fake breadcrumbs.
153
            ctx['breadcrumbs'].extend(self.extra_breadcrumbs)
1294.2.101 by William Grant
Allow views to specify extra breadcrumbs.
154
1099.1.38 by William Grant
ivle.webapp.base.xhtml.XHTMLView: Split the global and view contexts.
155
        self.populate_headings(req, ctx)
1706 by William Grant
Use a single (XHTMLView._loader, a class (not instance) variable) genshi TemplateLoader, so we can cache parsed templates.
156
        tmpl = self._loader.load(os.path.join(os.path.dirname(__file__), 
1099.1.34 by William Grant
Split up ivle.webapp.base.views into ivle.webapp.base.{rest,xhtml}, as it was
157
                                                        'ivle-headings.html'))
158
        req.write(tmpl.generate(ctx).render('xhtml', doctype='xhtml'))
1099.1.74 by Nick Chadwick
Added overlay system and console overlay. Note that the console overlay
159
        
160
    def populate(self, req, ctx):
161
        raise NotImplementedError()
1099.1.34 by William Grant
Split up ivle.webapp.base.views into ivle.webapp.base.{rest,xhtml}, as it was
162
163
    def populate_headings(self, req, ctx):
164
        ctx['favicon'] = None
1202 by William Grant
XHTMLView no longer uses ivle.conf.
165
        ctx['root_dir'] = req.config['urls']['root']
166
        ctx['public_host'] = req.config['urls']['public_host']
167
        ctx['svn_base'] = req.config['urls']['svn_addr']
1099.1.34 by William Grant
Split up ivle.webapp.base.views into ivle.webapp.base.{rest,xhtml}, as it was
168
        ctx['write_javascript_settings'] = req.write_javascript_settings
169
        if req.user:
170
            ctx['login'] = req.user.login
171
            ctx['logged_in'] = True
172
            ctx['nick'] = req.user.nick
173
        else:
174
            ctx['login'] = None
1099.1.92 by William Grant
Fix the XHTML base template to not crash when not logged in.
175
            ctx['logged_in'] = False
1099.1.34 by William Grant
Split up ivle.webapp.base.views into ivle.webapp.base.{rest,xhtml}, as it was
176
        ctx['publicmode'] = req.publicmode
1099.1.100 by Nick Chadwick
Created a new help system.
177
        if hasattr(self, 'help'):
178
            ctx['help_path'] = self.help
1099.1.115 by William Grant
Add tabs to the new framework. Move the app icons into the apps themselves.
179
180
        ctx['apps_in_tabs'] = []
1092.1.59 by William Grant
Move the plugin loading/indexing logic into ivle.config.Config.
181
        for plugin in req.config.plugin_index[ViewPlugin]:
1099.1.115 by William Grant
Add tabs to the new framework. Move the app icons into the apps themselves.
182
            if not hasattr(plugin, 'tabs'):
183
                continue
184
185
            for tab in plugin.tabs:
1497 by Matt Giuca
Added Users tab to drop-down menu, for admins only.
186
                # tab is a tuple: name, title, desc, icon, path, weight, admin
187
                # (Admin is optional, defaults to false)
1099.1.115 by William Grant
Add tabs to the new framework. Move the app icons into the apps themselves.
188
                new_app = {}
1116 by William Grant
Move the old tutorial views into the 'subjects' tab, so they get the right
189
                new_app['this_app'] = hasattr(self, 'tab') \
190
                                      and tab[0] == self.tab
1099.1.115 by William Grant
Add tabs to the new framework. Move the app icons into the apps themselves.
191
192
                # Icon name
193
                if tab[3] is not None:
194
                    new_app['has_icon'] = True
195
                    icon_url = media_url(req, plugin, tab[3])
196
                    new_app['icon_url'] = icon_url
197
                    if new_app['this_app']:
198
                        ctx['favicon'] = icon_url
199
                else:
200
                    new_app['has_icon'] = False
1498 by Matt Giuca
user.py: All user pages are now in the tab "users", so they show the user icon
201
                # The following check is here, so it is AFTER setting the
202
                # icon, but BEFORE actually installing the tab in the menu
203
                if len(tab) > 6 and tab[6]:
204
                    # Admin-only tab
205
                    if not (req.user and req.user.admin):
206
                        break
1210 by William Grant
Use Request.make_path everywhere.
207
                new_app['path'] = req.make_path(tab[4])
1099.1.115 by William Grant
Add tabs to the new framework. Move the app icons into the apps themselves.
208
                new_app['desc'] = tab[2]
209
                new_app['name'] = tab[1]
210
                new_app['weight'] = tab[5]
211
                ctx['apps_in_tabs'].append(new_app)
212
213
        ctx['apps_in_tabs'].sort(key=lambda tab: tab['weight'])
214
1099.1.74 by Nick Chadwick
Added overlay system and console overlay. Note that the console overlay
215
    def render_overlays(self, req):
216
        """Generate XML streams for the overlays.
217
        
218
        Returns a list of streams. Populates the scripts, styles, and 
219
        scripts_init.
220
        """
221
        overlays = []
1282.1.2 by William Grant
Remove use of req.{styles,scripts{,_init}} from XHTMLView.
222
        styles = []
223
        scripts = []
224
        scripts_init = []
1099.1.129 by William Grant
Allow XHTML views to specify that they cannot have overlays.
225
        if not self.allow_overlays:
1282.1.2 by William Grant
Remove use of req.{styles,scripts{,_init}} from XHTMLView.
226
            return (overlays, styles, scripts, scripts_init)
1099.1.129 by William Grant
Allow XHTML views to specify that they cannot have overlays.
227
1092.1.59 by William Grant
Move the plugin loading/indexing logic into ivle.config.Config.
228
        for plugin in req.config.plugin_index[OverlayPlugin]:
1099.1.74 by Nick Chadwick
Added overlay system and console overlay. Note that the console overlay
229
            for overclass in plugin.overlays:
230
                if overclass in self.overlay_blacklist:
231
                    continue
232
                overlay = overclass(req)
233
                #TODO: Re-factor this to look nicer
234
                for mplugin in overlay.plugin_scripts:
235
                    for path in overlay.plugin_scripts[mplugin]:
1282.1.2 by William Grant
Remove use of req.{styles,scripts{,_init}} from XHTMLView.
236
                        scripts.append(media_url(req, mplugin, path))
1099.1.74 by Nick Chadwick
Added overlay system and console overlay. Note that the console overlay
237
238
                for mplugin in overlay.plugin_styles:
239
                    for path in overlay.plugin_styles[mplugin]:
1282.1.2 by William Grant
Remove use of req.{styles,scripts{,_init}} from XHTMLView.
240
                        styles.append(media_url(req, mplugin, path))
1099.1.91 by William Grant
Add support for custom error views. Plugins can now declare that errors
241
1282.1.2 by William Grant
Remove use of req.{styles,scripts{,_init}} from XHTMLView.
242
                scripts_init += overlay.plugin_scripts_init
1099.1.91 by William Grant
Add support for custom error views. Plugins can now declare that errors
243
1099.1.74 by Nick Chadwick
Added overlay system and console overlay. Note that the console overlay
244
                overlays.append(overlay.render(req))
1282.1.2 by William Grant
Remove use of req.{styles,scripts{,_init}} from XHTMLView.
245
        return (overlays, styles, scripts, scripts_init)
1099.1.91 by William Grant
Add support for custom error views. Plugins can now declare that errors
246
1099.1.95 by William Grant
Error views are now retrieved from a class method of the view, not the plugin
247
    @classmethod
248
    def get_error_view(cls, e):
1099.1.96 by William Grant
Add an XHTMLUnauthorizedView which redirects unauthenticated users to the
249
        view_map = {HTTPError:    XHTMLErrorView,
250
                    Unauthorized: XHTMLUnauthorizedView}
1099.1.95 by William Grant
Error views are now retrieved from a class method of the view, not the plugin
251
        for exccls in inspect.getmro(type(e)):
252
            if exccls in view_map:
253
                return view_map[exccls]
254
1099.1.91 by William Grant
Add support for custom error views. Plugins can now declare that errors
255
class XHTMLErrorView(XHTMLView):
256
    template = 'xhtmlerror.html'
257
1294.2.108 by William Grant
Display breadcrumbs on error pages too.
258
    def __init__(self, req, context, lastobj):
259
        super(XHTMLErrorView, self).__init__(req, context)
260
        self.lastobj = lastobj
261
262
    def get_context_ancestry(self, req):
1294.2.118 by William Grant
Merge from object-publishing.
263
        return req.publisher.get_ancestors(self.lastobj)
1294.2.108 by William Grant
Display breadcrumbs on error pages too.
264
1099.1.91 by William Grant
Add support for custom error views. Plugins can now declare that errors
265
    def populate(self, req, ctx):
1294.2.50 by William Grant
Replace all base view __init__s with one at the root that sets the context.
266
        ctx['req'] = req
1099.1.91 by William Grant
Add support for custom error views. Plugins can now declare that errors
267
        ctx['exception'] = self.context
1606.1.5 by William Grant
Call the field X-IVLE-Error instead (to avoid potential conflicts with icky fileservice), and populate it in the root XHTMLErrorView.
268
        req.headers_out['X-IVLE-Error'] = self.context.message
1099.1.96 by William Grant
Add an XHTMLUnauthorizedView which redirects unauthenticated users to the
269
270
class XHTMLUnauthorizedView(XHTMLErrorView):
271
    template = 'xhtmlunauthorized.html'
272
1294.2.109 by William Grant
Fix XHTMLUnauthorizedView to take the extra breadcrumb-related arg.
273
    def __init__(self, req, exception, lastobj):
274
        super(XHTMLUnauthorizedView, self).__init__(req, exception, lastobj)
1099.1.120 by William Grant
Move the login machinery to the new framework.
275
1294.2.134 by William Grant
Use a different set of routes for public mode, too.
276
        if not req.publicmode and req.user is None:
1099.1.96 by William Grant
Add an XHTMLUnauthorizedView which redirects unauthenticated users to the
277
            # Not logged in. Redirect to login page.
1099.1.209 by William Grant
Don't escape / in paths when they are in +login/+tos URLs.
278
            if req.uri == '/':
279
                query_string = ''
280
            else:
281
                query_string = '?url=' + urllib.quote(req.uri, safe="/~")
282
            req.throw_redirect('/+login' + query_string)
1099.1.96 by William Grant
Add an XHTMLUnauthorizedView which redirects unauthenticated users to the
283
284
        req.status = 403
1294.2.127 by William Grant
Allow views to specify text for their breadcrumb.
285
286
class ViewBreadcrumb(object):
287
    def __init__(self, req, context):
288
        self.req = req
289
        self.context = context
290
291
    @property
292
    def text(self):
293
        return self.context.breadcrumb_text