PageRenderTime 34ms CodeModel.GetById 11ms app.highlight 19ms RepoModel.GetById 1ms app.codeStats 0ms

/django/contrib/staticfiles/management/commands/collectstatic.py

https://code.google.com/p/mango-py/
Python | 204 lines | 196 code | 4 blank | 4 comment | 0 complexity | 7db365e80540498598786ee75230a7ba MD5 | raw file
  1import os
  2import sys
  3import shutil
  4from optparse import make_option
  5
  6from django.conf import settings
  7from django.core.files.storage import get_storage_class
  8from django.core.management.base import CommandError, NoArgsCommand
  9from django.utils.encoding import smart_str
 10
 11from django.contrib.staticfiles import finders
 12
 13class Command(NoArgsCommand):
 14    """
 15    Command that allows to copy or symlink media files from different
 16    locations to the settings.STATIC_ROOT.
 17    """
 18    option_list = NoArgsCommand.option_list + (
 19        make_option('--noinput', action='store_false', dest='interactive',
 20            default=True, help="Do NOT prompt the user for input of any kind."),
 21        make_option('-i', '--ignore', action='append', default=[],
 22            dest='ignore_patterns', metavar='PATTERN',
 23            help="Ignore files or directories matching this glob-style "
 24                "pattern. Use multiple times to ignore more."),
 25        make_option('-n', '--dry-run', action='store_true', dest='dry_run',
 26            default=False, help="Do everything except modify the filesystem."),
 27        make_option('-l', '--link', action='store_true', dest='link',
 28            default=False, help="Create a symbolic link to each file instead of copying."),
 29        make_option('--no-default-ignore', action='store_false',
 30            dest='use_default_ignore_patterns', default=True,
 31            help="Don't ignore the common private glob-style patterns 'CVS', "
 32                "'.*' and '*~'."),
 33    )
 34    help = "Collect static files from apps and other locations in a single location."
 35
 36    def __init__(self, *args, **kwargs):
 37        super(NoArgsCommand, self).__init__(*args, **kwargs)
 38        self.copied_files = []
 39        self.symlinked_files = []
 40        self.unmodified_files = []
 41        self.storage = get_storage_class(settings.STATICFILES_STORAGE)()
 42        try:
 43            self.storage.path('')
 44        except NotImplementedError:
 45            self.local = False
 46        else:
 47            self.local = True
 48        # Use ints for file times (ticket #14665)
 49        os.stat_float_times(False)
 50
 51    def handle_noargs(self, **options):
 52        symlink = options['link']
 53        ignore_patterns = options['ignore_patterns']
 54        if options['use_default_ignore_patterns']:
 55            ignore_patterns += ['CVS', '.*', '*~']
 56        ignore_patterns = list(set(ignore_patterns))
 57        self.verbosity = int(options.get('verbosity', 1))
 58
 59        if symlink:
 60            if sys.platform == 'win32':
 61                raise CommandError("Symlinking is not supported by this "
 62                                   "platform (%s)." % sys.platform)
 63            if not self.local:
 64                raise CommandError("Can't symlink to a remote destination.")
 65
 66        # Warn before doing anything more.
 67        if options.get('interactive'):
 68            confirm = raw_input(u"""
 69You have requested to collect static files at the destination
 70location as specified in your settings file.
 71
 72This will overwrite existing files.
 73Are you sure you want to do this?
 74
 75Type 'yes' to continue, or 'no' to cancel: """)
 76            if confirm != 'yes':
 77                raise CommandError("Collecting static files cancelled.")
 78
 79        for finder in finders.get_finders():
 80            for path, storage in finder.list(ignore_patterns):
 81                # Prefix the relative path if the source storage contains it
 82                if getattr(storage, 'prefix', None):
 83                    prefixed_path = os.path.join(storage.prefix, path)
 84                else:
 85                    prefixed_path = path
 86                if symlink:
 87                    self.link_file(path, prefixed_path, storage, **options)
 88                else:
 89                    self.copy_file(path, prefixed_path, storage, **options)
 90
 91        actual_count = len(self.copied_files) + len(self.symlinked_files)
 92        unmodified_count = len(self.unmodified_files)
 93        if self.verbosity >= 1:
 94            self.stdout.write(smart_str(u"\n%s static file%s %s to '%s'%s.\n"
 95                              % (actual_count, actual_count != 1 and 's' or '',
 96                                 symlink and 'symlinked' or 'copied',
 97                                 settings.STATIC_ROOT,
 98                                 unmodified_count and ' (%s unmodified)'
 99                                 % unmodified_count or '')))
