Skip to content

Instantly share code, notes, and snippets.

@zerolab
Created June 20, 2019 09:55
Show Gist options
  • Save zerolab/373732895794bb6ad8a96c61b68fb02a to your computer and use it in GitHub Desktop.
Save zerolab/373732895794bb6ad8a96c61b68fb02a to your computer and use it in GitHub Desktop.
Wagtail responsive image tag
from django import template
from wagtail.images.models import SourceImageIOError
from wagtail.images.shortcuts import get_rendition_or_not_found
from wagtail.images.templatetags.wagtailimages_tags import ImageNode
register = template.Library()
@register.tag(name="responsiveimage")
def responsiveimage(parser, token):
bits = token.split_contents()[1:]
image_var = bits[0]
filter_spec = bits[1]
bits = bits[2:]
if len(bits) == 2 and bits[0] == 'as':
# token is of the form {% responsiveimage self.photo max-320x200 as img %}
return ImageNode(image_var, filter_spec, output_var_name=bits[1])
else:
# token is of the form {% responsiveimage self.photo max-320x200 %} - all additional tokens
# should be kwargs, which become attributes
attrs = {}
for bit in bits:
try:
name, value = bit.split('=')
except ValueError:
raise template.TemplateSyntaxError(
"""'responsiveimage' tag should be of the form
{% responsiveimage self.photo max-320x200 [ custom-attr=\"value\" ... ] %} or
{% responsiveimage self.photo max-320x200 as img %}"""
)
attrs[name] = parser.compile_filter(value) # setup to resolve context variables as value
return ResponsiveImageNode(image_var, filter_spec, attrs=attrs)
class ResponsiveImageNode(template.Node):
def __init__(self, image_var_name, filter_spec, output_var_name=None, attrs={}):
self.image_var = template.Variable(image_var_name)
self.output_var_name = output_var_name
self.attrs = attrs
self.filter_spec = filter_spec
def render(self, context):
try:
image = self.image_var.resolve(context)
except template.VariableDoesNotExist:
return ''
if not image:
return ''
Rendition = image.get_rendition_model()
rendition = get_rendition_or_not_found(image, self.filter_spec)
# Parse srcset format into array
try:
raw_sources = str(self.attrs['srcset']).replace('"', '').split(',')
srcset_renditions = []
widths = []
newsrcseturls = []
for source in raw_sources:
flt = source.strip().split(' ')[0]
width = source.strip().split(' ')[1]
# cache widths to be re-appended after filter has been converted to URL
widths.append(width)
try:
srcset_renditions.append(image.get_rendition(flt))
except SourceImageIOError:
tmprend = Rendition(image=image, width=0, height=0)
tmprend.file.name = 'not-found'
for index, rend in enumerate(srcset_renditions):
newsrcseturls.append(' '.join([rend.url, widths[index]]))
except KeyError:
newsrcseturls = []
pass
if self.output_var_name:
# return the rendition object in the given variable
context[self.output_var_name] = rendition
return ''
else:
# render the rendition's image tag now
resolved_attrs = {}
for key in self.attrs:
if key == 'srcset':
resolved_attrs[key] = ','.join(newsrcseturls)
continue
resolved_attrs[key] = self.attrs[key].resolve(context)
return rendition.img_tag(resolved_attrs)
<div class="my-responsive-image">
{% comment %}
Use the responsive image tag to generate an image object with various sources.
Declare the image tag separately to strip out width and height attributes that get added by default and which aren't appropriate
for a responsive image
{% endcomment %}
{% responsiveimage value.responsiveimage width-1000 srcset="width-200 200w, width-500 500w, width-750 750w" sizes="(min-width: 1025px) 60vw, 100vw" %}
{% comment %}
{% responsiveimage value.responsiveimage width-1000 as responsive_image %}
<img src="{{responsive_image.url}}" alt="{{ responsive_image.alt }}" srcset="width-200 200w, width-500 500w, width-750 750w" sizes="(min-width: 1025px) 60vw, 100vw" />
{% endcomment %}
</div>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment