Skip to content

Commit e07b2ed

Browse files
committed
#2154 Lazy-loading for images in blog content
1 parent d5e2d08 commit e07b2ed

File tree

1 file changed

+43
-4
lines changed

1 file changed

+43
-4
lines changed

blog/models.py

Lines changed: 43 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,13 @@
1212
from django.utils.translation import gettext_lazy as _
1313
from django_hosts.resolvers import get_host, reverse, reverse_host
1414
from docutils.core import publish_parts
15-
from markdown import markdown
15+
from docutils.nodes import document
16+
from docutils.writers.html4css1 import HTMLTranslator, Writer
17+
18+
from markdown import markdown, Markdown
1619
from markdown.extensions.toc import TocExtension, slugify as _md_title_slugify
20+
import markdown.treeprocessors
21+
import xml.etree.ElementTree as etree
1722

1823
BLOG_DOCUTILS_SETTINGS = {
1924
"doctitle_xform": False,
@@ -37,6 +42,36 @@ def published(self):
3742
def active(self):
3843
return self.filter(is_active=True)
3944

45+
_IMG_LAZY_ATTRIBUTES = {"loading":"lazy"}
46+
47+
class LazyImageHTMLTranslator(HTMLTranslator):
48+
"""Alter the img tags to include the lazy attribute."""
49+
def __init__(self, document: document, img_attributes: dict[str,str]|None=None) -> None:
50+
super().__init__(document)
51+
self._img_attributes=img_attributes or _IMG_LAZY_ATTRIBUTES
52+
53+
def emptytag(self, node, tagname, suffix='\n', **attributes):
54+
"""Construct and return an XML-compatible empty tag."""
55+
if tagname=="img":
56+
attributes.update(self._img_attributes)
57+
return super().emptytag(node,tagname,suffix,**attributes)
58+
59+
class LazyImageTreeprocessor(markdown.treeprocessors.Treeprocessor):
60+
"""
61+
`Treeprocessor`s are run on the `ElementTree` object before serialization.
62+
63+
This processor will add loading=lazy attribute on img tags
64+
65+
"""
66+
def __init__(self, img_attributes: dict[str,str]|None=None, md: Markdown | None = None) -> None:
67+
super().__init__(md)
68+
self._img_attributes=img_attributes or _IMG_LAZY_ATTRIBUTES
69+
70+
def run(self, root: etree.Element) -> etree.Element | None:
71+
"""Alter img tags with the supplemental attributes."""
72+
for img_elem in root.iter('img'):
73+
img_elem.attrib.update(self._img_attributes)
74+
4075

4176
class ContentFormat(models.TextChoices):
4277
REST = "reST", "reStructuredText"
@@ -51,20 +86,24 @@ def to_html(cls, fmt, source):
5186
if not fmt or fmt == cls.HTML:
5287
return source
5388
if fmt == cls.REST:
89+
writer=Writer()
90+
writer.translator_class=LazyImageHTMLTranslator
91+
5492
return publish_parts(
5593
source=source,
56-
writer_name="html",
94+
writer=writer,
5795
settings_overrides=BLOG_DOCUTILS_SETTINGS,
5896
)["fragment"]
5997
if fmt == cls.MARKDOWN:
60-
return markdown(
61-
source,
98+
md = Markdown(
6299
output_format="html",
63100
extensions=[
64101
# baselevel matches `initial_header_level` from BLOG_DOCUTILS_SETTINGS
65102
TocExtension(baselevel=3, slugify=_md_slugify),
66103
],
67104
)
105+
md.treeprocessors.register(LazyImageTreeprocessor(),"lazyimage",0.3)
106+
return md.convert(source)
68107
raise ValueError(f"Unsupported format {fmt}")
69108

70109
def img(self, url, alt_text):

0 commit comments

Comments
 (0)