54
47
return req.make_path(os.path.join(media_path, plugin, path))
56
class MediaFile(object):
57
def __init__(self, root, external, version, ns, path):
59
self.external = external
60
self.version = version
49
class BaseMediaFileView(BaseView):
50
'''A view for media files.
52
This serves static files from directories registered by plugins.
54
Plugins wishing to export media should declare a 'media' attribute,
55
pointing to the directory to serve (relative to the module's directory).
56
The contents of that directory will then be available under
57
/+media/python.path.to.module.
59
def __init__(self, req, ns, path):
68
extern = EXTERNAL_MEDIA_MAP[self.ns]
72
# Unless it's a whitelisted path, we don't want to hear about it.
73
if self.path not in extern[1]:
76
# Grab the admin-configured path for this particular external dep.
77
externdir = self.root.config['media']['externals'][extern[0]]
79
assert isinstance(externdir, basestring)
81
return os.path.join(externdir, self.path)
84
plugin = self.root.config.plugins[self.ns]
88
if not issubclass(plugin, MediaPlugin):
91
mediadir = plugin.media
92
plugindir = os.path.dirname(inspect.getmodule(plugin).__file__)
94
return os.path.join(plugindir, mediadir, self.path)
96
class MediaFileView(BaseView):
63
def _make_filename(self, req):
64
raise NotImplementedError()
99
66
def render(self, req):
100
67
# If it begins with ".." or separator, it's illegal. Die.
101
if self.context.path.startswith("..") or \
102
self.context.path.startswith('/'):
68
if self.path.startswith("..") or self.path.startswith('/'):
105
filename = self.get_filename(req)
71
filename = self._make_filename(req)
109
73
# Find an appropriate MIME type.
110
74
(type, _) = mimetypes.guess_type(filename)
117
81
if not os.access(filename, os.R_OK) or os.path.isdir(filename):
120
if self.context.version is not None:
121
req.headers_out['Expires'] = email.utils.formatdate(
122
timeval=time.time() + (60*60*24*365),
127
84
req.content_type = type
128
85
req.sendfile(filename)
130
def get_filename(self, req):
131
return self.context.filename
133
def get_permissions(self, user, config):
136
def root_to_media(root, *segments):
137
if segments[0].startswith('+'):
138
if segments[0] == '+external':
143
version = segments[0][1:]
144
if segments[1] == '+external':
155
if version is not None and version != root.config['media']['version']:
159
path = os.path.normpath(os.path.join(*path[1:]))
161
return MediaFile(root, external, version, ns, path)
88
class MediaFileView(BaseMediaFileView):
89
'''A view for media files.
91
This serves static files from directories registered by plugins.
93
Plugins wishing to export media should declare a 'media' attribute,
94
pointing to the directory to serve (relative to the module's directory).
95
The contents of that directory will then be available under
96
/+media/python.path.to.module.
100
def _make_filename(self, req):
102
plugin = req.config.plugins[self.ns]
106
if not issubclass(plugin, MediaPlugin):
109
mediadir = plugin.media
110
plugindir = os.path.dirname(inspect.getmodule(plugin).__file__)
112
return os.path.join(plugindir, mediadir, self.path)
114
def get_permissions(self, user):
117
class VersionedMediaFileView(MediaFileView):
118
'''A view for versioned media files, with aggressive caching.
120
This serves static media files with a version string, and requests that
121
browsers cache them for a long time.
124
def __init__(self, req, ns, path, version):
125
super(VersionedMediaFileView, self).__init__(req, ns, path)
126
self.version = version
128
def _make_filename(self, req):
129
if self.version != req.config['media']['version']:
132
# Don't expire for a year.
133
req.headers_out['Expires'] = email.utils.formatdate(
134
timeval=time.time() + (60*60*24*365),
137
return super(VersionedMediaFileView, self)._make_filename(req)
140
# This maps a media namespace to an external dependency directory (in this
141
# case specified by the configuration option media/externals/jquery) and a
142
# list of permitted subpaths.
143
EXTERNAL_MEDIA_MAP = {'jquery': ('jquery', ['jquery.js'])}
145
class ExternalMediaFileView(BaseMediaFileView):
146
'''A view for media files from external dependencies.
148
This serves specific static files from external dependencies as defined in
149
the IVLE configuration.
153
def _make_filename(self, req):
155
extern = EXTERNAL_MEDIA_MAP[self.ns]
159
# Unless it's a whitelisted path, we don't want to hear about it.
160
if self.path not in extern[1]:
163
# Grab the admin-configured path for this particular external dep.
164
externdir = req.config['media']['externals'][extern[0]]
166
assert isinstance(externdir, basestring)
168
return os.path.join(externdir, self.path)
170
def get_permissions(self, user):
173
class ExternalVersionedMediaFileView(ExternalMediaFileView):
174
'''A view for versioned media files from external dependencies, with
177
This serves specific static media files from external dependencies with a
178
version string, and requests that browsers cache them for a long time.
181
def __init__(self, req, ns, path, version):
182
super(ExternalVersionedMediaFileView, self).__init__(req, ns, path)
183
self.version = version
185
def _make_filename(self, req):
186
if self.version != req.config['media']['version']:
189
# Don't expire for a year.
190
req.headers_out['Expires'] = email.utils.formatdate(
191
timeval=time.time() + (60*60*24*365),
194
return super(ExternalVersionedMediaFileView, self)._make_filename(req)
163
197
class Plugin(ViewPlugin, PublicViewPlugin):
164
forward_routes = [(ApplicationRoot, '+media', root_to_media, INF)]
165
views = [(MediaFile, '+index', MediaFileView)]
166
public_forward_routes = forward_routes
199
('+media/+:version/+external/:ns/*path', ExternalVersionedMediaFileView),
200
('+media/+external/:ns/*path', ExternalMediaFileView),
201
('+media/+:version/:ns/*path', VersionedMediaFileView),
202
('+media/:ns/*path', MediaFileView),