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)
167
if len(tab) > 6 and tab[6]:
169
if not (req.user and req.user.admin):
172
new_app['this_app'] = hasattr(self, 'tab') \
173
and tab[0] == self.tab
176
if tab[3] is not None:
177
new_app['has_icon'] = True
178
icon_url = media_url(req, plugin, tab[3])
179
new_app['icon_url'] = icon_url
180
if new_app['this_app']:
181
ctx['favicon'] = icon_url
183
new_app['has_icon'] = False
184
new_app['path'] = req.make_path(tab[4])
185
new_app['desc'] = tab[2]
186
new_app['name'] = tab[1]
187
new_app['weight'] = tab[5]
188
ctx['apps_in_tabs'].append(new_app)
190
ctx['apps_in_tabs'].sort(key=lambda tab: tab['weight'])
192
def render_overlays(self, req):
193
"""Generate XML streams for the overlays.
195
Returns a list of streams. Populates the scripts, styles, and
202
if not self.allow_overlays:
203
return (overlays, styles, scripts, scripts_init)
205
for plugin in req.config.plugin_index[OverlayPlugin]:
206
for overclass in plugin.overlays:
207
if overclass in self.overlay_blacklist:
209
overlay = overclass(req)
210
#TODO: Re-factor this to look nicer
211
for mplugin in overlay.plugin_scripts:
212
for path in overlay.plugin_scripts[mplugin]:
213
scripts.append(media_url(req, mplugin, path))
215
for mplugin in overlay.plugin_styles:
216
for path in overlay.plugin_styles[mplugin]:
217
styles.append(media_url(req, mplugin, path))
219
scripts_init += overlay.plugin_scripts_init
221
overlays.append(overlay.render(req))
222
return (overlays, styles, scripts, scripts_init)
225
def get_error_view(cls, e):
226
view_map = {HTTPError: XHTMLErrorView,
227
Unauthorized: XHTMLUnauthorizedView}
228
for exccls in inspect.getmro(type(e)):
229
if exccls in view_map:
230
return view_map[exccls]
232
class XHTMLErrorView(XHTMLView):
233
template = 'xhtmlerror.html'
235
def __init__(self, req, context, lastobj):
236
super(XHTMLErrorView, self).__init__(req, context)
237
self.lastobj = lastobj
239
def get_context_ancestry(self, req):
240
return req.publisher.get_ancestors(self.lastobj)
242
def populate(self, req, ctx):
244
ctx['exception'] = self.context
246
class XHTMLUnauthorizedView(XHTMLErrorView):
247
template = 'xhtmlunauthorized.html'
249
def __init__(self, req, exception, lastobj):
250
super(XHTMLUnauthorizedView, self).__init__(req, exception, lastobj)
252
if not req.publicmode and req.user is None:
253
# Not logged in. Redirect to login page.
257
query_string = '?url=' + urllib.quote(req.uri, safe="/~")
258
req.throw_redirect('/+login' + query_string)
262
class ViewBreadcrumb(object):
263
def __init__(self, req, context):
265
self.context = context
269
return self.context.breadcrumb_text