kopia lustrzana https://github.com/wagtail/wagtail
Merge pull request #811 from gasman/fix/catch-missing-images
Render missing images within rich text as broken images, rather than throwing IOError during renderingpull/822/head
commit
2eff5e85c9
|
@ -81,7 +81,8 @@
|
||||||
"audience": "public",
|
"audience": "public",
|
||||||
"location": "The North Pole",
|
"location": "The North Pole",
|
||||||
"body": "<p>Chestnuts roasting on an open fire</p>",
|
"body": "<p>Chestnuts roasting on an open fire</p>",
|
||||||
"cost": "Free"
|
"cost": "Free",
|
||||||
|
"feed_image": 1
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -223,7 +224,7 @@
|
||||||
"date_from": "2015-04-22",
|
"date_from": "2015-04-22",
|
||||||
"audience": "public",
|
"audience": "public",
|
||||||
"location": "Ameristralia",
|
"location": "Ameristralia",
|
||||||
"body": "<p>come celebrate the independence of Ameristralia</p>",
|
"body": "<p>come celebrate the independence of Ameristralia <embed embedtype=\"image\" format=\"fullwidth\" id=\"1\" alt=\"where did my image go?\" /></p>",
|
||||||
"cost": "Free"
|
"cost": "Free"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -625,5 +626,16 @@
|
||||||
"page": 11,
|
"page": 11,
|
||||||
"password": "swordfish"
|
"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"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
{% load wagtailcore_tags %}
|
{% load wagtailcore_tags wagtailimages_tags %}
|
||||||
|
|
||||||
<!DOCTYPE HTML>
|
<!DOCTYPE HTML>
|
||||||
<html>
|
<html>
|
||||||
|
@ -8,6 +8,10 @@
|
||||||
<body>
|
<body>
|
||||||
<h1>{{ self.title }}</h1>
|
<h1>{{ self.title }}</h1>
|
||||||
<h2>Event</h2>
|
<h2>Event</h2>
|
||||||
|
{% if self.feed_image %}
|
||||||
|
{% image self.feed_image width-200 class="feed-image" %}
|
||||||
|
{% endif %}
|
||||||
|
{{ self.body|richtext }}
|
||||||
<p><a href="{% slugurl 'events' %}">Back to events index</a></p>
|
<p><a href="{% slugurl 'events' %}">Back to events index</a></p>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
@ -1,6 +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
|
||||||
|
|
||||||
|
|
||||||
class Format(object):
|
class Format(object):
|
||||||
|
@ -25,7 +26,15 @@ 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 = image.get_rendition(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)
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import os.path
|
import os.path
|
||||||
import re
|
import re
|
||||||
|
|
||||||
from six import BytesIO
|
from six import BytesIO, text_type
|
||||||
|
|
||||||
from taggit.managers import TaggableManager
|
from taggit.managers import TaggableManager
|
||||||
|
|
||||||
|
@ -28,6 +28,13 @@ from wagtail.wagtailimages.rect import Rect
|
||||||
from wagtail.wagtailadmin.utils import get_object_usage
|
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):
|
def get_upload_to(instance, filename):
|
||||||
folder_name = 'original_images'
|
folder_name = 'original_images'
|
||||||
filename = instance.file.field.storage.get_valid_name(filename)
|
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
|
# If we have a backend attribute then pass it to process
|
||||||
# image - else pass 'default'
|
# image - else pass 'default'
|
||||||
backend_name = getattr(self, 'backend', '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
|
# generate new filename derived from old one, inserting the filter spec and focal point key before the extension
|
||||||
if self.has_focal_point():
|
if self.has_focal_point():
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
from django import template
|
from django import template
|
||||||
|
|
||||||
from wagtail.wagtailimages.models import Filter
|
from wagtail.wagtailimages.models import Filter, SourceImageIOError
|
||||||
|
|
||||||
register = template.Library()
|
register = template.Library()
|
||||||
|
|
||||||
|
@ -53,10 +53,10 @@ class ImageNode(template.Node):
|
||||||
|
|
||||||
try:
|
try:
|
||||||
rendition = image.get_rendition(self.filter)
|
rendition = image.get_rendition(self.filter)
|
||||||
except IOError:
|
except SourceImageIOError:
|
||||||
# It's fairly routine for people to pull down remote databases to their
|
# It's fairly routine for people to pull down remote databases to their
|
||||||
# local dev versions without retrieving the corresponding image files.
|
# 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
|
# create the resized version of a non-existent image. Since this is a
|
||||||
# bit catastrophic for a missing image, we'll substitute a dummy
|
# bit catastrophic for a missing image, we'll substitute a dummy
|
||||||
# Rendition object so that we just output a broken link instead.
|
# Rendition object so that we just output a broken link instead.
|
||||||
|
|
|
@ -63,6 +63,25 @@ class TestImageTag(TestCase):
|
||||||
self.assertTrue('title="my wonderful title"' in result)
|
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, '<img src="/media/not-found" width="0" height="0" alt="A missing image" class="feed-image">', 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, '<img class="richtext-image full-width" src="/media/not-found" width="0" height="0" alt="where did my image go?">', html=True)
|
||||||
|
|
||||||
|
|
||||||
class TestFormat(TestCase):
|
class TestFormat(TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
# test format
|
# test format
|
||||||
|
|
Ładowanie…
Reference in New Issue