"""
Much of this module was taken/inspired from sorl-thumbnails, by mikko and
smileychris. In simple cases, we retain compatibility with sorl-thumbnails, but
we don't generate thumbs on the fly (they are generated at the time of upload,
and only the specified sizes).
Sources from sorl-thumbnails in this module are Copyright (c) 2007,
Mikko Hellsing, Chris Beaven.
Modifications and new ideas, Copyright (c) 2010, DUO Interactive, LLC.
"""
import re
import math
from django.template import Library, Node, Variable, VariableDoesNotExist, TemplateSyntaxError
from django.conf import settings
from django.utils.encoding import force_unicode
register = Library()
# Various regular expressions compiled here to avoid having to compile them
# repeatedly.
REGEXP_THUMB_SIZES = re.compile(r'(\d+)x(\d+)$')
REGEXP_ARGS = re.compile('(?<!quality)=')
# List of valid keys for key=value tag arguments.
TAG_SETTINGS = ['force_ssl']
def split_args(args):
"""
Split a list of argument strings into a dictionary where each key is an
argument name.
An argument looks like ``force_ssl=True``.
"""
if not args:
return {}
# Handle the old comma separated argument format.
if len(args) == 1 and not REGEXP_ARGS.search(args[0]):
args = args[0].split(',')
# Separate out the key and value for each argument.
args_dict = {}
for arg in args:
split_arg = arg.split('=', 1)
value = len(split_arg) > 1 and split_arg[1] or None
args_dict[split_arg[0]] = value
return args_dict
class ThumbnailNode(Node):
"""
Handles the rendering of a thumbnail URL, based on the input gathered
from the thumbnail() tag function.
"""
def __init__(self, source_var, thumb_name_var, opts=None,
context_name=None, **kwargs):
# Name of the object/attribute pair, ie: some_obj.image
self.source_var = source_var
# Typically a string, '85x85'.
self.thumb_name_var = thumb_name_var
# If an 'as some_var' is given, this is the context variable name
# to store the URL in instead of returning it for rendering.
self.context_name = context_name
# Storage for optional keyword args processed by the tag parser.
self.kwargs = kwargs
def render(self, context):
try:
# This evaluates to a ImageWithThumbsField, as long as the
# user specified a valid model field.
relative_source = Variable(self.source_var).resolve(context)
except VariableDoesNotExist:
if settings.TEMPLATE_DEBUG:
raise VariableDoesNotExist("Variable '%s' does not exist." %
self.source_var)
else:
relative_source = None
try:
requested_name = Variable(self.thumb_name_var).resolve(context)
except VariableDoesNotExist:
if settings.TEMPLATE_DEBUG:
raise TemplateSyntaxError("Name argument '%s' is not a valid thumbnail." % self.thumb_name_var)
else:
requested_name = None
if relative_source is None or requested_name is None:
# Couldn't resolve the given template variable. Fail silently.
thumbnail = ''
else:
# Spaces at the end of sizes is just not OK.
requested_name = requested_name.strip()
# This is typically a athumb.fields.ImageWithThumbsFieldFile object.
try:
# Allow the user to override the protocol in the tag.
force_ssl = self.kwargs.get('force_ssl', False)
# Try to detect SSL mode in the request context. Front-facing
# server or proxy must be passing the correct headers for
# this to work. Also, factor in force_ssl.
ssl_mode = self.is_secure(context) or force_ssl
# Get the URL for the thumbnail from the
# ImageWithThumbsFieldFile object.
try:
thumbnail = relative_source.generate_url(requested_name,
ssl_mode=ssl_mode)
except:
#import traceback
#traceback.print_stack()
print "ERROR: Using {% thumbnail %} tag with "\
"a regular ImageField instead of ImageWithThumbsField:", self.source_var
return ''
except ValueError:
# This file object doesn't actually have a file. Probably
# model field with a None value.
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 is_secure(self, context):
"""
Looks at the RequestContext object and determines if this page is
secured with SSL. Linking unencrypted media on an encrypted page will
show a warning icon on some browsers. We need to be able to serve from
an encrypted source for encrypted pages, if our backend supports it.
'django.core.context_processors.request' must be added to
TEMPLATE_CONTEXT_PROCESSORS in settings.py.
"""
return 'request' in context and context['request'].is_secure()
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 force_ssl=True %}
To put the thumbnail URL on the context instead of just rendering
it, finish the tag with ``as [context_var_name]``::
{% thumbnail image 80x80 as thumb %}
<img src="{{thumb}}" />
"""
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 = args[1]
# If the size argument was a correct static format, wrap it in quotes so
# that it is compiled correctly.
m = REGEXP_THUMB_SIZES.match(args[2])
if m:
args[2] = '"%s"' % args[2]
size_var = 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
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)
register.tag(thumbnail)