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
ctx['ancestry'] = self.get_context_ancestry(req)
113
# If the view has specified text for a breadcrumb, add one.
114
if self.breadcrumb_text:
115
ctx['extra_breadcrumbs'] = [ViewBreadcrumb(req, self)]
117
ctx['extra_breadcrumbs'] = []
119
# Allow the view to add its own fake breadcrumbs.
120
ctx['extra_breadcrumbs'] += self.extra_breadcrumbs
122
ctx['crumb'] = Breadcrumber(req).crumb
123
self.populate_headings(req, ctx)
124
tmpl = loader.load(os.path.join(os.path.dirname(__file__),
125
'ivle-headings.html'))
126
req.write(tmpl.generate(ctx).render('xhtml', doctype='xhtml'))
128
def populate(self, req, ctx):
129
raise NotImplementedError()
131
def populate_headings(self, req, ctx):
132
ctx['favicon'] = None
133
ctx['root_dir'] = req.config['urls']['root']
134
ctx['public_host'] = req.config['urls']['public_host']
135
ctx['svn_base'] = req.config['urls']['svn_addr']
136
ctx['write_javascript_settings'] = req.write_javascript_settings
138
ctx['login'] = req.user.login
139
ctx['logged_in'] = True
140
ctx['nick'] = req.user.nick
143
ctx['logged_in'] = False
144
ctx['publicmode'] = req.publicmode
145
if hasattr(self, 'help'):
146
ctx['help_path'] = self.help
148
ctx['apps_in_tabs'] = []
149
for plugin in req.config.plugin_index[ViewPlugin]:
150
if not hasattr(plugin, 'tabs'):
153
for tab in plugin.tabs:
154
# tab is a tuple: name, title, desc, icon, path
156
new_app['this_app'] = hasattr(self, 'tab') \
157
and tab[0] == self.tab
160
if tab[3] is not None:
161
new_app['has_icon'] = True
162
icon_url = media_url(req, plugin, tab[3])
163
new_app['icon_url'] = icon_url
164
if new_app['this_app']:
165
ctx['favicon'] = icon_url
167
new_app['has_icon'] = False
168
new_app['path'] = req.make_path(tab[4])
169
new_app['desc'] = tab[2]
170
new_app['name'] = tab[1]
171
new_app['weight'] = tab[5]
172
ctx['apps_in_tabs'].append(new_app)
174
ctx['apps_in_tabs'].sort(key=lambda tab: tab['weight'])
176
def render_overlays(self, req):
177
"""Generate XML streams for the overlays.
179
Returns a list of streams. Populates the scripts, styles, and
186
if not self.allow_overlays:
187
return (overlays, styles, scripts, scripts_init)
189
for plugin in req.config.plugin_index[OverlayPlugin]:
190
for overclass in plugin.overlays:
191
if overclass in self.overlay_blacklist:
193
overlay = overclass(req)
194
#TODO: Re-factor this to look nicer
195
for mplugin in overlay.plugin_scripts:
196
for path in overlay.plugin_scripts[mplugin]:
197
scripts.append(media_url(req, mplugin, path))
199
for mplugin in overlay.plugin_styles:
200
for path in overlay.plugin_styles[mplugin]:
201
styles.append(media_url(req, mplugin, path))
203
scripts_init += overlay.plugin_scripts_init
205
overlays.append(overlay.render(req))
206
return (overlays, styles, scripts, scripts_init)
209
def get_error_view(cls, e):
210
view_map = {HTTPError: XHTMLErrorView,
211
Unauthorized: XHTMLUnauthorizedView}
212
for exccls in inspect.getmro(type(e)):
213
if exccls in view_map:
214
return view_map[exccls]
216
class XHTMLErrorView(XHTMLView):
217
template = 'xhtmlerror.html'
219
def __init__(self, req, context, lastobj):
220
super(XHTMLErrorView, self).__init__(req, context)
221
self.lastobj = lastobj
223
def get_context_ancestry(self, req):
224
return req.publisher.get_ancestors(self.lastobj)
226
def populate(self, req, ctx):
228
ctx['exception'] = self.context
230
class XHTMLUnauthorizedView(XHTMLErrorView):
231
template = 'xhtmlunauthorized.html'
233
def __init__(self, req, exception, lastobj):
234
super(XHTMLUnauthorizedView, self).__init__(req, exception, lastobj)
236
if not req.publicmode and req.user is None:
237
# Not logged in. Redirect to login page.
241
query_string = '?url=' + urllib.quote(req.uri, safe="/~")
242
req.throw_redirect('/+login' + query_string)
246
class ViewBreadcrumb(object):
247
def __init__(self, req, context):
249
self.context = context
253
return self.context.breadcrumb_text