diff --git a/wagtail/tests/fixtures/test.json b/wagtail/tests/fixtures/test.json
index d16affe480..de8b1e27e8 100644
--- a/wagtail/tests/fixtures/test.json
+++ b/wagtail/tests/fixtures/test.json
@@ -81,7 +81,8 @@
"audience": "public",
"location": "The North Pole",
"body": "
Chestnuts roasting on an open fire
",
- "cost": "Free"
+ "cost": "Free",
+ "feed_image": 1
}
},
@@ -223,7 +224,7 @@
"date_from": "2015-04-22",
"audience": "public",
"location": "Ameristralia",
- "body": "come celebrate the independence of Ameristralia
",
+ "body": "come celebrate the independence of Ameristralia
",
"cost": "Free"
}
},
@@ -625,5 +626,16 @@
"page": 11,
"password": "swordfish"
}
+},
+{
+ "pk": 1,
+ "model": "wagtailimages.image",
+ "fields": {
+ "title": "A missing image",
+ "file": "original_images/missing.jpg",
+ "width": 1000,
+ "height": 1000,
+ "created_at": "2014-01-01T12:00:00.000Z"
+ }
}
]
diff --git a/wagtail/tests/templates/tests/event_page.html b/wagtail/tests/templates/tests/event_page.html
index 795b7bf0a7..a3ccaa7181 100644
--- a/wagtail/tests/templates/tests/event_page.html
+++ b/wagtail/tests/templates/tests/event_page.html
@@ -1,4 +1,4 @@
-{% load wagtailcore_tags %}
+{% load wagtailcore_tags wagtailimages_tags %}
@@ -8,6 +8,10 @@
{{ self.title }}
Event
+ {% if self.feed_image %}
+ {% image self.feed_image width-200 class="feed-image" %}
+ {% endif %}
+ {{ self.body|richtext }}
Back to events index
diff --git a/wagtail/wagtailimages/formats.py b/wagtail/wagtailimages/formats.py
index 3c5644a078..1ad99e1188 100644
--- a/wagtail/wagtailimages/formats.py
+++ b/wagtail/wagtailimages/formats.py
@@ -1,6 +1,7 @@
from django.utils.html import escape
from wagtail.utils.apps import get_app_submodules
+from wagtail.wagtailimages.models import SourceImageIOError
class Format(object):
@@ -25,7 +26,15 @@ class Format(object):
)
def image_to_html(self, image, alt_text, extra_attributes=''):
- rendition = image.get_rendition(self.filter_spec)
+ try:
+ 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:
class_attr = 'class="%s" ' % escape(self.classnames)
diff --git a/wagtail/wagtailimages/models.py b/wagtail/wagtailimages/models.py
index 163dfd6767..9e19a03f46 100644
--- a/wagtail/wagtailimages/models.py
+++ b/wagtail/wagtailimages/models.py
@@ -1,7 +1,7 @@
import os.path
import re
-from six import BytesIO
+from six import BytesIO, text_type
from taggit.managers import TaggableManager
@@ -28,6 +28,13 @@ from wagtail.wagtailimages.rect import Rect
from wagtail.wagtailadmin.utils import get_object_usage
+class SourceImageIOError(IOError):
+ """
+ Custom exception to distinguish IOErrors that were thrown while opening the source image
+ """
+ pass
+
+
def get_upload_to(instance, filename):
folder_name = 'original_images'
filename = instance.file.field.storage.get_valid_name(filename)
@@ -178,7 +185,15 @@ class AbstractImage(models.Model, TagSearchable):
# If we have a backend attribute then pass it to process
# image - else pass 'default'
backend_name = getattr(self, 'backend', 'default')
- generated_image = filter.process_image(file_field.file, backend_name=backend_name, focal_point=self.get_focal_point())
+
+ try:
+ image_file = file_field.file # triggers a call to self.storage.open, so IOErrors from missing files will be raised at this point
+ except IOError as e:
+ # re-throw this as a SourceImageIOError so that calling code can distinguish
+ # these from IOErrors elsewhere in the process
+ raise SourceImageIOError(text_type(e))
+
+ generated_image = filter.process_image(image_file, backend_name=backend_name, focal_point=self.get_focal_point())
# generate new filename derived from old one, inserting the filter spec and focal point key before the extension
if self.has_focal_point():
diff --git a/wagtail/wagtailimages/templatetags/wagtailimages_tags.py b/wagtail/wagtailimages/templatetags/wagtailimages_tags.py
index 5c72734177..c8f609c6b9 100644
--- a/wagtail/wagtailimages/templatetags/wagtailimages_tags.py
+++ b/wagtail/wagtailimages/templatetags/wagtailimages_tags.py
@@ -1,6 +1,6 @@
from django import template
-from wagtail.wagtailimages.models import Filter
+from wagtail.wagtailimages.models import Filter, SourceImageIOError
register = template.Library()
@@ -53,10 +53,10 @@ class ImageNode(template.Node):
try:
rendition = image.get_rendition(self.filter)
- except IOError:
+ 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 an IOError at the point where we try to
+ # 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.
diff --git a/wagtail/wagtailimages/tests/tests.py b/wagtail/wagtailimages/tests/tests.py
index 1030a3b7ef..6df1dba56b 100644
--- a/wagtail/wagtailimages/tests/tests.py
+++ b/wagtail/wagtailimages/tests/tests.py
@@ -63,6 +63,25 @@ class TestImageTag(TestCase):
self.assertTrue('title="my wonderful title"' in result)
+class TestMissingImage(TestCase):
+ """
+ Missing image files in media/original_images should be handled gracefully, to cope with
+ pulling live databases to a development instance without copying the corresponding image files.
+ In this case, it's acceptable to render broken images, but not to fail rendering the page outright.
+ """
+ fixtures = ['test.json']
+
+ def test_image_tag_with_missing_image(self):
+ # the page /events/christmas/ has a missing image as the feed image
+ response = self.client.get('/events/christmas/')
+ self.assertContains(response, '
', html=True)
+
+ def test_rich_text_with_missing_image(self):
+ # the page /events/final-event/ has a missing image in the rich text body
+ response = self.client.get('/events/final-event/')
+ self.assertContains(response, '
', html=True)
+
+
class TestFormat(TestCase):
def setUp(self):
# test format