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
167
new_app['this_app'] = hasattr(self, 'tab') \
168
and tab[0] == self.tab
171
if tab[3] is not None:
172
new_app['has_icon'] = True
173
icon_url = media_url(req, plugin, tab[3])
174
new_app['icon_url'] = icon_url
175
if new_app['this_app']:
176
ctx['favicon'] = icon_url
178
new_app['has_icon'] = False
179
new_app['path'] = req.make_path(tab[4])
180
new_app['desc'] = tab[2]
181
new_app['name'] = tab[1]
182
new_app['weight'] = tab[5]
183
ctx['apps_in_tabs'].append(new_app)
185
ctx['apps_in_tabs'].sort(key=lambda tab: tab['weight'])
187
def render_overlays(self, req):
188
"""Generate XML streams for the overlays.
190
Returns a list of streams. Populates the scripts, styles, and
197
if not self.allow_overlays:
198
return (overlays, styles, scripts, scripts_init)
200
for plugin in req.config.plugin_index[OverlayPlugin]:
201
for overclass in plugin.overlays:
202
if overclass in self.overlay_blacklist:
204
overlay = overclass(req)
205
#TODO: Re-factor this to look nicer
206
for mplugin in overlay.plugin_scripts:
207
for path in overlay.plugin_scripts[mplugin]:
208
scripts.append(media_url(req, mplugin, path))
210
for mplugin in overlay.plugin_styles:
211
for path in overlay.plugin_styles[mplugin]:
212
styles.append(media_url(req, mplugin, path))
214
scripts_init += overlay.plugin_scripts_init
216
overlays.append(overlay.render(req))
217
return (overlays, styles, scripts, scripts_init)
220
def get_error_view(cls, e):
221
view_map = {HTTPError: XHTMLErrorView,
222
Unauthorized: XHTMLUnauthorizedView}
223
for exccls in inspect.getmro(type(e)):
224
if exccls in view_map:
225
return view_map[exccls]
227
class XHTMLErrorView(XHTMLView):
228
template = 'xhtmlerror.html'
230
def __init__(self, req, context, lastobj):
231
super(XHTMLErrorView, self).__init__(req, context)
232
self.lastobj = lastobj
234
def get_context_ancestry(self, req):
235
return req.publisher.get_ancestors(self.lastobj)
237
def populate(self, req, ctx):
239
ctx['exception'] = self.context
241
class XHTMLUnauthorizedView(XHTMLErrorView):
242
template = 'xhtmlunauthorized.html'
244
def __init__(self, req, exception, lastobj):
245
super(XHTMLUnauthorizedView, self).__init__(req, exception, lastobj)
247
if not req.publicmode and req.user is None:
248
# Not logged in. Redirect to login page.
252
query_string = '?url=' + urllib.quote(req.uri, safe="/~")
253
req.throw_redirect('/+login' + query_string)
257
class ViewBreadcrumb(object):
258
def __init__(self, req, context):
260
self.context = context
264
return self.context.breadcrumb_text