test_storage.py 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597
  1. import os
  2. import shutil
  3. import sys
  4. import tempfile
  5. import unittest
  6. from io import StringIO
  7. from django.conf import settings
  8. from django.contrib.staticfiles import finders, storage
  9. from django.contrib.staticfiles.management.commands.collectstatic import (
  10. Command as CollectstaticCommand,
  11. )
  12. from django.core.cache.backends.base import BaseCache
  13. from django.core.management import call_command
  14. from django.test import override_settings
  15. from .cases import CollectionTestCase
  16. from .settings import TEST_ROOT
  17. def hashed_file_path(test, path):
  18. fullpath = test.render_template(test.static_template_snippet(path))
  19. return fullpath.replace(settings.STATIC_URL, '')
  20. class TestHashedFiles:
  21. hashed_file_path = hashed_file_path
  22. def setUp(self):
  23. self._max_post_process_passes = storage.staticfiles_storage.max_post_process_passes
  24. super().setUp()
  25. def tearDown(self):
  26. # Clear hashed files to avoid side effects among tests.
  27. storage.staticfiles_storage.hashed_files.clear()
  28. storage.staticfiles_storage.max_post_process_passes = self._max_post_process_passes
  29. def assertPostCondition(self):
  30. """
  31. Assert post conditions for a test are met. Must be manually called at
  32. the end of each test.
  33. """
  34. pass
  35. def test_template_tag_return(self):
  36. """
  37. Test the CachedStaticFilesStorage backend.
  38. """
  39. self.assertStaticRaises(ValueError, "does/not/exist.png", "/static/does/not/exist.png")
  40. self.assertStaticRenders("test/file.txt", "/static/test/file.dad0999e4f8f.txt")
  41. self.assertStaticRenders("test/file.txt", "/static/test/file.dad0999e4f8f.txt", asvar=True)
  42. self.assertStaticRenders("cached/styles.css", "/static/cached/styles.5e0040571e1a.css")
  43. self.assertStaticRenders("path/", "/static/path/")
  44. self.assertStaticRenders("path/?query", "/static/path/?query")
  45. self.assertPostCondition()
  46. def test_template_tag_simple_content(self):
  47. relpath = self.hashed_file_path("cached/styles.css")
  48. self.assertEqual(relpath, "cached/styles.5e0040571e1a.css")
  49. with storage.staticfiles_storage.open(relpath) as relfile:
  50. content = relfile.read()
  51. self.assertNotIn(b"cached/other.css", content)
  52. self.assertIn(b"other.d41d8cd98f00.css", content)
  53. self.assertPostCondition()
  54. def test_path_ignored_completely(self):
  55. relpath = self.hashed_file_path("cached/css/ignored.css")
  56. self.assertEqual(relpath, "cached/css/ignored.554da52152af.css")
  57. with storage.staticfiles_storage.open(relpath) as relfile:
  58. content = relfile.read()
  59. self.assertIn(b'#foobar', content)
  60. self.assertIn(b'http:foobar', content)
  61. self.assertIn(b'https:foobar', content)
  62. self.assertIn(b'data:foobar', content)
  63. self.assertIn(b'chrome:foobar', content)
  64. self.assertIn(b'//foobar', content)
  65. self.assertPostCondition()
  66. def test_path_with_querystring(self):
  67. relpath = self.hashed_file_path("cached/styles.css?spam=eggs")
  68. self.assertEqual(relpath, "cached/styles.5e0040571e1a.css?spam=eggs")
  69. with storage.staticfiles_storage.open("cached/styles.5e0040571e1a.css") as relfile:
  70. content = relfile.read()
  71. self.assertNotIn(b"cached/other.css", content)
  72. self.assertIn(b"other.d41d8cd98f00.css", content)
  73. self.assertPostCondition()
  74. def test_path_with_fragment(self):
  75. relpath = self.hashed_file_path("cached/styles.css#eggs")
  76. self.assertEqual(relpath, "cached/styles.5e0040571e1a.css#eggs")
  77. with storage.staticfiles_storage.open("cached/styles.5e0040571e1a.css") as relfile:
  78. content = relfile.read()
  79. self.assertNotIn(b"cached/other.css", content)
  80. self.assertIn(b"other.d41d8cd98f00.css", content)
  81. self.assertPostCondition()
  82. def test_path_with_querystring_and_fragment(self):
  83. relpath = self.hashed_file_path("cached/css/fragments.css")
  84. self.assertEqual(relpath, "cached/css/fragments.a60c0e74834f.css")
  85. with storage.staticfiles_storage.open(relpath) as relfile:
  86. content = relfile.read()
  87. self.assertIn(b'fonts/font.b9b105392eb8.eot?#iefix', content)
  88. self.assertIn(b'fonts/font.b8d603e42714.svg#webfontIyfZbseF', content)
  89. self.assertIn(b'fonts/font.b8d603e42714.svg#path/to/../../fonts/font.svg', content)
  90. self.assertIn(b'data:font/woff;charset=utf-8;base64,d09GRgABAAAAADJoAA0AAAAAR2QAAQAAAAAAAAAAAAA', content)
  91. self.assertIn(b'#default#VML', content)
  92. self.assertPostCondition()
  93. def test_template_tag_absolute(self):
  94. relpath = self.hashed_file_path("cached/absolute.css")
  95. self.assertEqual(relpath, "cached/absolute.eb04def9f9a4.css")
  96. with storage.staticfiles_storage.open(relpath) as relfile:
  97. content = relfile.read()
  98. self.assertNotIn(b"/static/cached/styles.css", content)
  99. self.assertIn(b"/static/cached/styles.5e0040571e1a.css", content)
  100. self.assertNotIn(b"/static/styles_root.css", content)
  101. self.assertIn(b"/static/styles_root.401f2509a628.css", content)
  102. self.assertIn(b'/static/cached/img/relative.acae32e4532b.png', content)
  103. self.assertPostCondition()
  104. def test_template_tag_absolute_root(self):
  105. """
  106. Like test_template_tag_absolute, but for a file in STATIC_ROOT (#26249).
  107. """
  108. relpath = self.hashed_file_path("absolute_root.css")
  109. self.assertEqual(relpath, "absolute_root.f821df1b64f7.css")
  110. with storage.staticfiles_storage.open(relpath) as relfile:
  111. content = relfile.read()
  112. self.assertNotIn(b"/static/styles_root.css", content)
  113. self.assertIn(b"/static/styles_root.401f2509a628.css", content)
  114. self.assertPostCondition()
  115. def test_template_tag_relative(self):
  116. relpath = self.hashed_file_path("cached/relative.css")
  117. self.assertEqual(relpath, "cached/relative.c3e9e1ea6f2e.css")
  118. with storage.staticfiles_storage.open(relpath) as relfile:
  119. content = relfile.read()
  120. self.assertNotIn(b"../cached/styles.css", content)
  121. self.assertNotIn(b'@import "styles.css"', content)
  122. self.assertNotIn(b'url(img/relative.png)', content)
  123. self.assertIn(b'url("img/relative.acae32e4532b.png")', content)
  124. self.assertIn(b"../cached/styles.5e0040571e1a.css", content)
  125. self.assertPostCondition()
  126. def test_import_replacement(self):
  127. "See #18050"
  128. relpath = self.hashed_file_path("cached/import.css")
  129. self.assertEqual(relpath, "cached/import.f53576679e5a.css")
  130. with storage.staticfiles_storage.open(relpath) as relfile:
  131. self.assertIn(b"""import url("styles.5e0040571e1a.css")""", relfile.read())
  132. self.assertPostCondition()
  133. def test_template_tag_deep_relative(self):
  134. relpath = self.hashed_file_path("cached/css/window.css")
  135. self.assertEqual(relpath, "cached/css/window.5d5c10836967.css")
  136. with storage.staticfiles_storage.open(relpath) as relfile:
  137. content = relfile.read()
  138. self.assertNotIn(b'url(img/window.png)', content)
  139. self.assertIn(b'url("img/window.acae32e4532b.png")', content)
  140. self.assertPostCondition()
  141. def test_template_tag_url(self):
  142. relpath = self.hashed_file_path("cached/url.css")
  143. self.assertEqual(relpath, "cached/url.902310b73412.css")
  144. with storage.staticfiles_storage.open(relpath) as relfile:
  145. self.assertIn(b"https://", relfile.read())
  146. self.assertPostCondition()
  147. @override_settings(
  148. STATICFILES_DIRS=[os.path.join(TEST_ROOT, 'project', 'loop')],
  149. STATICFILES_FINDERS=['django.contrib.staticfiles.finders.FileSystemFinder'],
  150. )
  151. def test_import_loop(self):
  152. finders.get_finder.cache_clear()
  153. err = StringIO()
  154. with self.assertRaisesMessage(RuntimeError, 'Max post-process passes exceeded'):
  155. call_command('collectstatic', interactive=False, verbosity=0, stderr=err)
  156. self.assertEqual("Post-processing 'All' failed!\n\n", err.getvalue())
  157. self.assertPostCondition()
  158. def test_post_processing(self):
  159. """
  160. post_processing behaves correctly.
  161. Files that are alterable should always be post-processed; files that
  162. aren't should be skipped.
  163. collectstatic has already been called once in setUp() for this testcase,
  164. therefore we check by verifying behavior on a second run.
  165. """
  166. collectstatic_args = {
  167. 'interactive': False,
  168. 'verbosity': 0,
  169. 'link': False,
  170. 'clear': False,
  171. 'dry_run': False,
  172. 'post_process': True,
  173. 'use_default_ignore_patterns': True,
  174. 'ignore_patterns': ['*.ignoreme'],
  175. }
  176. collectstatic_cmd = CollectstaticCommand()
  177. collectstatic_cmd.set_options(**collectstatic_args)
  178. stats = collectstatic_cmd.collect()
  179. self.assertIn(os.path.join('cached', 'css', 'window.css'), stats['post_processed'])
  180. self.assertIn(os.path.join('cached', 'css', 'img', 'window.png'), stats['unmodified'])
  181. self.assertIn(os.path.join('test', 'nonascii.css'), stats['post_processed'])
  182. self.assertPostCondition()
  183. def test_css_import_case_insensitive(self):
  184. relpath = self.hashed_file_path("cached/styles_insensitive.css")
  185. self.assertEqual(relpath, "cached/styles_insensitive.3fa427592a53.css")
  186. with storage.staticfiles_storage.open(relpath) as relfile:
  187. content = relfile.read()
  188. self.assertNotIn(b"cached/other.css", content)
  189. self.assertIn(b"other.d41d8cd98f00.css", content)
  190. self.assertPostCondition()
  191. @override_settings(
  192. STATICFILES_DIRS=[os.path.join(TEST_ROOT, 'project', 'faulty')],
  193. STATICFILES_FINDERS=['django.contrib.staticfiles.finders.FileSystemFinder'],
  194. )
  195. def test_post_processing_failure(self):
  196. """
  197. post_processing indicates the origin of the error when it fails.
  198. """
  199. finders.get_finder.cache_clear()
  200. err = StringIO()
  201. with self.assertRaises(Exception):
  202. call_command('collectstatic', interactive=False, verbosity=0, stderr=err)
  203. self.assertEqual("Post-processing 'faulty.css' failed!\n\n", err.getvalue())
  204. self.assertPostCondition()
  205. @override_settings(
  206. STATICFILES_STORAGE='django.contrib.staticfiles.storage.CachedStaticFilesStorage',
  207. )
  208. class TestCollectionCachedStorage(TestHashedFiles, CollectionTestCase):
  209. """
  210. Tests for the Cache busting storage
  211. """
  212. def test_cache_invalidation(self):
  213. name = "cached/styles.css"
  214. hashed_name = "cached/styles.5e0040571e1a.css"
  215. # check if the cache is filled correctly as expected
  216. cache_key = storage.staticfiles_storage.hash_key(name)
  217. cached_name = storage.staticfiles_storage.hashed_files.get(cache_key)
  218. self.assertEqual(self.hashed_file_path(name), cached_name)
  219. # clearing the cache to make sure we re-set it correctly in the url method
  220. storage.staticfiles_storage.hashed_files.clear()
  221. cached_name = storage.staticfiles_storage.hashed_files.get(cache_key)
  222. self.assertIsNone(cached_name)
  223. self.assertEqual(self.hashed_file_path(name), hashed_name)
  224. cached_name = storage.staticfiles_storage.hashed_files.get(cache_key)
  225. self.assertEqual(cached_name, hashed_name)
  226. # Check files that had to be hashed multiple times since their content
  227. # includes other files that were hashed.
  228. name = 'cached/relative.css'
  229. hashed_name = 'cached/relative.c3e9e1ea6f2e.css'
  230. cache_key = storage.staticfiles_storage.hash_key(name)
  231. cached_name = storage.staticfiles_storage.hashed_files.get(cache_key)
  232. self.assertIsNone(cached_name)
  233. self.assertEqual(self.hashed_file_path(name), hashed_name)
  234. cached_name = storage.staticfiles_storage.hashed_files.get(cache_key)
  235. self.assertEqual(cached_name, hashed_name)
  236. def test_cache_key_memcache_validation(self):
  237. """
  238. Handle cache key creation correctly, see #17861.
  239. """
  240. name = (
  241. "/some crazy/long filename/ with spaces Here and ?#%#$/other/stuff"
  242. "/some crazy/long filename/ with spaces Here and ?#%#$/other/stuff"
  243. "/some crazy/long filename/ with spaces Here and ?#%#$/other/stuff"
  244. "/some crazy/long filename/ with spaces Here and ?#%#$/other/stuff"
  245. "/some crazy/long filename/ with spaces Here and ?#%#$/other/stuff"
  246. "/some crazy/\x16\xb4"
  247. )
  248. cache_key = storage.staticfiles_storage.hash_key(name)
  249. cache_validator = BaseCache({})
  250. cache_validator.validate_key(cache_key)
  251. self.assertEqual(cache_key, 'staticfiles:821ea71ef36f95b3922a77f7364670e7')
  252. def test_corrupt_intermediate_files(self):
  253. configured_storage = storage.staticfiles_storage
  254. # Clear cache to force rehashing of the files
  255. configured_storage.hashed_files.clear()
  256. # Simulate a corrupt chain of intermediate files by ensuring they don't
  257. # resolve before the max post-process count, which would normally be
  258. # high enough.
  259. configured_storage.max_post_process_passes = 1
  260. # File without intermediates that can be rehashed without a problem.
  261. self.hashed_file_path('cached/css/img/window.png')
  262. # File with too many intermediates to rehash with the low max
  263. # post-process passes.
  264. err_msg = "The name 'cached/styles.css' could not be hashed with %r." % (configured_storage._wrapped,)
  265. with self.assertRaisesMessage(ValueError, err_msg):
  266. self.hashed_file_path('cached/styles.css')
  267. @override_settings(
  268. STATICFILES_STORAGE='staticfiles_tests.storage.ExtraPatternsCachedStaticFilesStorage',
  269. )
  270. class TestExtraPatternsCachedStorage(CollectionTestCase):
  271. def setUp(self):
  272. storage.staticfiles_storage.hashed_files.clear() # avoid cache interference
  273. super().setUp()
  274. def cached_file_path(self, path):
  275. fullpath = self.render_template(self.static_template_snippet(path))
  276. return fullpath.replace(settings.STATIC_URL, '')
  277. def test_multi_extension_patterns(self):
  278. """
  279. With storage classes having several file extension patterns, only the
  280. files matching a specific file pattern should be affected by the
  281. substitution (#19670).
  282. """
  283. # CSS files shouldn't be touched by JS patterns.
  284. relpath = self.cached_file_path("cached/import.css")
  285. self.assertEqual(relpath, "cached/import.f53576679e5a.css")
  286. with storage.staticfiles_storage.open(relpath) as relfile:
  287. self.assertIn(b'import url("styles.5e0040571e1a.css")', relfile.read())
  288. # Confirm JS patterns have been applied to JS files.
  289. relpath = self.cached_file_path("cached/test.js")
  290. self.assertEqual(relpath, "cached/test.388d7a790d46.js")
  291. with storage.staticfiles_storage.open(relpath) as relfile:
  292. self.assertIn(b'JS_URL("import.f53576679e5a.css")', relfile.read())
  293. @override_settings(
  294. STATICFILES_STORAGE='django.contrib.staticfiles.storage.ManifestStaticFilesStorage',
  295. )
  296. class TestCollectionManifestStorage(TestHashedFiles, CollectionTestCase):
  297. """
  298. Tests for the Cache busting storage
  299. """
  300. def setUp(self):
  301. super().setUp()
  302. temp_dir = tempfile.mkdtemp()
  303. os.makedirs(os.path.join(temp_dir, 'test'))
  304. self._clear_filename = os.path.join(temp_dir, 'test', 'cleared.txt')
  305. with open(self._clear_filename, 'w') as f:
  306. f.write('to be deleted in one test')
  307. self.patched_settings = self.settings(
  308. STATICFILES_DIRS=settings.STATICFILES_DIRS + [temp_dir])
  309. self.patched_settings.enable()
  310. self.addCleanup(shutil.rmtree, temp_dir)
  311. self._manifest_strict = storage.staticfiles_storage.manifest_strict
  312. def tearDown(self):
  313. self.patched_settings.disable()
  314. if os.path.exists(self._clear_filename):
  315. os.unlink(self._clear_filename)
  316. storage.staticfiles_storage.manifest_strict = self._manifest_strict
  317. super().tearDown()
  318. def assertPostCondition(self):
  319. hashed_files = storage.staticfiles_storage.hashed_files
  320. # The in-memory version of the manifest matches the one on disk
  321. # since a properly created manifest should cover all filenames.
  322. if hashed_files:
  323. manifest = storage.staticfiles_storage.load_manifest()
  324. self.assertEqual(hashed_files, manifest)
  325. def test_manifest_exists(self):
  326. filename = storage.staticfiles_storage.manifest_name
  327. path = storage.staticfiles_storage.path(filename)
  328. self.assertTrue(os.path.exists(path))
  329. def test_loaded_cache(self):
  330. self.assertNotEqual(storage.staticfiles_storage.hashed_files, {})
  331. manifest_content = storage.staticfiles_storage.read_manifest()
  332. self.assertIn(
  333. '"version": "%s"' % storage.staticfiles_storage.manifest_version,
  334. manifest_content
  335. )
  336. def test_parse_cache(self):
  337. hashed_files = storage.staticfiles_storage.hashed_files
  338. manifest = storage.staticfiles_storage.load_manifest()
  339. self.assertEqual(hashed_files, manifest)
  340. def test_clear_empties_manifest(self):
  341. cleared_file_name = storage.staticfiles_storage.clean_name(os.path.join('test', 'cleared.txt'))
  342. # collect the additional file
  343. self.run_collectstatic()
  344. hashed_files = storage.staticfiles_storage.hashed_files
  345. self.assertIn(cleared_file_name, hashed_files)
  346. manifest_content = storage.staticfiles_storage.load_manifest()
  347. self.assertIn(cleared_file_name, manifest_content)
  348. original_path = storage.staticfiles_storage.path(cleared_file_name)
  349. self.assertTrue(os.path.exists(original_path))
  350. # delete the original file form the app, collect with clear
  351. os.unlink(self._clear_filename)
  352. self.run_collectstatic(clear=True)
  353. self.assertFileNotFound(original_path)
  354. hashed_files = storage.staticfiles_storage.hashed_files
  355. self.assertNotIn(cleared_file_name, hashed_files)
  356. manifest_content = storage.staticfiles_storage.load_manifest()
  357. self.assertNotIn(cleared_file_name, manifest_content)
  358. def test_missing_entry(self):
  359. missing_file_name = 'cached/missing.css'
  360. configured_storage = storage.staticfiles_storage
  361. self.assertNotIn(missing_file_name, configured_storage.hashed_files)
  362. # File name not found in manifest
  363. with self.assertRaisesMessage(ValueError, "Missing staticfiles manifest entry for '%s'" % missing_file_name):
  364. self.hashed_file_path(missing_file_name)
  365. configured_storage.manifest_strict = False
  366. # File doesn't exist on disk
  367. err_msg = "The file '%s' could not be found with %r." % (missing_file_name, configured_storage._wrapped)
  368. with self.assertRaisesMessage(ValueError, err_msg):
  369. self.hashed_file_path(missing_file_name)
  370. content = StringIO()
  371. content.write('Found')
  372. configured_storage.save(missing_file_name, content)
  373. # File exists on disk
  374. self.hashed_file_path(missing_file_name)
  375. @override_settings(
  376. STATICFILES_STORAGE='staticfiles_tests.storage.SimpleCachedStaticFilesStorage',
  377. )
  378. class TestCollectionSimpleCachedStorage(CollectionTestCase):
  379. """
  380. Tests for the Cache busting storage
  381. """
  382. hashed_file_path = hashed_file_path
  383. def setUp(self):
  384. storage.staticfiles_storage.hashed_files.clear() # avoid cache interference
  385. super().setUp()
  386. def test_template_tag_return(self):
  387. """
  388. Test the CachedStaticFilesStorage backend.
  389. """
  390. self.assertStaticRaises(ValueError, "does/not/exist.png", "/static/does/not/exist.png")
  391. self.assertStaticRenders("test/file.txt", "/static/test/file.deploy12345.txt")
  392. self.assertStaticRenders("cached/styles.css", "/static/cached/styles.deploy12345.css")
  393. self.assertStaticRenders("path/", "/static/path/")
  394. self.assertStaticRenders("path/?query", "/static/path/?query")
  395. def test_template_tag_simple_content(self):
  396. relpath = self.hashed_file_path("cached/styles.css")
  397. self.assertEqual(relpath, "cached/styles.deploy12345.css")
  398. with storage.staticfiles_storage.open(relpath) as relfile:
  399. content = relfile.read()
  400. self.assertNotIn(b"cached/other.css", content)
  401. self.assertIn(b"other.deploy12345.css", content)
  402. class CustomStaticFilesStorage(storage.StaticFilesStorage):
  403. """
  404. Used in TestStaticFilePermissions
  405. """
  406. def __init__(self, *args, **kwargs):
  407. kwargs['file_permissions_mode'] = 0o640
  408. kwargs['directory_permissions_mode'] = 0o740
  409. super().__init__(*args, **kwargs)
  410. @unittest.skipIf(sys.platform.startswith('win'), "Windows only partially supports chmod.")
  411. class TestStaticFilePermissions(CollectionTestCase):
  412. command_params = {
  413. 'interactive': False,
  414. 'verbosity': 0,
  415. 'ignore_patterns': ['*.ignoreme'],
  416. }
  417. def setUp(self):
  418. self.umask = 0o027
  419. self.old_umask = os.umask(self.umask)
  420. super().setUp()
  421. def tearDown(self):
  422. os.umask(self.old_umask)
  423. super().tearDown()
  424. # Don't run collectstatic command in this test class.
  425. def run_collectstatic(self, **kwargs):
  426. pass
  427. @override_settings(
  428. FILE_UPLOAD_PERMISSIONS=0o655,
  429. FILE_UPLOAD_DIRECTORY_PERMISSIONS=0o765,
  430. )
  431. def test_collect_static_files_permissions(self):
  432. call_command('collectstatic', **self.command_params)
  433. test_file = os.path.join(settings.STATIC_ROOT, "test.txt")
  434. test_dir = os.path.join(settings.STATIC_ROOT, "subdir")
  435. file_mode = os.stat(test_file)[0] & 0o777
  436. dir_mode = os.stat(test_dir)[0] & 0o777
  437. self.assertEqual(file_mode, 0o655)
  438. self.assertEqual(dir_mode, 0o765)
  439. @override_settings(
  440. FILE_UPLOAD_PERMISSIONS=None,
  441. FILE_UPLOAD_DIRECTORY_PERMISSIONS=None,
  442. )
  443. def test_collect_static_files_default_permissions(self):
  444. call_command('collectstatic', **self.command_params)
  445. test_file = os.path.join(settings.STATIC_ROOT, "test.txt")
  446. test_dir = os.path.join(settings.STATIC_ROOT, "subdir")
  447. file_mode = os.stat(test_file)[0] & 0o777
  448. dir_mode = os.stat(test_dir)[0] & 0o777
  449. self.assertEqual(file_mode, 0o666 & ~self.umask)
  450. self.assertEqual(dir_mode, 0o777 & ~self.umask)
  451. @override_settings(
  452. FILE_UPLOAD_PERMISSIONS=0o655,
  453. FILE_UPLOAD_DIRECTORY_PERMISSIONS=0o765,
  454. STATICFILES_STORAGE='staticfiles_tests.test_storage.CustomStaticFilesStorage',
  455. )
  456. def test_collect_static_files_subclass_of_static_storage(self):
  457. call_command('collectstatic', **self.command_params)
  458. test_file = os.path.join(settings.STATIC_ROOT, "test.txt")
  459. test_dir = os.path.join(settings.STATIC_ROOT, "subdir")
  460. file_mode = os.stat(test_file)[0] & 0o777
  461. dir_mode = os.stat(test_dir)[0] & 0o777
  462. self.assertEqual(file_mode, 0o640)
  463. self.assertEqual(dir_mode, 0o740)
  464. @override_settings(
  465. STATICFILES_STORAGE='django.contrib.staticfiles.storage.CachedStaticFilesStorage',
  466. )
  467. class TestCollectionHashedFilesCache(CollectionTestCase):
  468. """
  469. Files referenced from CSS use the correct final hashed name regardless of
  470. the order in which the files are post-processed.
  471. """
  472. hashed_file_path = hashed_file_path
  473. def setUp(self):
  474. super().setUp()
  475. self._temp_dir = temp_dir = tempfile.mkdtemp()
  476. os.makedirs(os.path.join(temp_dir, 'test'))
  477. self.addCleanup(shutil.rmtree, temp_dir)
  478. def _get_filename_path(self, filename):
  479. return os.path.join(self._temp_dir, 'test', filename)
  480. def test_file_change_after_collectstatic(self):
  481. # Create initial static files.
  482. file_contents = (
  483. ('foo.png', 'foo'),
  484. ('bar.css', 'url("foo.png")\nurl("xyz.png")'),
  485. ('xyz.png', 'xyz'),
  486. )
  487. for filename, content in file_contents:
  488. with open(self._get_filename_path(filename), 'w') as f:
  489. f.write(content)
  490. with self.modify_settings(STATICFILES_DIRS={'append': self._temp_dir}):
  491. finders.get_finder.cache_clear()
  492. err = StringIO()
  493. # First collectstatic run.
  494. call_command('collectstatic', interactive=False, verbosity=0, stderr=err)
  495. relpath = self.hashed_file_path('test/bar.css')
  496. with storage.staticfiles_storage.open(relpath) as relfile:
  497. content = relfile.read()
  498. self.assertIn(b'foo.acbd18db4cc2.png', content)
  499. self.assertIn(b'xyz.d16fb36f0911.png', content)
  500. # Change the contents of the png files.
  501. for filename in ('foo.png', 'xyz.png'):
  502. with open(self._get_filename_path(filename), 'w+b') as f:
  503. f.write(b"new content of file to change its hash")
  504. # The hashes of the png files in the CSS file are updated after
  505. # a second collectstatic.
  506. call_command('collectstatic', interactive=False, verbosity=0, stderr=err)
  507. relpath = self.hashed_file_path('test/bar.css')
  508. with storage.staticfiles_storage.open(relpath) as relfile:
  509. content = relfile.read()
  510. self.assertIn(b'foo.57a5cb9ba68d.png', content)
  511. self.assertIn(b'xyz.57a5cb9ba68d.png', content)