5 from django.conf import settings as django_settings
6 from django.utils.http import urlquote
7 from django.dispatch import dispatcher
9 from compress.conf import settings
10 from compress.signals import css_filtered, js_filtered
12 def get_filter(compressor_class):
14 Convert a string version of a function name to the callable object.
17 if not hasattr(compressor_class, '__bases__'):
20 compressor_class = compressor_class.encode('ascii')
21 mod_name, class_name = get_mod_func(compressor_class)
23 compressor_class = getattr(__import__(mod_name, {}, {}, ['']), class_name)
24 except (ImportError, AttributeError):
25 raise Exception('Failed to import filter %s' % compressor_class)
27 return compressor_class
29 def get_mod_func(callback):
31 Converts 'django.views.news.stories.story_detail' to
32 ('django.views.news.stories', 'story_detail')
36 dot = callback.rindex('.')
39 return callback[:dot], callback[dot+1:]
41 def needs_update(output_file, source_files):
43 Scan the source files for changes and returns True if the output_file needs to be updated.
46 mtime = max_mtime(source_files)
47 version = get_version(mtime)
49 compressed_file_full = media_root(get_output_filename(output_file, version))
51 if not os.path.exists(compressed_file_full):
54 # Check if the output file is outdated
55 return (os.stat(compressed_file_full).st_mtime < mtime), mtime
57 def media_root(filename):
59 Return the full path to ``filename``. ``filename`` is a relative path name in MEDIA_ROOT
61 return os.path.join(django_settings.STATIC_ROOT, filename)
64 return django_settings.STATIC_URL + urlquote(url)
66 def concat(filenames, separator=''):
68 Concatenate the files from the list of the ``filenames``, ouput separated with ``separator``.
72 for filename in filenames:
73 fd = open(media_root(filename), 'rb')
81 return int(max([os.stat(media_root(f)).st_mtime for f in files]))
83 def save_file(filename, contents):
84 fd = open(media_root(filename), 'wb+')
88 def get_output_filename(filename, version):
89 if settings.COMPRESS_VERSION and version is not None:
90 return filename.replace(settings.COMPRESS_VERSION_PLACEHOLDER, get_version(version))
92 return filename.replace(settings.COMPRESS_VERSION_PLACEHOLDER, settings.COMPRESS_VERSION_DEFAULT)
94 def get_version(version):
96 return str(int(version))
100 def remove_files(path, filename, verbosity=0):
101 regex = re.compile(r'^%s$' % (os.path.basename(get_output_filename(settings.COMPRESS_VERSION_PLACEHOLDER.join([re.escape(part) for part in filename.split(settings.COMPRESS_VERSION_PLACEHOLDER)]), r'\d+'))))
103 for f in os.listdir(path):
106 print "Removing outdated file %s" % f
108 os.unlink(os.path.join(path, f))
110 def filter_common(obj, verbosity, filters, attr, separator, signal):
111 output = concat(obj['source_filenames'], separator)
112 filename = get_output_filename(obj['output_filename'], get_version(max_mtime(obj['source_filenames'])))
114 if settings.COMPRESS_VERSION:
115 remove_files(os.path.dirname(media_root(filename)), obj['output_filename'], verbosity)
118 print "Saving %s" % filename
121 output = getattr(get_filter(f)(verbose=(verbosity >= 2)), attr)(output)
123 save_file(filename, output)
126 def filter_css(css, verbosity=0):
127 return filter_common(css, verbosity, filters=settings.COMPRESS_CSS_FILTERS, attr='filter_css', separator='', signal=css_filtered)
129 def filter_js(js, verbosity=0):
130 return filter_common(js, verbosity, filters=settings.COMPRESS_JS_FILTERS, attr='filter_js', separator=';', signal=js_filtered)