Don't render a StructValue's template on calls to str(value)

This is liable to cause infinite loops on debug / logging code that attempts to log the fact that it's rendered a template with this value in the context.

Fixes #2874, https://github.com/jazzband/django-debug-toolbar/issues/950
This commit is contained in:
Matt Westcott 2017-07-27 19:56:52 +01:00
parent 17939e5190
commit 8a055addad
4 changed files with 28 additions and 9 deletions

View file

@ -17,6 +17,7 @@ Changelog
* Fix: Prevent explorer view from crashing when page model definitions are missing, allowing the offending pages to be deleted (Matt Westcott)
* Fix: Hide the userbar from printed page representation (Eugene Morozov)
* Fix: Prevent the page editor footer content from collapsing into two lines unnecessarily (Jack Paine)
* Fix: StructBlock values no longer render HTML templates as their `str` representation, to prevent infinite loops in debugging / logging tools (Matt Westcott)
1.11.1 (07.07.2017)

View file

@ -37,6 +37,7 @@ Bug fixes
* Prevent explorer view from crashing when page model definitions are missing, allowing the offending pages to be deleted (Matt Westcott)
* Hide the userbar from printed page representation (Eugene Morozov)
* Prevent the page editor footer content from collapsing into two lines unnecessarily (Jack Paine)
* StructBlock values no longer render HTML templates as their ``str`` representation, to prevent infinite loops in debugging / logging tools (Matt Westcott)
Upgrade considerations
======================

View file

@ -9,7 +9,6 @@ from django.forms.utils import ErrorList
from django.template.loader import render_to_string
# Must be imported from Django so we get the new implementation of with_metaclass
from django.utils import six
from django.utils.encoding import python_2_unicode_compatible
from django.utils.functional import cached_property
from django.utils.html import format_html, format_html_join
@ -194,13 +193,12 @@ class StructBlock(six.with_metaclass(DeclarativeSubBlocksMetaclass, BaseStructBl
pass
@python_2_unicode_compatible # provide equivalent __unicode__ and __str__ methods on Py2
class StructValue(collections.OrderedDict):
def __init__(self, block, *args):
super(StructValue, self).__init__(*args)
self.block = block
def __str__(self):
def __html__(self):
return self.block.render(self)
def render_as_block(self, context=None):

View file

@ -1316,16 +1316,35 @@ class TestStructBlock(SimpleTestCase):
def test_render_structvalue(self):
"""
The string representation of a StructValue should use the block's template
The HTML representation of a StructValue should use the block's template
"""
block = SectionBlock()
value = block.to_python({'title': 'Hello', 'body': '<i>italic</i> world'})
result = value.__html__()
self.assertEqual(result, """<h1>Hello</h1><div class="rich-text"><i>italic</i> world</div>""")
# value.render_as_block() should be equivalent to value.__html__()
result = value.render_as_block()
self.assertEqual(result, """<h1>Hello</h1><div class="rich-text"><i>italic</i> world</div>""")
def test_str_structvalue(self):
"""
The str() representation of a StructValue should NOT render the template, as that's liable
to cause an infinite loop if any debugging / logging code attempts to log the fact that
it rendered a template with this object in the context:
https://github.com/wagtail/wagtail/issues/2874
https://github.com/jazzband/django-debug-toolbar/issues/950
"""
block = SectionBlock()
value = block.to_python({'title': 'Hello', 'body': '<i>italic</i> world'})
result = str(value)
self.assertEqual(result, """<h1>Hello</h1><div class="rich-text"><i>italic</i> world</div>""")
# value.render_as_block() should be equivalent to str(value)
result = value.render_as_block()
self.assertEqual(result, """<h1>Hello</h1><div class="rich-text"><i>italic</i> world</div>""")
self.assertNotIn('<h1>', result)
# The expected rendering should correspond to the native representation of an OrderedDict:
# "StructValue([('title', u'Hello'), ('body', <wagtail.wagtailcore.rich_text.RichText object at 0xb12d5eed>)])"
# - give or take some quoting differences between Python versions
self.assertIn('StructValue', result)
self.assertIn('title', result)
self.assertIn('Hello', result)
def test_render_structvalue_with_extra_context(self):
block = SectionBlock()