Fixed #21482 -- Uplifted restriction of collectstatic using symlink option in Windows NT 6.

Original patch by Vajrasky Kok. Reviewed by Florian Apolloner, Aymeric Augustin.
This commit is contained in:
Jannis Leidel 2014-02-09 12:37:14 +00:00
parent 9c4ad454d1
commit 5cc0555603
4 changed files with 56 additions and 24 deletions

View File

@ -1,7 +1,6 @@
from __future__ import unicode_literals from __future__ import unicode_literals
import os import os
import sys
from collections import OrderedDict from collections import OrderedDict
from optparse import make_option from optparse import make_option
@ -82,12 +81,8 @@ class Command(NoArgsCommand):
Split off from handle_noargs() to facilitate testing. Split off from handle_noargs() to facilitate testing.
""" """
if self.symlink: if self.symlink and not self.local:
if sys.platform == 'win32': raise CommandError("Can't symlink to a remote destination.")
raise CommandError("Symlinking is not supported by this "
"platform (%s)." % sys.platform)
if not self.local:
raise CommandError("Can't symlink to a remote destination.")
if self.clear: if self.clear:
self.clear_dir('') self.clear_dir('')
@ -279,7 +274,18 @@ class Command(NoArgsCommand):
os.makedirs(os.path.dirname(full_path)) os.makedirs(os.path.dirname(full_path))
except OSError: except OSError:
pass pass
os.symlink(source_path, full_path) try:
os.symlink(source_path, full_path)
except AttributeError:
import platform
raise CommandError("Symlinking is not supported by Python %s." %
platform.python_version())
except NotImplementedError:
import platform
raise CommandError("Symlinking is not supported in this "
"platform (%s)." % platform.platform())
except OSError as e:
raise CommandError(e)
if prefixed_path not in self.symlinked_files: if prefixed_path not in self.symlinked_files:
self.symlinked_files.append(prefixed_path) self.symlinked_files.append(prefixed_path)

View File

@ -1,6 +1,7 @@
import os import os
import stat import stat
import sys import sys
import tempfile
from os.path import join, normcase, normpath, abspath, isabs, sep, dirname from os.path import join, normcase, normpath, abspath, isabs, sep, dirname
from django.utils.encoding import force_text from django.utils.encoding import force_text
@ -99,3 +100,24 @@ def rmtree_errorhandler(func, path, exc_info):
os.chmod(path, stat.S_IWRITE) os.chmod(path, stat.S_IWRITE)
# use the original function to repeat the operation # use the original function to repeat the operation
func(path) func(path)
def symlinks_supported():
"""
A function to check if creating symlinks are supported in the
host platform and/or if they are allowed to be created (e.g.
on Windows it requires admin permissions).
"""
tmpdir = tempfile.mkdtemp()
original_path = os.path.join(tmpdir, 'original')
symlink_path = os.path.join(tmpdir, 'symlink')
os.makedirs(original_path)
try:
os.symlink(original_path, symlink_path)
supported = True
except (OSError, NotImplementedError, AttributeError):
supported = False
else:
os.remove(symlink_path)
finally:
return supported

View File

@ -565,6 +565,9 @@ Management Commands
* Management commands can now produce syntax colored output under Windows if * Management commands can now produce syntax colored output under Windows if
the ANSICON third-party tool is installed and active. the ANSICON third-party tool is installed and active.
* :djadmin:`collectstatic` command with symlink option is now supported on
Windows NT 6 (Windows Vista and newer).
Models Models
^^^^^^ ^^^^^^

View File

@ -17,7 +17,7 @@ from django.core.management import call_command
from django.test import TestCase, override_settings from django.test import TestCase, override_settings
from django.utils.encoding import force_text from django.utils.encoding import force_text
from django.utils.functional import empty from django.utils.functional import empty
from django.utils._os import rmtree_errorhandler, upath from django.utils._os import rmtree_errorhandler, upath, symlinks_supported
from django.utils import six from django.utils import six
from django.contrib.staticfiles import finders, storage from django.contrib.staticfiles import finders, storage
@ -645,24 +645,25 @@ class TestCollectionSimpleCachedStorage(BaseCollectionTestCase,
self.assertNotIn(b"cached/other.css", content) self.assertNotIn(b"cached/other.css", content)
self.assertIn(b"other.deploy12345.css", content) self.assertIn(b"other.deploy12345.css", content)
if sys.platform != 'win32':
class TestCollectionLinks(CollectionTestCase, TestDefaults): @unittest.skipUnless(symlinks_supported(),
"Must be able to symlink to run this test.")
class TestCollectionLinks(CollectionTestCase, TestDefaults):
"""
Test ``--link`` option for ``collectstatic`` management command.
Note that by inheriting ``TestDefaults`` we repeat all
the standard file resolving tests here, to make sure using
``--link`` does not change the file-selection semantics.
"""
def run_collectstatic(self):
super(TestCollectionLinks, self).run_collectstatic(link=True)
def test_links_created(self):
""" """
Test ``--link`` option for ``collectstatic`` management command. With ``--link``, symbolic links are created.
Note that by inheriting ``TestDefaults`` we repeat all
the standard file resolving tests here, to make sure using
``--link`` does not change the file-selection semantics.
""" """
def run_collectstatic(self): self.assertTrue(os.path.islink(os.path.join(settings.STATIC_ROOT, 'test.txt')))
super(TestCollectionLinks, self).run_collectstatic(link=True)
def test_links_created(self):
"""
With ``--link``, symbolic links are created.
"""
self.assertTrue(os.path.islink(os.path.join(settings.STATIC_ROOT, 'test.txt')))
class TestServeStatic(StaticFilesTestCase): class TestServeStatic(StaticFilesTestCase):