1
# IVLE - Informatics Virtual Learning Environment
2
# Copyright (C) 2007-2009 The University of Melbourne
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.
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.
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
18
# Author: Nick Chadwick
24
import genshi.template
26
from ivle.webapp.media import media_url
27
from ivle.webapp.core import Plugin as CorePlugin
28
from ivle.webapp.base.views import BaseView
29
from ivle.webapp.base.plugins import ViewPlugin, OverlayPlugin
30
from ivle.webapp.errors import HTTPError, Unauthorized
31
from ivle.webapp.publisher import NoPath
32
from ivle.webapp.breadcrumbs import Breadcrumber
34
class XHTMLView(BaseView):
36
A view which provides a base class for views which need to return XHTML
37
It is expected that apps which use this view will be written using Genshi
41
template = 'template.html'
43
breadcrumb_text = None
46
def __init__(self, *args, **kwargs):
47
super(XHTMLView, self).__init__(*args, **kwargs)
49
# We use a single loader for all views, so we can cache the
50
# parsed templates. auto_reload is convenient and has a minimal
51
# performance penalty, so we'll leave it on.
52
if self.__class__._loader is None:
53
self.__class__._loader = genshi.template.TemplateLoader(
54
".", auto_reload=True,
57
self.overlay_blacklist = []
59
self.plugin_scripts = {}
60
self.plugin_styles = {}
61
self.scripts_init = []
63
self.extra_breadcrumbs = []
64
self.overlay_blacklist = []
66
def get_context_ancestry(self, req):
67
return req.publisher.get_ancestors(self.context)
69
def filter(self, stream, ctx):
72
def render(self, req):
73
req.content_type = 'text/html' # TODO: Detect application/xhtml+xml
76
viewctx = genshi.template.Context()
77
self.populate(req, viewctx)
79
# The template is found in the directory of the module containing the
81
app_template = os.path.join(os.path.dirname(
82
inspect.getmodule(self).__file__), self.template)
83
tmpl = self._loader.load(app_template)
84
app = self.filter(tmpl.generate(viewctx), viewctx)
87
for plugin in self.plugin_scripts:
88
for path in self.plugin_scripts[plugin]:
89
view_scripts.append(media_url(req, plugin, path))
92
for plugin in self.plugin_styles:
93
for path in self.plugin_styles[plugin]:
94
view_styles.append(media_url(req, plugin, path))
97
ctx = genshi.template.Context()
99
overlay_bits = self.render_overlays(req) if req.user else [[]]*4
100
ctx['overlays'] = overlay_bits[0]
102
ctx['styles'] = [media_url(req, CorePlugin, 'ivle.css')]
103
ctx['styles'] += view_styles
104
ctx['styles'] += overlay_bits[1]
106
ctx['scripts'] = [media_url(req, CorePlugin, path) for path in
107
('util.js', 'json2.js', 'md5.js')]
108
ctx['scripts'].append(media_url(req, '+external/jquery', 'jquery.js'))
109
ctx['scripts'] += view_scripts
110
ctx['scripts'] += overlay_bits[2]
112
ctx['scripts_init'] = self.scripts_init + overlay_bits[3]
113
ctx['app_template'] = app
114
ctx['title_img'] = media_url(req, CorePlugin,
115
"images/chrome/root-breadcrumb.png")
117
ancestry = self.get_context_ancestry(req)
121
crumber = Breadcrumber(req)
123
ctx['breadcrumbs'] = []
124
if not req.publicmode:
125
for ancestor in ancestry:
126
crumb = crumber.crumb(ancestor)
130
if hasattr(crumb, 'extra_breadcrumbs_before'):
131
ctx['breadcrumbs'].extend(crumb.extra_breadcrumbs_before)
132
ctx['breadcrumbs'].append(crumb)
133
if hasattr(crumb, 'extra_breadcrumbs_after'):
134
ctx['breadcrumbs'].extend(crumb.extra_breadcrumbs_after)
136
# If the view has specified text for a breadcrumb, add one.
137
if self.breadcrumb_text:
138
ctx['breadcrumbs'].append(ViewBreadcrumb(req, self))
140
# Allow the view to add its own fake breadcrumbs.
141
ctx['breadcrumbs'].extend(self.extra_breadcrumbs)
143
self.populate_headings(req, ctx)
144
tmpl = self._loader.load(os.path.join(os.path.dirname(__file__),
145
'ivle-headings.html'))
146
req.write(tmpl.generate(ctx).render('xhtml', doctype='xhtml'))
148
def populate(self, req, ctx):
149
raise NotImplementedError()
151
def populate_headings(self, req, ctx):
152
ctx['favicon'] = None
153
ctx['root_dir'] = req.config['urls']['root']
154
ctx['public_host'] = req.config['urls']['public_host']
155
ctx['svn_base'] = req.config['urls']['svn_addr']
156
ctx['write_javascript_settings'] = req.write_javascript_settings
158
ctx['login'] = req.user.login
159
ctx['logged_in'] = True
160
ctx['nick'] = req.user.nick
163
ctx['logged_in'] = False
164
ctx['publicmode'] = req.publicmode
165
if hasattr(self, 'help'):
166
ctx['help_path'] = self.help
168
ctx['apps_in_tabs'] = []
169
for plugin in req.config.plugin_index[ViewPlugin]:
170
if not hasattr(plugin, 'tabs'):
173
for tab in plugin.tabs:
174
# tab is a tuple: name, title, desc, icon, path, weight, admin
175
# (Admin is optional, defaults to false)
177
new_app['this_app'] = hasattr(self, 'tab') \
178
and tab[0] == self.tab
181
if tab[3] is not None:
182
new_app['has_icon'] = True
183
icon_url = media_url(req, plugin, tab[3])
184
new_app['icon_url'] = icon_url
185
if new_app['this_app']:
186
ctx['favicon'] = icon_url
188
new_app['has_icon'] = False
189
# The following check is here, so it is AFTER setting the
190
# icon, but BEFORE actually installing the tab in the menu
191
if len(tab) > 6 and tab[6]:
193
if not (req.user and req.user.admin):
195
new_app['path'] = req.make_path(tab[4])
196
new_app['desc'] = tab[2]
197
new_app['name'] = tab[1]
198
new_app['weight'] = tab[5]
199
ctx['apps_in_tabs'].append(new_app)
201
ctx['apps_in_tabs'].sort(key=lambda tab: tab['weight'])
203
def render_overlays(self, req):
204
"""Generate XML streams for the overlays.
206
Returns a list of streams. Populates the scripts, styles, and
213
if not self.allow_overlays:
214
return (overlays, styles, scripts, scripts_init)
216
for plugin in req.config.plugin_index[OverlayPlugin]:
217
for overclass in plugin.overlays:
218
if overclass in self.overlay_blacklist:
220
overlay = overclass(req)
221
#TODO: Re-factor this to look nicer
222
for mplugin in overlay.plugin_scripts:
223
for path in overlay.plugin_scripts[mplugin]:
224
scripts.append(media_url(req, mplugin, path))
226
for mplugin in overlay.plugin_styles:
227
for path in overlay.plugin_styles[mplugin]:
228
styles.append(media_url(req, mplugin, path))
230
scripts_init += overlay.plugin_scripts_init
232
overlays.append(overlay.render(req))
233
return (overlays, styles, scripts, scripts_init)
236
def get_error_view(cls, e):
237
view_map = {HTTPError: XHTMLErrorView,
238
Unauthorized: XHTMLUnauthorizedView}
239
for exccls in inspect.getmro(type(e)):
240
if exccls in view_map:
241
return view_map[exccls]
243
class XHTMLErrorView(XHTMLView):
244
template = 'xhtmlerror.html'
246
def __init__(self, req, context, lastobj):
247
super(XHTMLErrorView, self).__init__(req, context)
248
self.lastobj = lastobj
250
def get_context_ancestry(self, req):
251
return req.publisher.get_ancestors(self.lastobj)
253
def populate(self, req, ctx):
255
ctx['exception'] = self.context
256
req.headers_out['X-IVLE-Error'] = self.context.message
258
class XHTMLUnauthorizedView(XHTMLErrorView):
259
template = 'xhtmlunauthorized.html'
261
def __init__(self, req, exception, lastobj):
262
super(XHTMLUnauthorizedView, self).__init__(req, exception, lastobj)
264
if not req.publicmode and req.user is None:
265
# Not logged in. Redirect to login page.
269
query_string = '?url=' + urllib.quote(req.uri, safe="/~")
270
req.throw_redirect('/+login' + query_string)
274
class ViewBreadcrumb(object):
275
def __init__(self, req, context):
277
self.context = context
281
return self.context.breadcrumb_text