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
45
def __init__(self, *args, **kwargs):
46
super(XHTMLView, self).__init__(*args, **kwargs)
48
self.overlay_blacklist = []
50
self.plugin_scripts = {}
51
self.plugin_styles = {}
52
self.scripts_init = []
54
self.extra_breadcrumbs = []
55
self.overlay_blacklist = []
57
def get_context_ancestry(self, req):
58
return req.publisher.get_ancestors(self.context)
60
def filter(self, stream, ctx):
63
def render(self, req):
64
req.content_type = 'text/html' # TODO: Detect application/xhtml+xml
67
viewctx = genshi.template.Context()
68
self.populate(req, viewctx)
70
# The template is found in the directory of the module containing the
72
app_template = os.path.join(os.path.dirname(
73
inspect.getmodule(self).__file__), self.template)
74
loader = genshi.template.TemplateLoader(".", auto_reload=True)
75
tmpl = loader.load(app_template)
76
app = self.filter(tmpl.generate(viewctx), viewctx)
79
for plugin in self.plugin_scripts:
80
for path in self.plugin_scripts[plugin]:
81
view_scripts.append(media_url(req, plugin, path))
84
for plugin in self.plugin_styles:
85
for path in self.plugin_styles[plugin]:
86
view_styles.append(media_url(req, plugin, path))
89
ctx = genshi.template.Context()
91
overlay_bits = self.render_overlays(req) if req.user else [[]]*4
92
ctx['overlays'] = overlay_bits[0]
94
ctx['styles'] = [media_url(req, CorePlugin, 'ivle.css')]
95
ctx['styles'] += view_styles
96
ctx['styles'] += overlay_bits[1]
98
ctx['scripts'] = [media_url(req, CorePlugin, path) for path in
99
('util.js', 'json2.js', 'md5.js')]
100
ctx['scripts'].append(media_url(req, '+external/jquery', 'jquery.js'))
101
ctx['scripts'] += view_scripts
102
ctx['scripts'] += overlay_bits[2]
104
ctx['scripts_init'] = self.scripts_init + overlay_bits[3]
105
ctx['app_template'] = app
106
ctx['title_img'] = media_url(req, CorePlugin,
107
"images/chrome/root-breadcrumb.png")
109
ancestry = self.get_context_ancestry(req)
113
crumber = Breadcrumber(req)
115
ctx['breadcrumbs'] = []
116
if not req.publicmode:
117
for ancestor in ancestry:
118
crumb = crumber.crumb(ancestor)
122
if hasattr(crumb, 'extra_breadcrumbs_before'):
123
ctx['breadcrumbs'].extend(crumb.extra_breadcrumbs_before)
124
ctx['breadcrumbs'].append(crumb)
125
if hasattr(crumb, 'extra_breadcrumbs_after'):
126
ctx['breadcrumbs'].extend(crumb.extra_breadcrumbs_after)
128
# If the view has specified text for a breadcrumb, add one.
129
if self.breadcrumb_text:
130
ctx['breadcrumbs'].append(ViewBreadcrumb(req, self))
132
# Allow the view to add its own fake breadcrumbs.
133
ctx['breadcrumbs'].extend(self.extra_breadcrumbs)
135
self.populate_headings(req, ctx)
136
tmpl = loader.load(os.path.join(os.path.dirname(__file__),
137
'ivle-headings.html'))
138
req.write(tmpl.generate(ctx).render('xhtml', doctype='xhtml'))
140
def populate(self, req, ctx):
141
raise NotImplementedError()
143
def populate_headings(self, req, ctx):
144
ctx['favicon'] = None
145
ctx['root_dir'] = req.config['urls']['root']
146
ctx['public_host'] = req.config['urls']['public_host']
147
ctx['svn_base'] = req.config['urls']['svn_addr']
148
ctx['write_javascript_settings'] = req.write_javascript_settings
150
ctx['login'] = req.user.login
151
ctx['logged_in'] = True
152
ctx['nick'] = req.user.nick
155
ctx['logged_in'] = False
156
ctx['publicmode'] = req.publicmode
157
if hasattr(self, 'help'):
158
ctx['help_path'] = self.help
160
ctx['apps_in_tabs'] = []
161
for plugin in req.config.plugin_index[ViewPlugin]:
162
if not hasattr(plugin, 'tabs'):
165
for tab in plugin.tabs:
166
# tab is a tuple: name, title, desc, icon, path, weight, admin
167
# (Admin is optional, defaults to false)
169
new_app['this_app'] = hasattr(self, 'tab') \
170
and tab[0] == self.tab
173
if tab[3] is not None:
174
new_app['has_icon'] = True
175
icon_url = media_url(req, plugin, tab[3])
176
new_app['icon_url'] = icon_url
177
if new_app['this_app']:
178
ctx['favicon'] = icon_url
180
new_app['has_icon'] = False
181
# The following check is here, so it is AFTER setting the
182
# icon, but BEFORE actually installing the tab in the menu
183
if len(tab) > 6 and tab[6]:
185
if not (req.user and req.user.admin):
187
new_app['path'] = req.make_path(tab[4])
188
new_app['desc'] = tab[2]
189
new_app['name'] = tab[1]
190
new_app['weight'] = tab[5]
191
ctx['apps_in_tabs'].append(new_app)
193
ctx['apps_in_tabs'].sort(key=lambda tab: tab['weight'])
195
def render_overlays(self, req):
196
"""Generate XML streams for the overlays.
198
Returns a list of streams. Populates the scripts, styles, and
205
if not self.allow_overlays:
206
return (overlays, styles, scripts, scripts_init)
208
for plugin in req.config.plugin_index[OverlayPlugin]:
209
for overclass in plugin.overlays:
210
if overclass in self.overlay_blacklist:
212
overlay = overclass(req)
213
#TODO: Re-factor this to look nicer
214
for mplugin in overlay.plugin_scripts:
215
for path in overlay.plugin_scripts[mplugin]:
216
scripts.append(media_url(req, mplugin, path))
218
for mplugin in overlay.plugin_styles:
219
for path in overlay.plugin_styles[mplugin]:
220
styles.append(media_url(req, mplugin, path))
222
scripts_init += overlay.plugin_scripts_init
224
overlays.append(overlay.render(req))
225
return (overlays, styles, scripts, scripts_init)
228
def get_error_view(cls, e):
229
view_map = {HTTPError: XHTMLErrorView,
230
Unauthorized: XHTMLUnauthorizedView}
231
for exccls in inspect.getmro(type(e)):
232
if exccls in view_map:
233
return view_map[exccls]
235
class XHTMLErrorView(XHTMLView):
236
template = 'xhtmlerror.html'
238
def __init__(self, req, context, lastobj):
239
super(XHTMLErrorView, self).__init__(req, context)
240
self.lastobj = lastobj
242
def get_context_ancestry(self, req):
243
return req.publisher.get_ancestors(self.lastobj)
245
def populate(self, req, ctx):
247
ctx['exception'] = self.context
249
class XHTMLUnauthorizedView(XHTMLErrorView):
250
template = 'xhtmlunauthorized.html'
252
def __init__(self, req, exception, lastobj):
253
super(XHTMLUnauthorizedView, self).__init__(req, exception, lastobj)
255
if not req.publicmode and req.user is None:
256
# Not logged in. Redirect to login page.
260
query_string = '?url=' + urllib.quote(req.uri, safe="/~")
261
req.throw_redirect('/+login' + query_string)
265
class ViewBreadcrumb(object):
266
def __init__(self, req, context):
268
self.context = context
272
return self.context.breadcrumb_text