X-Git-Url: https://git.mdrn.pl/wolnelektury.git/blobdiff_plain/b49dd31cdbff70d595284d7f8591d2b85e7281a8..74488bcce98f59ac255fa342ff61def2251ebdb5:/apps/sorl/thumbnail/templatetags/thumbnail.py diff --git a/apps/sorl/thumbnail/templatetags/thumbnail.py b/apps/sorl/thumbnail/templatetags/thumbnail.py new file mode 100755 index 000000000..e7c2177ee --- /dev/null +++ b/apps/sorl/thumbnail/templatetags/thumbnail.py @@ -0,0 +1,251 @@ +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)