diff --git a/wagtail/wagtailcore/rich_text.py b/wagtail/wagtailcore/rich_text.py index 16d64be511..d4ac8d6561 100644 --- a/wagtail/wagtailcore/rich_text.py +++ b/wagtail/wagtailcore/rich_text.py @@ -4,15 +4,6 @@ from django.utils.html import escape from wagtail.wagtailcore.whitelist import Whitelister from wagtail.wagtailcore.models import Page - -from wagtail.wagtaildocs.models import Document - -# FIXME: we don't really want to import wagtailimages within core. -# For that matter, we probably don't want core to be concerned about translating -# HTML for the benefit of the hallo.js editor... -from wagtail.wagtailimages.models import get_image_model -from wagtail.wagtailimages.formats import get_image_format - from wagtail.wagtailcore import hooks @@ -22,80 +13,6 @@ from wagtail.wagtailcore import hooks # elsewhere in the database and is liable to change - from real HTML representation # to DB representation and back again. -class ImageEmbedHandler(object): - """ - ImageEmbedHandler will be invoked whenever we encounter an element in HTML content - with an attribute of data-embedtype="image". The resulting element in the database - representation will be: - - """ - @staticmethod - def get_db_attributes(tag): - """ - Given a tag that we've identified as an image embed (because it has a - data-embedtype="image" attribute), return a dict of the attributes we should - have on the resulting element. - """ - return { - 'id': tag['data-id'], - 'format': tag['data-format'], - 'alt': tag['data-alt'], - } - - @staticmethod - def expand_db_attributes(attrs, for_editor): - """ - Given a dict of attributes from the tag, return the real HTML - representation. - """ - Image = get_image_model() - try: - image = Image.objects.get(id=attrs['id']) - format = get_image_format(attrs['format']) - - if for_editor: - try: - return format.image_to_editor_html(image, attrs['alt']) - except: - return '' - else: - return format.image_to_html(image, attrs['alt']) - - except Image.DoesNotExist: - return "" - - -class MediaEmbedHandler(object): - """ - MediaEmbedHandler will be invoked whenever we encounter an element in HTML content - with an attribute of data-embedtype="media". The resulting element in the database - representation will be: - - """ - @staticmethod - def get_db_attributes(tag): - """ - Given a tag that we've identified as a media embed (because it has a - data-embedtype="media" attribute), return a dict of the attributes we should - have on the resulting element. - """ - return { - 'url': tag['data-url'], - } - - @staticmethod - def expand_db_attributes(attrs, for_editor): - """ - Given a dict of attributes from the tag, return the real HTML - representation. - """ - from wagtail.wagtailembeds import format - if for_editor: - return format.embed_to_editor_html(attrs['url']) - else: - return format.embed_to_frontend_html(attrs['url']) - - class PageLinkHandler(object): """ PageLinkHandler will be invoked whenever we encounter an element in HTML content @@ -127,35 +44,38 @@ class PageLinkHandler(object): return "" -class DocumentLinkHandler(object): - @staticmethod - def get_db_attributes(tag): - return {'id': tag['data-id']} - - @staticmethod - def expand_db_attributes(attrs, for_editor): - try: - doc = Document.objects.get(id=attrs['id']) - - if for_editor: - editor_attrs = 'data-linktype="document" data-id="%d" ' % doc.id - else: - editor_attrs = '' - - return '' % (editor_attrs, escape(doc.url)) - except Document.DoesNotExist: - return "" - - -EMBED_HANDLERS = { - 'image': ImageEmbedHandler, - 'media': MediaEmbedHandler, -} +EMBED_HANDLERS = {} LINK_HANDLERS = { 'page': PageLinkHandler, - 'document': DocumentLinkHandler, } +has_loaded_embed_handlers = False +has_loaded_link_handlers = False + +def get_embed_handler(embed_type): + global EMBED_HANDLERS, has_loaded_embed_handlers + + if not has_loaded_embed_handlers: + for hook in hooks.get_hooks('register_rich_text_embed_handler'): + handler_name, handler = hook() + EMBED_HANDLERS[handler_name] = handler + + has_loaded_embed_handlers = True + + return EMBED_HANDLERS[embed_type] + +def get_link_handler(link_type): + global LINK_HANDLERS, has_loaded_link_handlers + + if not has_loaded_link_handlers: + for hook in hooks.get_hooks('register_rich_text_link_handler'): + handler_name, handler = hook() + LINK_HANDLERS[handler_name] = handler + + has_loaded_link_handlers = True + + return LINK_HANDLERS[link_type] + class DbWhitelister(Whitelister): """ @@ -189,7 +109,7 @@ class DbWhitelister(Whitelister): if 'data-embedtype' in tag.attrs: embed_type = tag['data-embedtype'] # fetch the appropriate embed handler for this embedtype - embed_handler = EMBED_HANDLERS[embed_type] + embed_handler = get_embed_handler(embed_type) embed_attrs = embed_handler.get_db_attributes(tag) embed_attrs['embedtype'] = embed_type @@ -202,7 +122,7 @@ class DbWhitelister(Whitelister): cls.clean_node(doc, child) link_type = tag['data-linktype'] - link_handler = LINK_HANDLERS[link_type] + link_handler = get_link_handler(link_type) link_attrs = link_handler.get_db_attributes(tag) link_attrs['linktype'] = link_type tag.attrs.clear() @@ -238,12 +158,12 @@ def expand_db_html(html, for_editor=False): if 'linktype' not in attrs: # return unchanged return m.group(0) - handler = LINK_HANDLERS[attrs['linktype']] + handler = get_link_handler(attrs['linktype']) return handler.expand_db_attributes(attrs, for_editor) def replace_embed_tag(m): attrs = extract_attrs(m.group(1)) - handler = EMBED_HANDLERS[attrs['embedtype']] + handler = get_embed_handler(attrs['embedtype']) return handler.expand_db_attributes(attrs, for_editor) html = FIND_A_TAG.sub(replace_a_tag, html) diff --git a/wagtail/wagtailcore/tests/test_rich_text.py b/wagtail/wagtailcore/tests/test_rich_text.py index 9c98c6d3f0..8ffb917d87 100644 --- a/wagtail/wagtailcore/tests/test_rich_text.py +++ b/wagtail/wagtailcore/tests/test_rich_text.py @@ -3,10 +3,7 @@ from mock import patch from django.test import TestCase from wagtail.wagtailcore.rich_text import ( - ImageEmbedHandler, - MediaEmbedHandler, PageLinkHandler, - DocumentLinkHandler, DbWhitelister, extract_attrs, expand_db_html @@ -14,112 +11,6 @@ from wagtail.wagtailcore.rich_text import ( from bs4 import BeautifulSoup -class TestImageEmbedHandler(TestCase): - fixtures = ['wagtail/tests/fixtures/test.json'] - - def test_get_db_attributes(self): - soup = BeautifulSoup( - 'foo' - ) - tag = soup.b - result = ImageEmbedHandler.get_db_attributes(tag) - self.assertEqual(result, - {'alt': 'test-alt', - 'id': 'test-id', - 'format': 'test-format'}) - - def test_expand_db_attributes_page_does_not_exist(self): - result = ImageEmbedHandler.expand_db_attributes( - {'id': 0}, - False - ) - self.assertEqual(result, '') - - @patch('wagtail.wagtailimages.models.Image') - @patch('django.core.files.File') - def test_expand_db_attributes_not_for_editor(self, mock_file, mock_image): - result = ImageEmbedHandler.expand_db_attributes( - {'id': 1, - 'alt': 'test-alt', - 'format': 'left'}, - False - ) - self.assertIn('foo' - ) - tag = soup.b - result = MediaEmbedHandler.get_db_attributes(tag) - self.assertEqual(result, - {'url': 'test-url'}) - - @patch('wagtail.wagtailembeds.embeds.oembed') - def test_expand_db_attributes_for_editor(self, oembed): - oembed.return_value = { - 'title': 'test title', - 'author_name': 'test author name', - 'provider_name': 'test provider name', - 'type': 'test type', - 'thumbnail_url': 'test thumbnail url', - 'width': 'test width', - 'height': 'test height', - 'html': 'test html' - } - result = MediaEmbedHandler.expand_db_attributes( - {'url': 'http://www.youtube.com/watch/'}, - True - ) - self.assertIn('
', result) - self.assertIn('

