123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663 |
- import os
- import unittest
- from django import forms, template
- from django.conf import settings
- from django.core.exceptions import ImproperlyConfigured
- from django.test import TestCase, override_settings
- from django.urls import reverse
- from mock import MagicMock
- from taggit.forms import TagField, TagWidget
- from wagtail.images import get_image_model, get_image_model_string
- from wagtail.images.fields import WagtailImageField
- from wagtail.images.formats import Format, get_image_format, register_image_format
- from wagtail.images.forms import get_image_form
- from wagtail.images.models import Image as WagtailImage
- from wagtail.images.rect import Rect, Vector
- from wagtail.images.views.serve import ServeView, generate_signature, verify_signature
- from wagtail.tests.testapp.models import CustomImage, CustomImageFilePath
- from wagtail.tests.utils import WagtailTestUtils
- from .utils import Image, get_test_image_file
- try:
- import sendfile # noqa
- sendfile_mod = True
- except ImportError:
- sendfile_mod = False
- class TestImageTag(TestCase):
- def setUp(self):
- # Create an image for running tests on
- self.image = Image.objects.create(
- title="Test image",
- file=get_test_image_file(),
- )
- def render_image_tag(self, image, filter_spec):
- temp = template.Template('{% load wagtailimages_tags %}{% image image_obj ' + filter_spec + '%}')
- context = template.Context({'image_obj': image})
- return temp.render(context)
- def test_image_tag(self):
- result = self.render_image_tag(self.image, 'width-400')
- # Check that all the required HTML attributes are set
- self.assertTrue('width="400"' in result)
- self.assertTrue('height="300"' in result)
- self.assertTrue('alt="Test image"' in result)
- def test_image_tag_none(self):
- result = self.render_image_tag(None, "width-500")
- self.assertEqual(result, '')
- def render_image_tag_as(self, image, filter_spec):
- temp = template.Template(
- '{% load wagtailimages_tags %}{% image image_obj ' + filter_spec +
- ' as test_img %}<img {{ test_img.attrs }} />'
- )
- context = template.Context({'image_obj': image})
- return temp.render(context)
- def test_image_tag_attrs(self):
- result = self.render_image_tag_as(self.image, 'width-400')
- # Check that all the required HTML attributes are set
- self.assertTrue('width="400"' in result)
- self.assertTrue('height="300"' in result)
- self.assertTrue('alt="Test image"' in result)
- def render_image_tag_with_extra_attributes(self, image, title):
- temp = template.Template(
- '{% load wagtailimages_tags %}{% image image_obj width-400 \
- class="photo" title=title|lower alt="Alternate" %}'
- )
- context = template.Context({'image_obj': image, 'title': title})
- return temp.render(context)
- def test_image_tag_with_extra_attributes(self):
- result = self.render_image_tag_with_extra_attributes(self.image, 'My Wonderful Title')
- # Check that all the required HTML attributes are set
- self.assertTrue('width="400"' in result)
- self.assertTrue('height="300"' in result)
- self.assertTrue('class="photo"' in result)
- self.assertTrue('alt="Alternate"' in result)
- self.assertTrue('title="my wonderful title"' in result)
- def render_image_tag_with_filters(self, image):
- temp = template.Template(
- '{% load wagtailimages_tags %}{% image image_primary|default:image_alternate width-400 %}'
- )
- context = template.Context({'image_primary': None, 'image_alternate': image})
- return temp.render(context)
- def test_image_tag_with_filters(self):
- result = self.render_image_tag_with_filters(self.image)
- self.assertTrue('width="400"' in result)
- self.assertTrue('height="300"' in result)
- def test_image_tag_with_chained_filters(self):
- result = self.render_image_tag(self.image, 'fill-200x200 height-150')
- self.assertTrue('width="150"' in result)
- self.assertTrue('height="150"' in result)
- def test_filter_specs_must_match_allowed_pattern(self):
- with self.assertRaises(template.TemplateSyntaxError):
- self.render_image_tag(self.image, 'fill-200x200|height-150')
- with self.assertRaises(template.TemplateSyntaxError):
- self.render_image_tag(self.image, 'fill-800x600 alt"test"')
- def test_context_may_only_contain_one_argument(self):
- with self.assertRaises(template.TemplateSyntaxError):
- temp = template.Template(
- '{% load wagtailimages_tags %}{% image image_obj fill-200x200'
- ' as test_img this_one_should_not_be_there %}<img {{ test_img.attrs }} />'
- )
- context = template.Context({'image_obj': self.image})
- temp.render(context)
- def test_no_image_filter_provided(self):
- # if image template gets the image but no filters
- with self.assertRaises(template.TemplateSyntaxError):
- temp = template.Template(
- '{% load wagtailimages_tags %}{% image image_obj %}'
- )
- context = template.Context({'image_obj': self.image})
- temp.render(context)
- def test_no_image_filter_provided_when_using_as(self):
- # if image template gets the image but no filters
- with self.assertRaises(template.TemplateSyntaxError):
- temp = template.Template(
- '{% load wagtailimages_tags %}{% image image_obj as foo %}'
- )
- context = template.Context({'image_obj': self.image})
- temp.render(context)
- def test_no_image_filter_provided_but_attributes_provided(self):
- # if image template gets the image but no filters
- with self.assertRaises(template.TemplateSyntaxError):
- temp = template.Template(
- '{% load wagtailimages_tags %}{% image image_obj class="cover-image"%}'
- )
- context = template.Context({'image_obj': self.image})
- temp.render(context)
- 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):
- def setUp(self):
- # test format
- self.format = Format(
- 'test name',
- 'test label',
- 'test classnames',
- 'test filter spec'
- )
- # test image
- self.image = MagicMock()
- self.image.id = 0
- def test_editor_attributes(self):
- result = self.format.editor_attributes(
- self.image,
- 'test alt text'
- )
- self.assertEqual(result,
- 'data-embedtype="image" data-id="0" data-format="test name" data-alt="test alt text" ')
- def test_image_to_editor_html(self):
- result = self.format.image_to_editor_html(
- self.image,
- 'test alt text'
- )
- self.assertRegex(
- result,
- '<img data-embedtype="image" data-id="0" data-format="test name" '
- 'data-alt="test alt text" class="test classnames" src="[^"]+" width="1" height="1" alt="test alt text">',
- )
- def test_image_to_editor_html_with_quoting(self):
- result = self.format.image_to_editor_html(
- self.image,
- 'Arthur "two sheds" Jackson'
- )
- self.assertRegex(
- result,
- '<img data-embedtype="image" data-id="0" data-format="test name" '
- 'data-alt="Arthur "two sheds" Jackson" class="test classnames" src="[^"]+" width="1" height="1" alt="Arthur "two sheds" Jackson">',
- )
- def test_image_to_html_no_classnames(self):
- self.format.classnames = None
- result = self.format.image_to_html(self.image, 'test alt text')
- self.assertRegex(
- result,
- '<img src="[^"]+" width="1" height="1" alt="test alt text">'
- )
- self.format.classnames = 'test classnames'
- def test_image_to_html_with_quoting(self):
- result = self.format.image_to_html(self.image, 'Arthur "two sheds" Jackson')
- self.assertRegex(
- result,
- '<img class="test classnames" src="[^"]+" width="1" height="1" alt="Arthur "two sheds" Jackson">'
- )
- def test_get_image_format(self):
- register_image_format(self.format)
- result = get_image_format('test name')
- self.assertEqual(result, self.format)
- class TestSignatureGeneration(TestCase):
- def test_signature_generation(self):
- self.assertEqual(generate_signature(100, 'fill-800x600'), 'xnZOzQyUg6pkfciqcfRJRosOrGg=')
- def test_signature_verification(self):
- self.assertTrue(verify_signature('xnZOzQyUg6pkfciqcfRJRosOrGg=', 100, 'fill-800x600'))
- def test_signature_changes_on_image_id(self):
- self.assertFalse(verify_signature('xnZOzQyUg6pkfciqcfRJRosOrGg=', 200, 'fill-800x600'))
- def test_signature_changes_on_filter_spec(self):
- self.assertFalse(verify_signature('xnZOzQyUg6pkfciqcfRJRosOrGg=', 100, 'fill-800x700'))
- class TestFrontendServeView(TestCase):
- def setUp(self):
- # Create an image for running tests on
- self.image = Image.objects.create(
- title="Test image",
- file=get_test_image_file(),
- )
- def test_get(self):
- """
- Test a valid GET request to the view
- """
- # Generate signature
- signature = generate_signature(self.image.id, 'fill-800x600')
- # Get the image
- response = self.client.get(reverse('wagtailimages_serve', args=(signature, self.image.id, 'fill-800x600')))
- # Check response
- self.assertEqual(response.status_code, 200)
- self.assertTrue(response.streaming)
- self.assertEqual(response['Content-Type'], 'image/png')
- def test_get_with_extra_component(self):
- """
- Test that a filename can be optionally added to the end of the URL.
- """
- # Generate signature
- signature = generate_signature(self.image.id, 'fill-800x600')
- # Get the image
- response = self.client.get(reverse('wagtailimages_serve', args=(signature, self.image.id, 'fill-800x600')) + 'test.png')
- # Check response
- self.assertEqual(response.status_code, 200)
- self.assertTrue(response.streaming)
- self.assertEqual(response['Content-Type'], 'image/png')
- def test_get_with_too_many_extra_components(self):
- """
- A filename can be appended to the end of the URL, but it must not contain a '/'
- """
- # Generate signature
- signature = generate_signature(self.image.id, 'fill-800x600')
- # Get the image
- response = self.client.get(reverse('wagtailimages_serve', args=(signature, self.image.id, 'fill-800x600')) + 'test/test.png')
- # URL pattern should not match
- self.assertEqual(response.status_code, 404)
- def test_get_with_serve_action(self):
- signature = generate_signature(self.image.id, 'fill-800x600')
- response = self.client.get(reverse('wagtailimages_serve_action_serve', args=(signature, self.image.id, 'fill-800x600')))
- self.assertEqual(response.status_code, 200)
- self.assertTrue(response.streaming)
- self.assertEqual(response['Content-Type'], 'image/png')
- def test_get_with_redirect_action(self):
- signature = generate_signature(self.image.id, 'fill-800x600')
- response = self.client.get(reverse('wagtailimages_serve_action_redirect', args=(signature, self.image.id, 'fill-800x600')))
- expected_redirect_url = '/media/images/{filename[0]}.2e16d0ba.fill-800x600{filename[1]}'.format(
- filename=os.path.splitext(os.path.basename(self.image.file.path))
- )
- self.assertRedirects(response, expected_redirect_url, status_code=301, fetch_redirect_response=False)
- def test_init_with_unknown_action_raises_error(self):
- with self.assertRaises(ImproperlyConfigured):
- ServeView.as_view(action='unknown')
- def test_get_with_custom_key(self):
- """
- Test that that the key can be changed on the view
- """
- # Generate signature
- signature = generate_signature(self.image.id, 'fill-800x600', key='custom')
- # Get the image
- response = self.client.get(reverse('wagtailimages_serve_custom_key', args=(signature, self.image.id, 'fill-800x600')) + 'test.png')
- # Check response
- self.assertEqual(response.status_code, 200)
- def test_get_with_custom_key_using_default_key(self):
- """
- Test that that the key can be changed on the view
- This tests that the default key no longer works when the key is changed on the view
- """
- # Generate signature
- signature = generate_signature(self.image.id, 'fill-800x600')
- # Get the image
- response = self.client.get(reverse('wagtailimages_serve_custom_key', args=(signature, self.image.id, 'fill-800x600')) + 'test.png')
- # Check response
- self.assertEqual(response.status_code, 403)
- def test_get_invalid_signature(self):
- """
- Test that an invalid signature returns a 403 response
- """
- # Generate a signature for the incorrect image id
- signature = generate_signature(self.image.id + 1, 'fill-800x600')
- # Get the image
- response = self.client.get(reverse('wagtailimages_serve', args=(signature, self.image.id, 'fill-800x600')))
- # Check response
- self.assertEqual(response.status_code, 403)
- def test_get_invalid_filter_spec(self):
- """
- Test that an invalid filter spec returns a 400 response
- This is very unlikely to happen in reality. A user would have
- to create signature for the invalid filter spec which can't be
- done with Wagtails built in URL generator. We should test it
- anyway though.
- """
- # Generate a signature with the invalid filterspec
- signature = generate_signature(self.image.id, 'bad-filter-spec')
- # Get the image
- response = self.client.get(reverse('wagtailimages_serve', args=(signature, self.image.id, 'bad-filter-spec')))
- # Check response
- self.assertEqual(response.status_code, 400)
- def test_get_missing_source_image_file(self):
- """
- Test that a missing image file gives a 410 response
- When the source image file is missing, it is presumed deleted so we
- return a 410 "Gone" response.
- """
- # Delete the image file
- os.remove(self.image.file.path)
- # Get the image
- signature = generate_signature(self.image.id, 'fill-800x600')
- response = self.client.get(reverse('wagtailimages_serve', args=(signature, self.image.id, 'fill-800x600')))
- # Check response
- self.assertEqual(response.status_code, 410)
- class TestFrontendSendfileView(TestCase):
- def setUp(self):
- self.image = Image.objects.create(
- title="Test image",
- file=get_test_image_file(),
- )
- @override_settings(SENDFILE_BACKEND='sendfile.backends.development')
- @unittest.skipIf(not sendfile_mod, 'Missing django-sendfile app.')
- def test_sendfile_nobackend(self):
- signature = generate_signature(self.image.id, 'fill-800x600')
- response = self.client.get(reverse('wagtailimages_sendfile',
- args=(signature, self.image.id,
- 'fill-800x600')))
- self.assertEqual(response.status_code, 200)
- self.assertEqual(response['Content-Type'], 'image/png')
- @override_settings(SENDFILE_BACKEND='sendfile.backends.development')
- def test_sendfile_dummy_backend(self):
- signature = generate_signature(self.image.id, 'fill-800x600')
- response = self.client.get(reverse('wagtailimages_sendfile_dummy',
- args=(signature, self.image.id,
- 'fill-800x600')))
- self.assertEqual(response.status_code, 200)
- self.assertTrue(response.content, 'Dummy backend response')
- class TestRect(TestCase):
- def test_init(self):
- rect = Rect(100, 150, 200, 250)
- self.assertEqual(rect.left, 100)
- self.assertEqual(rect.top, 150)
- self.assertEqual(rect.right, 200)
- self.assertEqual(rect.bottom, 250)
- def test_equality(self):
- self.assertEqual(Rect(100, 150, 200, 250), Rect(100, 150, 200, 250))
- self.assertNotEqual(Rect(100, 150, 200, 250), Rect(10, 15, 20, 25))
- def test_getitem(self):
- rect = Rect(100, 150, 200, 250)
- self.assertEqual(rect[0], 100)
- self.assertEqual(rect[1], 150)
- self.assertEqual(rect[2], 200)
- self.assertEqual(rect[3], 250)
- self.assertRaises(IndexError, rect.__getitem__, 4)
- def test_as_tuple(self):
- rect = Rect(100, 150, 200, 250)
- self.assertEqual(rect.as_tuple(), (100, 150, 200, 250))
- def test_size(self):
- rect = Rect(100, 150, 200, 350)
- self.assertIsInstance(rect.size, Vector)
- self.assertEqual(rect.size, (100, 200))
- self.assertEqual(rect.width, 100)
- self.assertEqual(rect.height, 200)
- def test_set_size_with_tuple(self):
- rect = Rect(100, 150, 200, 350)
- rect.size = (200, 400)
- self.assertEqual(rect, (50, 50, 250, 450))
- def test_set_size_with_vector(self):
- rect = Rect(100, 150, 200, 350)
- rect.size = Vector(200, 400)
- self.assertEqual(rect, (50, 50, 250, 450))
- def test_centroid(self):
- rect = Rect(100, 150, 200, 350)
- self.assertIsInstance(rect.centroid, Vector)
- self.assertEqual(rect.centroid, (150, 250))
- self.assertEqual(rect.x, 150)
- self.assertEqual(rect.y, 250)
- self.assertEqual(rect.centroid_x, 150)
- self.assertEqual(rect.centroid_y, 250)
- def test_set_centroid_with_tuple(self):
- rect = Rect(100, 150, 200, 350)
- rect.centroid = (500, 500)
- self.assertEqual(rect, (450, 400, 550, 600))
- def test_set_centroid_with_vector(self):
- rect = Rect(100, 150, 200, 350)
- rect.centroid = Vector(500, 500)
- self.assertEqual(rect, (450, 400, 550, 600))
- def test_repr(self):
- rect = Rect(100, 150, 200, 250)
- self.assertEqual(repr(rect), "Rect(left: 100, top: 150, right: 200, bottom: 250)")
- def test_from_point(self):
- rect = Rect.from_point(100, 200, 50, 20)
- self.assertEqual(rect, Rect(75, 190, 125, 210))
- class TestGetImageForm(TestCase, WagtailTestUtils):
- def test_fields(self):
- form = get_image_form(Image)
- self.assertEqual(list(form.base_fields.keys()), [
- 'title',
- 'file',
- 'collection',
- 'tags',
- 'focal_point_x',
- 'focal_point_y',
- 'focal_point_width',
- 'focal_point_height',
- ])
- def test_admin_form_fields_attribute(self):
- form = get_image_form(CustomImage)
- self.assertEqual(list(form.base_fields.keys()), [
- 'title',
- 'file',
- 'collection',
- 'tags',
- 'focal_point_x',
- 'focal_point_y',
- 'focal_point_width',
- 'focal_point_height',
- 'caption',
- ])
- def test_file_field(self):
- form = get_image_form(WagtailImage)
- self.assertIsInstance(form.base_fields['file'], WagtailImageField)
- self.assertIsInstance(form.base_fields['file'].widget, forms.FileInput)
- def test_tags_field(self):
- form = get_image_form(WagtailImage)
- self.assertIsInstance(form.base_fields['tags'], TagField)
- self.assertIsInstance(form.base_fields['tags'].widget, TagWidget)
- def test_focal_point_fields(self):
- form = get_image_form(WagtailImage)
- self.assertIsInstance(form.base_fields['focal_point_x'], forms.IntegerField)
- self.assertIsInstance(form.base_fields['focal_point_y'], forms.IntegerField)
- self.assertIsInstance(form.base_fields['focal_point_width'], forms.IntegerField)
- self.assertIsInstance(form.base_fields['focal_point_height'], forms.IntegerField)
- self.assertIsInstance(form.base_fields['focal_point_x'].widget, forms.HiddenInput)
- self.assertIsInstance(form.base_fields['focal_point_y'].widget, forms.HiddenInput)
- self.assertIsInstance(form.base_fields['focal_point_width'].widget, forms.HiddenInput)
- self.assertIsInstance(form.base_fields['focal_point_height'].widget, forms.HiddenInput)
- class TestRenditionFilenames(TestCase):
- # Can't create image in setUp as we need a unique filename for each test.
- # This stops Django appending some rubbish to the filename which makes
- # the assertions difficult.
- def test_normal_filter(self):
- image = Image.objects.create(
- title="Test image",
- file=get_test_image_file(filename='test_rf1.png'),
- )
- rendition = image.get_rendition('width-100')
- self.assertEqual(rendition.file.name, 'images/test_rf1.width-100.png')
- def test_fill_filter(self):
- image = Image.objects.create(
- title="Test image",
- file=get_test_image_file(filename='test_rf2.png'),
- )
- rendition = image.get_rendition('fill-100x100')
- self.assertEqual(rendition.file.name, 'images/test_rf2.2e16d0ba.fill-100x100.png')
- def test_fill_filter_with_focal_point(self):
- image = Image.objects.create(
- title="Test image",
- file=get_test_image_file(filename='test_rf3.png'),
- )
- image.set_focal_point(Rect(100, 100, 200, 200))
- image.save()
- rendition = image.get_rendition('fill-100x100')
- self.assertEqual(rendition.file.name, 'images/test_rf3.15ee4958.fill-100x100.png')
- def test_filter_with_pipe_gets_dotted(self):
- image = Image.objects.create(
- title="Test image",
- file=get_test_image_file(filename='test_rf4.png'),
- )
- image.set_focal_point(Rect(100, 100, 200, 200))
- image.save()
- rendition = image.get_rendition('fill-200x200|height-150')
- self.assertEqual(rendition.file.name, 'images/test_rf4.15ee4958.fill-200x200.height-150.png')
- class TestDifferentUpload(TestCase):
- def test_upload_path(self):
- image = CustomImageFilePath.objects.create(
- title="Test image",
- file=get_test_image_file(),
- )
- second_image = CustomImageFilePath.objects.create(
- title="Test Image",
- file=get_test_image_file(colour='black'),
- )
- # The files should be uploaded based on it's content, not just
- # it's filename
- self.assertFalse(image.file.url == second_image.file.url)
- class TestGetImageModel(WagtailTestUtils, TestCase):
- @override_settings(WAGTAILIMAGES_IMAGE_MODEL='tests.CustomImage')
- def test_custom_get_image_model(self):
- """Test get_image_model with a custom image model"""
- self.assertIs(get_image_model(), CustomImage)
- @override_settings(WAGTAILIMAGES_IMAGE_MODEL='tests.CustomImage')
- def test_custom_get_image_model_string(self):
- """Test get_image_model_string with a custom image model"""
- self.assertEqual(get_image_model_string(), 'tests.CustomImage')
- @override_settings()
- def test_standard_get_image_model(self):
- """Test get_image_model with no WAGTAILIMAGES_IMAGE_MODEL"""
- del settings.WAGTAILIMAGES_IMAGE_MODEL
- from wagtail.images.models import Image
- self.assertIs(get_image_model(), Image)
- @override_settings()
- def test_standard_get_image_model_string(self):
- """Test get_image_model_STRING with no WAGTAILIMAGES_IMAGE_MODEL"""
- del settings.WAGTAILIMAGES_IMAGE_MODEL
- self.assertEqual(get_image_model_string(), 'wagtailimages.Image')
- @override_settings(WAGTAILIMAGES_IMAGE_MODEL='tests.UnknownModel')
- def test_unknown_get_image_model(self):
- """Test get_image_model with an unknown model"""
- with self.assertRaises(ImproperlyConfigured):
- get_image_model()
- @override_settings(WAGTAILIMAGES_IMAGE_MODEL='invalid-string')
- def test_invalid_get_image_model(self):
- """Test get_image_model with an invalid model string"""
- with self.assertRaises(ImproperlyConfigured):
- get_image_model()
|