diff --git a/wagtail/core/rich_text/__init__.py b/wagtail/core/rich_text/__init__.py
index 4b9c63b17..2066ff2d0 100644
--- a/wagtail/core/rich_text/__init__.py
+++ b/wagtail/core/rich_text/__init__.py
@@ -1,3 +1,4 @@
+from django.db.models import Model
from django.utils.safestring import mark_safe
from wagtail.core.rich_text.feature_registry import FeatureRegistry
@@ -50,3 +51,41 @@ class RichText:
def __bool__(self):
return bool(self.source)
__nonzero__ = __bool__
+
+
+class EntityHandler:
+ """
+ An 'entity' is a placeholder tag within the saved rich text, which needs to be rewritten
+ into real HTML at the point of rendering. Typically (but not necessarily) the entity will
+ be a reference to a model to be fetched to have its data output into the rich text content
+ (so that we aren't storing potentially changeable data within the saved rich text).
+
+ An EntityHandler defines how this rewriting is performed.
+
+ Currently Wagtail supports two kinds of entity: links (represented as ...)
+ and embeds (represented as ).
+ """
+ @staticmethod
+ def get_model():
+ return NotImplementedError
+
+ @classmethod
+ def get_instance(cls, attrs: dict) -> Model:
+ model = cls.get_model()
+ return model._default_manager.get(id=attrs['id'])
+
+ @staticmethod
+ def expand_db_attributes(attrs: dict) -> str:
+ """
+ Given a dict of attributes from the entity tag
+ stored in the database, returns the real HTML representation.
+ """
+ raise NotImplementedError
+
+
+class LinkHandler(EntityHandler):
+ pass
+
+
+class EmbedHandler(EntityHandler):
+ pass
diff --git a/wagtail/core/rich_text/pages.py b/wagtail/core/rich_text/pages.py
index 9cf331463..d5f8bbdfb 100644
--- a/wagtail/core/rich_text/pages.py
+++ b/wagtail/core/rich_text/pages.py
@@ -1,13 +1,22 @@
from django.utils.html import escape
from wagtail.core.models import Page
+from wagtail.core.rich_text import LinkHandler
-class PageLinkHandler:
+class PageLinkHandler(LinkHandler):
@staticmethod
- def expand_db_attributes(attrs):
+ def get_model():
+ return Page
+
+ @classmethod
+ def get_instance(cls, attrs):
+ return super().get_instance(attrs).specific
+
+ @classmethod
+ def expand_db_attributes(cls, attrs):
try:
- page = Page.objects.get(id=attrs['id'])
+ page = cls.get_instance(attrs)
return '' % escape(page.specific.url)
except Page.DoesNotExist:
return ""
diff --git a/wagtail/documents/rich_text/__init__.py b/wagtail/documents/rich_text/__init__.py
index 876fb6093..f0a307e51 100644
--- a/wagtail/documents/rich_text/__init__.py
+++ b/wagtail/documents/rich_text/__init__.py
@@ -1,16 +1,21 @@
+from django.core.exceptions import ObjectDoesNotExist
from django.utils.html import escape
+from wagtail.core.rich_text import LinkHandler
from wagtail.documents.models import get_document_model
# Front-end conversion
-class DocumentLinkHandler:
+class DocumentLinkHandler(LinkHandler):
@staticmethod
- def expand_db_attributes(attrs):
- Document = get_document_model()
+ def get_model():
+ return get_document_model()
+
+ @classmethod
+ def expand_db_attributes(cls, attrs):
try:
- doc = Document.objects.get(id=attrs['id'])
+ doc = cls.get_instance(attrs)
return '' % escape(doc.url)
- except (Document.DoesNotExist, KeyError):
+ except (ObjectDoesNotExist, KeyError):
return ""
diff --git a/wagtail/embeds/rich_text/__init__.py b/wagtail/embeds/rich_text/__init__.py
index 5a8c2c729..b852c16a4 100644
--- a/wagtail/embeds/rich_text/__init__.py
+++ b/wagtail/embeds/rich_text/__init__.py
@@ -1,9 +1,20 @@
+from wagtail.core.rich_text import EmbedHandler
from wagtail.embeds import format
+from wagtail.embeds.embeds import get_embed
+from wagtail.embeds.models import Embed
# Front-end conversion
-class MediaEmbedHandler:
+class MediaEmbedHandler(EmbedHandler):
+ @staticmethod
+ def get_model():
+ return Embed
+
+ @staticmethod
+ def get_instance(attrs):
+ return get_embed(attrs['url'])
+
@staticmethod
def expand_db_attributes(attrs):
"""
diff --git a/wagtail/images/rich_text/__init__.py b/wagtail/images/rich_text/__init__.py
index f6a2a938c..429c439dc 100644
--- a/wagtail/images/rich_text/__init__.py
+++ b/wagtail/images/rich_text/__init__.py
@@ -1,20 +1,26 @@
+from django.core.exceptions import ObjectDoesNotExist
+
+from wagtail.core.rich_text import EmbedHandler
from wagtail.images import get_image_model
from wagtail.images.formats import get_image_format
# Front-end conversion
-class ImageEmbedHandler:
+class ImageEmbedHandler(EmbedHandler):
@staticmethod
- def expand_db_attributes(attrs):
+ def get_model():
+ return get_image_model()
+
+ @classmethod
+ def expand_db_attributes(cls, attrs):
"""
Given a dict of attributes from the