0
0
mirror of https://github.com/wagtail/wagtail.git synced 2024-12-01 11:41:20 +01:00

Merge pull request #1093 from gasman/feature/richtext-hooks

Register rich text handlers for docs / images / media through hooks
This commit is contained in:
Karl Hobley 2015-03-24 10:28:19 +00:00
commit 391e3825f0
11 changed files with 300 additions and 258 deletions

View File

@ -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:
<embed embedtype="image" id="42" format="thumb" alt="some custom alt text">
"""
@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 <embed> 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 <embed> 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 "<img>"
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:
<embed embedtype="media" url="http://vimeo.com/XXXXX">
"""
@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 <embed> element.
"""
return {
'url': tag['data-url'],
}
@staticmethod
def expand_db_attributes(attrs, for_editor):
"""
Given a dict of attributes from the <embed> 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 <a> element in HTML content
@ -127,35 +44,38 @@ class PageLinkHandler(object):
return "<a>"
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 '<a %shref="%s">' % (editor_attrs, escape(doc.url))
except Document.DoesNotExist:
return "<a>"
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)

View File

@ -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(
'<b data-id="test-id" data-format="test-format" data-alt="test-alt">foo</b>'
)
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, '<img>')
@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('<img class="richtext-image left"', result)
@patch('wagtail.wagtailimages.models.Image')
@patch('django.core.files.File')
def test_expand_db_attributes_for_editor(self, mock_file, mock_image):
result = ImageEmbedHandler.expand_db_attributes(
{'id': 1,
'alt': 'test-alt',
'format': 'left'},
True
)
self.assertIn('<img data-embedtype="image" data-id="1" data-format="left" data-alt="test-alt" class="richtext-image left"', result)
@patch('wagtail.wagtailimages.models.Image')
@patch('django.core.files.File')
def test_expand_db_attributes_for_editor_throws_exception(self, mock_file, mock_image):
result = ImageEmbedHandler.expand_db_attributes(
{'id': 1,
'format': 'left'},
True
)
self.assertEqual(result, '')
class TestMediaEmbedHandler(TestCase):
def test_get_db_attributes(self):
soup = BeautifulSoup(
'<b data-url="test-url">foo</b>'
)
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('<div class="embed-placeholder" contenteditable="false" data-embedtype="media" data-url="http://www.youtube.com/watch/">', result)
self.assertIn('<h3>test title</h3>', result)
self.assertIn('<p>URL: http://www.youtube.com/watch/</p>', result)
self.assertIn('<p>Provider: test provider name</p>', result)
self.assertIn('<p>Author: test author name</p>', result)
self.assertIn('<img src="test thumbnail url" alt="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, '<a href="None">')
class TestDocumentLinkHandler(TestCase):
fixtures = ['wagtail/tests/fixtures/test.json']
def test_get_db_attributes(self):
soup = BeautifulSoup(
'<a data-id="test-id">foo</a>'
)
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, '<a>')
def test_expand_db_attributes_for_editor(self):
result = DocumentLinkHandler.expand_db_attributes(
{'id': 1},
True
)
self.assertEqual(result,
'<a data-linktype="document" data-id="1" href="/documents/1/">')
def test_expand_db_attributes_not_for_editor(self):
result = DocumentLinkHandler.expand_db_attributes(
{'id': 1},
False
)
self.assertEqual(result,
'<a href="/documents/1/">')
class TestDbWhiteLister(TestCase):
def test_clean_tag_node_div(self):
soup = BeautifulSoup(

View File

@ -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 '<a %shref="%s">' % (editor_attrs, escape(doc.url))
except Document.DoesNotExist:
return "<a>"

View File

@ -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(
'<a data-id="test-id">foo</a>'
)
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, '<a>')
def test_expand_db_attributes_for_editor(self):
result = DocumentLinkHandler.expand_db_attributes(
{'id': 1},
True
)
self.assertEqual(result,
'<a data-linktype="document" data-id="1" href="/documents/1/">')
def test_expand_db_attributes_not_for_editor(self):
result = DocumentLinkHandler.expand_db_attributes(
{'id': 1},
False
)
self.assertEqual(result,
'<a href="/documents/1/">')

View File

@ -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)

View File

@ -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:
<embed embedtype="media" url="http://vimeo.com/XXXXX">
"""
@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 <embed> element.
"""
return {
'url': tag['data-url'],
}
@staticmethod
def expand_db_attributes(attrs, for_editor):
"""
Given a dict of attributes from the <embed> 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'])

View File

@ -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('<h1>Hello world!</h1>', html)
class TestMediaEmbedHandler(TestCase):
def test_get_db_attributes(self):
soup = BeautifulSoup(
'<b data-url="test-url">foo</b>'
)
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('<div class="embed-placeholder" contenteditable="false" data-embedtype="media" data-url="http://www.youtube.com/watch/">', result)
self.assertIn('<h3>test title</h3>', result)
self.assertIn('<p>URL: http://www.youtube.com/watch/</p>', result)
self.assertIn('<p>Provider: test provider name</p>', result)
self.assertIn('<p>Author: test author name</p>', result)
self.assertIn('<img src="test thumbnail url" alt="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)

View File

@ -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)

View File

@ -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:
<embed embedtype="image" id="42" format="thumb" alt="some custom alt text">
"""
@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 <embed> 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 <embed> 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 "<img>"

View File

@ -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(
'<b data-id="test-id" data-format="test-format" data-alt="test-alt">foo</b>'
)
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, '<img>')
@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('<img class="richtext-image left"', result)
@patch('wagtail.wagtailimages.models.Image')
@patch('django.core.files.File')
def test_expand_db_attributes_for_editor(self, mock_file, mock_image):
result = ImageEmbedHandler.expand_db_attributes(
{'id': 1,
'alt': 'test-alt',
'format': 'left'},
True
)
self.assertIn('<img data-embedtype="image" data-id="1" data-format="left" data-alt="test-alt" class="richtext-image left"', result)
@patch('wagtail.wagtailimages.models.Image')
@patch('django.core.files.File')
def test_expand_db_attributes_for_editor_throws_exception(self, mock_file, mock_image):
result = ImageEmbedHandler.expand_db_attributes(
{'id': 1,
'format': 'left'},
True
)
self.assertEqual(result, '')

View File

@ -11,6 +11,7 @@ from wagtail.wagtailcore import hooks
from wagtail.wagtailadmin.menu import MenuItem
from wagtail.wagtailimages import admin_urls, image_operations
from wagtail.wagtailimages.rich_text import ImageEmbedHandler
@hooks.register('register_admin_urls')
@ -108,3 +109,8 @@ def register_image_operations():
('width', image_operations.WidthHeightOperation),
('height', image_operations.WidthHeightOperation),
]
@hooks.register('register_rich_text_embed_handler')
def register_image_embed_handler():
return ('image', ImageEmbedHandler)