3 from django.template import Library, Node, VariableDoesNotExist, \
5 from sorl.thumbnail.main import DjangoThumbnail, get_thumbnail_setting
6 from sorl.thumbnail.processors import dynamic_import, get_valid_options
7 from sorl.thumbnail.utils import split_args
11 size_pat = re.compile(r'(\d+)x(\d+)$')
13 filesize_formats = ['k', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y']
14 filesize_long_formats = {
15 'k': 'kilo', 'M': 'mega', 'G': 'giga', 'T': 'tera', 'P': 'peta',
16 'E': 'exa', 'Z': 'zetta', 'Y': 'yotta',
20 PROCESSORS = dynamic_import(get_thumbnail_setting('PROCESSORS'))
21 VALID_OPTIONS = get_valid_options(PROCESSORS)
23 if get_thumbnail_setting('DEBUG'):
28 TAG_SETTINGS = ['quality']
31 class ThumbnailNode(Node):
32 def __init__(self, source_var, size_var, opts=None,
33 context_name=None, **kwargs):
34 self.source_var = source_var
35 self.size_var = size_var
37 self.context_name = context_name
40 def render(self, context):
41 # Note that this isn't a global constant because we need to change the
43 DEBUG = get_thumbnail_setting('DEBUG')
45 # A file object will be allowed in DjangoThumbnail class
46 relative_source = self.source_var.resolve(context)
47 except VariableDoesNotExist:
49 raise VariableDoesNotExist("Variable '%s' does not exist." %
52 relative_source = None
54 requested_size = self.size_var.resolve(context)
55 except VariableDoesNotExist:
57 raise TemplateSyntaxError("Size argument '%s' is not a"
58 " valid size nor a valid variable." % self.size_var)
61 # Size variable can be either a tuple/list of two integers or a valid
62 # string, only the string is checked.
64 if isinstance(requested_size, basestring):
65 m = size_pat.match(requested_size)
67 requested_size = (int(m.group(1)), int(m.group(2)))
69 raise TemplateSyntaxError("Variable '%s' was resolved but "
70 "'%s' is not a valid size." %
71 (self.size_var, requested_size))
74 if relative_source is None or requested_size is None:
79 for key, value in self.kwargs.items():
80 kwargs[key] = value.resolve(context)
81 opts = dict([(k, v and v.resolve(context))
82 for k, v in self.opts.items()])
83 thumbnail = DjangoThumbnail(relative_source, requested_size,
84 opts=opts, processors=PROCESSORS, **kwargs)
90 # Return the thumbnail class, or put it on the context
91 if self.context_name is None:
93 # We need to get here so we don't have old values in the context
95 context[self.context_name] = thumbnail
99 def thumbnail(parser, token):
101 Creates a thumbnail of for an ImageField.
103 To just output the absolute url to the thumbnail::
105 {% thumbnail image 80x80 %}
107 After the image path and dimensions, you can put any options::
109 {% thumbnail image 80x80 quality=95 crop %}
111 To put the DjangoThumbnail class on the context instead of just rendering
112 the absolute url, finish the tag with ``as [context_var_name]``::
114 {% thumbnail image 80x80 as thumb %}
115 {{ thumb.width }} x {{ thumb.height }}
117 args = token.split_contents()
119 # Check to see if we're setting to a context variable.
120 if len(args) > 4 and args[-2] == 'as':
121 context_name = args[-1]
127 raise TemplateSyntaxError("Invalid syntax. Expected "
128 "'{%% %s source size [option1 option2 ...] %%}' or "
129 "'{%% %s source size [option1 option2 ...] as variable %%}'" %
132 # Get the source image path and requested size.
133 source_var = parser.compile_filter(args[1])
134 # If the size argument was a correct static format, wrap it in quotes so
135 # that it is compiled correctly.
136 m = size_pat.match(args[2])
138 args[2] = '"%s"' % args[2]
139 size_var = parser.compile_filter(args[2])
142 args_list = split_args(args[3:]).items()
146 kwargs = {} # key,values here override settings and defaults
148 for arg, value in args_list:
149 value = value and parser.compile_filter(value)
150 if arg in TAG_SETTINGS and value is not None:
151 kwargs[str(arg)] = value
153 if arg in VALID_OPTIONS:
156 raise TemplateSyntaxError("'%s' tag received a bad argument: "
158 return ThumbnailNode(source_var, size_var, opts=opts,
159 context_name=context_name, **kwargs)
162 def filesize(bytes, format='auto1024'):
164 Returns the number of bytes in either the nearest unit or a specific unit
165 (depending on the chosen format method).
167 Acceptable formats are:
170 convert to the nearest unit, appending the abbreviated unit name to the
171 string (e.g. '2 KiB' or '2 kB').
172 auto1024 is the default format.
173 auto1024long, auto1000long
174 convert to the nearest multiple of 1024 or 1000, appending the correctly
175 pluralized unit name to the string (e.g. '2 kibibytes' or '2 kilobytes').
176 kB, MB, GB, TB, PB, EB, ZB or YB
177 convert to the exact unit (using multiples of 1000).
178 KiB, MiB, GiB, TiB, PiB, EiB, ZiB or YiB
179 convert to the exact unit (using multiples of 1024).
181 The auto1024 and auto1000 formats return a string, appending the correct
182 unit to the value. All other formats return the floating point value.
184 If an invalid format is specified, the bytes are returned unchanged.
186 format_len = len(format)
187 # Check for valid format
188 if format_len in (2, 3):
189 if format_len == 3 and format[0] == 'K':
190 format = 'k%s' % format[1:]
191 if not format[-1] == 'B' or format[0] not in filesize_formats:
193 if format_len == 3 and format[1] != 'i':
195 elif format not in ('auto1024', 'auto1000',
196 'auto1024long', 'auto1000long'):
198 # Check for valid bytes
201 except (ValueError, TypeError):
204 # Auto multiple of 1000 or 1024
205 if format.startswith('auto'):
206 if format[4:8] == '1000':
210 logarithm = bytes and math.log(bytes, base) or 0
211 index = min(int(logarithm) - 1, len(filesize_formats) - 1)
214 bytes = bytes and bytes / math.pow(1000, index + 1)
216 bytes = bytes >> (10 * (index))
217 bytes = bytes and bytes / 1024.0
218 unit = filesize_formats[index]
220 # Change the base to 1000 so the unit will just output 'B' not 'iB'
223 if bytes >= 10 or ('%.1f' % bytes).endswith('.0'):
224 bytes = '%.0f' % bytes
226 bytes = '%.1f' % bytes
227 if format.endswith('long'):
228 unit = filesize_long_formats.get(unit, '')
229 if base == 1024 and unit:
230 unit = '%sbi' % unit[:2]
231 unit = '%sbyte%s' % (unit, bytes != '1' and 's' or '')
233 unit = '%s%s' % (base == 1024 and unit.upper() or unit,
234 base == 1024 and 'iB' or 'B')
236 return '%s %s' % (bytes, unit)
240 base = filesize_formats.index(format[0]) + 1
241 # Exact multiple of 1000
243 return bytes / (1000.0 ** base)
244 # Exact multiple of 1024
245 elif format_len == 3:
246 bytes = bytes >> (10 * (base - 1))
247 return bytes / 1024.0
250 register.tag(thumbnail)
251 register.filter(filesize)