finders.py 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276
  1. from collections import OrderedDict
  2. import os
  3. from django.apps import apps
  4. from django.conf import settings
  5. from django.core.exceptions import ImproperlyConfigured
  6. from django.core.files.storage import default_storage, Storage, FileSystemStorage
  7. from django.utils.functional import empty, LazyObject
  8. from django.utils.module_loading import import_string
  9. from django.utils._os import safe_join
  10. from django.utils import six, lru_cache
  11. from django.contrib.staticfiles import utils
  12. # To keep track on which directories the finder has searched the static files.
  13. searched_locations = []
  14. class BaseFinder(object):
  15. """
  16. A base file finder to be used for custom staticfiles finder classes.
  17. """
  18. def find(self, path, all=False):
  19. """
  20. Given a relative file path this ought to find an
  21. absolute file path.
  22. If the ``all`` parameter is ``False`` (default) only
  23. the first found file path will be returned; if set
  24. to ``True`` a list of all found files paths is returned.
  25. """
  26. raise NotImplementedError('subclasses of BaseFinder must provide a find() method')
  27. def list(self, ignore_patterns):
  28. """
  29. Given an optional list of paths to ignore, this should return
  30. a two item iterable consisting of the relative path and storage
  31. instance.
  32. """
  33. raise NotImplementedError('subclasses of BaseFinder must provide a list() method')
  34. class FileSystemFinder(BaseFinder):
  35. """
  36. A static files finder that uses the ``STATICFILES_DIRS`` setting
  37. to locate files.
  38. """
  39. def __init__(self, app_names=None, *args, **kwargs):
  40. # List of locations with static files
  41. self.locations = []
  42. # Maps dir paths to an appropriate storage instance
  43. self.storages = OrderedDict()
  44. if not isinstance(settings.STATICFILES_DIRS, (list, tuple)):
  45. raise ImproperlyConfigured(
  46. "Your STATICFILES_DIRS setting is not a tuple or list; "
  47. "perhaps you forgot a trailing comma?")
  48. for root in settings.STATICFILES_DIRS:
  49. if isinstance(root, (list, tuple)):
  50. prefix, root = root
  51. else:
  52. prefix = ''
  53. if settings.STATIC_ROOT and os.path.abspath(settings.STATIC_ROOT) == os.path.abspath(root):
  54. raise ImproperlyConfigured(
  55. "The STATICFILES_DIRS setting should "
  56. "not contain the STATIC_ROOT setting")
  57. if (prefix, root) not in self.locations:
  58. self.locations.append((prefix, root))
  59. for prefix, root in self.locations:
  60. filesystem_storage = FileSystemStorage(location=root)
  61. filesystem_storage.prefix = prefix
  62. self.storages[root] = filesystem_storage
  63. super(FileSystemFinder, self).__init__(*args, **kwargs)
  64. def find(self, path, all=False):
  65. """
  66. Looks for files in the extra locations
  67. as defined in ``STATICFILES_DIRS``.
  68. """
  69. matches = []
  70. for prefix, root in self.locations:
  71. if root not in searched_locations:
  72. searched_locations.append(root)
  73. matched_path = self.find_location(root, path, prefix)
  74. if matched_path:
  75. if not all:
  76. return matched_path
  77. matches.append(matched_path)
  78. return matches
  79. def find_location(self, root, path, prefix=None):
  80. """
  81. Finds a requested static file in a location, returning the found
  82. absolute path (or ``None`` if no match).
  83. """
  84. if prefix:
  85. prefix = '%s%s' % (prefix, os.sep)
  86. if not path.startswith(prefix):
  87. return None
  88. path = path[len(prefix):]
  89. path = safe_join(root, path)
  90. if os.path.exists(path):
  91. return path
  92. def list(self, ignore_patterns):
  93. """
  94. List all files in all locations.
  95. """
  96. for prefix, root in self.locations:
  97. storage = self.storages[root]
  98. for path in utils.get_files(storage, ignore_patterns):
  99. yield path, storage
  100. class AppDirectoriesFinder(BaseFinder):
  101. """
  102. A static files finder that looks in the directory of each app as
  103. specified in the source_dir attribute.
  104. """
  105. storage_class = FileSystemStorage
  106. source_dir = 'static'
  107. def __init__(self, app_names=None, *args, **kwargs):
  108. # The list of apps that are handled
  109. self.apps = []
  110. # Mapping of app names to storage instances
  111. self.storages = OrderedDict()
  112. app_configs = apps.get_app_configs()
  113. if app_names:
  114. app_names = set(app_names)
  115. app_configs = [ac for ac in app_configs if ac.name in app_names]
  116. for app_config in app_configs:
  117. app_storage = self.storage_class(
  118. os.path.join(app_config.path, self.source_dir))
  119. if os.path.isdir(app_storage.location):
  120. self.storages[app_config.name] = app_storage
  121. if app_config.name not in self.apps:
  122. self.apps.append(app_config.name)
  123. super(AppDirectoriesFinder, self).__init__(*args, **kwargs)
  124. def list(self, ignore_patterns):
  125. """
  126. List all files in all app storages.
  127. """
  128. for storage in six.itervalues(self.storages):
  129. if storage.exists(''): # check if storage location exists
  130. for path in utils.get_files(storage, ignore_patterns):
  131. yield path, storage
  132. def find(self, path, all=False):
  133. """
  134. Looks for files in the app directories.
  135. """
  136. matches = []
  137. for app in self.apps:
  138. app_location = self.storages[app].location
  139. if app_location not in searched_locations:
  140. searched_locations.append(app_location)
  141. match = self.find_in_app(app, path)
  142. if match:
  143. if not all:
  144. return match
  145. matches.append(match)
  146. return matches
  147. def find_in_app(self, app, path):
  148. """
  149. Find a requested static file in an app's static locations.
  150. """
  151. storage = self.storages.get(app, None)
  152. if storage:
  153. # only try to find a file if the source dir actually exists
  154. if storage.exists(path):
  155. matched_path = storage.path(path)
  156. if matched_path:
  157. return matched_path
  158. class BaseStorageFinder(BaseFinder):
  159. """
  160. A base static files finder to be used to extended
  161. with an own storage class.
  162. """
  163. storage = None
  164. def __init__(self, storage=None, *args, **kwargs):
  165. if storage is not None:
  166. self.storage = storage
  167. if self.storage is None:
  168. raise ImproperlyConfigured("The staticfiles storage finder %r "
  169. "doesn't have a storage class "
  170. "assigned." % self.__class__)
  171. # Make sure we have an storage instance here.
  172. if not isinstance(self.storage, (Storage, LazyObject)):
  173. self.storage = self.storage()
  174. super(BaseStorageFinder, self).__init__(*args, **kwargs)
  175. def find(self, path, all=False):
  176. """
  177. Looks for files in the default file storage, if it's local.
  178. """
  179. try:
  180. self.storage.path('')
  181. except NotImplementedError:
  182. pass
  183. else:
  184. if self.storage.location not in searched_locations:
  185. searched_locations.append(self.storage.location)
  186. if self.storage.exists(path):
  187. match = self.storage.path(path)
  188. if all:
  189. match = [match]
  190. return match
  191. return []
  192. def list(self, ignore_patterns):
  193. """
  194. List all files of the storage.
  195. """
  196. for path in utils.get_files(self.storage, ignore_patterns):
  197. yield path, self.storage
  198. class DefaultStorageFinder(BaseStorageFinder):
  199. """
  200. A static files finder that uses the default storage backend.
  201. """
  202. storage = default_storage
  203. def __init__(self, *args, **kwargs):
  204. super(DefaultStorageFinder, self).__init__(*args, **kwargs)
  205. base_location = getattr(self.storage, 'base_location', empty)
  206. if not base_location:
  207. raise ImproperlyConfigured("The storage backend of the "
  208. "staticfiles finder %r doesn't have "
  209. "a valid location." % self.__class__)
  210. def find(path, all=False):
  211. """
  212. Find a static file with the given path using all enabled finders.
  213. If ``all`` is ``False`` (default), return the first matching
  214. absolute path (or ``None`` if no match). Otherwise return a list.
  215. """
  216. searched_locations[:] = []
  217. matches = []
  218. for finder in get_finders():
  219. result = finder.find(path, all=all)
  220. if not all and result:
  221. return result
  222. if not isinstance(result, (list, tuple)):
  223. result = [result]
  224. matches.extend(result)
  225. if matches:
  226. return matches
  227. # No match.
  228. return [] if all else None
  229. def get_finders():
  230. for finder_path in settings.STATICFILES_FINDERS:
  231. yield get_finder(finder_path)
  232. @lru_cache.lru_cache(maxsize=None)
  233. def get_finder(import_path):
  234. """
  235. Imports the staticfiles finder class described by import_path, where
  236. import_path is the full Python path to the class.
  237. """
  238. Finder = import_string(import_path)
  239. if not issubclass(Finder, BaseFinder):
  240. raise ImproperlyConfigured('Finder "%s" is not a subclass of "%s"' %
  241. (Finder, BaseFinder))
  242. return Finder()