diff --git a/flask_s3.py b/flask_s3.py index ff2f220..39eedce 100644 --- a/flask_s3.py +++ b/flask_s3.py @@ -39,6 +39,15 @@ __version__ = (0, 2, 10) + +def _get_statics_prefix(app): + """ + Get the complete prefix that should be used by static files. + """ + upload_prefix = app.config.get('FLASKS3_PREFIX', '') + return '/%s' % upload_prefix.lstrip('/').rstrip('/') + + def split_metadata_params(headers): """ Given a dict of headers for s3, seperates those that are boto3 @@ -122,6 +131,10 @@ def url_for(endpoint, **values): if app.config['FLASKS3_CDN_DOMAIN']: bucket_path = '%s' % app.config['FLASKS3_CDN_DOMAIN'] + + # Both S3 and CDN urls should use the prefix if it exists + bucket_path += _get_statics_prefix(app).rstrip('/') + urls = app.url_map.bind(bucket_path, url_scheme=scheme) return urls.build(endpoint, values=values, force_external=True) return flask_url_for(endpoint, **values) @@ -271,9 +284,11 @@ def _write_files(s3, app, static_url_loc, static_folder, files, bucket, def _upload_files(s3, app, files_, bucket, hashes=None): new_hashes = [] + prefix = _get_statics_prefix(app) for (static_folder, static_url), names in six.iteritems(files_): - new_hashes.extend(_write_files(s3, app, static_url, static_folder, names, - bucket, hashes=hashes)) + static_upload_url = '%s/%s' % (prefix.rstrip('/'), static_url.lstrip('/')) + new_hashes.extend(_write_files(s3, app, static_upload_url, static_folder, + names, bucket, hashes=hashes)) return new_hashes @@ -473,7 +488,9 @@ def init_app(self, app): ('FLASKS3_URL_STYLE', 'host'), ('FLASKS3_GZIP', False), ('FLASKS3_GZIP_ONLY_EXTS', []), - ('FLASKS3_FORCE_MIMETYPE', False)] + ('FLASKS3_FORCE_MIMETYPE', False), + ('FLASKS3_PREFIX', '')] + for k, v in defaults: app.config.setdefault(k, v) diff --git a/test_flask_static.py b/test_flask_static.py index 598523e..0ab1eff 100644 --- a/test_flask_static.py +++ b/test_flask_static.py @@ -182,6 +182,7 @@ def setUp(self): self.app.config['FLASKS3_BUCKET_NAME'] = 'foo' self.app.config['FLASKS3_USE_CACHE_CONTROL'] = True self.app.config['FLASKS3_CACHE_CONTROL'] = 'cache instruction' + self.app.config['S3_CACHE_CONTROL'] = '3600' self.app.config['FLASKS3_HEADERS'] = { 'Expires': 'Thu, 31 Dec 2037 23:59:59 GMT', 'Content-Encoding': 'gzip', @@ -394,6 +395,44 @@ def test_static_folder_path(self): for i, e in zip(inputs, expected): self.assertEquals(e, flask_s3._static_folder_path(*i)) + @patch('flask_s3._write_files') + def test__upload_uses_prefix(self, mock_write_files): + s3_mock = Mock() + local_path = '/local_path/static' + file_paths = ['/local_path/static/file1', '/local_path/static/file2'] + files = {(local_path, '/static'): file_paths} + + flask_s3._upload_files(s3_mock, self.app, files, 's3_bucket') + expected_call = call( + s3_mock, self.app, '/static', local_path, file_paths, 's3_bucket', hashes=None) + self.assertEquals(mock_write_files.call_args_list, [expected_call]) + + for supported_prefix in ['foo', '/foo', 'foo/', '/foo/']: + mock_write_files.reset_mock() + self.app.config['FLASKS3_PREFIX'] = supported_prefix + flask_s3._upload_files(s3_mock, self.app, files, 's3_bucket') + expected_call = call(s3_mock, self.app, '/foo/static', + local_path, file_paths, 's3_bucket', hashes=None) + self.assertEquals(mock_write_files.call_args_list, [expected_call]) + + @patch('flask_s3.current_app') + def test__url_for_uses_prefix(self, mock_current_app): + bucket_path = 'foo.s3.amazonaws.com' + flask_s3.FlaskS3(self.app) + mock_current_app.config = self.app.config + mock_bind = mock_current_app.url_map.bind + + flask_s3.url_for('static', **{'filename': 'test_file.txt'}) + self.assertEqual(mock_bind.call_args_list, [call(bucket_path, url_scheme='https')]) + + for supported_prefix in ['bar', '/bar', 'bar/', '/bar/']: + mock_bind.reset_mock() + self.app.config['FLASKS3_PREFIX'] = supported_prefix + flask_s3.url_for('static', **{'filename': 'test_file.txt'}) + expected_path = '%s/%s' % (bucket_path, 'bar') + self.assertEqual(mock_bind.call_args_list, + [call(expected_path, url_scheme='https')]) + if __name__ == '__main__': unittest.main()