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
for ancestor in ancestry:
117
crumb = crumber.crumb(ancestor)
121
if hasattr(crumb, 'extra_breadcrumbs_before'):
122
ctx['breadcrumbs'].extend(crumb.extra_breadcrumbs_before)
123
ctx['breadcrumbs'].append(crumb)
124
if hasattr(crumb, 'extra_breadcrumbs_after'):
125
ctx['breadcrumbs'].extend(crumb.extra_breadcrumbs_after)
127
# If the view has specified text for a breadcrumb, add one.
128
if self.breadcrumb_text:
129
ctx['breadcrumbs'].append(ViewBreadcrumb(req, self))
131
# Allow the view to add its own fake breadcrumbs.
132
ctx['breadcrumbs'].extend(self.extra_breadcrumbs)
134
self.populate_headings(req, ctx)
135
tmpl = loader.load(os.path.join(os.path.dirname(__file__),
136
'ivle-headings.html'))
137
req.write(tmpl.generate(ctx).render('xhtml', doctype='xhtml'))
139
def populate(self, req, ctx):
140
raise NotImplementedError()
142
def populate_headings(self, req, ctx):
143
ctx['favicon'] = None
144
ctx['root_dir'] = req.config['urls']['root']
145
ctx['public_host'] = req.config['urls']['public_host']
146
ctx['svn_base'] = req.config['urls']['svn_addr']
147
ctx['write_javascript_settings'] = req.write_javascript_settings
149
ctx['login'] = req.user.login
150
ctx['logged_in'] = True
151
ctx['nick'] = req.user.nick
154
ctx['logged_in'] = False
155
ctx['publicmode'] = req.publicmode
156
if hasattr(self, 'help'):
157
ctx['help_path'] = self.help
159
ctx['apps_in_tabs'] = []
160
for plugin in req.config.plugin_index[ViewPlugin]:
161
if not hasattr(plugin, 'tabs'):
164
for tab in plugin.tabs:
165
# tab is a tuple: name, title, desc, icon, path, weight, admin
166
# (Admin is optional, defaults to false)
168
new_app['this_app'] = hasattr(self, 'tab') \
169
and tab[0] == self.tab
172
if tab[3] is not None:
173
new_app['has_icon'] = True
174
icon_url = media_url(req, plugin, tab[3])
175
new_app['icon_url'] = icon_url
176
if new_app['this_app']:
177
ctx['favicon'] = icon_url
179
new_app['has_icon'] = False
180
# The following check is here, so it is AFTER setting the
181
# icon, but BEFORE actually installing the tab in the menu
182
if len(tab) > 6 and tab[6]:
184
if not (req.user and req.user.admin):
186
new_app['path'] = req.make_path(tab[4])
187
new_app['desc'] = tab[2]
188
new_app['name'] = tab[1]
189
new_app['weight'] = tab[5]
190
ctx['apps_in_tabs'].append(new_app)
192
ctx['apps_in_tabs'].sort(key=lambda tab: tab['weight'])
194
def render_overlays(self, req):
195
"""Generate XML streams for the overlays.
197
Returns a list of streams. Populates the scripts, styles, and
204
if not self.allow_overlays:
205
return (overlays, styles, scripts, scripts_init)
207
for plugin in req.config.plugin_index[OverlayPlugin]:
208
for overclass in plugin.overlays:
209
if overclass in self.overlay_blacklist:
211
overlay = overclass(req)
212
#TODO: Re-factor this to look nicer
213
for mplugin in overlay.plugin_scripts:
214
for path in overlay.plugin_scripts[mplugin]:
215
scripts.append(media_url(req, mplugin, path))
217
for mplugin in overlay.plugin_styles:
218
for path in overlay.plugin_styles[mplugin]:
219
styles.append(media_url(req, mplugin, path))
221
scripts_init += overlay.plugin_scripts_init
223
overlays.append(overlay.render(req))
224
return (overlays, styles, scripts, scripts_init)
227
def get_error_view(cls, e):
228
view_map = {HTTPError: XHTMLErrorView,
229
Unauthorized: XHTMLUnauthorizedView}
230
for exccls in inspect.getmro(type(e)):
231
if exccls in view_map:
232
return view_map[exccls]
234
class XHTMLErrorView(XHTMLView):
235
template = 'xhtmlerror.html'
237
def __init__(self, req, context, lastobj):
238
super(XHTMLErrorView, self).__init__(req, context)
239
self.lastobj = lastobj
241
def get_context_ancestry(self, req):
242
return req.publisher.get_ancestors(self.lastobj)
244
def populate(self, req, ctx):
246
ctx['exception'] = self.context
248
class XHTMLUnauthorizedView(XHTMLErrorView):
249
template = 'xhtmlunauthorized.html'
251
def __init__(self, req, exception, lastobj):
252
super(XHTMLUnauthorizedView, self).__init__(req, exception, lastobj)
254
if not req.publicmode and req.user is None:
255
# Not logged in. Redirect to login page.
259
query_string = '?url=' + urllib.quote(req.uri, safe="/~")
260
req.throw_redirect('/+login' + query_string)
264
class ViewBreadcrumb(object):
265
def __init__(self, req, context):
267
self.context = context
271
return self.context.breadcrumb_text