Kaydet (Commit) 5cd56128 authored tarafından Jannis Leidel's avatar Jannis Leidel

Fixed #15190 -- Refactored the collectstatic command to improve the symlink mode…

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
üst 67a2bb63
...@@ -34,16 +34,16 @@ class Command(NoArgsCommand): ...@@ -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): ...@@ -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: """) ...@@ -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:
return False # The full path of the target file
if dry_run: if self.local:
self.log("Pretending to delete '%s'" % destination) 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: else:
self.log("Deleting '%s'" % destination) self.log("Deleting '%s'" % path)
self.destination_storage.delete(destination) self.storage.delete(prefixed_path)
return True
if symlink: def link_file(self, path, prefixed_path, source_storage, **options):
destination_path = self.destination_storage.path(destination) """
if dry_run: Attempt to link ``path``
self.log("Pretending to link '%s' to '%s'" % """
(source_path, destination_path), level=1) # Skip this file if it was already copied earlier
else: if prefixed_path in self.symlinked_files:
self.log("Linking '%s' to '%s'" % return self.log("Skipping '%s' (already linked earlier)" % path)
(source_path, destination_path), level=1) # 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: try:
os.makedirs(os.path.dirname(destination_path)) os.makedirs(os.path.dirname(full_path))
except OSError: except OSError:
pass pass
os.symlink(source_path, destination_path) shutil.copy2(source_path, full_path)
self.symlinked_files.add(destination)
else:
if dry_run:
self.log("Pretending to copy '%s' to '%s'" %
(source_path, destination), level=1)
else: else:
if self.destination_local: source_file = source_storage.open(path)
destination_path = self.destination_storage.path(destination) self.storage.save(prefixed_path, source_file)
try: if not prefixed_path in self.copied_files:
os.makedirs(os.path.dirname(destination_path)) self.copied_files.append(prefixed_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
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment