24
23
import genshi.template
26
from ivle.webapp.media import media_url
27
from ivle.webapp.core import Plugin as CorePlugin
28
25
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
35
class GenshiLoaderMixin(object):
36
"""Mixin for classes which need to render Genshi templates.
38
A TemplateLoader is shared between all instances, so templates are
39
cached across multiple instances and therefore also requests.
43
def __init__(self, *args, **kwargs):
44
super(GenshiLoaderMixin, self).__init__(*args, **kwargs)
46
# We use a single loader for all views, so we can cache the
47
# parsed templates. auto_reload is convenient and has a minimal
48
# performance penalty, so we'll leave it on.
49
if GenshiLoaderMixin._loader is None:
50
GenshiLoaderMixin._loader = genshi.template.TemplateLoader(
51
".", auto_reload=True,
55
class XHTMLView(GenshiLoaderMixin, BaseView):
29
class XHTMLView(BaseView):
57
31
A view which provides a base class for views which need to return XHTML
58
32
It is expected that apps which use this view will be written using Genshi
62
template = 'template.html'
64
breadcrumb_text = None
66
def __init__(self, *args, **kwargs):
67
super(XHTMLView, self).__init__(*args, **kwargs)
69
self.overlay_blacklist = []
71
self.plugin_scripts = {}
72
self.plugin_styles = {}
73
self.scripts_init = []
75
self.extra_breadcrumbs = []
76
self.overlay_blacklist = []
78
def get_context_ancestry(self, req):
79
return req.publisher.get_ancestors(self.context)
81
def filter(self, stream, ctx):
35
def __init__(self, req, **kwargs):
37
setattr(self, key, kwargs[key])
84
39
def render(self, req):
85
40
req.content_type = 'text/html' # TODO: Detect application/xhtml+xml
93
48
app_template = os.path.join(os.path.dirname(
94
49
inspect.getmodule(self).__file__), self.template)
95
tmpl = self._loader.load(app_template)
96
app = self.filter(tmpl.generate(viewctx), viewctx)
99
for plugin in self.plugin_scripts:
100
for path in self.plugin_scripts[plugin]:
101
view_scripts.append(media_url(req, plugin, path))
104
for plugin in self.plugin_styles:
105
for path in self.plugin_styles[plugin]:
106
view_styles.append(media_url(req, plugin, path))
50
req.write_html_head_foot = False
51
loader = genshi.template.TemplateLoader(".", auto_reload=True)
52
tmpl = loader.load(app_template)
53
app = tmpl.generate(viewctx)
109
56
ctx = genshi.template.Context()
111
overlay_bits = self.render_overlays(req) if req.user else [[]]*4
112
ctx['overlays'] = overlay_bits[0]
114
ctx['styles'] = [media_url(req, CorePlugin, 'ivle.css')]
115
ctx['styles'] += view_styles
116
ctx['styles'] += overlay_bits[1]
118
ctx['scripts'] = [media_url(req, CorePlugin, path) for path in
119
('util.js', 'json2.js', 'md5.js')]
120
ctx['scripts'].append(media_url(req, '+external/jquery', 'jquery.js'))
121
ctx['scripts'] += view_scripts
122
ctx['scripts'] += overlay_bits[2]
124
ctx['scripts_init'] = self.scripts_init + overlay_bits[3]
57
ctx['app_styles'] = req.styles
58
ctx['scripts'] = req.scripts
59
ctx['scripts_init'] = req.scripts_init
125
60
ctx['app_template'] = app
126
ctx['title_img'] = media_url(req, CorePlugin,
127
"images/chrome/root-breadcrumb.png")
129
ancestry = self.get_context_ancestry(req)
133
crumber = Breadcrumber(req)
135
ctx['breadcrumbs'] = []
136
if not req.publicmode:
137
for ancestor in ancestry:
138
crumb = crumber.crumb(ancestor)
142
if hasattr(crumb, 'extra_breadcrumbs_before'):
143
ctx['breadcrumbs'].extend(crumb.extra_breadcrumbs_before)
144
ctx['breadcrumbs'].append(crumb)
145
if hasattr(crumb, 'extra_breadcrumbs_after'):
146
ctx['breadcrumbs'].extend(crumb.extra_breadcrumbs_after)
148
# If the view has specified text for a breadcrumb, add one.
149
if self.breadcrumb_text:
150
ctx['breadcrumbs'].append(ViewBreadcrumb(req, self))
152
# Allow the view to add its own fake breadcrumbs.
153
ctx['breadcrumbs'].extend(self.extra_breadcrumbs)
155
61
self.populate_headings(req, ctx)
156
tmpl = self._loader.load(os.path.join(os.path.dirname(__file__),
62
tmpl = loader.load(os.path.join(os.path.dirname(__file__),
157
63
'ivle-headings.html'))
158
64
req.write(tmpl.generate(ctx).render('xhtml', doctype='xhtml'))
160
def populate(self, req, ctx):
161
raise NotImplementedError()
163
66
def populate_headings(self, req, ctx):
164
67
ctx['favicon'] = None
165
ctx['root_dir'] = req.config['urls']['root']
166
ctx['public_host'] = req.config['urls']['public_host']
167
ctx['svn_base'] = req.config['urls']['svn_addr']
68
ctx['root_dir'] = ivle.conf.root_dir
69
ctx['public_host'] = ivle.conf.public_host
168
70
ctx['write_javascript_settings'] = req.write_javascript_settings
170
72
ctx['login'] = req.user.login
172
74
ctx['nick'] = req.user.nick
174
76
ctx['login'] = None
175
ctx['logged_in'] = False
176
77
ctx['publicmode'] = req.publicmode
177
if hasattr(self, 'help'):
178
ctx['help_path'] = self.help
180
78
ctx['apps_in_tabs'] = []
181
for plugin in req.config.plugin_index[ViewPlugin]:
182
if not hasattr(plugin, 'tabs'):
185
for tab in plugin.tabs:
186
# tab is a tuple: name, title, desc, icon, path, weight, admin
187
# (Admin is optional, defaults to false)
189
new_app['this_app'] = hasattr(self, 'tab') \
190
and tab[0] == self.tab
193
if tab[3] is not None:
194
new_app['has_icon'] = True
195
icon_url = media_url(req, plugin, tab[3])
196
new_app['icon_url'] = icon_url
197
if new_app['this_app']:
198
ctx['favicon'] = icon_url
200
new_app['has_icon'] = False
201
# The following check is here, so it is AFTER setting the
202
# icon, but BEFORE actually installing the tab in the menu
203
if len(tab) > 6 and tab[6]:
205
if not (req.user and req.user.admin):
207
new_app['path'] = req.make_path(tab[4])
208
new_app['desc'] = tab[2]
209
new_app['name'] = tab[1]
210
new_app['weight'] = tab[5]
211
ctx['apps_in_tabs'].append(new_app)
213
ctx['apps_in_tabs'].sort(key=lambda tab: tab['weight'])
215
def render_overlays(self, req):
216
"""Generate XML streams for the overlays.
218
Returns a list of streams. Populates the scripts, styles, and
225
if not self.allow_overlays:
226
return (overlays, styles, scripts, scripts_init)
228
for plugin in req.config.plugin_index[OverlayPlugin]:
229
for overclass in plugin.overlays:
230
if overclass in self.overlay_blacklist:
232
overlay = overclass(req)
233
#TODO: Re-factor this to look nicer
234
for mplugin in overlay.plugin_scripts:
235
for path in overlay.plugin_scripts[mplugin]:
236
scripts.append(media_url(req, mplugin, path))
238
for mplugin in overlay.plugin_styles:
239
for path in overlay.plugin_styles[mplugin]:
240
styles.append(media_url(req, mplugin, path))
242
scripts_init += overlay.plugin_scripts_init
244
overlays.append(overlay.render(req))
245
return (overlays, styles, scripts, scripts_init)
248
def get_error_view(cls, e):
249
view_map = {HTTPError: XHTMLErrorView,
250
Unauthorized: XHTMLUnauthorizedView}
251
for exccls in inspect.getmro(type(e)):
252
if exccls in view_map:
253
return view_map[exccls]
255
class XHTMLErrorView(XHTMLView):
256
template = 'xhtmlerror.html'
258
def __init__(self, req, context, lastobj):
259
super(XHTMLErrorView, self).__init__(req, context)
260
self.lastobj = lastobj
262
def get_context_ancestry(self, req):
263
return req.publisher.get_ancestors(self.lastobj)
265
def populate(self, req, ctx):
267
ctx['exception'] = self.context
268
req.headers_out['X-IVLE-Error'] = self.context.message
270
class XHTMLUnauthorizedView(XHTMLErrorView):
271
template = 'xhtmlunauthorized.html'
273
def __init__(self, req, exception, lastobj):
274
super(XHTMLUnauthorizedView, self).__init__(req, exception, lastobj)
276
if not req.publicmode and req.user is None:
277
# Not logged in. Redirect to login page.
79
for urlname in ivle.conf.apps.apps_in_tabs:
81
app = ivle.conf.apps.app_url[urlname]
82
new_app['this_app'] = hasattr(self, 'appname') \
83
and urlname == self.appname
85
new_app['has_icon'] = True
86
icon_dir = ivle.conf.apps.app_icon_dir
87
icon_url = ivle.util.make_path(os.path.join(icon_dir, app.icon))
88
new_app['icon_url'] = icon_url
89
if new_app['this_app']:
90
ctx['favicon'] = icon_url
281
query_string = '?url=' + urllib.quote(req.uri, safe="/~")
282
req.throw_redirect('/+login' + query_string)
286
class ViewBreadcrumb(object):
287
def __init__(self, req, context):
289
self.context = context
293
return self.context.breadcrumb_text
92
new_app['has_icon'] = False
93
new_app['path'] = ivle.util.make_path(urlname)
94
new_app['desc'] = app.desc
95
new_app['name'] = app.name
96
ctx['apps_in_tabs'].append(new_app)