Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Fixed #15190 -- Refactored the collectstatic command to improve the s…

…ymlink mode and generally straighten out its behavior.

git-svn-id: http://code.djangoproject.com/svn/django/trunk@15388 bcc190cf-cafb-0310-a4f2-bffc1f526a37
  • Loading branch information...
commit 5cd561280844865182277ef49070e75ad5ec881e 1 parent 67a2bb6
@jezdez jezdez authored
Showing with 104 additions and 83 deletions.
  1. +104 −83 django/contrib/staticfiles/management/commands/collectstatic.py
View
187 django/contrib/staticfiles/management/commands/collectstatic.py
@@ -34,16 +34,16 @@ class Command(NoArgsCommand):
def __init__(self, *args, **kwargs):
super(NoArgsCommand, self).__init__(*args, **kwargs)
- self.copied_files = set()
- self.symlinked_files = set()
- self.unmodified_files = set()
- self.destination_storage = get_storage_class(settings.STATICFILES_STORAGE)()
+ self.copied_files = []
+ self.symlinked_files = []
+ self.unmodified_files = []
+ self.storage = get_storage_class(settings.STATICFILES_STORAGE)()
try:
- self.destination_storage.path('')
+ self.storage.path('')
except NotImplementedError:
- self.destination_local = False
+ self.local = False
else:
- self.destination_local = True
+ self.local = True
# Use ints for file times (ticket #14665)
os.stat_float_times(False)
@@ -59,25 +59,33 @@ def handle_noargs(self, **options):
if sys.platform == 'win32':
raise CommandError("Symlinking is not supported by this "
"platform (%s)." % sys.platform)
- if not self.destination_local:
+ if not self.local:
raise CommandError("Can't symlink to a remote destination.")
# Warn before doing anything more.
if options.get('interactive'):
confirm = raw_input("""
-You have requested to collate static files and collect them at the destination
-location as specified in your settings file.
+You have requested to collect static files at the destination
+location as specified in your settings file ('%s').
This will overwrite existing files.
Are you sure you want to do this?
-Type 'yes' to continue, or 'no' to cancel: """)
+Type 'yes' to continue, or 'no' to cancel: """ % settings.STATIC_ROOT)
if confirm != 'yes':
raise CommandError("Collecting static files cancelled.")
for finder in finders.get_finders():
- for source, storage in finder.list(ignore_patterns):
- self.copy_file(source, storage, **options)
+ for path, storage in finder.list(ignore_patterns):
+ # Prefix the relative path if the source storage contains it
+ if getattr(storage, 'prefix', None):
+ prefixed_path = os.path.join(storage.prefix, path)
+ else:
+ prefixed_path = path
+ if symlink:
+ self.link_file(path, prefixed_path, storage, **options)
+ else:
+ self.copy_file(path, prefixed_path, storage, **options)
actual_count = len(self.copied_files) + len(self.symlinked_files)
unmodified_count = len(self.unmodified_files)
@@ -98,84 +106,97 @@ def log(self, msg, level=2):
if self.verbosity >= level:
self.stdout.write(msg)
- def copy_file(self, source, source_storage, **options):
- """
- Attempt to copy (or symlink) ``source`` to ``destination``,
- returning True if successful.
- """
- source_path = source_storage.path(source)
- try:
- source_last_modified = source_storage.modified_time(source)
- except (OSError, NotImplementedError):
- source_last_modified = None
- if getattr(source_storage, 'prefix', None):
- destination = os.path.join(source_storage.prefix, source)
- else:
- destination = source
+ def delete_file(self, path, prefixed_path, source_storage, **options):
+ # Whether we are in symlink mode
symlink = options['link']
- dry_run = options['dry_run']
-
- if destination in self.copied_files:
- self.log("Skipping '%s' (already copied earlier)" % destination)
- return False
-
- if destination in self.symlinked_files:
- self.log("Skipping '%s' (already linked earlier)" % destination)
- return False
-
- if self.destination_storage.exists(destination):
+ # Checks if the target file should be deleted if it already exists
+ if self.storage.exists(prefixed_path):
try:
- destination_last_modified = \
- self.destination_storage.modified_time(destination)
+ # When was the target file modified last time?
+ target_last_modified = self.storage.modified_time(prefixed_path)
except (OSError, NotImplementedError):
- # storage doesn't support ``modified_time`` or failed.
+ # The storage doesn't support ``modified_time`` or failed
pass
else:
- destination_is_link = (self.destination_local and
- os.path.islink(self.destination_storage.path(destination)))
- if destination_last_modified >= source_last_modified:
- if (not symlink and not destination_is_link):
- self.log("Skipping '%s' (not modified)" % destination)
- self.unmodified_files.add(destination)
- return False
- if dry_run:
- self.log("Pretending to delete '%s'" % destination)
+ try:
+ # When was the source file modified last time?
+ source_last_modified = source_storage.modified_time(path)
+ except (OSError, NotImplementedError):
+ pass
+ else:
+ # The full path of the target file
+ if self.local:
+ full_path = self.storage.path(prefixed_path)
+ else:
+ full_path = None
+ # Skip the file if the source file is younger
+ if target_last_modified >= source_last_modified:
+ if not ((symlink and full_path and not os.path.islink(full_path)) or
+ (not symlink and full_path and os.path.islink(full_path))):
+ if prefixed_path not in self.unmodified_files:
+ self.unmodified_files.append(prefixed_path)
+ self.log("Skipping '%s' (not modified)" % path)
+ return False
+ # Then delete the existing file if really needed
+ if options['dry_run']:
+ self.log("Pretending to delete '%s'" % path)
else:
- self.log("Deleting '%s'" % destination)
- self.destination_storage.delete(destination)
+ self.log("Deleting '%s'" % path)
+ self.storage.delete(prefixed_path)
+ return True
- if symlink:
- destination_path = self.destination_storage.path(destination)
- if dry_run:
- self.log("Pretending to link '%s' to '%s'" %
- (source_path, destination_path), level=1)
- else:
- self.log("Linking '%s' to '%s'" %
- (source_path, destination_path), level=1)
+ def link_file(self, path, prefixed_path, source_storage, **options):
+ """
+ Attempt to link ``path``
+ """
+ # Skip this file if it was already copied earlier
+ if prefixed_path in self.symlinked_files:
+ return self.log("Skipping '%s' (already linked earlier)" % path)
+ # Delete the target file if needed or break
+ if not self.delete_file(path, prefixed_path, source_storage, **options):
+ return
+ # The full path of the source file
+ source_path = source_storage.path(path)
+ # Finally link the file
+ if options['dry_run']:
+ self.log("Pretending to link '%s'" % source_path, level=1)
+ else:
+ self.log("Linking '%s'" % source_path, level=1)
+ full_path = self.storage.path(prefixed_path)
+ try:
+ os.makedirs(os.path.dirname(full_path))
+ except OSError:
+ pass
+ os.symlink(source_path, full_path)
+ if prefixed_path not in self.symlinked_files:
+ self.symlinked_files.append(prefixed_path)
+
+ def copy_file(self, path, prefixed_path, source_storage, **options):
+ """
+ Attempt to copy ``path`` with storage
+ """
+ # Skip this file if it was already copied earlier
+ if prefixed_path in self.copied_files:
+ return self.log("Skipping '%s' (already copied earlier)" % path)
+ # Delete the target file if needed or break
+ if not self.delete_file(path, prefixed_path, source_storage, **options):
+ return
+ # The full path of the source file
+ source_path = source_storage.path(path)
+ # Finally start copying
+ if options['dry_run']:
+ self.log("Pretending to copy '%s'" % source_path, level=1)
+ else:
+ self.log("Copying '%s'" % source_path, level=1)
+ if self.local:
+ full_path = self.storage.path(prefixed_path)
try:
- os.makedirs(os.path.dirname(destination_path))
+ os.makedirs(os.path.dirname(full_path))
except OSError:
pass
- os.symlink(source_path, destination_path)
- self.symlinked_files.add(destination)
- else:
- if dry_run:
- self.log("Pretending to copy '%s' to '%s'" %
- (source_path, destination), level=1)
+ shutil.copy2(source_path, full_path)
else:
- if self.destination_local:
- destination_path = self.destination_storage.path(destination)
- try:
- os.makedirs(os.path.dirname(destination_path))
- except OSError:
- pass
- shutil.copy2(source_path, destination_path)
- self.log("Copying '%s' to '%s'" %
- (source_path, destination_path), level=1)
- else:
- source_file = source_storage.open(source)
- self.destination_storage.save(destination, source_file)
- self.log("Copying %s to %s" %
- (source_path, destination), level=1)
- self.copied_files.add(destination)
- return True
+ source_file = source_storage.open(path)
+ self.storage.save(prefixed_path, source_file)
+ if not prefixed_path in self.copied_files:
+ self.copied_files.append(prefixed_path)
Please sign in to comment.
Something went wrong with that request. Please try again.