blob: 8882ae20db11449f1d8c0bd4ccd8c5a6af4062e7 [file] [log] [blame]
import os
import sys
import shutil
from optparse import make_option
from django.conf import settings
from django.core.files.storage import get_storage_class
from django.core.management.base import CommandError, NoArgsCommand
from django.contrib.staticfiles import finders
class Command(NoArgsCommand):
"""
Command that allows to copy or symlink media files from different
locations to the settings.STATIC_ROOT.
"""
option_list = NoArgsCommand.option_list + (
make_option('--noinput', action='store_false', dest='interactive',
default=True, help="Do NOT prompt the user for input of any "
"kind."),
make_option('-i', '--ignore', action='append', default=[],
dest='ignore_patterns', metavar='PATTERN',
help="Ignore files or directories matching this glob-style "
"pattern. Use multiple times to ignore more."),
make_option('-n', '--dry-run', action='store_true', dest='dry_run',
default=False, help="Do everything except modify the filesystem."),
make_option('-l', '--link', action='store_true', dest='link',
default=False, help="Create a symbolic link to each file instead of copying."),
make_option('--no-default-ignore', action='store_false',
dest='use_default_ignore_patterns', default=True,
help="Don't ignore the common private glob-style patterns 'CVS', "
"'.*' and '*~'."),
)
help = "Collect static files from apps and other locations in a single location."
def handle_noargs(self, **options):
symlink = options['link']
ignore_patterns = options['ignore_patterns']
if options['use_default_ignore_patterns']:
ignore_patterns += ['CVS', '.*', '*~']
ignore_patterns = list(set(ignore_patterns))
self.copied_files = set()
self.symlinked_files = set()
self.unmodified_files = set()
self.destination_storage = get_storage_class(settings.STATICFILES_STORAGE)()
try:
self.destination_storage.path('')
except NotImplementedError:
self.destination_local = False
else:
self.destination_local = True
if symlink:
if sys.platform == 'win32':
raise CommandError("Symlinking is not supported by this "
"platform (%s)." % sys.platform)
if not self.destination_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.
This will overwrite existing files.
Are you sure you want to do this?
Type 'yes' to continue, or 'no' to cancel: """)
if confirm != 'yes':
raise CommandError("Static files build cancelled.")
# Use ints for file times (ticket #14665)
os.stat_float_times(False)
for finder in finders.get_finders():
for source, prefix, storage in finder.list(ignore_patterns):
self.copy_file(source, prefix, storage, **options)
verbosity = int(options.get('verbosity', 1))
actual_count = len(self.copied_files) + len(self.symlinked_files)
unmodified_count = len(self.unmodified_files)
if verbosity >= 1:
self.stdout.write("\n%s static file%s %s to '%s'%s.\n"
% (actual_count, actual_count != 1 and 's' or '',
symlink and 'symlinked' or 'copied',
settings.STATIC_ROOT,
unmodified_count and ' (%s unmodified)'
% unmodified_count or ''))
def copy_file(self, source, prefix, 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 prefix:
destination = '/'.join([prefix, source])
else:
destination = source
symlink = options['link']
dry_run = options['dry_run']
verbosity = int(options.get('verbosity', 1))
if destination in self.copied_files:
if verbosity >= 2:
self.stdout.write("Skipping '%s' (already copied earlier)\n"
% destination)
return False
if destination in self.symlinked_files:
if verbosity >= 2:
self.stdout.write("Skipping '%s' (already linked earlier)\n"
% destination)
return False
if self.destination_storage.exists(destination):
try:
destination_last_modified = \
self.destination_storage.modified_time(destination)
except (OSError, NotImplementedError):
# storage doesn't support ``modified_time`` or failed.
pass
else:
destination_is_link = os.path.islink(
self.destination_storage.path(destination))
if destination_last_modified >= source_last_modified:
if (not symlink and not destination_is_link):
if verbosity >= 2:
self.stdout.write("Skipping '%s' (not modified)\n"
% destination)
self.unmodified_files.add(destination)
return False
if dry_run:
if verbosity >= 2:
self.stdout.write("Pretending to delete '%s'\n"
% destination)
else:
if verbosity >= 2:
self.stdout.write("Deleting '%s'\n" % destination)
self.destination_storage.delete(destination)
if symlink:
destination_path = self.destination_storage.path(destination)
if dry_run:
if verbosity >= 1:
self.stdout.write("Pretending to symlink '%s' to '%s'\n"
% (source_path, destination_path))
else:
if verbosity >= 1:
self.stdout.write("Symlinking '%s' to '%s'\n"
% (source_path, destination_path))
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:
if verbosity >= 1:
self.stdout.write("Pretending to copy '%s' to '%s'\n"
% (source_path, destination))
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)
if verbosity >= 1:
self.stdout.write("Copying '%s' to '%s'\n"
% (source_path, destination_path))
else:
source_file = source_storage.open(source)
self.destination_storage.save(destination, source_file)
if verbosity >= 1:
self.stdout.write("Copying %s to %s\n"
% (source_path, destination))
self.copied_files.add(destination)
return True