-
-
Save davecranwell/baf42b089d71b94fd229 to your computer and use it in GitHub Desktop.
from django import template | |
from django.template import Context | |
from django.template.base import parse_bits | |
from wagtail.wagtailimages.templatetags.wagtailimages_tags import ImageNode | |
from wagtail.wagtailimages.models import Filter, SourceImageIOError, InvalidFilterSpecError | |
from britishswimming.utils.models import SocialMediaSettings | |
register = template.Library() | |
@register.tag(name="responsiveimage") | |
def responsiveimage(parser, token): | |
bits = token.split_contents()[1:] | |
image_expr = parser.compile_filter(bits[0]) | |
filter_spec = bits[1] | |
remaining_bits = bits[2:] | |
attrs = {} | |
if remaining_bits[-2:][0] == 'as': | |
for bit in remaining_bits[:-2]: | |
try: | |
name, value = bit.split('=') | |
except ValueError: | |
raise template.TemplateSyntaxError("'responsiveimage' tag should be of the form {% responsiveimage self.photo max-320x200 srcset=\"whatever\" [ custom-attr=\"value\" ... ] %} or {% responsiveimage self.photo max-320x200 srcset=\"whatever\" as img %}") | |
attrs[name] = parser.compile_filter(value) # setup to resolve context variables as value | |
# token is of the form {% responsiveimage self.photo max-320x200 srcset="whatever" as img %} | |
return ResponsiveImageNode(image_expr, filter_spec, attrs=attrs, output_var_name=remaining_bits[-2:][1]) | |
else: | |
# token is of the form {% responsiveimage self.photo max-320x200 srcset="whatever" %} - all additional tokens | |
# should be kwargs, which become attributes | |
for bit in remaining_bits: | |
try: | |
name, value = bit.split('=') | |
except ValueError: | |
raise template.TemplateSyntaxError("'responsiveimage' tag should be of the form {% responsiveimage self.photo max-320x200 srcset=\"whatever\" [ custom-attr=\"value\" ... ] %} or {% responsiveimage self.photo max-320x200 srcset=\"whatever\" as img %}") | |
attrs[name] = parser.compile_filter(value) # setup to resolve context variables as value | |
return ResponsiveImageNode(image_expr, filter_spec, attrs=attrs) | |
class ResponsiveImageNode(ImageNode, template.Node): | |
def render(self, context): | |
try: | |
image = self.image_expr.resolve(context) | |
except template.VariableDoesNotExist: | |
return '' | |
if not image: | |
return '' | |
try: | |
rendition = image.get_rendition(self.filter) | |
except SourceImageIOError: | |
# It's fairly routine for people to pull down remote databases to their | |
# local dev versions without retrieving the corresponding image files. | |
# In such a case, we would get a SourceImageIOError at the point where we try to | |
# create the resized version of a non-existent image. Since this is a | |
# bit catastrophic for a missing image, we'll substitute a dummy | |
# Rendition object so that we just output a broken link instead. | |
Rendition = image.renditions.model # pick up any custom Image / Rendition classes that may be in use | |
rendition = Rendition(image=image, width=0, height=0) | |
rendition.file.name = 'not-found' | |
# 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: | |
TmpRendition = image.renditions.model # pick up any custom Image / Rendition classes that may be in use | |
tmprend = TmpRendition(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: | |
rendition.srcset = ', '.join(newsrcseturls) | |
# 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) |
An example of what the string value for srcset
is supposed to look like would be nice ("whatever" is not very informative). Reading the code makes me think it'd be something like srcset=\"width-400 400w, width-600 600w\"
, but I'm not really sure.
Further experimentation led me to discover that the following working example:
{% responsiveimage self.photo width-400 srcset="width-700 700w, width-1200 1200w" sizes="100vw" %}
The width-400 image will be used as the fallback for unsupported browsers. In supported browsers, all three (400, 700, and 1200) will be chosen by the browser as whatever "fits best" within the viewport.
I'm loving this tag, so I went in and made a few improvements. I got assigning srcset
and sizes
as variables in the context working, and did some significant code cleanup. I'm not sure the "as varname" functionality is working, though I'm not sure why (or if it actually worked before I edited this).
I made a fork, but there doesn't seem to be any way to do a pull request on a gist. What do you think of my fork?
@danreeves correct, updated.