Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Initial commit

  • Loading branch information...
commit 586e94ed3b2a26043100bb9667a7fbff786b36f9 0 parents
@olivier-m authored
25 LICENSE
@@ -0,0 +1,25 @@
+Copyright (c) 2010, Olivier Meunier <om@neokraft.net>
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+ * Neither the name of the University of California, Berkeley nor the
+ names of its contributors may be used to endorse or promote products
+ derived from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY
+EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY
+DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
130 README.rst
@@ -0,0 +1,130 @@
+Introduction
+============
+
+Django facets provides a simple way to handle your media during development
+and in production. It changes media URLs and offers a feature to create
+collections (many files becoming one in production).
+
+Here's an example::
+
+ <link rel="stylesheet" href="{% mediafile "base.css" %}" />
+
+During development the ``mediafile`` tag returns ``{{ MEDIA_URL }}base.css``. If the cache is active, the tag returns the cached file's URL.
+
+Installation
+============
+
+Download the package and type ``python setup.py install``. Then, add
+``facets`` to your ``INSTALLED_APPS``.
+
+Configuration
+=============
+
+Django facets needs some configuration settings.
+
+MEDIA_CACHE_ACTIVE
+------------------
+
+This setting enables cache. You should set it to True when in production.
+
+MEDIA_CACHE_ROOT
+----------------
+
+The full path to your cached files.
+
+MEDIA_CACHE_URL
+---------------
+
+URL to your cached files.
+
+MEDIA_CACHE_STORE
+-----------------
+
+After media generation, Django facets creates a ``store.py`` file. By default,
+it is located in ``MEDIA_CACHE_ROOT`` but you can set it to any place you
+want.
+
+MEDIA_CACHE_HANDLERS
+--------------------
+
+A dictionnary of handlers by file types. The default value is::
+
+ {
+ 'css': (
+ 'facets.handlers.CssUrls',
+ 'facets.handlers.CssMin',
+ ),
+ 'js': (
+ 'facets.handlers.UglifyJs',
+ ),
+ }
+
+These handlers are called during cache creation. You should not remove
+``CssUrls`` handler as it is the one responsible for URL translation in CSS
+files.
+
+MEDIA_CACHE_UGLIFYJS
+--------------------
+
+If you want to use ``UglifyJs`` handler, you should first install `UglifyJs
+<http://github.com/mishoo/UglifyJS>`_ (and node.js) and then give its path in
+this setting.
+
+Usage
+=====
+
+Links to media
+--------------
+
+When you need a media URL, use ``mediafile`` template tag. Here's an example::
+
+ {% load mediafiles %}
+ <link rel="stylesheet" href="{% mediafile "css/my.css"} %}" />
+ <script src="{% mediafile "js/script.js" %}"></script>
+
+Collections
+-----------
+
+Collections are files you want to concatenate while in production. To create
+a collection, you should use the ``mediacollection`` template tag. Here's an
+example::
+
+ {% load mediafiles %}
+
+ {% mediacollection "css/main.css" %}
+ <link rel="stylesheet" href="{% mediafile "css/reset.css" %}" />
+ <link rel="stylesheet" href="{% mediafile "css/screen.css" %}" />
+ {% endmediacollection %}
+
+The argument of the tag is the collection's final name.
+
+Collections follow some rules:
+
+* Only for ``link`` and ``script`` HTML tags.
+* You can't mix ``link`` and ``script`` tags together.
+* With ``link`` tags, the following attributes must have the same values on
+ each tag: rel, type, media
+* With ``script`` tags, the following attributes must have the same values on
+ each tag: type
+
+Management command
+==================
+
+Before using the cache, you should run ``./manage.py generate_media``. This
+command generates cached files.
+
+You could run this command during project deployment.
+
+Writing your own handlers
+=========================
+
+Writing a handler is quite easy. It should be a class that inherits
+``facets.handlers.BaseHandler`` and implements a ``render`` method. This
+method returns the modified data or nothing if you don't want any
+transformation.
+
+License
+=======
+
+Django facets is released under the BSD license. See the LICENSE
+file for the complete license.
42 facets/__init__.py
@@ -0,0 +1,42 @@
+# -*- coding: utf-8 -*-
+#
+# This file is part of Django facets released under the BSD license.
+# See the LICENSE for more information.
+import os.path
+
+from django.conf import settings
+
+__HANDLERS = {
+ 'css': (
+ 'facets.handlers.CssUrls',
+ 'facets.handlers.CssMin',
+ ),
+ 'js': (
+ 'facets.handlers.UglifyJs',
+ ),
+}
+
+setattr(settings, 'MEDIA_CACHE_ACTIVE', getattr(settings,
+ 'MEDIA_CACHE_ACTIVE', False
+))
+
+setattr(settings, 'MEDIA_CACHE_ROOT', getattr(settings,
+ 'MEDIA_CACHE_ROOT', None
+))
+
+setattr(settings, 'MEDIA_CACHE_URL', getattr(settings,
+ 'MEDIA_CACHE_URL', settings.MEDIA_URL
+))
+
+setattr(settings, 'MEDIA_CACHE_STORE', getattr(settings,
+ 'MEDIA_CACHE_STORE',
+ settings.MEDIA_CACHE_ROOT and os.path.join(settings.MEDIA_CACHE_ROOT, 'store.py') or None
+))
+
+setattr(settings, 'MEDIA_CACHE_HANDLERS', getattr(settings,
+ 'MEDIA_CACHE_HANDLERS', __HANDLERS
+))
+
+setattr(settings, 'MEDIA_CACHE_UGLIFYJS', getattr(settings,
+ 'MEDIA_CACHE_UGLIFYJS', None
+))
155 facets/collections.py
@@ -0,0 +1,155 @@
+# -*- coding: utf-8 -*-
+#
+# This file is part of Django facets released under the BSD license.
+# See the LICENSE for more information.
+try:
+ from cStringIO import StringIO
+except ImportError:
+ from StringIO import StringIO
+import hashlib
+import os.path
+from urlparse import urljoin
+
+import html5lib
+from html5lib import treebuilders
+
+from facets.utils import files_checksum
+
+def _attr_data(data):
+ data = data.replace("&", "&amp;").replace("<", "&lt;")
+ data = data.replace("\"", "&quot;").replace(">", "&gt;")
+ return data
+
+class CollectionException(Exception):
+ pass
+
+class MediaCollection(object):
+ def __init__(self, data, path):
+ parser = html5lib.HTMLParser(tree=treebuilders.getTreeBuilder("dom"))
+ node = parser.parseFragment(data)
+ self.node = node
+ self.path = path
+ self.init_collection()
+
+ def __eq__(self, other):
+ return isinstance(other, self.__class__) and hash(self) == hash(other)
+
+ def __hash__(self):
+ m = hashlib.md5()
+ [m.update(x) for x in self.media]
+ m.update(self.path)
+ return int(m.hexdigest(), 16)
+
+ def clear(self):
+ self.type = None
+ self.attrs = {}
+ self.media = []
+
+ def init_collection(self):
+ self.clear()
+ for n in self.node.childNodes:
+ if n.nodeType == n.TEXT_NODE:
+ continue
+ if n.nodeType != n.ELEMENT_NODE:
+ raise CollectionException('Collection contains non element nodes.')
+
+ name = n.nodeName.lower()
+ if self.type and self.type != name:
+ raise CollectionException('Collection contains elements of type %s only.' % self.type)
+
+ self.type = name
+
+ if name == 'link':
+ self.set_prop_link(n)
+ elif name == 'script':
+ self.set_prop_script(n)
+
+ def attr_value(self, node, name, default=None):
+ attr = node.attributes.get(name)
+ if attr is None:
+ return default
+
+ return attr.value
+
+ def set_prop_link(self, node):
+ rel = self.attr_value(node, 'rel')
+ href = self.attr_value(node, 'href')
+ ctype = self.attr_value(node, 'type', 'text/css')
+ media = self.attr_value(node, 'media', 'screen')
+
+ if self.attrs.get('rel') not in (None, rel):
+ raise CollectionException('All rel attributes should be the same in collection.')
+ if self.attrs.get('type') not in (None, ctype):
+ raise CollectionException('All type attributes should be the same in collection.')
+ if self.attrs.get('media') not in (None, media):
+ raise CollectionException('All media attributes should be the same in collection.')
+
+ self.attrs.update({
+ 'rel': rel, 'type': ctype, 'media': media
+ })
+
+ self.media.append(href)
+
+ def set_prop_script(self, node):
+ ctype = self.attr_value(node, 'type', 'text/javascript')
+ src = self.attr_value(node, 'src')
+
+ if src is None:
+ raise CollectionException('No src attribute in collection script tag.')
+ if self.attrs.get('type') not in (None, ctype):
+ raise CollectionException('All type attributes should be the same in collection.')
+
+ self.attrs.update({
+ 'type': ctype,
+ })
+
+ self.media.append(src)
+
+ def get_html(self, base_url):
+ if self.type == 'link':
+ return self.get_html_link(base_url)
+ elif self.type == 'script':
+ return self.get_html_script(base_url)
+
+ def get_html_link(self, base_url):
+ attrs = dict(self.attrs)
+ attrs['href'] = urljoin(base_url, self.path)
+ tag = '<link %s />'
+ return tag % ' '.join(
+ ['%s="%s"' % (k, _attr_data(v)) for k,v in attrs.items()]
+ )
+
+ def get_html_script(self, base_url):
+ attrs = dict(self.attrs)
+ attrs['src'] = urljoin(base_url, self.path)
+ tag = '<script %s></script>'
+ return tag % ' '.join(
+ ['%s="%s"' % (k, _attr_data(v)) for k,v in attrs.items()]
+ )
+
+ def make_file(self, media_store, media_url, cache_root):
+ """
+ Creates a collection file and return its destination path and url
+ """
+ files = [os.path.join(cache_root, media_store[y]) for y in
+ [x[len(media_url):] for x in self.media]
+ ]
+
+ out = StringIO()
+ for f in files:
+ fp = open(f, 'rb')
+ out.write(fp.read())
+ out.write('\n')
+ fp.close()
+
+ outurl = ('-%s' % files_checksum(*files)).join(
+ os.path.splitext(self.path)
+ )
+ outfile = os.path.join(cache_root, outurl)
+
+ fp = open(outfile, 'wb')
+ fp.write(out.getvalue())
+ fp.close()
+
+ return outfile, outurl
+
122 facets/handlers.py
@@ -0,0 +1,122 @@
+# -*- coding: utf-8 -*-
+#
+# This file is part of Django facets released under the BSD license.
+# See the LICENSE for more information.
+import os.path
+import re
+from subprocess import Popen, PIPE
+from urlparse import urljoin
+
+from django.conf import settings
+from django.utils.importlib import import_module
+
+class MediaHandlers(dict):
+ """
+ Our media handlers. A dict with lists of handlers by type.
+ Instance of this class is created at the end of the file
+ """
+ def __init__(self, *args, **kwargs):
+ super(MediaHandlers, self).__init__(*args, **kwargs)
+ self.init_handlers()
+
+ def init_handlers(self):
+ self.clear()
+ for handler_type, handlers in settings.MEDIA_CACHE_HANDLERS.items():
+ self[handler_type] = []
+ for handler in handlers:
+ try:
+ module_name, klass = handler.rsplit('.', 1)
+ m = import_module(module_name)
+ self[handler_type].append(getattr(m, klass))
+ except ImportError:
+ pass
+
+ def apply_handlers(self, src, data, media_store):
+ for klass in self.get(os.path.splitext(src)[1][1:], list()):
+ _handler = klass(src, data, media_store)
+ try:
+ new_data = _handler.render()
+ if new_data:
+ data = new_data
+ except NotImplementedError:
+ pass
+
+ return data
+
+
+class BaseHandler(object):
+ """
+ All custom handlers shoul inherit this class and implement a ``render``
+ method.
+ """
+ def __init__(self, src, data, media_store):
+ self.src = src
+ self.data = data
+ self.media_store = media_store
+
+ def render(self):
+ raise NotImplementedError()
+
+class InOutHandler(BaseHandler):
+ """
+ A base handler for all stdin to stdout operation through an external
+ command. ``cmd`` should be a tuple or a list.
+ """
+ cmd = None
+
+ def render(self):
+ if not isinstance(self.cmd, (tuple, list)) or len(self.cmd) == 0:
+ raise NotImplementedError()
+
+ if not self.cmd[0] or not os.path.isfile(self.cmd[0]):
+ return
+
+ try:
+ p = Popen(self.cmd, stdin=PIPE, stdout=PIPE)
+ except OSError:
+ return
+ else:
+ res, __ = p.communicate(self.data)
+ if res:
+ return res
+
+
+class CssUrls(BaseHandler):
+ re_url = re.compile(r'(url\(["\']?)(.+?)(["\']?\))', re.M)
+
+ def render(self):
+ self.base_src = '%s/' % urljoin(settings.MEDIA_URL, os.path.dirname(self.src))
+ self.base_dst = '%s/' % urljoin(settings.MEDIA_CACHE_URL, os.path.dirname(self.src))
+
+ return self.re_url.sub(self.adapt_url, self.data)
+
+ def adapt_url(self, match):
+ groups = list(match.groups())
+ if groups[1].find('://') != -1:
+ return ''.join(groups)
+
+ url = urljoin(self.base_src, groups[1])
+ store_key = url[len(settings.MEDIA_URL):]
+
+ if store_key in self.media_store.keys():
+ groups[1] = urljoin(settings.MEDIA_CACHE_URL, self.media_store[store_key])
+
+ return ''.join(groups)
+
+
+class CssMin(BaseHandler):
+ def render(self):
+ try:
+ from cssmin import cssmin
+ except ImportError:
+ return
+ else:
+ data = cssmin(self.data)
+ if data:
+ return data
+
+
+class UglifyJs(InOutHandler):
+ cmd = (settings.MEDIA_CACHE_UGLIFYJS,)
+
+media_handlers = MediaHandlers()
0  facets/management/__init__.py
No changes.
0  facets/management/commands/__init__.py
No changes.
124 facets/management/commands/generate_media.py
@@ -0,0 +1,124 @@
+# -*- coding: utf-8 -*-
+#
+# This file is part of Django facets released under the BSD license.
+# See the LICENSE for more information.
+import os
+import shutil
+from urlparse import urljoin
+
+from django.conf import settings
+from django.core.management.base import NoArgsCommand, CommandError
+from django import template
+
+from facets import store
+from facets.templatetags import mediafiles as media_tags
+from facets.utils import copy_file, files_checksum
+
+class Command(NoArgsCommand):
+ help = "Generate media files for production"
+
+ def handle_noargs(self, *args, **options):
+ verbosity = options.get('verbosity', 1)
+ media_store = store.MediaStore()
+
+ if not settings.MEDIA_CACHE_ROOT:
+ raise CommandError('You should have a MEDIA_CACHE_ROOT setting.')
+
+ if settings.MEDIA_CACHE_ROOT == settings.MEDIA_ROOT:
+ raise CommandError('MEDIA_CACHE_ROOT should not be the same directory as MEDIA_ROOT.')
+
+ root = settings.MEDIA_CACHE_ROOT
+ if os.path.exists(root) and not os.path.isdir(root):
+ raise CommandError('MEDIA_CACHE_ROOT "%s" is not a directory.' % root)
+
+ if os.path.exists(root):
+ if verbosity:
+ print "Empty %s directory" % root
+ shutil.rmtree(root)
+
+ if verbosity:
+ print "Making %s directory" % root
+ os.makedirs(root)
+
+ # Before generating collections we need to set MEDIA_CACHE_ACTIVE
+ # to False
+ setattr(settings, 'MEDIA_CACHE_ACTIVE', False)
+
+ collections = self.parse_templates(options)
+
+ # Copy all files in media_store
+ for src, dst in media_store.items():
+ copy_file(src, dst, media_store)
+
+ for col in collections:
+ col_file, col_url = col.make_file(media_store,
+ settings.MEDIA_URL, settings.MEDIA_CACHE_ROOT)
+
+ media_store[col.path] = col_url
+
+ # Copy media_store representation in a python file
+ fp = open(settings.MEDIA_CACHE_STORE, 'wb')
+ fp.write('# -*- coding: utf-8 -*-\n')
+ fp.write('media_store = %s' % repr(media_store))
+ fp.close()
+
+ def parse_templates(self, options):
+ # Most parts of this code comes from django assets
+ #
+ template_dirs = []
+ if 'django.template.loaders.filesystem.Loader' in settings.TEMPLATE_LOADERS:
+ template_dirs.extend(settings.TEMPLATE_DIRS)
+ if 'django.template.loaders.app_directories.Loader' in settings.TEMPLATE_LOADERS:
+ from django.template.loaders.app_directories import app_template_dirs
+ template_dirs.extend(app_template_dirs)
+
+ collections = set()
+ total_count = 0
+ for template_dir in template_dirs:
+ for directory, _ds, files in os.walk(template_dir):
+ for filename in files:
+ if filename.endswith('.html'):
+ total_count += 1
+ tmpl_path = os.path.join(directory, filename)
+ collections.update(self.parse_template(options, tmpl_path))
+
+ if options.get('verbosity') >= 1:
+ print "Parsed %d templates, found %d valid collections." % (
+ total_count, len(collections))
+ return collections
+
+ def parse_template(self, options, tmpl_path):
+ contents = open(tmpl_path, 'rb').read()
+ try:
+ t = template.Template(contents)
+ except template.TemplateSyntaxError, e:
+ if options.get('verbosity') >= 2:
+ print self.style.ERROR('\tdjango parser failed, error was: %s'%e)
+ return set()
+ else:
+ result = set()
+ def _recurse_node(node):
+ # depending on whether the template tag is added to
+ # builtins, or loaded via {% load %}, it will be
+ # available in a different module
+ if isinstance(node, media_tags.MediaCollectionNode):
+ # try to resolve this node's data; if we fail,
+ # then it depends on view data and we cannot
+ # manually rebuild it.
+ try:
+ collection = node.resolve()
+ except template.VariableDoesNotExist:
+ if options.get('verbosity') >= 2:
+ print self.style.ERROR('\tskipping asset %s, depends on runtime data.' % node.output)
+ else:
+ result.add(collection)
+ # see Django #7430
+ for subnode in hasattr(node, 'nodelist') and node.nodelist or []:
+ _recurse_node(subnode)
+
+ for node in t: # don't move into _recurse_node, ``Template`` has a .nodelist attribute
+ _recurse_node(node)
+
+ return result
+
+
43 facets/store.py
@@ -0,0 +1,43 @@
+# -*- coding: utf-8 -*-
+#
+# This file is part of Django facets released under the BSD license.
+# See the LICENSE for more information.
+import imp
+import os
+
+from django.conf import settings
+
+from facets.utils import files_checksum
+
+class MediaStore(dict):
+ def __init__(self, *args, **kwargs):
+ super(MediaStore, self).__init__(*args, **kwargs)
+ self.__root = None
+ if os.path.isdir(settings.MEDIA_ROOT):
+ self.__root = os.path.realpath(settings.MEDIA_ROOT)
+
+ self.rebuild()
+
+ def __media_version(self, path):
+ media_path = path[len(self.__root)+1:]
+ splited = os.path.splitext(media_path)
+ version = files_checksum(path)
+ return media_path, '%s-%s%s' % (splited[0], version, splited[1])
+
+ def rebuild(self):
+ if not self.__root:
+ return
+
+ self.clear()
+
+ for dirpath, dirnames, filenames in os.walk(self.__root):
+ for path in filenames:
+ media_path, version = self.__media_version(os.path.join(dirpath, path))
+ self[media_path] = version
+
+
+try:
+ m = imp.load_source('cache_store', settings.MEDIA_CACHE_STORE)
+ media_store = getattr(m, 'media_store', {})
+except (IOError, ImportError, TypeError):
+ media_store = {}
0  facets/templatetags/__init__.py
No changes.
81 facets/templatetags/mediafiles.py
@@ -0,0 +1,81 @@
+# -*- coding: utf-8 -*-
+#
+# This file is part of Django facets released under the BSD license.
+# See the LICENSE for more information.
+from urlparse import urljoin
+
+import html5lib
+from html5lib import sanitizer
+
+from django import template
+from django.conf import settings
+
+from facets.store import media_store
+from facets.collections import MediaCollection, CollectionException
+
+register = template.Library()
+
+class MediaFileNode(template.Node):
+ def __init__(self, path):
+ self.path = path
+
+ def render(self, context):
+ return self.path
+
+class MediaCollectionNode(template.Node):
+ def __init__(self, nodelist, path):
+ self.nodelist = nodelist
+ self.path = path
+
+ def render(self, context):
+ output = self.nodelist.render(context)
+
+ if not settings.MEDIA_CACHE_ACTIVE:
+ return output
+
+ collection = MediaCollection(output, media_store[self.path])
+ return collection.get_html(settings.MEDIA_CACHE_URL)
+
+ def resolve(self, context=None):
+ context = context or template.Context()
+ output = self.nodelist.render(context)
+
+ return MediaCollection(output, self.path)
+
+
+def do_mediafile(parser, token):
+ try:
+ tag_name, path = token.split_contents()
+ except ValueError:
+ raise template.TemplateSyntaxError, "%r tag requires a single argument" % token.contents.split()[0]
+ if not (path[0] == path[-1] and path[0] in ('"', "'")):
+ raise template.TemplateSyntaxError, "%r tag's argument should be in quotes" % tag_name
+
+ path = path[1:-1]
+ if settings.MEDIA_CACHE_ACTIVE:
+ if not path in media_store:
+ raise template.TemplateSyntaxError("%r media file does not exist." % path)
+ path = urljoin(settings.MEDIA_CACHE_URL, media_store[path])
+ else:
+ path = urljoin(settings.MEDIA_URL, path)
+
+ return MediaFileNode(path)
+
+
+def do_mediacollection(parser, token):
+ try:
+ tag_name, path = token.split_contents()
+ except ValueError:
+ raise template.TemplateSyntaxError, "%r tag requires a single argument" % token.contents.split()[0]
+ if not (path[0] == path[-1] and path[0] in ('"', "'")):
+ raise template.TemplateSyntaxError, "%r tag's argument should be in quotes" % tag_name
+
+ path = path[1:-1]
+
+ nodelist = parser.parse(('endmediacollection',))
+ parser.delete_first_token()
+ return MediaCollectionNode(nodelist, path)
+
+
+do_mediafile = register.tag('mediafile', do_mediafile)
+do_mediacollection = register.tag('mediacollection', do_mediacollection)
44 facets/utils.py
@@ -0,0 +1,44 @@
+# -*- coding: utf-8 -*-
+#
+# This file is part of Django facets released under the BSD license.
+# See the LICENSE for more information.
+import hashlib
+import os
+import shutil
+
+from django.conf import settings
+from django.utils.importlib import import_module
+
+from facets.handlers import media_handlers
+
+def files_checksum(*files):
+ m = hashlib.md5()
+ for f in files:
+ fp = open(f, 'rb')
+ while True:
+ data = fp.read(128)
+ if not data:
+ break
+ m.update(data)
+
+ return m.hexdigest()[0:8]
+
+def copy_file(src, dst, media_store):
+ src_path = os.path.join(settings.MEDIA_ROOT, src)
+ dst_path = os.path.join(settings.MEDIA_CACHE_ROOT, dst)
+ dst_dir = os.path.dirname(dst_path)
+
+ data = open(src_path, 'rb').read()
+
+ # Applying media handlers
+ data = media_handlers.apply_handlers(src, data, media_store)
+
+ if not os.path.exists(dst_dir):
+ os.makedirs(dst_dir)
+
+ fp = open(dst_path, 'wb')
+ fp.write(data)
+ fp.close
+ shutil.copymode(src_path, dst_path)
+ shutil.copystat(src_path, dst_path)
+
27 setup.py
@@ -0,0 +1,27 @@
+# -*- coding: utf-8 -*-
+#
+# This file is part of Django facets released under the BSD license.
+# See the LICENSE for more information.
+from setuptools import setup, find_packages
+
+version = '0.2'
+packages = ['facets'] + ['facets.%s' % x for x in find_packages('facets',)]
+
+setup(
+ name='facets',
+ version=version,
+ description='Media collections manager for Django.',
+ author='Olivier Meunier',
+ author_email='om@neokraft.net',
+ url='http://bitbucket.org/cedarlab/django-facets/',
+ packages=packages,
+ classifiers=[
+ 'Development Status :: %s' % version,
+ 'Environment :: Web Environment',
+ 'Intended Audience :: Developers',
+ 'License :: OSI Approved :: BSD License',
+ 'Operating System :: OS Independent',
+ 'Programming Language :: Python',
+ 'Topic :: Utilities'
+ ],
+)
Please sign in to comment.
Something went wrong with that request. Please try again.