Fixed #15190 -- Refactored the collectstatic command to improve the symlink mode and generally straighten out its behavior.
git-svn-id: http://code.djangoproject.com/svn/django/trunk@15388 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
parent
67a2bb6341
commit
5cd5612808
|
@ -34,16 +34,16 @@ class Command(NoArgsCommand):
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super(NoArgsCommand, self).__init__(*args, **kwargs)
|
super(NoArgsCommand, self).__init__(*args, **kwargs)
|
||||||
self.copied_files = set()
|
self.copied_files = []
|
||||||
self.symlinked_files = set()
|
self.symlinked_files = []
|
||||||
self.unmodified_files = set()
|
self.unmodified_files = []
|
||||||
self.destination_storage = get_storage_class(settings.STATICFILES_STORAGE)()
|
self.storage = get_storage_class(settings.STATICFILES_STORAGE)()
|
||||||
try:
|
try:
|
||||||
self.destination_storage.path('')
|
self.storage.path('')
|
||||||
except NotImplementedError:
|
except NotImplementedError:
|
||||||
self.destination_local = False
|
self.local = False
|
||||||
else:
|
else:
|
||||||
self.destination_local = True
|
self.local = True
|
||||||
# Use ints for file times (ticket #14665)
|
# Use ints for file times (ticket #14665)
|
||||||
os.stat_float_times(False)
|
os.stat_float_times(False)
|
||||||
|
|
||||||
|
@ -59,25 +59,33 @@ class Command(NoArgsCommand):
|
||||||
if sys.platform == 'win32':
|
if sys.platform == 'win32':
|
||||||
raise CommandError("Symlinking is not supported by this "
|
raise CommandError("Symlinking is not supported by this "
|
||||||
"platform (%s)." % sys.platform)
|
"platform (%s)." % sys.platform)
|
||||||
if not self.destination_local:
|
if not self.local:
|
||||||
raise CommandError("Can't symlink to a remote destination.")
|
raise CommandError("Can't symlink to a remote destination.")
|
||||||
|
|
||||||
# Warn before doing anything more.
|
# Warn before doing anything more.
|
||||||
if options.get('interactive'):
|
if options.get('interactive'):
|
||||||
confirm = raw_input("""
|
confirm = raw_input("""
|
||||||
You have requested to collate static files and collect them at the destination
|
You have requested to collect static files at the destination
|
||||||
location as specified in your settings file.
|
location as specified in your settings file ('%s').
|
||||||
|
|
||||||
This will overwrite existing files.
|
This will overwrite existing files.
|
||||||
Are you sure you want to do this?
|
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':
|
if confirm != 'yes':
|
||||||
raise CommandError("Collecting static files cancelled.")
|
raise CommandError("Collecting static files cancelled.")
|
||||||
|
|
||||||
for finder in finders.get_finders():
|
for finder in finders.get_finders():
|
||||||
for source, storage in finder.list(ignore_patterns):
|
for path, storage in finder.list(ignore_patterns):
|
||||||
self.copy_file(source, storage, **options)
|
# 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)
|
actual_count = len(self.copied_files) + len(self.symlinked_files)
|
||||||
unmodified_count = len(self.unmodified_files)
|
unmodified_count = len(self.unmodified_files)
|
||||||
|
@ -98,84 +106,97 @@ Type 'yes' to continue, or 'no' to cancel: """)
|
||||||
if self.verbosity >= level:
|
if self.verbosity >= level:
|
||||||
self.stdout.write(msg)
|
self.stdout.write(msg)
|
||||||
|
|
||||||
def copy_file(self, source, source_storage, **options):
|
def delete_file(self, path, prefixed_path, source_storage, **options):
|
||||||
"""
|
# Whether we are in symlink mode
|
||||||
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
|
|
||||||
symlink = options['link']
|
symlink = options['link']
|
||||||
dry_run = options['dry_run']
|
# Checks if the target file should be deleted if it already exists
|
||||||
|
if self.storage.exists(prefixed_path):
|
||||||
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):
|
|
||||||
try:
|
try:
|
||||||
destination_last_modified = \
|
# When was the target file modified last time?
|
||||||
self.destination_storage.modified_time(destination)
|
target_last_modified = self.storage.modified_time(prefixed_path)
|
||||||
except (OSError, NotImplementedError):
|
except (OSError, NotImplementedError):
|
||||||
# storage doesn't support ``modified_time`` or failed.
|
# The storage doesn't support ``modified_time`` or failed
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
destination_is_link = (self.destination_local and
|
try:
|
||||||
os.path.islink(self.destination_storage.path(destination)))
|
# When was the source file modified last time?
|
||||||
if destination_last_modified >= source_last_modified:
|
source_last_modified = source_storage.modified_time(path)
|
||||||
if (not symlink and not destination_is_link):
|
except (OSError, NotImplementedError):
|
||||||
self.log("Skipping '%s' (not modified)" % destination)
|
pass
|
||||||
self.unmodified_files.add(destination)
|
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
|
return False
|
||||||
if dry_run:
|
# Then delete the existing file if really needed
|
||||||
self.log("Pretending to delete '%s'" % destination)
|
if options['dry_run']:
|
||||||
|
self.log("Pretending to delete '%s'" % path)
|
||||||
else:
|
else:
|
||||||
self.log("Deleting '%s'" % destination)
|
self.log("Deleting '%s'" % path)
|
||||||
self.destination_storage.delete(destination)
|
self.storage.delete(prefixed_path)
|
||||||
|
|
||||||
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)
|
|
||||||
try:
|
|
||||||
os.makedirs(os.path.dirname(destination_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)
|
|
||||||
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
|
return True
|
||||||
|
|
||||||
|
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(full_path))
|
||||||
|
except OSError:
|
||||||
|
pass
|
||||||
|
shutil.copy2(source_path, full_path)
|
||||||
|
else:
|
||||||
|
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)
|
||||||
|
|
Loading…
Reference in New Issue