Skip to content

Commit 5020744

Browse files
committed
Add a helper for rendering sections to the region renderer
1 parent bc76062 commit 5020744

File tree

4 files changed

+103
-2
lines changed

4 files changed

+103
-2
lines changed

CHANGELOG.rst

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,17 @@ Change log
66
Next version
77
~~~~~~~~~~~~
88

9-
- Added Django 5.2a1 to the CI.
9+
- Added Django 5.2 to the CI.
1010
- Undeprecated the :mod:`~feincms3.mixins.TemplateMixin`, it's useful even
1111
though using :mod:`~feincms3.applications.PageTypeMixin` is obviously
1212
preferred.
13+
- Added tests for using ``reverse_app`` with the new ``query`` and ``fragment``
14+
parameters from Django 5.2 to ensure everything works as it should.
15+
- Converted the testsuite to pytest and organized tests a bit.
16+
- Added experimental support for section rendering to the ``RegionRenderer``.
17+
Sections are a more powerful alternative or addition to subregions already
18+
supported by django-content-editor. The main upside of sections is that they
19+
can be nested.
1320

1421

1522
5.3 (2024-11-18)

feincms3/renderer.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ def template_renderer(template_name, local_context=default_context, /):
8383
_subregion = 2
8484
_marks = 3
8585
_fetch = 4
86+
CLOSE_SECTION = "__close_section__"
8687

8788

8889
class RegionRenderer:
@@ -294,6 +295,13 @@ def handle_default(self, plugins, context):
294295
for plugin in self.takewhile_subregion(plugins, "default"):
295296
yield self.render_plugin(plugin, context)
296297

298+
def handle___close_section__(self, plugins, context):
299+
# Internal helper which discards superfluous CLOSE_SECTION plugins
300+
# This method assumes that we're working in a string/HTML context and
301+
# therefore yields an empty string.
302+
plugins.popleft()
303+
yield ""
304+
297305
def render_region(self, *, region, contents, context):
298306
"""
299307
Render one region.
@@ -313,6 +321,23 @@ def render_regions(self, *, regions, contents, context):
313321
for region in regions
314322
}
315323

324+
# Sections support
325+
326+
def render_section_plugins(self, section, plugins, context):
327+
out = []
328+
while plugins:
329+
subregion = self.subregion(plugins[0])
330+
if subregion == CLOSE_SECTION:
331+
plugins.popleft()
332+
break
333+
elif subregion is not None:
334+
out.extend(self._handlers[subregion](plugins, context))
335+
else:
336+
out.append(self.render_plugin(plugins.popleft(), context))
337+
return out
338+
339+
# Main external rendering API
340+
316341
def regions_from_contents(self, contents, **kwargs):
317342
"""
318343
Return an opaque object encapsulating

tests/testapp/test_region_renderer.py

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,14 @@
22

33
import pytest
44
from content_editor.contents import contents_for_item
5+
from content_editor.models import Region
56
from django.core.exceptions import ImproperlyConfigured
67
from django.template import Context
78
from django.utils.html import format_html, mark_safe
89
from pytest_django.asserts import assertHTMLEqual
910

1011
from feincms3.renderer import (
12+
CLOSE_SECTION,
1113
PluginNotRegisteredError,
1214
RegionRenderer,
1315
template_renderer,
@@ -194,3 +196,70 @@ def test_register_unregister():
194196
renderer.unregister(HTML, keep=[RichText])
195197
with pytest.raises(ImproperlyConfigured):
196198
renderer.unregister()
199+
200+
201+
def test_sections():
202+
class SectionRenderer(RegionRenderer):
203+
def handle_images(self, plugins, context):
204+
content = "".join(
205+
self.render_plugin(p, context)
206+
for p in self.takewhile_subregion(plugins, "images")
207+
)
208+
yield f"<gallery>{content}</gallery>"
209+
210+
def handle_section(self, plugins, context):
211+
section = plugins.popleft()
212+
content = self.render_section_plugins(section, plugins, context)
213+
return f"<section>{''.join(content)}</section>"
214+
215+
class Text:
216+
pass
217+
218+
class Image:
219+
pass
220+
221+
class Section:
222+
pass
223+
224+
class CloseSection:
225+
pass
226+
227+
renderer = SectionRenderer()
228+
renderer.register(Text, "Text")
229+
renderer.register(Image, "Image", subregion="images")
230+
renderer.register(Section, "", subregion="section")
231+
renderer.register(CloseSection, "", subregion=CLOSE_SECTION)
232+
233+
def render(plugins):
234+
return renderer.render_region(
235+
region=Region(key="content", title="Content"),
236+
contents={"content": plugins},
237+
context=None,
238+
)
239+
240+
t = Text()
241+
i = Image()
242+
s = Section()
243+
c = CloseSection()
244+
245+
# Basic tests
246+
assert render([t, t]) == "TextText"
247+
assert render([t, t, i, i, t]) == "TextText<gallery>ImageImage</gallery>Text"
248+
249+
# Automatic closing of sections
250+
assert render([s, t]) == "<section>Text</section>"
251+
252+
# Superfluous CloseSection objects are ignored
253+
assert render([s, t, c, c]) == "<section>Text</section>"
254+
255+
# Nested sections, the images subregion is also rendered within the outer section
256+
assert (
257+
render([t, s, t, s, t, c, i])
258+
== "Text<section>Text<section>Text</section><gallery>Image</gallery></section>"
259+
)
260+
261+
# The images subregion doesn't support nested sections and is automatically closed
262+
assert (
263+
render([t, i, i, s, t])
264+
== "Text<gallery>ImageImage</gallery><section>Text</section>"
265+
)

tox.ini

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ deps =
1919
dj42: Django>=4.2,<5.0
2020
dj50: Django>=5.0,<5.1
2121
dj51: Django>=5.1,<5.2
22-
dj52: Django>=5.2a1,<6.0
22+
dj52: Django>=5.2,<6.0
2323
djmain: https://github.com/django/django/archive/main.tar.gz
2424

2525
[testenv:docs]

0 commit comments

Comments
 (0)