mirror of
https://github.com/Hopiu/django-imagekit.git
synced 2026-03-16 21:30:23 +00:00
Rework template tag for generators
This commit is contained in:
parent
e0567e8fa7
commit
9188499965
6 changed files with 215 additions and 110 deletions
|
|
@ -8,7 +8,7 @@ class ImageKitConf(AppConf):
|
|||
CACHE_DIR = 'CACHE/images'
|
||||
CACHE_PREFIX = 'imagekit:'
|
||||
DEFAULT_IMAGE_CACHE_STRATEGY = 'imagekit.imagecache.strategies.JustInTime'
|
||||
DEFAULT_FILE_STORAGE = None # Indicates that the source storage should be used
|
||||
DEFAULT_FILE_STORAGE = 'django.core.files.storage.FileSystemStorage'
|
||||
|
||||
def configure_cache_backend(self, value):
|
||||
if value is None:
|
||||
|
|
|
|||
|
|
@ -95,7 +95,7 @@ class GeneratedImageCacheFile(BaseIKFile, ImageFile):
|
|||
self.args = args
|
||||
self.kwargs = kwargs
|
||||
storage = getattr(generator, 'storage', None)
|
||||
if not storage and settings.IMAGEKIT_DEFAULT_FILE_STORAGE:
|
||||
if not storage:
|
||||
storage = get_singleton(settings.IMAGEKIT_DEFAULT_FILE_STORAGE,
|
||||
'file storage backend')
|
||||
super(GeneratedImageCacheFile, self).__init__(storage=storage)
|
||||
|
|
|
|||
|
|
@ -132,7 +132,7 @@ class Register(object):
|
|||
|
||||
"""
|
||||
def spec(self, id, spec):
|
||||
generator_registry.register(id, spec)
|
||||
generator_registry.register(spec, id)
|
||||
|
||||
def sources(self, spec_id, sources):
|
||||
source_group_registry.register(spec_id, sources)
|
||||
|
|
|
|||
160
imagekit/templatetags/compat.py
Normal file
160
imagekit/templatetags/compat.py
Normal file
|
|
@ -0,0 +1,160 @@
|
|||
"""
|
||||
This module contains code from django.template.base
|
||||
(sha 90d3af380e8efec0301dd91600c6686232de3943). Bundling this code allows us to
|
||||
support older versions of Django that did not contain it (< 1.4).
|
||||
|
||||
|
||||
Copyright (c) Django Software Foundation and individual contributors.
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice,
|
||||
this list of conditions and the following disclaimer.
|
||||
|
||||
2. 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.
|
||||
|
||||
3. Neither the name of Django 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 COPYRIGHT HOLDERS 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 COPYRIGHT OWNER OR 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.
|
||||
|
||||
"""
|
||||
|
||||
from django.template import TemplateSyntaxError
|
||||
import re
|
||||
|
||||
|
||||
# Regex for token keyword arguments
|
||||
kwarg_re = re.compile(r"(?:(\w+)=)?(.+)")
|
||||
|
||||
|
||||
def token_kwargs(bits, parser, support_legacy=False):
|
||||
"""
|
||||
A utility method for parsing token keyword arguments.
|
||||
|
||||
:param bits: A list containing remainder of the token (split by spaces)
|
||||
that is to be checked for arguments. Valid arguments will be removed
|
||||
from this list.
|
||||
|
||||
:param support_legacy: If set to true ``True``, the legacy format
|
||||
``1 as foo`` will be accepted. Otherwise, only the standard ``foo=1``
|
||||
format is allowed.
|
||||
|
||||
:returns: A dictionary of the arguments retrieved from the ``bits`` token
|
||||
list.
|
||||
|
||||
There is no requirement for all remaining token ``bits`` to be keyword
|
||||
arguments, so the dictionary will be returned as soon as an invalid
|
||||
argument format is reached.
|
||||
"""
|
||||
if not bits:
|
||||
return {}
|
||||
match = kwarg_re.match(bits[0])
|
||||
kwarg_format = match and match.group(1)
|
||||
if not kwarg_format:
|
||||
if not support_legacy:
|
||||
return {}
|
||||
if len(bits) < 3 or bits[1] != 'as':
|
||||
return {}
|
||||
|
||||
kwargs = {}
|
||||
while bits:
|
||||
if kwarg_format:
|
||||
match = kwarg_re.match(bits[0])
|
||||
if not match or not match.group(1):
|
||||
return kwargs
|
||||
key, value = match.groups()
|
||||
del bits[:1]
|
||||
else:
|
||||
if len(bits) < 3 or bits[1] != 'as':
|
||||
return kwargs
|
||||
key, value = bits[2], bits[0]
|
||||
del bits[:3]
|
||||
kwargs[key] = parser.compile_filter(value)
|
||||
if bits and not kwarg_format:
|
||||
if bits[0] != 'and':
|
||||
return kwargs
|
||||
del bits[:1]
|
||||
return kwargs
|
||||
|
||||
|
||||
def parse_bits(parser, bits, params, varargs, varkw, defaults,
|
||||
takes_context, name):
|
||||
"""
|
||||
Parses bits for template tag helpers (simple_tag, include_tag and
|
||||
assignment_tag), in particular by detecting syntax errors and by
|
||||
extracting positional and keyword arguments.
|
||||
"""
|
||||
if takes_context:
|
||||
if params[0] == 'context':
|
||||
params = params[1:]
|
||||
else:
|
||||
raise TemplateSyntaxError(
|
||||
"'%s' is decorated with takes_context=True so it must "
|
||||
"have a first argument of 'context'" % name)
|
||||
args = []
|
||||
kwargs = {}
|
||||
unhandled_params = list(params)
|
||||
for bit in bits:
|
||||
# First we try to extract a potential kwarg from the bit
|
||||
kwarg = token_kwargs([bit], parser)
|
||||
if kwarg:
|
||||
# The kwarg was successfully extracted
|
||||
param, value = list(kwarg.items())[0]
|
||||
if param not in params and varkw is None:
|
||||
# An unexpected keyword argument was supplied
|
||||
raise TemplateSyntaxError(
|
||||
"'%s' received unexpected keyword argument '%s'" %
|
||||
(name, param))
|
||||
elif param in kwargs:
|
||||
# The keyword argument has already been supplied once
|
||||
raise TemplateSyntaxError(
|
||||
"'%s' received multiple values for keyword argument '%s'" %
|
||||
(name, param))
|
||||
else:
|
||||
# All good, record the keyword argument
|
||||
kwargs[str(param)] = value
|
||||
if param in unhandled_params:
|
||||
# If using the keyword syntax for a positional arg, then
|
||||
# consume it.
|
||||
unhandled_params.remove(param)
|
||||
else:
|
||||
if kwargs:
|
||||
raise TemplateSyntaxError(
|
||||
"'%s' received some positional argument(s) after some "
|
||||
"keyword argument(s)" % name)
|
||||
else:
|
||||
# Record the positional argument
|
||||
args.append(parser.compile_filter(bit))
|
||||
try:
|
||||
# Consume from the list of expected positional arguments
|
||||
unhandled_params.pop(0)
|
||||
except IndexError:
|
||||
if varargs is None:
|
||||
raise TemplateSyntaxError(
|
||||
"'%s' received too many positional arguments" %
|
||||
name)
|
||||
if defaults is not None:
|
||||
# Consider the last n params handled, where n is the
|
||||
# number of defaults.
|
||||
unhandled_params = unhandled_params[:-len(defaults)]
|
||||
if unhandled_params:
|
||||
# Some positional arguments were not supplied
|
||||
raise TemplateSyntaxError(
|
||||
"'%s' did not receive value(s) for the argument(s): %s" %
|
||||
(name, ", ".join(["'%s'" % p for p in unhandled_params])))
|
||||
return args, kwargs
|
||||
|
|
@ -1,134 +1,79 @@
|
|||
from django import template
|
||||
from django.utils.safestring import mark_safe
|
||||
import re
|
||||
from ..files import ImageSpecCacheFile
|
||||
from .compat import parse_bits
|
||||
from ..files import GeneratedImageCacheFile
|
||||
from ..registry import generator_registry
|
||||
|
||||
|
||||
register = template.Library()
|
||||
|
||||
|
||||
html_attr_pattern = r"""
|
||||
(?P<name>\w+) # The attribute name
|
||||
(
|
||||
\s*=\s* # an equals sign, that may or may not have spaces around it
|
||||
(?P<value>
|
||||
("[^"]*") # a double-quoted value
|
||||
| # or
|
||||
('[^']*') # a single-quoted value
|
||||
| # or
|
||||
([^"'<>=\s]+) # an unquoted value
|
||||
)
|
||||
)?
|
||||
"""
|
||||
class GenerateImageAssignmentNode(template.Node):
|
||||
_kwarg_map = {
|
||||
'from': 'source_file',
|
||||
}
|
||||
|
||||
html_attr_re = re.compile(html_attr_pattern, re.VERBOSE)
|
||||
|
||||
|
||||
class SpecResultNodeMixin(object):
|
||||
def __init__(self, spec_id, source_file):
|
||||
self._spec_id = spec_id
|
||||
self._source_file = source_file
|
||||
|
||||
def get_spec(self, context):
|
||||
from ..utils import autodiscover
|
||||
autodiscover()
|
||||
spec_id = self._spec_id.resolve(context)
|
||||
spec = generator_registry.get(spec_id) # TODO: What "hints" here?
|
||||
return spec
|
||||
|
||||
def get_source_file(self, context):
|
||||
return self._source_file.resolve(context)
|
||||
|
||||
def get_file(self, context):
|
||||
spec = self.get_spec(context)
|
||||
source_file = self.get_source_file(context)
|
||||
return ImageSpecCacheFile(spec, source_file)
|
||||
|
||||
|
||||
class SpecResultAssignmentNode(template.Node, SpecResultNodeMixin):
|
||||
def __init__(self, spec_id, source_file, variable_name):
|
||||
super(SpecResultAssignmentNode, self).__init__(spec_id, source_file)
|
||||
def __init__(self, variable_name, generator_id, **kwargs):
|
||||
self.generator_id = generator_id
|
||||
self.kwargs = kwargs
|
||||
self._variable_name = variable_name
|
||||
|
||||
def get_variable_name(self, context):
|
||||
return unicode(self._variable_name)
|
||||
|
||||
def get_kwargs(self, context):
|
||||
return dict((self._kwarg_map.get(k, k), v.resolve(context)) for k,
|
||||
v in self.kwargs.items())
|
||||
|
||||
def render(self, context):
|
||||
from ..utils import autodiscover
|
||||
autodiscover()
|
||||
|
||||
variable_name = self.get_variable_name(context)
|
||||
context[variable_name] = self.get_file(context)
|
||||
generator_id = self.generator_id.resolve(context)
|
||||
kwargs = self.get_kwargs(context)
|
||||
generator = generator_registry.get(generator_id)
|
||||
context[variable_name] = GeneratedImageCacheFile(generator, **kwargs)
|
||||
return ''
|
||||
|
||||
|
||||
class SpecResultImgTagNode(template.Node, SpecResultNodeMixin):
|
||||
def __init__(self, spec_id, source_file, html_attrs):
|
||||
super(SpecResultImgTagNode, self).__init__(spec_id, source_file)
|
||||
self._html_attrs = html_attrs
|
||||
|
||||
def get_html_attrs(self, context):
|
||||
attrs = []
|
||||
for attr in self._html_attrs:
|
||||
match = html_attr_re.search(attr)
|
||||
if match:
|
||||
attrs.append((match.group('name'), match.group('value')))
|
||||
return attrs
|
||||
|
||||
def get_attr_str(self, k, v):
|
||||
return k if v is None else '%s=%s' % (k, v)
|
||||
|
||||
def render(self, context):
|
||||
file = self.get_file(context)
|
||||
attrs = self.get_html_attrs(context)
|
||||
attr_dict = dict(attrs)
|
||||
if not 'width' in attr_dict and not 'height' in attr_dict:
|
||||
attrs = attrs + [('width', '"%s"' % file.width),
|
||||
('height', '"%s"' % file.height)]
|
||||
attrs = [('src', '"%s"' % file.url)] + attrs
|
||||
attr_str = ' '.join(self.get_attr_str(k, v) for k, v in attrs)
|
||||
return mark_safe(u'<img %s />' % attr_str)
|
||||
|
||||
|
||||
#@register.tag
|
||||
# TODO: Should this be renamed to something like 'process'?
|
||||
def spec(parser, token):
|
||||
def generateimage(parser, token):
|
||||
"""
|
||||
Creates an image based on the provided spec and source image.
|
||||
|
||||
By default::
|
||||
|
||||
{% spec 'myapp:thumbnail' mymodel.profile_image %}
|
||||
|
||||
Generates an ``<img>``::
|
||||
|
||||
<img src="/cache/34d944f200dd794bf1e6a7f37849f72b.jpg" />
|
||||
|
||||
Storing it as a context variable allows more flexibility::
|
||||
|
||||
{% spec 'myapp:thumbnail' mymodel.profile_image as th %}
|
||||
<img src="{{ th.url }}" width="{{ th.width }}" height="{{ th.height }}" />
|
||||
Creates an image based on the provided arguments.
|
||||
|
||||
"""
|
||||
|
||||
bits = token.split_contents()
|
||||
tag_name = bits.pop(0) # noqa
|
||||
tag_name = bits.pop(0)
|
||||
|
||||
if len(bits) == 4 and bits[2] == 'as':
|
||||
return SpecResultAssignmentNode(
|
||||
parser.compile_filter(bits[0]), # spec id
|
||||
parser.compile_filter(bits[1]), # source file
|
||||
parser.compile_filter(bits[3]), # var name
|
||||
)
|
||||
elif len(bits) > 1:
|
||||
return SpecResultImgTagNode(
|
||||
parser.compile_filter(bits[0]), # spec id
|
||||
parser.compile_filter(bits[1]), # source file
|
||||
bits[2:], # html attributes
|
||||
)
|
||||
if bits[-2] == 'as':
|
||||
varname = bits[-1]
|
||||
bits = bits[:-2]
|
||||
|
||||
# (params, varargs, varkwargs, defaults) = getargspec(g)
|
||||
# (params, 'args', 'kwargs', defaults) = getargspec(g)
|
||||
|
||||
args, kwargs = parse_bits(parser, bits,
|
||||
['generator_id'], 'args', 'kwargs', None,
|
||||
False, tag_name)
|
||||
if len(args) != 1:
|
||||
raise template.TemplateSyntaxError("The 'generateimage' tag "
|
||||
' requires exactly one unnamed argument.')
|
||||
generator_id = args[0]
|
||||
return GenerateImageAssignmentNode(varname, generator_id, **kwargs)
|
||||
else:
|
||||
raise template.TemplateSyntaxError('\'spec\' tags must be in the form'
|
||||
' "{% spec spec_id image %}" or'
|
||||
' "{% spec spec_id image'
|
||||
' as varname %}"')
|
||||
raise Exception('!!')
|
||||
# elif len(bits) > 1:
|
||||
# return GenerateImageTagNode(
|
||||
# parser.compile_filter(bits[0]), # spec id
|
||||
# parser.compile_filter(bits[1]), # source file
|
||||
# bits[2:], # html attributes
|
||||
# )
|
||||
# else:
|
||||
# raise template.TemplateSyntaxError('\'generateimage\' tags must be in the form'
|
||||
# ' "{% generateimage id image %}" or'
|
||||
# ' "{% generateimage id image'
|
||||
# ' as varname %}"')
|
||||
|
||||
|
||||
spec = spec_tag = register.tag(spec)
|
||||
generateimage = register.tag(generateimage)
|
||||
|
|
|
|||
|
|
@ -371,7 +371,7 @@ def autodiscover():
|
|||
import_module('%s.imagespecs' % app)
|
||||
except:
|
||||
# Decide whether to bubble up this error. If the app just
|
||||
# doesn't have an admin module, we can ignore the error
|
||||
# doesn't have an imagespecs module, we can ignore the error
|
||||
# attempting to import it, otherwise we want it to bubble up.
|
||||
if module_has_submodule(mod, 'imagespecs'):
|
||||
raise
|
||||
|
|
|
|||
Loading…
Reference in a new issue