catch errors on non existing images

pull/2044/head
shredding 2015-10-23 13:11:44 +02:00 zatwierdzone przez Matt Westcott
rodzic ab45915a90
commit 2dd3f42e25
10 zmienionych plików z 132 dodań i 37 usunięć

Wyświetl plik

@ -38,6 +38,7 @@ Changelog
* Fix: `MenuItem` `url` parameter can now take a lazy URL (Adon Metcalfe, rayrayndwiga) * Fix: `MenuItem` `url` parameter can now take a lazy URL (Adon Metcalfe, rayrayndwiga)
* Fix: Added missing translation tag to InlinePanel 'Add' button (jnns) * Fix: Added missing translation tag to InlinePanel 'Add' button (jnns)
* Fix: Restored correct highlighting behaviour of rich text toolbar buttons * Fix: Restored correct highlighting behaviour of rich text toolbar buttons
* Fix: Rendering a missing image through ImageChooserBlock no longer breaks the whole page (Christian Peters)
1.2 (12.11.2015) 1.2 (12.11.2015)
~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~

Wyświetl plik

@ -88,6 +88,7 @@ Bug fixes
* ``MenuItem`` ``url`` parameter can now take a lazy URL (Adon Metcalfe, rayrayndwiga) * ``MenuItem`` ``url`` parameter can now take a lazy URL (Adon Metcalfe, rayrayndwiga)
* Added missing translation tag to InlinePanel 'Add' button (jnns) * Added missing translation tag to InlinePanel 'Add' button (jnns)
* Restored correct highlighting behaviour of rich text toolbar buttons * Restored correct highlighting behaviour of rich text toolbar buttons
* Rendering a missing image through ImageChooserBlock no longer breaks the whole page (Christian Peters)
Upgrade considerations Upgrade considerations

Wyświetl plik

@ -1,6 +1,7 @@
from django.utils.functional import cached_property from django.utils.functional import cached_property
from wagtail.wagtailcore.blocks import ChooserBlock from wagtail.wagtailcore.blocks import ChooserBlock
from .shortcuts import get_rendition_or_not_found
class ImageChooserBlock(ChooserBlock): class ImageChooserBlock(ChooserBlock):
@ -16,6 +17,6 @@ class ImageChooserBlock(ChooserBlock):
def render_basic(self, value): def render_basic(self, value):
if value: if value:
return value.get_rendition('original').img_tag() return get_rendition_or_not_found(value, 'original').img_tag()
else: else:
return '' return ''

Wyświetl plik

@ -1,7 +1,7 @@
from django.utils.html import escape from django.utils.html import escape
from wagtail.utils.apps import get_app_submodules from wagtail.utils.apps import get_app_submodules
from wagtail.wagtailimages.models import SourceImageIOError from .shortcuts import get_rendition_or_not_found
class Format(object): class Format(object):
@ -26,15 +26,7 @@ class Format(object):
) )
def image_to_html(self, image, alt_text, extra_attributes=''): def image_to_html(self, image, alt_text, extra_attributes=''):
try: rendition = get_rendition_or_not_found(image, self.filter_spec)
rendition = image.get_rendition(self.filter_spec)
except SourceImageIOError:
# Image file is (probably) missing from /media/original_images - generate a dummy
# rendition so that we just output a broken image, rather than crashing out completely
# during rendering
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'
if self.classnames: if self.classnames:
class_attr = 'class="%s" ' % escape(self.classnames) class_attr = 'class="%s" ' % escape(self.classnames)

Wyświetl plik

@ -2,25 +2,14 @@ from __future__ import absolute_import
from jinja2.ext import Extension from jinja2.ext import Extension
from wagtail.wagtailimages.models import SourceImageIOError from .shortcuts import get_rendition_or_not_found
def image(image, filterspec, **attrs): def image(image, filterspec, **attrs):
if not image: if not image:
return '' return ''
try: rendition = get_rendition_or_not_found(image, filterspec)
rendition = image.get_rendition(filterspec)
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'
if attrs: if attrs:
return rendition.img_tag(attrs) return rendition.img_tag(attrs)

Wyświetl plik

@ -0,0 +1,22 @@
# coding=utf-8
from wagtail.wagtailimages.models import SourceImageIOError
def get_rendition_or_not_found(image, specs):
"""
Tries to get / create the rendition for the image or renders a not-found image if it does not exist.
:param image: AbstractImage
:param specs: str or Filter
:return: Rendition
"""
try:
return image.get_rendition(specs)
except SourceImageIOError:
# Image file is (probably) missing from /media/original_images - generate a dummy
# rendition so that we just output a broken image, rather than crashing out completely
# during rendering.
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'
return rendition