test title

', result) - self.assertIn('

URL: http://www.youtube.com/watch/

', result) - self.assertIn('

Provider: test provider name

', result) - self.assertIn('

Author: test author name

', result) - self.assertIn('test title', result) - - @patch('wagtail.wagtailembeds.embeds.oembed') - def test_expand_db_attributes_not_for_editor(self, oembed): - oembed.return_value = { - 'title': 'test title', - 'author_name': 'test author name', - 'provider_name': 'test provider name', - 'type': 'test type', - 'thumbnail_url': 'test thumbnail url', - 'width': 'test width', - 'height': 'test height', - 'html': 'test html' - } - result = MediaEmbedHandler.expand_db_attributes( - {'url': 'http://www.youtube.com/watch/'}, - False - ) - self.assertIn('test html', result) - - class TestPageLinkHandler(TestCase): fixtures = ['wagtail/tests/fixtures/test.json'] @@ -155,42 +46,6 @@ class TestPageLinkHandler(TestCase): self.assertEqual(result, '
') -class TestDocumentLinkHandler(TestCase): - fixtures = ['wagtail/tests/fixtures/test.json'] - - def test_get_db_attributes(self): - soup = BeautifulSoup( - 'foo' - ) - tag = soup.a - result = DocumentLinkHandler.get_db_attributes(tag) - self.assertEqual(result, - {'id': 'test-id'}) - - def test_expand_db_attributes_document_does_not_exist(self): - result = DocumentLinkHandler.expand_db_attributes( - {'id': 0}, - False - ) - self.assertEqual(result, '') - - def test_expand_db_attributes_for_editor(self): - result = DocumentLinkHandler.expand_db_attributes( - {'id': 1}, - True - ) - self.assertEqual(result, - '') - - def test_expand_db_attributes_not_for_editor(self): - result = DocumentLinkHandler.expand_db_attributes( - {'id': 1}, - False - ) - self.assertEqual(result, - '') - - class TestDbWhiteLister(TestCase): def test_clean_tag_node_div(self): soup = BeautifulSoup( diff --git a/wagtail/wagtaildocs/rich_text.py b/wagtail/wagtaildocs/rich_text.py new file mode 100644 index 0000000000..44f42a2e9e --- /dev/null +++ b/wagtail/wagtaildocs/rich_text.py @@ -0,0 +1,23 @@ +from django.utils.html import escape + +from wagtail.wagtaildocs.models import Document + + +class DocumentLinkHandler(object): + @staticmethod + def get_db_attributes(tag): + return {'id': tag['data-id']} + + @staticmethod + def expand_db_attributes(attrs, for_editor): + try: + doc = Document.objects.get(id=attrs['id']) + + if for_editor: + editor_attrs = 'data-linktype="document" data-id="%d" ' % doc.id + else: + editor_attrs = '' + + return '' % (editor_attrs, escape(doc.url)) + except Document.DoesNotExist: + return "" diff --git a/wagtail/wagtaildocs/tests.py b/wagtail/wagtaildocs/tests.py index 1a38f9ea36..00a8227e0e 100644 --- a/wagtail/wagtaildocs/tests.py +++ b/wagtail/wagtaildocs/tests.py @@ -1,6 +1,7 @@ from six import b import unittest import mock +from bs4 import BeautifulSoup from django.test import TestCase from django.contrib.auth import get_user_model @@ -16,6 +17,7 @@ from wagtail.tests.models import EventPage, EventPageRelatedLink from wagtail.wagtaildocs.models import Document from wagtail.wagtaildocs import models +from wagtail.wagtaildocs.rich_text import DocumentLinkHandler class TestDocumentPermissions(TestCase): @@ -576,3 +578,39 @@ class TestServeView(TestCase): def test_with_incorrect_filename(self): response = self.client.get(reverse('wagtaildocs_serve', args=(self.document.id, 'incorrectfilename'))) self.assertEqual(response.status_code, 404) + + +class TestDocumentLinkHandler(TestCase): + fixtures = ['wagtail/tests/fixtures/test.json'] + + def test_get_db_attributes(self): + soup = BeautifulSoup( + 'foo' + ) + tag = soup.a + result = DocumentLinkHandler.get_db_attributes(tag) + self.assertEqual(result, + {'id': 'test-id'}) + + def test_expand_db_attributes_document_does_not_exist(self): + result = DocumentLinkHandler.expand_db_attributes( + {'id': 0}, + False + ) + self.assertEqual(result, '') + + def test_expand_db_attributes_for_editor(self): + result = DocumentLinkHandler.expand_db_attributes( + {'id': 1}, + True + ) + self.assertEqual(result, + '') + + def test_expand_db_attributes_not_for_editor(self): + result = DocumentLinkHandler.expand_db_attributes( + {'id': 1}, + False + ) + self.assertEqual(result, + '') diff --git a/wagtail/wagtaildocs/wagtail_hooks.py b/wagtail/wagtaildocs/wagtail_hooks.py index 41ce020bf1..597d1652d6 100644 --- a/wagtail/wagtaildocs/wagtail_hooks.py +++ b/wagtail/wagtaildocs/wagtail_hooks.py @@ -10,6 +10,7 @@ from wagtail.wagtailcore import hooks from wagtail.wagtailadmin.menu import MenuItem from wagtail.wagtaildocs import admin_urls +from wagtail.wagtaildocs.rich_text import DocumentLinkHandler @hooks.register('register_admin_urls') @@ -53,3 +54,8 @@ def register_permissions(): document_content_type = ContentType.objects.get(app_label='wagtaildocs', model='document') document_permissions = Permission.objects.filter(content_type = document_content_type) return document_permissions + + +@hooks.register('register_rich_text_link_handler') +def register_document_link_handler(): + return ('document', DocumentLinkHandler) diff --git a/wagtail/wagtailembeds/rich_text.py b/wagtail/wagtailembeds/rich_text.py new file mode 100644 index 0000000000..e099d5dd69 --- /dev/null +++ b/wagtail/wagtailembeds/rich_text.py @@ -0,0 +1,30 @@ +from wagtail.wagtailembeds import format + +class MediaEmbedHandler(object): + """ + MediaEmbedHandler will be invoked whenever we encounter an element in HTML content + with an attribute of data-embedtype="media". The resulting element in the database + representation will be: + + """ + @staticmethod + def get_db_attributes(tag): + """ + Given a tag that we've identified as a media embed (because it has a + data-embedtype="media" attribute), return a dict of the attributes we should + have on the resulting element. + """ + return { + 'url': tag['data-url'], + } + + @staticmethod + def expand_db_attributes(attrs, for_editor): + """ + Given a dict of attributes from the tag, return the real HTML + representation. + """ + if for_editor: + return format.embed_to_editor_html(attrs['url']) + else: + return format.embed_to_frontend_html(attrs['url']) diff --git a/wagtail/wagtailembeds/tests.py b/wagtail/wagtailembeds/tests.py index 93231372ed..b0799da383 100644 --- a/wagtail/wagtailembeds/tests.py +++ b/wagtail/wagtailembeds/tests.py @@ -2,8 +2,10 @@ import six.moves.urllib.request from six.moves.urllib.error import URLError from mock import patch -import warnings import unittest +from bs4 import BeautifulSoup + +from wagtail.wagtailembeds.rich_text import MediaEmbedHandler try: import embedly @@ -320,3 +322,55 @@ class TestEmbedBlock(TestCase): # Check that the embed was in the returned HTML self.assertIn('

Hello world!

', html) + + +class TestMediaEmbedHandler(TestCase): + def test_get_db_attributes(self): + soup = BeautifulSoup( + 'foo' + ) + tag = soup.b + result = MediaEmbedHandler.get_db_attributes(tag) + self.assertEqual(result, + {'url': 'test-url'}) + + @patch('wagtail.wagtailembeds.embeds.oembed') + def test_expand_db_attributes_for_editor(self, oembed): + oembed.return_value = { + 'title': 'test title', + 'author_name': 'test author name', + 'provider_name': 'test provider name', + 'type': 'test type', + 'thumbnail_url': 'test thumbnail url', + 'width': 'test width', + 'height': 'test height', + 'html': 'test html' + } + result = MediaEmbedHandler.expand_db_attributes( + {'url': 'http://www.youtube.com/watch/'}, + True + ) + self.assertIn('
', result) + self.assertIn('

test title

', result) + self.assertIn('

URL: http://www.youtube.com/watch/

', result) + self.assertIn('

Provider: test provider name

', result) + self.assertIn('

Author: test author name

', result) + self.assertIn('test title', result) + + @patch('wagtail.wagtailembeds.embeds.oembed') + def test_expand_db_attributes_not_for_editor(self, oembed): + oembed.return_value = { + 'title': 'test title', + 'author_name': 'test author name', + 'provider_name': 'test provider name', + 'type': 'test type', + 'thumbnail_url': 'test thumbnail url', + 'width': 'test width', + 'height': 'test height', + 'html': 'test html' + } + result = MediaEmbedHandler.expand_db_attributes( + {'url': 'http://www.youtube.com/watch/'}, + False + ) + self.assertIn('test html', result) diff --git a/wagtail/wagtailembeds/wagtail_hooks.py b/wagtail/wagtailembeds/wagtail_hooks.py index 955f8216b0..863544c5a3 100644 --- a/wagtail/wagtailembeds/wagtail_hooks.py +++ b/wagtail/wagtailembeds/wagtail_hooks.py @@ -5,6 +5,7 @@ from django.utils.html import format_html from wagtail.wagtailcore import hooks from wagtail.wagtailembeds import urls +from wagtail.wagtailembeds.rich_text import MediaEmbedHandler @hooks.register('register_admin_urls') @@ -27,3 +28,8 @@ def editor_js(): 'wagtailembeds/js/hallo-plugins/hallo-wagtailembeds.js', urlresolvers.reverse('wagtailembeds_chooser') ) + + +@hooks.register('register_rich_text_embed_handler') +def register_media_embed_handler(): + return ('media', MediaEmbedHandler) diff --git a/wagtail/wagtailimages/rich_text.py b/wagtail/wagtailimages/rich_text.py new file mode 100644 index 0000000000..aa2f9edd09 --- /dev/null +++ b/wagtail/wagtailimages/rich_text.py @@ -0,0 +1,44 @@ +from wagtail.wagtailimages.models import get_image_model +from wagtail.wagtailimages.formats import get_image_format + +class ImageEmbedHandler(object): + """ + ImageEmbedHandler will be invoked whenever we encounter an element in HTML content + with an attribute of data-embedtype="image". The resulting element in the database + representation will be: + + """ + @staticmethod + def get_db_attributes(tag): + """ + Given a tag that we've identified as an image embed (because it has a + data-embedtype="image" attribute), return a dict of the attributes we should + have on the resulting element. + """ + return { + 'id': tag['data-id'], + 'format': tag['data-format'], + 'alt': tag['data-alt'], + } + + @staticmethod + def expand_db_attributes(attrs, for_editor): + """ + Given a dict of attributes from the tag, return the real HTML + representation. + """ + Image = get_image_model() + try: + image = Image.objects.get(id=attrs['id']) + format = get_image_format(attrs['format']) + + if for_editor: + try: + return format.image_to_editor_html(image, attrs['alt']) + except: + return '' + else: + return format.image_to_html(image, attrs['alt']) + + except Image.DoesNotExist: + return "" diff --git a/wagtail/wagtailimages/tests/test_rich_text.py b/wagtail/wagtailimages/tests/test_rich_text.py new file mode 100644 index 0000000000..a9f91a4688 --- /dev/null +++ b/wagtail/wagtailimages/tests/test_rich_text.py @@ -0,0 +1,60 @@ +from django.test import TestCase + +from bs4 import BeautifulSoup +from mock import patch + +from wagtail.wagtailimages.rich_text import ImageEmbedHandler + + +class TestImageEmbedHandler(TestCase): + fixtures = ['wagtail/tests/fixtures/test.json'] + + def test_get_db_attributes(self): + soup = BeautifulSoup( + 'foo' + ) + tag = soup.b + result = ImageEmbedHandler.get_db_attributes(tag) + self.assertEqual(result, + {'alt': 'test-alt', + 'id': 'test-id', + 'format': 'test-format'}) + + def test_expand_db_attributes_page_does_not_exist(self): + result = ImageEmbedHandler.expand_db_attributes( + {'id': 0}, + False + ) + self.assertEqual(result, '') + + @patch('wagtail.wagtailimages.models.Image') + @patch('django.core.files.File') + def test_expand_db_attributes_not_for_editor(self, mock_file, mock_image): + result = ImageEmbedHandler.expand_db_attributes( + {'id': 1, + 'alt': 'test-alt', + 'format': 'left'}, + False + ) + self.assertIn('