django.contrib.staticfiles.management.commands.collectstatic: 164 total statements, 0.0% covered

Generated: Wed 2013-03-13 10:33 CET

Source file: /media/Envs/Envs/filer-gallery/lib/python2.7/site-packages/django/contrib/staticfiles/management/commands/collectstatic.py

Stats: 0 executed, 155 missed, 9 excluded, 141 ignored

  1. from __future__ import with_statement
  2. import os
  3. import sys
  4. from optparse import make_option
  5. from django.core.files.storage import FileSystemStorage
  6. from django.core.management.base import CommandError, NoArgsCommand
  7. from django.utils.encoding import smart_str, smart_unicode
  8. from django.utils.datastructures import SortedDict
  9. from django.contrib.staticfiles import finders, storage
  10. class Command(NoArgsCommand):
  11. """
  12. Command that allows to copy or symlink static files from different
  13. locations to the settings.STATIC_ROOT.
  14. """
  15. option_list = NoArgsCommand.option_list + (
  16. make_option('--noinput',
  17. action='store_false', dest='interactive', default=True,
  18. help="Do NOT prompt the user for input of any kind."),
  19. make_option('--no-post-process',
  20. action='store_false', dest='post_process', default=True,
  21. help="Do NOT post process collected files."),
  22. make_option('-i', '--ignore', action='append', default=[],
  23. dest='ignore_patterns', metavar='PATTERN',
  24. help="Ignore files or directories matching this glob-style "
  25. "pattern. Use multiple times to ignore more."),
  26. make_option('-n', '--dry-run',
  27. action='store_true', dest='dry_run', default=False,
  28. help="Do everything except modify the filesystem."),
  29. make_option('-c', '--clear',
  30. action='store_true', dest='clear', default=False,
  31. help="Clear the existing files using the storage "
  32. "before trying to copy or link the original file."),
  33. make_option('-l', '--link',
  34. action='store_true', dest='link', default=False,
  35. help="Create a symbolic link to each file instead of copying."),
  36. make_option('--no-default-ignore', action='store_false',
  37. dest='use_default_ignore_patterns', default=True,
  38. help="Don't ignore the common private glob-style patterns 'CVS', "
  39. "'.*' and '*~'."),
  40. )
  41. help = "Collect static files in a single location."
  42. requires_model_validation = False
  43. def __init__(self, *args, **kwargs):
  44. super(NoArgsCommand, self).__init__(*args, **kwargs)
  45. self.copied_files = []
  46. self.symlinked_files = []
  47. self.unmodified_files = []
  48. self.post_processed_files = []
  49. self.storage = storage.staticfiles_storage
  50. try:
  51. self.storage.path('')
  52. except NotImplementedError:
  53. self.local = False
  54. else:
  55. self.local = True
  56. # Use ints for file times (ticket #14665), if supported
  57. if hasattr(os, 'stat_float_times'):
  58. os.stat_float_times(False)
  59. def set_options(self, **options):
  60. """
  61. Set instance variables based on an options dict
  62. """
  63. self.interactive = options['interactive']
  64. self.verbosity = int(options.get('verbosity', 1))
  65. self.symlink = options['link']
  66. self.clear = options['clear']
  67. self.dry_run = options['dry_run']
  68. ignore_patterns = options['ignore_patterns']
  69. if options['use_default_ignore_patterns']:
  70. ignore_patterns += ['CVS', '.*', '*~']
  71. self.ignore_patterns = list(set(ignore_patterns))
  72. self.post_process = options['post_process']
  73. def collect(self):
  74. """
  75. Perform the bulk of the work of collectstatic.
  76. Split off from handle_noargs() to facilitate testing.
  77. """
  78. if self.symlink:
  79. if sys.platform == 'win32':
  80. raise CommandError("Symlinking is not supported by this "
  81. "platform (%s)." % sys.platform)
  82. if not self.local:
  83. raise CommandError("Can't symlink to a remote destination.")
  84. if self.clear:
  85. self.clear_dir('')
  86. if self.symlink:
  87. handler = self.link_file
  88. else:
  89. handler = self.copy_file
  90. found_files = SortedDict()
  91. for finder in finders.get_finders():
  92. for path, storage in finder.list(self.ignore_patterns):
  93. # Prefix the relative path if the source storage contains it
  94. if getattr(storage, 'prefix', None):
  95. prefixed_path = os.path.join(storage.prefix, path)
  96. else:
  97. prefixed_path = path
  98. if prefixed_path not in found_files:
  99. found_files[prefixed_path] = (storage, path)
  100. handler(path, prefixed_path, storage)
  101. # Here we check if the storage backend has a post_process
  102. # method and pass it the list of modified files.
  103. if self.post_process and hasattr(self.storage, 'post_process'):
  104. processor = self.storage.post_process(found_files,
  105. dry_run=self.dry_run)
  106. for original_path, processed_path, processed in processor:
  107. if processed:
  108. self.log(u"Post-processed '%s' as '%s" %
  109. (original_path, processed_path), level=1)
  110. self.post_processed_files.append(original_path)
  111. else:
  112. self.log(u"Skipped post-processing '%s'" % original_path)
  113. return {
  114. 'modified': self.copied_files + self.symlinked_files,
  115. 'unmodified': self.unmodified_files,
  116. 'post_processed': self.post_processed_files,
  117. }
  118. def handle_noargs(self, **options):
  119. self.set_options(**options)
  120. # Warn before doing anything more.
  121. if (isinstance(self.storage, FileSystemStorage) and
  122. self.storage.location):
  123. destination_path = self.storage.location
  124. destination_display = ':\n\n %s' % destination_path
  125. else:
  126. destination_path = None
  127. destination_display = '.'
  128. if self.clear:
  129. clear_display = 'This will DELETE EXISTING FILES!'
  130. else:
  131. clear_display = 'This will overwrite existing files!'
  132. if self.interactive:
  133. confirm = raw_input(u"""
  134. You have requested to collect static files at the destination
  135. location as specified in your settings%s
  136. %s
  137. Are you sure you want to do this?
  138. Type 'yes' to continue, or 'no' to cancel: """
  139. % (destination_display, clear_display))
  140. if confirm != 'yes':
  141. raise CommandError("Collecting static files cancelled.")
  142. collected = self.collect()
  143. modified_count = len(collected['modified'])
  144. unmodified_count = len(collected['unmodified'])
  145. post_processed_count = len(collected['post_processed'])
  146. if self.verbosity >= 1:
  147. template = ("\n%(modified_count)s %(identifier)s %(action)s"
  148. "%(destination)s%(unmodified)s%(post_processed)s.\n")
  149. summary = template % {
  150. 'modified_count': modified_count,
  151. 'identifier': 'static file' + (modified_count != 1 and 's' or ''),
  152. 'action': self.symlink and 'symlinked' or 'copied',
  153. 'destination': (destination_path and " to '%s'"
  154. % destination_path or ''),
  155. 'unmodified': (collected['unmodified'] and ', %s unmodified'
  156. % unmodified_count or ''),
  157. 'post_processed': (collected['post_processed'] and
  158. ', %s post-processed'
  159. % post_processed_count or ''),
  160. }
  161. self.stdout.write(smart_str(summary))
  162. def log(self, msg, level=2):
  163. """
  164. Small log helper
  165. """
  166. msg = smart_str(msg)
  167. if not msg.endswith("\n"):
  168. msg += "\n"
  169. if self.verbosity >= level:
  170. self.stdout.write(msg)
  171. def clear_dir(self, path):
  172. """
  173. Deletes the given relative path using the destinatin storage backend.
  174. """
  175. dirs, files = self.storage.listdir(path)
  176. for f in files:
  177. fpath = os.path.join(path, f)
  178. if self.dry_run:
  179. self.log(u"Pretending to delete '%s'" %
  180. smart_unicode(fpath), level=1)
  181. else:
  182. self.log(u"Deleting '%s'" % smart_unicode(fpath), level=1)
  183. self.storage.delete(fpath)
  184. for d in dirs:
  185. self.clear_dir(os.path.join(path, d))
  186. def delete_file(self, path, prefixed_path, source_storage):
  187. """
  188. Checks if the target file should be deleted if it already exists
  189. """
  190. if self.storage.exists(prefixed_path):
  191. try:
  192. # When was the target file modified last time?
  193. target_last_modified = \
  194. self.storage.modified_time(prefixed_path)
  195. except (OSError, NotImplementedError, AttributeError):
  196. # The storage doesn't support ``modified_time`` or failed
  197. pass
  198. else:
  199. try:
  200. # When was the source file modified last time?
  201. source_last_modified = source_storage.modified_time(path)
  202. except (OSError, NotImplementedError, AttributeError):
  203. pass
  204. else:
  205. # The full path of the target file
  206. if self.local:
  207. full_path = self.storage.path(prefixed_path)
  208. else:
  209. full_path = None
  210. # Skip the file if the source file is younger
  211. if target_last_modified >= source_last_modified:
  212. if not ((self.symlink and full_path
  213. and not os.path.islink(full_path)) or
  214. (not self.symlink and full_path
  215. and os.path.islink(full_path))):
  216. if prefixed_path not in self.unmodified_files:
  217. self.unmodified_files.append(prefixed_path)
  218. self.log(u"Skipping '%s' (not modified)" % path)
  219. return False
  220. # Then delete the existing file if really needed
  221. if self.dry_run:
  222. self.log(u"Pretending to delete '%s'" % path)
  223. else:
  224. self.log(u"Deleting '%s'" % path)
  225. self.storage.delete(prefixed_path)
  226. return True
  227. def link_file(self, path, prefixed_path, source_storage):
  228. """
  229. Attempt to link ``path``
  230. """
  231. # Skip this file if it was already copied earlier
  232. if prefixed_path in self.symlinked_files:
  233. return self.log(u"Skipping '%s' (already linked earlier)" % path)
  234. # Delete the target file if needed or break
  235. if not self.delete_file(path, prefixed_path, source_storage):
  236. return
  237. # The full path of the source file
  238. source_path = source_storage.path(path)
  239. # Finally link the file
  240. if self.dry_run:
  241. self.log(u"Pretending to link '%s'" % source_path, level=1)
  242. else:
  243. self.log(u"Linking '%s'" % source_path, level=1)
  244. full_path = self.storage.path(prefixed_path)
  245. try:
  246. os.makedirs(os.path.dirname(full_path))
  247. except OSError:
  248. pass
  249. os.symlink(source_path, full_path)
  250. if prefixed_path not in self.symlinked_files:
  251. self.symlinked_files.append(prefixed_path)
  252. def copy_file(self, path, prefixed_path, source_storage):
  253. """
  254. Attempt to copy ``path`` with storage
  255. """
  256. # Skip this file if it was already copied earlier
  257. if prefixed_path in self.copied_files:
  258. return self.log(u"Skipping '%s' (already copied earlier)" % path)
  259. # Delete the target file if needed or break
  260. if not self.delete_file(path, prefixed_path, source_storage):
  261. return
  262. # The full path of the source file
  263. source_path = source_storage.path(path)
  264. # Finally start copying
  265. if self.dry_run:
  266. self.log(u"Pretending to copy '%s'" % source_path, level=1)
  267. else:
  268. self.log(u"Copying '%s'" % source_path, level=1)
  269. if self.local:
  270. full_path = self.storage.path(prefixed_path)
  271. try:
  272. os.makedirs(os.path.dirname(full_path))
  273. except OSError:
  274. pass
  275. with source_storage.open(path) as source_file:
  276. self.storage.save(prefixed_path, source_file)
  277. if not prefixed_path in self.copied_files:
  278. self.copied_files.append(prefixed_path)