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

https://code.google.com/p/mango-py/ · Python · 204 lines · 159 code · 13 blank · 32 comment · 58 complexity · 7db365e80540498598786ee75230a7ba MD5 · raw file

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