--- /dev/null
+import re
+import math
+from django.template import Library, Node, VariableDoesNotExist, \
+ TemplateSyntaxError
+from sorl.thumbnail.main import DjangoThumbnail, get_thumbnail_setting
+from sorl.thumbnail.processors import dynamic_import, get_valid_options
+from sorl.thumbnail.utils import split_args
+
+register = Library()
+
+size_pat = re.compile(r'(\d+)x(\d+)$')
+
+filesize_formats = ['k', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y']
+filesize_long_formats = {
+ 'k': 'kilo', 'M': 'mega', 'G': 'giga', 'T': 'tera', 'P': 'peta',
+ 'E': 'exa', 'Z': 'zetta', 'Y': 'yotta',
+}
+
+try:
+ PROCESSORS = dynamic_import(get_thumbnail_setting('PROCESSORS'))
+ VALID_OPTIONS = get_valid_options(PROCESSORS)
+except:
+ if get_thumbnail_setting('DEBUG'):
+ raise
+ else:
+ PROCESSORS = []
+ VALID_OPTIONS = []
+TAG_SETTINGS = ['quality']
+
+
+class ThumbnailNode(Node):
+ def __init__(self, source_var, size_var, opts=None,
+ context_name=None, **kwargs):
+ self.source_var = source_var
+ self.size_var = size_var
+ self.opts = opts
+ self.context_name = context_name
+ self.kwargs = kwargs
+
+ def render(self, context):
+ # Note that this isn't a global constant because we need to change the
+ # value for tests.
+ DEBUG = get_thumbnail_setting('DEBUG')
+ try:
+ # A file object will be allowed in DjangoThumbnail class
+ relative_source = self.source_var.resolve(context)
+ except VariableDoesNotExist:
+ if DEBUG:
+ raise VariableDoesNotExist("Variable '%s' does not exist." %
+ self.source_var)
+ else:
+ relative_source = None
+ try:
+ requested_size = self.size_var.resolve(context)
+ except VariableDoesNotExist:
+ if DEBUG:
+ raise TemplateSyntaxError("Size argument '%s' is not a"
+ " valid size nor a valid variable." % self.size_var)
+ else:
+ requested_size = None
+ # Size variable can be either a tuple/list of two integers or a valid
+ # string, only the string is checked.
+ else:
+ if isinstance(requested_size, basestring):
+ m = size_pat.match(requested_size)
+ if m:
+ requested_size = (int(m.group(1)), int(m.group(2)))
+ elif DEBUG:
+ raise TemplateSyntaxError("Variable '%s' was resolved but "
+ "'%s' is not a valid size." %
+ (self.size_var, requested_size))
+ else:
+ requested_size = None
+ if relative_source is None or requested_size is None:
+ thumbnail = ''
+ else:
+ try:
+ kwargs = {}
+ for key, value in self.kwargs.items():
+ kwargs[key] = value.resolve(context)
+ opts = dict([(k, v and v.resolve(context))
+ for k, v in self.opts.items()])
+ thumbnail = DjangoThumbnail(relative_source, requested_size,
+ opts=opts, processors=PROCESSORS, **kwargs)
+ except:
+ if DEBUG:
+ raise
+ else:
+ thumbnail = ''
+ # Return the thumbnail class, or put it on the context
+ if self.context_name is None:
+ return thumbnail
+ # We need to get here so we don't have old values in the context
+ # variable.
+ context[self.context_name] = thumbnail
+ return ''
+
+
+def thumbnail(parser, token):
+ """
+ Creates a thumbnail of for an ImageField.
+
+ To just output the absolute url to the thumbnail::
+
+ {% thumbnail image 80x80 %}
+
+ After the image path and dimensions, you can put any options::
+
+ {% thumbnail image 80x80 quality=95 crop %}
+
+ To put the DjangoThumbnail class on the context instead of just rendering
+ the absolute url, finish the tag with ``as [context_var_name]``::
+
+ {% thumbnail image 80x80 as thumb %}
+ {{ thumb.width }} x {{ thumb.height }}
+ """
+ args = token.split_contents()
+ tag = args[0]
+ # Check to see if we're setting to a context variable.
+ if len(args) > 4 and args[-2] == 'as':
+ context_name = args[-1]
+ args = args[:-2]
+ else:
+ context_name = None
+
+ if len(args) < 3:
+ raise TemplateSyntaxError("Invalid syntax. Expected "
+ "'{%% %s source size [option1 option2 ...] %%}' or "
+ "'{%% %s source size [option1 option2 ...] as variable %%}'" %
+ (tag, tag))
+
+ # Get the source image path and requested size.
+ source_var = parser.compile_filter(args[1])
+ # If the size argument was a correct static format, wrap it in quotes so
+ # that it is compiled correctly.
+ m = size_pat.match(args[2])
+ if m:
+ args[2] = '"%s"' % args[2]
+ size_var = parser.compile_filter(args[2])
+
+ # Get the options.
+ args_list = split_args(args[3:]).items()
+
+ # Check the options.
+ opts = {}
+ kwargs = {} # key,values here override settings and defaults
+
+ for arg, value in args_list:
+ value = value and parser.compile_filter(value)
+ if arg in TAG_SETTINGS and value is not None:
+ kwargs[str(arg)] = value
+ continue
+ if arg in VALID_OPTIONS:
+ opts[arg] = value
+ else:
+ raise TemplateSyntaxError("'%s' tag received a bad argument: "
+ "'%s'" % (tag, arg))
+ return ThumbnailNode(source_var, size_var, opts=opts,
+ context_name=context_name, **kwargs)
+
+
+def filesize(bytes, format='auto1024'):
+ """
+ Returns the number of bytes in either the nearest unit or a specific unit
+ (depending on the chosen format method).
+
+ Acceptable formats are:
+
+ auto1024, auto1000
+ convert to the nearest unit, appending the abbreviated unit name to the
+ string (e.g. '2 KiB' or '2 kB').
+ auto1024 is the default format.
+ auto1024long, auto1000long
+ convert to the nearest multiple of 1024 or 1000, appending the correctly
+ pluralized unit name to the string (e.g. '2 kibibytes' or '2 kilobytes').
+ kB, MB, GB, TB, PB, EB, ZB or YB
+ convert to the exact unit (using multiples of 1000).
+ KiB, MiB, GiB, TiB, PiB, EiB, ZiB or YiB
+ convert to the exact unit (using multiples of 1024).
+
+ The auto1024 and auto1000 formats return a string, appending the correct
+ unit to the value. All other formats return the floating point value.
+
+ If an invalid format is specified, the bytes are returned unchanged.
+ """
+ format_len = len(format)
+ # Check for valid format
+ if format_len in (2, 3):
+ if format_len == 3 and format[0] == 'K':
+ format = 'k%s' % format[1:]
+ if not format[-1] == 'B' or format[0] not in filesize_formats:
+ return bytes
+ if format_len == 3 and format[1] != 'i':
+ return bytes
+ elif format not in ('auto1024', 'auto1000',
+ 'auto1024long', 'auto1000long'):
+ return bytes
+ # Check for valid bytes
+ try:
+ bytes = long(bytes)
+ except (ValueError, TypeError):
+ return bytes
+
+ # Auto multiple of 1000 or 1024
+ if format.startswith('auto'):
+ if format[4:8] == '1000':
+ base = 1000
+ else:
+ base = 1024
+ logarithm = bytes and math.log(bytes, base) or 0
+ index = min(int(logarithm) - 1, len(filesize_formats) - 1)
+ if index >= 0:
+ if base == 1000:
+ bytes = bytes and bytes / math.pow(1000, index + 1)
+ else:
+ bytes = bytes >> (10 * (index))
+ bytes = bytes and bytes / 1024.0
+ unit = filesize_formats[index]
+ else:
+ # Change the base to 1000 so the unit will just output 'B' not 'iB'
+ base = 1000
+ unit = ''
+ if bytes >= 10 or ('%.1f' % bytes).endswith('.0'):
+ bytes = '%.0f' % bytes
+ else:
+ bytes = '%.1f' % bytes
+ if format.endswith('long'):
+ unit = filesize_long_formats.get(unit, '')
+ if base == 1024 and unit:
+ unit = '%sbi' % unit[:2]
+ unit = '%sbyte%s' % (unit, bytes != '1' and 's' or '')
+ else:
+ unit = '%s%s' % (base == 1024 and unit.upper() or unit,
+ base == 1024 and 'iB' or 'B')
+
+ return '%s %s' % (bytes, unit)
+
+ if bytes == 0:
+ return bytes
+ base = filesize_formats.index(format[0]) + 1
+ # Exact multiple of 1000
+ if format_len == 2:
+ return bytes / (1000.0 ** base)
+ # Exact multiple of 1024
+ elif format_len == 3:
+ bytes = bytes >> (10 * (base - 1))
+ return bytes / 1024.0
+
+
+register.tag(thumbnail)
+register.filter(filesize)