28
28
from ivle.webapp.base.views import BaseView
29
29
from ivle.webapp.base.plugins import ViewPlugin, OverlayPlugin
30
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):
34
class XHTMLView(BaseView):
57
36
A view which provides a base class for views which need to return XHTML
58
37
It is expected that apps which use this view will be written using Genshi
62
41
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):
44
overlay_blacklist = []
46
def __init__(self, req, **kwargs):
48
setattr(self, key, kwargs[key])
84
50
def render(self, req):
85
51
req.content_type = 'text/html' # TODO: Detect application/xhtml+xml
93
59
app_template = os.path.join(os.path.dirname(
94
60
inspect.getmodule(self).__file__), self.template)
95
tmpl = self._loader.load(app_template)
96
app = self.filter(tmpl.generate(viewctx), viewctx)
61
req.write_html_head_foot = False
62
loader = genshi.template.TemplateLoader(".", auto_reload=True)
63
tmpl = loader.load(app_template)
64
app = tmpl.generate(viewctx)
99
66
for plugin in self.plugin_scripts:
100
67
for path in self.plugin_scripts[plugin]:
101
view_scripts.append(media_url(req, plugin, path))
68
req.scripts.append(media_url(req, plugin, path))
104
70
for plugin in self.plugin_styles:
105
71
for path in self.plugin_styles[plugin]:
106
view_styles.append(media_url(req, plugin, path))
72
req.styles.append(media_url(req, plugin, path))
109
75
ctx = genshi.template.Context()
111
overlay_bits = self.render_overlays(req) if req.user else [[]]*4
112
ctx['overlays'] = overlay_bits[0]
76
# XXX: Leave this here!! (Before req.styles is read)
77
ctx['overlays'] = self.render_overlays(req)
114
79
ctx['styles'] = [media_url(req, CorePlugin, 'ivle.css')]
115
ctx['styles'] += view_styles
116
ctx['styles'] += overlay_bits[1]
80
ctx['styles'] += req.styles
118
82
ctx['scripts'] = [media_url(req, CorePlugin, path) for path in
119
83
('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]
84
ctx['scripts'] += req.scripts
124
ctx['scripts_init'] = self.scripts_init + overlay_bits[3]
86
ctx['scripts_init'] = req.scripts_init
125
87
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
88
self.populate_headings(req, ctx)
156
tmpl = self._loader.load(os.path.join(os.path.dirname(__file__),
89
tmpl = loader.load(os.path.join(os.path.dirname(__file__),
157
90
'ivle-headings.html'))
158
91
req.write(tmpl.generate(ctx).render('xhtml', doctype='xhtml'))
178
110
ctx['help_path'] = self.help
180
112
ctx['apps_in_tabs'] = []
181
for plugin in req.config.plugin_index[ViewPlugin]:
113
for plugin in req.plugin_index[ViewPlugin]:
182
114
if not hasattr(plugin, 'tabs'):
185
117
for tab in plugin.tabs:
186
# tab is a tuple: name, title, desc, icon, path, weight, admin
187
# (Admin is optional, defaults to false)
118
# tab is a tuple: name, title, desc, icon, path
189
new_app['this_app'] = hasattr(self, 'tab') \
190
and tab[0] == self.tab
120
new_app['this_app'] = hasattr(self, 'appname') \
121
and tab[0] == self.appname
193
124
if tab[3] is not None:
233
152
#TODO: Re-factor this to look nicer
234
153
for mplugin in overlay.plugin_scripts:
235
154
for path in overlay.plugin_scripts[mplugin]:
236
scripts.append(media_url(req, mplugin, path))
155
req.scripts.append(media_url(req, mplugin, path))
238
157
for mplugin in overlay.plugin_styles:
239
158
for path in overlay.plugin_styles[mplugin]:
240
styles.append(media_url(req, mplugin, path))
159
req.styles.append(media_url(req, mplugin, path))
242
scripts_init += overlay.plugin_scripts_init
161
req.scripts_init += overlay.plugin_scripts_init
244
163
overlays.append(overlay.render(req))
245
return (overlays, styles, scripts, scripts_init)
248
167
def get_error_view(cls, e):
255
174
class XHTMLErrorView(XHTMLView):
256
175
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)
177
def __init__(self, req, exception):
178
self.context = exception
265
180
def populate(self, req, ctx):
267
181
ctx['exception'] = self.context
268
req.headers_out['X-IVLE-Error'] = self.context.message
270
183
class XHTMLUnauthorizedView(XHTMLErrorView):
271
184
template = 'xhtmlunauthorized.html'
273
def __init__(self, req, exception, lastobj):
274
super(XHTMLUnauthorizedView, self).__init__(req, exception, lastobj)
186
def __init__(self, req, exception):
187
super(XHTMLUnauthorizedView, self).__init__(req, exception)
276
if not req.publicmode and req.user is None:
277
190
# Not logged in. Redirect to login page.
281
query_string = '?url=' + urllib.quote(req.uri, safe="/~")
282
req.throw_redirect('/+login' + query_string)
191
req.throw_redirect('/+login?' +
192
urllib.urlencode([('url', req.uri)]))
286
class ViewBreadcrumb(object):
287
def __init__(self, req, context):
289
self.context = context
293
return self.context.breadcrumb_text