diff --git a/django/contrib/staticfiles/storage.py b/django/contrib/staticfiles/storage.py
index 47aa693fac..9b273e3efd 100644
--- a/django/contrib/staticfiles/storage.py
+++ b/django/contrib/staticfiles/storage.py
@@ -5,6 +5,7 @@ from importlib import import_module
 import os
 import posixpath
 import re
+import json
 
 from django.conf import settings
 from django.core.cache import (caches, InvalidCacheBackendError,
@@ -49,7 +50,7 @@ class StaticFilesStorage(FileSystemStorage):
         return super(StaticFilesStorage, self).path(name)
 
 
-class CachedFilesMixin(object):
+class HashedFilesMixin(object):
     default_template = """url("%s")"""
     patterns = (
         ("*.css", (
@@ -59,13 +60,9 @@ class CachedFilesMixin(object):
     )
 
     def __init__(self, *args, **kwargs):
-        super(CachedFilesMixin, self).__init__(*args, **kwargs)
-        try:
-            self.cache = caches['staticfiles']
-        except InvalidCacheBackendError:
-            # Use the default backend
-            self.cache = default_cache
+        super(HashedFilesMixin, self).__init__(*args, **kwargs)
         self._patterns = OrderedDict()
+        self.hashed_files = {}
         for extension, patterns in self.patterns:
             for pattern in patterns:
                 if isinstance(pattern, (tuple, list)):
@@ -119,9 +116,6 @@ class CachedFilesMixin(object):
             unparsed_name[2] += '?'
         return urlunsplit(unparsed_name)
 
-    def cache_key(self, name):
-        return 'staticfiles:%s' % hashlib.md5(force_bytes(name)).hexdigest()
-
     def url(self, name, force=False):
         """
         Returns the real URL in DEBUG mode.
@@ -133,15 +127,9 @@ class CachedFilesMixin(object):
             if urlsplit(clean_name).path.endswith('/'):  # don't hash paths
                 hashed_name = name
             else:
-                cache_key = self.cache_key(name)
-                hashed_name = self.cache.get(cache_key)
-                if hashed_name is None:
-                    hashed_name = self.hashed_name(clean_name).replace('\\', '/')
-                    # set the cache if there was a miss
-                    # (e.g. if cache server goes down)
-                    self.cache.set(cache_key, hashed_name)
+                hashed_name = self.stored_name(clean_name)
 
-        final_url = super(CachedFilesMixin, self).url(hashed_name)
+        final_url = super(HashedFilesMixin, self).url(hashed_name)
 
         # Special casing for a @font-face hack, like url(myfont.eot?#iefix")
         # http://www.fontspring.com/blog/the-new-bulletproof-font-face-syntax
@@ -220,7 +208,7 @@ class CachedFilesMixin(object):
             return
 
         # where to store the new paths
-        hashed_paths = {}
+        hashed_files = OrderedDict()
 
         # build a list of adjustable files
         matches = lambda path: matches_patterns(path, self._patterns.keys())
@@ -261,7 +249,7 @@ class CachedFilesMixin(object):
                     # then save the processed result
                     content_file = ContentFile(force_bytes(content))
                     saved_name = self._save(hashed_name, content_file)
-                    hashed_name = force_text(saved_name.replace('\\', '/'))
+                    hashed_name = force_text(self.clean_name(saved_name))
                     processed = True
                 else:
                     # or handle the case in which neither processing nor
@@ -269,14 +257,114 @@ class CachedFilesMixin(object):
                     if not hashed_file_exists:
                         processed = True
                         saved_name = self._save(hashed_name, original_file)
-                        hashed_name = force_text(saved_name.replace('\\', '/'))
+                        hashed_name = force_text(self.clean_name(saved_name))
 
                 # and then set the cache accordingly
-                hashed_paths[self.cache_key(name.replace('\\', '/'))] = hashed_name
+                hashed_files[self.hash_key(name)] = hashed_name
                 yield name, hashed_name, processed
 
-        # Finally set the cache
-        self.cache.set_many(hashed_paths)
+        # Finally store the processed paths
+        self.hashed_files.update(hashed_files)
+
+    def clean_name(self, name):
+        return name.replace('\\', '/')
+
+    def hash_key(self, name):
+        return name
+
+    def stored_name(self, name):
+        hash_key = self.hash_key(name)
+        cache_name = self.hashed_files.get(hash_key)
+        if cache_name is None:
+            cache_name = self.clean_name(self.hashed_name(name))
+            # store the hashed name if there was a miss, e.g.
+            # when the files are still processed
+            self.hashed_files[hash_key] = cache_name
+        return cache_name
+
+
+class ManifestFilesMixin(HashedFilesMixin):
+    manifest_version = '1.0'  # the manifest format standard
+    manifest_name = 'staticfiles.json'
+
+    def __init__(self, *args, **kwargs):
+        super(ManifestFilesMixin, self).__init__(*args, **kwargs)
+        self.hashed_files = self.load_manifest()
+
+    def read_manifest(self):
+        try:
+            with self.open(self.manifest_name) as manifest:
+                return manifest.read()
+        except IOError:
+            return None
+
+    def load_manifest(self):
+        content = self.read_manifest()
+        if content is None:
+            return OrderedDict()
+        try:
+            stored = json.loads(content, object_pairs_hook=OrderedDict)
+        except ValueError:
+            pass
+        else:
+            version = stored.get('version', None)
+            if version == '1.0':
+                return stored.get('paths', OrderedDict())
+        raise ValueError("Couldn't load manifest '%s' (version %s)" %
+                         (self.manifest_name, self.manifest_version))
+
+    def post_process(self, *args, **kwargs):
+        all_post_processed = super(ManifestFilesMixin,
+                                   self).post_process(*args, **kwargs)
+        for post_processed in all_post_processed:
+            yield post_processed
+        payload = {'paths': self.hashed_files, 'version': self.manifest_version}
+        if self.exists(self.manifest_name):
+            self.delete(self.manifest_name)
+        self._save(self.manifest_name, ContentFile(json.dumps(payload)))
+
+
+class _MappingCache(object):
+    """
+    A small dict-like wrapper for a given cache backend instance.
+    """
+    def __init__(self, cache):
+        self.cache = cache
+
+    def __setitem__(self, key, value):
+        self.cache.set(key, value)
+
+    def __getitem__(self, key):
+        value = self.cache.get(key, None)
+        if value is None:
+            raise KeyError("Couldn't find a file name '%s'" % key)
+        return value
+
+    def clear(self):
+        self.cache.clear()
+
+    def update(self, data):
+        self.cache.set_many(data)
+
+    def get(self, key, default=None):
+        try:
+            return self[key]
+        except KeyError:
+            return default
+
+
+class CachedFilesMixin(HashedFilesMixin):
+    def __init__(self, *args, **kwargs):
+        super(CachedFilesMixin, self).__init__(*args, **kwargs)
+        try:
+            self.hashed_files = _MappingCache(caches['staticfiles'])
+        except InvalidCacheBackendError:
+            # Use the default backend
+            self.hashed_files = _MappingCache(default_cache)
+
+    def hash_key(self, name):
+        key = hashlib.md5(force_bytes(self.clean_name(name))).hexdigest()
+        return 'staticfiles:%s' % key
 
 
 class CachedStaticFilesStorage(CachedFilesMixin, StaticFilesStorage):
@@ -287,6 +375,14 @@ class CachedStaticFilesStorage(CachedFilesMixin, StaticFilesStorage):
     pass
 
 
+class ManifestStaticFilesStorage(ManifestFilesMixin, StaticFilesStorage):
+    """
+    A static file system storage backend which also saves
+    hashed copies of the files it saves.
+    """
+    pass
+
+
 class AppStaticStorage(FileSystemStorage):
     """
     A file system storage backend that takes an app module and works
diff --git a/docs/ref/contrib/staticfiles.txt b/docs/ref/contrib/staticfiles.txt
index 3202321269..2ad880fb2b 100644
--- a/docs/ref/contrib/staticfiles.txt
+++ b/docs/ref/contrib/staticfiles.txt
@@ -210,90 +210,106 @@ StaticFilesStorage
 
 .. class:: storage.StaticFilesStorage
 
-    A subclass of the :class:`~django.core.files.storage.FileSystemStorage`
-    storage backend that uses the :setting:`STATIC_ROOT` setting as the base
-    file system location and the :setting:`STATIC_URL` setting respectively
-    as the base URL.
+A subclass of the :class:`~django.core.files.storage.FileSystemStorage`
+storage backend that uses the :setting:`STATIC_ROOT` setting as the base
+file system location and the :setting:`STATIC_URL` setting respectively
+as the base URL.
 
-    .. method:: post_process(paths, **options)
+.. method:: post_process(paths, **options)
 
-    This method is called by the :djadmin:`collectstatic` management command
-    after each run and gets passed the local storages and paths of found
-    files as a dictionary, as well as the command line options.
+This method is called by the :djadmin:`collectstatic` management command
+after each run and gets passed the local storages and paths of found
+files as a dictionary, as well as the command line options.
 
-    The :class:`~django.contrib.staticfiles.storage.CachedStaticFilesStorage`
-    uses this behind the scenes to replace the paths with their hashed
-    counterparts and update the cache appropriately.
+The :class:`~django.contrib.staticfiles.storage.CachedStaticFilesStorage`
+uses this behind the scenes to replace the paths with their hashed
+counterparts and update the cache appropriately.
+
+ManifestStaticFilesStorage
+--------------------------
+
+.. versionadded:: 1.7
+
+.. class:: storage.ManifestStaticFilesStorage
+
+A subclass of the :class:`~django.contrib.staticfiles.storage.StaticFilesStorage`
+storage backend which stores the file names it handles by appending the MD5
+hash of the file's content to the filename. For example, the file
+``css/styles.css`` would also be saved as ``css/styles.55e7cbb9ba48.css``.
+
+The purpose of this storage is to keep serving the old files in case some
+pages still refer to those files, e.g. because they are cached by you or
+a 3rd party proxy server. Additionally, it's very helpful if you want to
+apply `far future Expires headers`_ to the deployed files to speed up the
+load time for subsequent page visits.
+
+The storage backend automatically replaces the paths found in the saved
+files matching other saved files with the path of the cached copy (using
+the :meth:`~django.contrib.staticfiles.storage.StaticFilesStorage.post_process`
+method). The regular expressions used to find those paths
+(``django.contrib.staticfiles.storage.HashedFilesMixin.patterns``)
+by default covers the `@import`_ rule and `url()`_ statement of `Cascading
+Style Sheets`_. For example, the ``'css/styles.css'`` file with the
+content
+
+.. code-block:: css+django
+
+    @import url("../admin/css/base.css");
+
+would be replaced by calling the :meth:`~django.core.files.storage.Storage.url`
+method of the ``ManifestStaticFilesStorage`` storage backend, ultimately
+saving a ``'css/styles.55e7cbb9ba48.css'`` file with the following
+content:
+
+.. code-block:: css+django
+
+    @import url("../admin/css/base.27e20196a850.css");
+
+To enable the ``ManifestStaticFilesStorage`` you have to make sure the
+following requirements are met:
+
+* the :setting:`STATICFILES_STORAGE` setting is set to
+  ``'django.contrib.staticfiles.storage.ManifestStaticFilesStorage'``
+* the :setting:`DEBUG` setting is set to ``False``
+* you use the ``staticfiles`` :ttag:`static<staticfiles-static>` template
+  tag to refer to your static files in your templates
+* you've collected all your static files by using the
+  :djadmin:`collectstatic` management command
+
+Since creating the MD5 hash can be a performance burden to your website
+during runtime, ``staticfiles`` will automatically store the mapping with
+hashed names for all processed files in a file called ``staticfiles.json``.
+This happens once when you run the :djadmin:`collectstatic` management
+command.
+
+.. method:: file_hash(name, content=None)
+
+The method that is used when creating the hashed name of a file.
+Needs to return a hash for the given file name and content.
+By default it calculates a MD5 hash from the content's chunks as
+mentioned above. Feel free to override this method to use your own
+hashing algorithm.
+
+.. _`far future Expires headers`: http://developer.yahoo.com/performance/rules.html#expires
+.. _`@import`: http://www.w3.org/TR/CSS2/cascade.html#at-import
+.. _`url()`: http://www.w3.org/TR/CSS2/syndata.html#uri
+.. _`Cascading Style Sheets`: http://www.w3.org/Style/CSS/
 
 CachedStaticFilesStorage
 ------------------------
 
 .. class:: storage.CachedStaticFilesStorage
 
-    A subclass of the :class:`~django.contrib.staticfiles.storage.StaticFilesStorage`
-    storage backend which caches the files it saves by appending the MD5 hash
-    of the file's content to the filename. For example, the file
-    ``css/styles.css`` would also be saved as ``css/styles.55e7cbb9ba48.css``.
+``CachedStaticFilesStorage`` is a similar class like the
+:class:`~django.contrib.staticfiles.storage.ManifestStaticFilesStorage` class
+but uses Django's :doc:`caching framework</topics/cache>` for storing the
+hashed names of processed files instead of a static manifest file called
+``staticfiles.json``. This is mostly useful for situations in which you don't
+have accesss to the file system.
 
-    The purpose of this storage is to keep serving the old files in case some
-    pages still refer to those files, e.g. because they are cached by you or
-    a 3rd party proxy server. Additionally, it's very helpful if you want to
-    apply `far future Expires headers`_ to the deployed files to speed up the
-    load time for subsequent page visits.
-
-    The storage backend automatically replaces the paths found in the saved
-    files matching other saved files with the path of the cached copy (using
-    the :meth:`~django.contrib.staticfiles.storage.StaticFilesStorage.post_process`
-    method). The regular expressions used to find those paths
-    (``django.contrib.staticfiles.storage.CachedStaticFilesStorage.cached_patterns``)
-    by default cover the `@import`_ rule and `url()`_ statement of `Cascading
-    Style Sheets`_. For example, the ``'css/styles.css'`` file with the
-    content
-
-    .. code-block:: css+django
-
-        @import url("../admin/css/base.css");
-
-    would be replaced by calling the
-    :meth:`~django.core.files.storage.Storage.url`
-    method of the ``CachedStaticFilesStorage`` storage backend, ultimately
-    saving a ``'css/styles.55e7cbb9ba48.css'`` file with the following
-    content:
-
-    .. code-block:: css+django
-
-        @import url("../admin/css/base.27e20196a850.css");
-
-    To enable the ``CachedStaticFilesStorage`` you have to make sure the
-    following requirements are met:
-
-    * the :setting:`STATICFILES_STORAGE` setting is set to
-      ``'django.contrib.staticfiles.storage.CachedStaticFilesStorage'``
-    * the :setting:`DEBUG` setting is set to ``False``
-    * you use the ``staticfiles`` :ttag:`static<staticfiles-static>` template
-      tag to refer to your static files in your templates
-    * you've collected all your static files by using the
-      :djadmin:`collectstatic` management command
-
-    Since creating the MD5 hash can be a performance burden to your website
-    during runtime, ``staticfiles`` will automatically try to cache the
-    hashed name for each file path using Django's :doc:`caching
-    framework</topics/cache>`. If you want to override certain options of the
-    cache backend the storage uses, simply specify a custom entry in the
-    :setting:`CACHES` setting named ``'staticfiles'``. It falls back to using
-    the ``'default'`` cache backend.
-
-    .. method:: file_hash(name, content=None)
-
-    The method that is used when creating the hashed name of a file.
-    Needs to return a hash for the given file name and content.
-    By default it calculates a MD5 hash from the content's chunks as
-    mentioned above.
-
-.. _`far future Expires headers`: http://developer.yahoo.com/performance/rules.html#expires
-.. _`@import`: http://www.w3.org/TR/CSS2/cascade.html#at-import
-.. _`url()`: http://www.w3.org/TR/CSS2/syndata.html#uri
-.. _`Cascading Style Sheets`: http://www.w3.org/Style/CSS/
+If you want to override certain options of the cache backend the storage uses,
+simply specify a custom entry in the :setting:`CACHES` setting named
+``'staticfiles'``. It falls back to using the ``'default'`` cache backend.
 
 .. currentmodule:: django.contrib.staticfiles.templatetags.staticfiles
 
diff --git a/docs/releases/1.7.txt b/docs/releases/1.7.txt
index 34b69e3e83..637778ccf9 100644
--- a/docs/releases/1.7.txt
+++ b/docs/releases/1.7.txt
@@ -340,6 +340,19 @@ Minor features
   and :attr:`~django.core.files.storage.FileSystemStorage.directory_permissions_mode`
   parameters. See :djadmin:`collectstatic` for example usage.
 
+* The :class:`~django.contrib.staticfiles.storage.CachedStaticFilesStorage`
+  backend gets a sibling class called
+  :class:`~django.contrib.staticfiles.storage.ManifestStaticFilesStorage`
+  that doesn't use the cache system at all but instead a JSON file called
+  ``staticfiles.json`` for storing the mapping between the original file name
+  (e.g. ``css/styles.css``) and the hashed file name (e.g.
+  ``css/styles.55e7cbb9ba48.css``. The ``staticfiles.json`` file is created
+  when running the :djadmin:`collectstatic` management command and should
+  be a less expensive alternative for remote storages such as Amazon S3.
+
+  See the :class:`~django.contrib.staticfiles.storage.ManifestStaticFilesStorage`
+  docs for more information.
+
 :mod:`django.contrib.syndication`
 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
diff --git a/tests/staticfiles_tests/tests.py b/tests/staticfiles_tests/tests.py
index 1de9f9ed84..affc03a5ed 100644
--- a/tests/staticfiles_tests/tests.py
+++ b/tests/staticfiles_tests/tests.py
@@ -371,20 +371,13 @@ class TestCollectionNonLocalStorage(CollectionTestCase, TestNoFilesCreated):
     pass
 
 
-# we set DEBUG to False here since the template tag wouldn't work otherwise
-@override_settings(**dict(
-    TEST_SETTINGS,
-    STATICFILES_STORAGE='django.contrib.staticfiles.storage.CachedStaticFilesStorage',
-    DEBUG=False,
-))
-class TestCollectionCachedStorage(BaseCollectionTestCase,
-        BaseStaticFilesTestCase, TestCase):
-    """
-    Tests for the Cache busting storage
-    """
-    def cached_file_path(self, path):
-        fullpath = self.render_template(self.static_template_snippet(path))
-        return fullpath.replace(settings.STATIC_URL, '')
+def hashed_file_path(test, path):
+    fullpath = test.render_template(test.static_template_snippet(path))
+    return fullpath.replace(settings.STATIC_URL, '')
+
+
+class TestHashedFiles(object):
+    hashed_file_path = hashed_file_path
 
     def test_template_tag_return(self):
         """
@@ -405,7 +398,7 @@ class TestCollectionCachedStorage(BaseCollectionTestCase,
                                  "/static/path/?query")
 
     def test_template_tag_simple_content(self):
-        relpath = self.cached_file_path("cached/styles.css")
+        relpath = self.hashed_file_path("cached/styles.css")
         self.assertEqual(relpath, "cached/styles.93b1147e8552.css")
         with storage.staticfiles_storage.open(relpath) as relfile:
             content = relfile.read()
@@ -413,7 +406,7 @@ class TestCollectionCachedStorage(BaseCollectionTestCase,
             self.assertIn(b"other.d41d8cd98f00.css", content)
 
     def test_path_ignored_completely(self):
-        relpath = self.cached_file_path("cached/css/ignored.css")
+        relpath = self.hashed_file_path("cached/css/ignored.css")
         self.assertEqual(relpath, "cached/css/ignored.6c77f2643390.css")
         with storage.staticfiles_storage.open(relpath) as relfile:
             content = relfile.read()
@@ -424,7 +417,7 @@ class TestCollectionCachedStorage(BaseCollectionTestCase,
             self.assertIn(b'//foobar', content)
 
     def test_path_with_querystring(self):
-        relpath = self.cached_file_path("cached/styles.css?spam=eggs")
+        relpath = self.hashed_file_path("cached/styles.css?spam=eggs")
         self.assertEqual(relpath,
                          "cached/styles.93b1147e8552.css?spam=eggs")
         with storage.staticfiles_storage.open(
@@ -434,7 +427,7 @@ class TestCollectionCachedStorage(BaseCollectionTestCase,
             self.assertIn(b"other.d41d8cd98f00.css", content)
 
     def test_path_with_fragment(self):
-        relpath = self.cached_file_path("cached/styles.css#eggs")
+        relpath = self.hashed_file_path("cached/styles.css#eggs")
         self.assertEqual(relpath, "cached/styles.93b1147e8552.css#eggs")
         with storage.staticfiles_storage.open(
                 "cached/styles.93b1147e8552.css") as relfile:
@@ -443,7 +436,7 @@ class TestCollectionCachedStorage(BaseCollectionTestCase,
             self.assertIn(b"other.d41d8cd98f00.css", content)
 
     def test_path_with_querystring_and_fragment(self):
-        relpath = self.cached_file_path("cached/css/fragments.css")
+        relpath = self.hashed_file_path("cached/css/fragments.css")
         self.assertEqual(relpath, "cached/css/fragments.75433540b096.css")
         with storage.staticfiles_storage.open(relpath) as relfile:
             content = relfile.read()
@@ -453,7 +446,7 @@ class TestCollectionCachedStorage(BaseCollectionTestCase,
             self.assertIn(b'#default#VML', content)
 
     def test_template_tag_absolute(self):
-        relpath = self.cached_file_path("cached/absolute.css")
+        relpath = self.hashed_file_path("cached/absolute.css")
         self.assertEqual(relpath, "cached/absolute.23f087ad823a.css")
         with storage.staticfiles_storage.open(relpath) as relfile:
             content = relfile.read()
@@ -462,7 +455,7 @@ class TestCollectionCachedStorage(BaseCollectionTestCase,
             self.assertIn(b'/static/cached/img/relative.acae32e4532b.png', content)
 
     def test_template_tag_denorm(self):
-        relpath = self.cached_file_path("cached/denorm.css")
+        relpath = self.hashed_file_path("cached/denorm.css")
         self.assertEqual(relpath, "cached/denorm.c5bd139ad821.css")
         with storage.staticfiles_storage.open(relpath) as relfile:
             content = relfile.read()
@@ -472,7 +465,7 @@ class TestCollectionCachedStorage(BaseCollectionTestCase,
             self.assertIn(b'url("img/relative.acae32e4532b.png', content)
 
     def test_template_tag_relative(self):
-        relpath = self.cached_file_path("cached/relative.css")
+        relpath = self.hashed_file_path("cached/relative.css")
         self.assertEqual(relpath, "cached/relative.2217ea7273c2.css")
         with storage.staticfiles_storage.open(relpath) as relfile:
             content = relfile.read()
@@ -484,13 +477,13 @@ class TestCollectionCachedStorage(BaseCollectionTestCase,
 
     def test_import_replacement(self):
         "See #18050"
-        relpath = self.cached_file_path("cached/import.css")
+        relpath = self.hashed_file_path("cached/import.css")
         self.assertEqual(relpath, "cached/import.2b1d40b0bbd4.css")
         with storage.staticfiles_storage.open(relpath) as relfile:
             self.assertIn(b"""import url("styles.93b1147e8552.css")""", relfile.read())
 
     def test_template_tag_deep_relative(self):
-        relpath = self.cached_file_path("cached/css/window.css")
+        relpath = self.hashed_file_path("cached/css/window.css")
         self.assertEqual(relpath, "cached/css/window.9db38d5169f3.css")
         with storage.staticfiles_storage.open(relpath) as relfile:
             content = relfile.read()
@@ -498,26 +491,11 @@ class TestCollectionCachedStorage(BaseCollectionTestCase,
             self.assertIn(b'url("img/window.acae32e4532b.png")', content)
 
     def test_template_tag_url(self):
-        relpath = self.cached_file_path("cached/url.css")
+        relpath = self.hashed_file_path("cached/url.css")
         self.assertEqual(relpath, "cached/url.615e21601e4b.css")
         with storage.staticfiles_storage.open(relpath) as relfile:
             self.assertIn(b"https://", relfile.read())
 
-    def test_cache_invalidation(self):
-        name = "cached/styles.css"
-        hashed_name = "cached/styles.93b1147e8552.css"
-        # check if the cache is filled correctly as expected
-        cache_key = storage.staticfiles_storage.cache_key(name)
-        cached_name = storage.staticfiles_storage.cache.get(cache_key)
-        self.assertEqual(self.cached_file_path(name), cached_name)
-        # clearing the cache to make sure we re-set it correctly in the url method
-        storage.staticfiles_storage.cache.clear()
-        cached_name = storage.staticfiles_storage.cache.get(cache_key)
-        self.assertEqual(cached_name, None)
-        self.assertEqual(self.cached_file_path(name), hashed_name)
-        cached_name = storage.staticfiles_storage.cache.get(cache_key)
-        self.assertEqual(cached_name, hashed_name)
-
     def test_post_processing(self):
         """Test that post_processing behaves correctly.
 
@@ -545,18 +523,8 @@ class TestCollectionCachedStorage(BaseCollectionTestCase,
         self.assertIn(os.path.join('cached', 'css', 'img', 'window.png'), stats['unmodified'])
         self.assertIn(os.path.join('test', 'nonascii.css'), stats['post_processed'])
 
-    def test_cache_key_memcache_validation(self):
-        """
-        Handle cache key creation correctly, see #17861.
-        """
-        name = "/some crazy/long filename/ with spaces Here and ?#%#$/other/stuff/some crazy/long filename/ with spaces Here and ?#%#$/other/stuff/some crazy/long filename/ with spaces Here and ?#%#$/other/stuff/some crazy/long filename/ with spaces Here and ?#%#$/other/stuff/some crazy/long filename/ with spaces Here and ?#%#$/other/stuff/some crazy/" + "\x16" + "\xb4"
-        cache_key = storage.staticfiles_storage.cache_key(name)
-        cache_validator = BaseCache({})
-        cache_validator.validate_key(cache_key)
-        self.assertEqual(cache_key, 'staticfiles:821ea71ef36f95b3922a77f7364670e7')
-
     def test_css_import_case_insensitive(self):
-        relpath = self.cached_file_path("cached/styles_insensitive.css")
+        relpath = self.hashed_file_path("cached/styles_insensitive.css")
         self.assertEqual(relpath, "cached/styles_insensitive.2f0151cca872.css")
         with storage.staticfiles_storage.open(relpath) as relfile:
             content = relfile.read()
@@ -579,6 +547,67 @@ class TestCollectionCachedStorage(BaseCollectionTestCase,
         self.assertEqual("Post-processing 'faulty.css' failed!\n\n", err.getvalue())
 
 
+# we set DEBUG to False here since the template tag wouldn't work otherwise
+@override_settings(**dict(
+    TEST_SETTINGS,
+    STATICFILES_STORAGE='django.contrib.staticfiles.storage.CachedStaticFilesStorage',
+    DEBUG=False,
+))
+class TestCollectionCachedStorage(TestHashedFiles, BaseCollectionTestCase,
+        BaseStaticFilesTestCase, TestCase):
+    """
+    Tests for the Cache busting storage
+    """
+    def test_cache_invalidation(self):
+        name = "cached/styles.css"
+        hashed_name = "cached/styles.93b1147e8552.css"
+        # check if the cache is filled correctly as expected
+        cache_key = storage.staticfiles_storage.hash_key(name)
+        cached_name = storage.staticfiles_storage.hashed_files.get(cache_key)
+        self.assertEqual(self.hashed_file_path(name), cached_name)
+        # clearing the cache to make sure we re-set it correctly in the url method
+        storage.staticfiles_storage.hashed_files.clear()
+        cached_name = storage.staticfiles_storage.hashed_files.get(cache_key)
+        self.assertEqual(cached_name, None)
+        self.assertEqual(self.hashed_file_path(name), hashed_name)
+        cached_name = storage.staticfiles_storage.hashed_files.get(cache_key)
+        self.assertEqual(cached_name, hashed_name)
+
+    def test_cache_key_memcache_validation(self):
+        """
+        Handle cache key creation correctly, see #17861.
+        """
+        name = "/some crazy/long filename/ with spaces Here and ?#%#$/other/stuff/some crazy/long filename/ with spaces Here and ?#%#$/other/stuff/some crazy/long filename/ with spaces Here and ?#%#$/other/stuff/some crazy/long filename/ with spaces Here and ?#%#$/other/stuff/some crazy/long filename/ with spaces Here and ?#%#$/other/stuff/some crazy/" + "\x16" + "\xb4"
+        cache_key = storage.staticfiles_storage.hash_key(name)
+        cache_validator = BaseCache({})
+        cache_validator.validate_key(cache_key)
+        self.assertEqual(cache_key, 'staticfiles:821ea71ef36f95b3922a77f7364670e7')
+
+
+# we set DEBUG to False here since the template tag wouldn't work otherwise
+@override_settings(**dict(
+    TEST_SETTINGS,
+    STATICFILES_STORAGE='django.contrib.staticfiles.storage.ManifestStaticFilesStorage',
+    DEBUG=False,
+))
+class TestCollectionManifestStorage(TestHashedFiles, BaseCollectionTestCase,
+        BaseStaticFilesTestCase, TestCase):
+    """
+    Tests for the Cache busting storage
+    """
+    def test_manifest_exists(self):
+        filename = storage.staticfiles_storage.manifest_name
+        path = storage.staticfiles_storage.path(filename)
+        self.assertTrue(os.path.exists(path))
+
+    def test_loaded_cache(self):
+        self.assertNotEqual(storage.staticfiles_storage.hashed_files, {})
+        manifest_content = storage.staticfiles_storage.read_manifest()
+        self.assertIn('"version": "%s"' %
+                      storage.staticfiles_storage.manifest_version,
+                      force_text(manifest_content))
+
+
 # we set DEBUG to False here since the template tag wouldn't work otherwise
 @override_settings(**dict(
     TEST_SETTINGS,
@@ -590,9 +619,7 @@ class TestCollectionSimpleCachedStorage(BaseCollectionTestCase,
     """
     Tests for the Cache busting storage
     """
-    def cached_file_path(self, path):
-        fullpath = self.render_template(self.static_template_snippet(path))
-        return fullpath.replace(settings.STATIC_URL, '')
+    hashed_file_path = hashed_file_path
 
     def test_template_tag_return(self):
         """
@@ -611,7 +638,7 @@ class TestCollectionSimpleCachedStorage(BaseCollectionTestCase,
                                  "/static/path/?query")
 
     def test_template_tag_simple_content(self):
-        relpath = self.cached_file_path("cached/styles.css")
+        relpath = self.hashed_file_path("cached/styles.css")
         self.assertEqual(relpath, "cached/styles.deploy12345.css")
         with storage.staticfiles_storage.open(relpath) as relfile:
             content = relfile.read()