100
101    def log(self, msg, level=2):
102        """
103        Small log helper
104        """
105        msg = smart_str(msg)
106        if not msg.endswith("\n"):
107            msg += "\n"
108        if self.verbosity >= level:
109            self.stdout.write(msg)
110
111    def delete_file(self, path, prefixed_path, source_storage, **options):
112        # Whether we are in symlink mode
113        symlink = options['link']
114        # Checks if the target file should be deleted if it already exists
115        if self.storage.exists(prefixed_path):
116            try:
117                # When was the target file modified last time?
118                target_last_modified = self.storage.modified_time(prefixed_path)
119            except (OSError, NotImplementedError):
120                # The storage doesn't support ``modified_time`` or failed
121                pass
122            else:
123                try:
124                    # When was the source file modified last time?
125                    source_last_modified = source_storage.modified_time(path)
126                except (OSError, NotImplementedError):
127                    pass
128                else:
129                    # The full path of the target file
130                    if self.local:
131                        full_path = self.storage.path(prefixed_path)
132                    else:
133                        full_path = None
134                    # Skip the file if the source file is younger
135                    if target_last_modified >= source_last_modified:
136                        if not ((symlink and full_path and not os.path.islink(full_path)) or
137                                (not symlink and full_path and os.path.islink(full_path))):
138                            if prefixed_path not in self.unmodified_files:
139                                self.unmodified_files.append(prefixed_path)
140                            self.log(u"Skipping '%s' (not modified)" % path)
141                            return False
142            # Then delete the existing file if really needed
143            if options['dry_run']:
144                self.log(u"Pretending to delete '%s'" % path)
145            else:
146                self.log(u"Deleting '%s'" % path)
147                self.storage.delete(prefixed_path)
148        return True
149
150    def link_file(self, path, prefixed_path, source_storage, **options):
151        """
152        Attempt to link ``path``
153        """
154        # Skip this file if it was already copied earlier
155        if prefixed_path in self.symlinked_files:
156            return self.log(u"Skipping '%s' (already linked earlier)" % path)
157        # Delete the target file if needed or break
158        if not self.delete_file(path, prefixed_path, source_storage, **options):
159            return
160        # The full path of the source file
161        source_path = source_storage.path(path)
162        # Finally link the file
163        if options['dry_run']:
164            self.log(u"Pretending to link '%s'" % source_path, level=1)
165        else:
166            self.log(u"Linking '%s'" % source_path, level=1)
167            full_path = self.storage.path(prefixed_path)
168            try:
169                os.makedirs(os.path.dirname(full_path))
170            except OSError:
171                pass
172            os.symlink(source_path, full_path)
173        if prefixed_path not in self.symlinked_files:
174            self.symlinked_files.append(prefixed_path)
175
176    def copy_file(self, path, prefixed_path, source_storage, **options):
177        """
178        Attempt to copy ``path`` with storage
179        """
180        # Skip this file if it was already copied earlier
181        if prefixed_path in self.copied_files:
182            return self.log(u"Skipping '%s' (already copied earlier)" % path)
183        # Delete the target file if needed or break
184        if not self.delete_file(path, prefixed_path, source_storage, **options):
185            return
186        # The full path of the source file
187        source_path = source_storage.path(path)
188        # Finally start copying
189        if options['dry_run']:
190            self.log(u"Pretending to copy '%s'" % source_path, level=1)
191        else:
192            self.log(u"Copying '%s'" % source_path, level=1)
193            if self.local:
194                full_path = self.storage.path(prefixed_path)
195                try:
196                    os.makedirs(os.path.dirname(full_path))
197                except OSError:
198                    pass
199                shutil.copy2(source_path, full_path)
200            else:
201                source_file = source_storage.open(path)
202                self.storage.save(prefixed_path, source_file)
203        if not prefixed_path in self.copied_files:
204            self.copied_files.append(prefixed_path)