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'
44
def __init__(self, *args, **kwargs):
45
super(XHTMLView, self).__init__(*args, **kwargs)
47
self.overlay_blacklist = []
49
self.plugin_scripts = {}
50
self.plugin_styles = {}
51
self.scripts_init = []
53
self.extra_breadcrumbs = []
54
self.overlay_blacklist = []
56
def get_context_ancestry(self, req):
57
return req.publisher.get_ancestors(self.context)
59
def filter(self, stream, ctx):
62
def render(self, req):
63
req.content_type = 'text/html' # TODO: Detect application/xhtml+xml
66
viewctx = genshi.template.Context()
67
self.populate(req, viewctx)
69
# The template is found in the directory of the module containing the
71
app_template = os.path.join(os.path.dirname(
72
inspect.getmodule(self).__file__), self.template)
73
loader = genshi.template.TemplateLoader(".", auto_reload=True)
74
tmpl = loader.load(app_template)
75
app = self.filter(tmpl.generate(viewctx), viewctx)
78
for plugin in self.plugin_scripts:
79
for path in self.plugin_scripts[plugin]:
80
view_scripts.append(media_url(req, plugin, path))
83
for plugin in self.plugin_styles:
84
for path in self.plugin_styles[plugin]:
85
view_styles.append(media_url(req, plugin, path))
88
ctx = genshi.template.Context()
90
overlay_bits = self.render_overlays(req) if req.user else [[]]*4
91
ctx['overlays'] = overlay_bits[0]
93
ctx['styles'] = [media_url(req, CorePlugin, 'ivle.css')]
94
ctx['styles'] += view_styles
95
ctx['styles'] += overlay_bits[1]
97
ctx['scripts'] = [media_url(req, CorePlugin, path) for path in
98
('util.js', 'json2.js', 'md5.js')]
99
ctx['scripts'].append(media_url(req, '+external/jquery', 'jquery.js'))
100
ctx['scripts'] += view_scripts
101
ctx['scripts'] += overlay_bits[2]
103
ctx['scripts_init'] = self.scripts_init + overlay_bits[3]
104
ctx['app_template'] = app
105
ctx['title_img'] = media_url(req, CorePlugin,
106
"images/chrome/root-breadcrumb.png")
108
ctx['ancestry'] = self.get_context_ancestry(req)
112
# Allow the view to add its own fake breadcrumbs.
113
ctx['extra_breadcrumbs'] = self.extra_breadcrumbs
115
ctx['crumb'] = Breadcrumber(req).crumb
116
self.populate_headings(req, ctx)
117
tmpl = loader.load(os.path.join(os.path.dirname(__file__),
118
'ivle-headings.html'))
119
req.write(tmpl.generate(ctx).render('xhtml', doctype='xhtml'))
121
def populate(self, req, ctx):
122
raise NotImplementedError()
124
def populate_headings(self, req, ctx):
125
ctx['favicon'] = None
126
ctx['root_dir'] = req.config['urls']['root']
127
ctx['public_host'] = req.config['urls']['public_host']
128
ctx['svn_base'] = req.config['urls']['svn_addr']
129
ctx['write_javascript_settings'] = req.write_javascript_settings
131
ctx['login'] = req.user.login
132
ctx['logged_in'] = True
133
ctx['nick'] = req.user.nick
136
ctx['logged_in'] = False
137
ctx['publicmode'] = req.publicmode
138
if hasattr(self, 'help'):
139
ctx['help_path'] = self.help
141
ctx['apps_in_tabs'] = []
142
for plugin in req.config.plugin_index[ViewPlugin]:
143
if not hasattr(plugin, 'tabs'):
146
for tab in plugin.tabs:
147
# tab is a tuple: name, title, desc, icon, path
149
new_app['this_app'] = hasattr(self, 'tab') \
150
and tab[0] == self.tab
153
if tab[3] is not None:
154
new_app['has_icon'] = True
155
icon_url = media_url(req, plugin, tab[3])
156
new_app['icon_url'] = icon_url
157
if new_app['this_app']:
158
ctx['favicon'] = icon_url
160
new_app['has_icon'] = False
161
new_app['path'] = req.make_path(tab[4])
162
new_app['desc'] = tab[2]
163
new_app['name'] = tab[1]
164
new_app['weight'] = tab[5]
165
ctx['apps_in_tabs'].append(new_app)
167
ctx['apps_in_tabs'].sort(key=lambda tab: tab['weight'])
169
def render_overlays(self, req):
170
"""Generate XML streams for the overlays.
172
Returns a list of streams. Populates the scripts, styles, and
179
if not self.allow_overlays:
180
return (overlays, styles, scripts, scripts_init)
182
for plugin in req.config.plugin_index[OverlayPlugin]:
183
for overclass in plugin.overlays:
184
if overclass in self.overlay_blacklist:
186
overlay = overclass(req)
187
#TODO: Re-factor this to look nicer
188
for mplugin in overlay.plugin_scripts:
189
for path in overlay.plugin_scripts[mplugin]:
190
scripts.append(media_url(req, mplugin, path))
192
for mplugin in overlay.plugin_styles:
193
for path in overlay.plugin_styles[mplugin]:
194
styles.append(media_url(req, mplugin, path))
196
scripts_init += overlay.plugin_scripts_init
198
overlays.append(overlay.render(req))
199
return (overlays, styles, scripts, scripts_init)
202
def get_error_view(cls, e):
203
view_map = {HTTPError: XHTMLErrorView,
204
Unauthorized: XHTMLUnauthorizedView}
205
for exccls in inspect.getmro(type(e)):
206
if exccls in view_map:
207
return view_map[exccls]
209
class XHTMLErrorView(XHTMLView):
210
template = 'xhtmlerror.html'
212
def __init__(self, req, context, lastobj):
213
super(XHTMLErrorView, self).__init__(req, context)
214
self.lastobj = lastobj
216
def get_context_ancestry(self, req):
217
return req.publisher.get_ancestors(self.lastobj)
219
def populate(self, req, ctx):
221
ctx['exception'] = self.context
223
class XHTMLUnauthorizedView(XHTMLErrorView):
224
template = 'xhtmlunauthorized.html'
226
def __init__(self, req, exception, lastobj):
227
super(XHTMLUnauthorizedView, self).__init__(req, exception, lastobj)
230
# Not logged in. Redirect to login page.
234
query_string = '?url=' + urllib.quote(req.uri, safe="/~")
235
req.throw_redirect('/+login' + query_string)