Wyświetl plik

@ -1,7 +1,8 @@
from django import template from django import template
from django.utils.functional import cached_property from django.utils.functional import cached_property
from wagtail.wagtailimages.models import Filter, SourceImageIOError from wagtail.wagtailimages.models import Filter
from wagtail.wagtailimages.shortcuts import get_rendition_or_not_found
register = template.Library() register = template.Library()
@ -54,18 +55,7 @@ class ImageNode(template.Node):
if not image: if not image:
return '' return ''
try: rendition = get_rendition_or_not_found(image, self.filter)
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'
if self.output_var_name: if self.output_var_name:
# return the rendition object in the given variable # return the rendition object in the given variable

Wyświetl plik

@ -0,0 +1,57 @@
# -*- coding: utf-8 -*
from __future__ import unicode_literals
import os
from django.test import TestCase
from django.core import serializers
from django.conf import settings
from wagtail.wagtailimages.blocks import ImageChooserBlock
from .utils import get_test_image_file, Image
class TestImageChooserBlock(TestCase):
def setUp(self):
self.image = Image.objects.create(
title="Test image",
file=get_test_image_file(),
)
# Create an image with a missing file, by deserializing fom a python object
# (which bypasses FileField's attempt to read the file)
self.bad_image = list(serializers.deserialize('python', [{
'fields': {
'title': 'missing image',
'height': 100,
'file': 'original_images/missing-image.jpg',
'width': 100,
},
'model': 'wagtailimages.image'
}]))[0].object
self.bad_image.save()
def get_image_filename(self, image, filterspec):
"""
Get the generated filename for a resized image
"""
name, ext = os.path.splitext(os.path.basename(image.file.name))
return '{}images/{}.{}{}'.format(
settings.MEDIA_URL, name, filterspec, ext)
def test_render(self):
block = ImageChooserBlock()
html = block.render(self.image)
expected_html = '<img alt="Test image" src="{}" width="640" height="480">'.format(
self.get_image_filename(self.image, "original")
)
self.assertHTMLEqual(html, expected_html)
def test_render_missing(self):
block = ImageChooserBlock()
html = block.render(self.bad_image)
expected_html = '<img alt="missing image" src="/media/not-found" width="0" height="0">'
self.assertHTMLEqual(html, expected_html)

Wyświetl plik

@ -5,6 +5,7 @@ import unittest
import django import django
from django.conf import settings from django.conf import settings
from django.core import serializers
from django.test import TestCase from django.test import TestCase
from wagtail.wagtailcore.models import Site from wagtail.wagtailcore.models import Site
@ -25,6 +26,19 @@ class TestImagesJinja(TestCase):
file=get_test_image_file(), file=get_test_image_file(),
) )
# Create an image with a missing file, by deserializing fom a python object
# (which bypasses FileField's attempt to read the file)
self.bad_image = list(serializers.deserialize('python', [{
'fields': {
'title': 'missing image',
'height': 100,
'file': 'original_images/missing-image.jpg',
'width': 100,
},
'model': 'wagtailimages.image'
}]))[0].object
self.bad_image.save()
def render(self, string, context=None, request_context=True): def render(self, string, context=None, request_context=True):
if context is None: if context is None:
context = {} context = {}
@ -64,3 +78,9 @@ class TestImagesJinja(TestCase):
'width: {{ background.width }}, url: {{ background.url }}') 'width: {{ background.width }}, url: {{ background.url }}')
output = ('width: 200, url: ' + self.get_image_filename(self.image, "width-200")) output = ('width: 200, url: ' + self.get_image_filename(self.image, "width-200"))
self.assertHTMLEqual(self.render(template, {'myimage': self.image}), output) self.assertHTMLEqual(self.render(template, {'myimage': self.image}), output)
def test_missing_image(self):
self.assertHTMLEqual(
self.render('{{ image(myimage, "width-200") }}', {'myimage': self.bad_image}),
'<img alt="missing image" src="/media/not-found" width="0" height="0">'
)

Wyświetl plik

@ -0,0 +1,22 @@
# coding=utf-8
from django.test import TestCase
from wagtail.wagtailimages.shortcuts import get_rendition_or_not_found
from .utils import Image, get_test_image_file
class TestShortcuts(TestCase):
fixtures = ['test.json']
def test_fallback_to_not_found(self):
bad_image = Image.objects.get(id=1)
good_image = Image.objects.create(
title="Test image",
file=get_test_image_file(),
)
rendition = get_rendition_or_not_found(good_image, 'width-400')
self.assertEqual(rendition.width, 400)
rendition = get_rendition_or_not_found(bad_image, 'width-400')
self.assertEqual(rendition.file.name, 'not-found')