56
47
return req.make_path(os.path.join(media_path, plugin, path))
58
class MediaFile(object):
59
def __init__(self, root, external, version, ns, path):
61
self.external = external
62
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):
70
extern = EXTERNAL_MEDIA_MAP[self.ns]
74
# Unless it's a whitelisted path, we don't want to hear about it.
75
# If the whitelist is None (not []), we allow all.
76
if extern[1] is not None and self.path not in extern[1]:
79
# Grab the admin-configured path for this particular external dep.
80
externdir = self.root.config['media']['externals'][extern[0]]
82
assert isinstance(externdir, basestring)
84
return os.path.join(externdir, self.path)
87
plugin = self.root.config.plugins[self.ns]
91
if not issubclass(plugin, MediaPlugin):
94
mediadir = plugin.media
95
plugindir = os.path.dirname(inspect.getmodule(plugin).__file__)
97
return os.path.join(plugindir, mediadir, self.path)
99
class MediaFileView(BaseView):
100
def authorize(self, req):
63
def _make_filename(self, req):
64
raise NotImplementedError()
103
66
def render(self, req):
104
67
# If it begins with ".." or separator, it's illegal. Die.
105
if self.context.path.startswith("..") or \
106
self.context.path.startswith('/'):
68
if self.path.startswith("..") or self.path.startswith('/'):
109
filename = self.get_filename(req)
71
filename = self._make_filename(req)
113
73
# Find an appropriate MIME type.
114
74
(type, _) = mimetypes.guess_type(filename)
121
81
if not os.access(filename, os.R_OK) or os.path.isdir(filename):
124
if self.context.version is not None:
125
req.headers_out['Expires'] = email.utils.formatdate(
126
timeval=time.time() + (60*60*24*365),
131
84
req.content_type = type
132
85
req.sendfile(filename)
134
def get_filename(self, req):
135
return self.context.filename
137
def root_to_media(root, *segments):
138
if segments[0].startswith('+'):
139
if segments[0] == '+external':
144
version = segments[0][1:]
145
if segments[1] == '+external':
156
if version is not None and version != root.config['media']['version']:
160
path = os.path.normpath(os.path.join(*path[1:]))
162
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)
164
197
class Plugin(ViewPlugin, PublicViewPlugin):
165
forward_routes = [(ApplicationRoot, '+media', root_to_media, INF)]
166
views = [(MediaFile, '+index', MediaFileView)]
167
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),