Fix infinite recursion bug (fixes #68) (#74)

* Add regression test for recursion bug, #68

* Only allow slots to access slot nodelists provided to their immediate parent component to prevent infinite recursions.

* Fix import ordering bug in test

* Add slot.super to docs

Remove unused imports

* Bump version

Co-authored-by: rbeard0330 <@dul2k3BKW6m>
This commit is contained in:
rbeard0330 2021-07-06 11:56:49 -04:00 committed by GitHub
parent 7f4661922a
commit e3c9ac76ce
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 74 additions and 13 deletions

View file

@ -214,7 +214,7 @@ Components support something called slots. They work a lot like Django blocks, b
{% slot "header" %}Calendar header{% endslot %} {% slot "header" %}Calendar header{% endslot %}
</div> </div>
<div class="body"> <div class="body">
{% slot "body" %}Calendar body{% endslot %} {% slot "body" %}Today's date is <span>{{ date }}</span>{% endslot %}
</div> </div>
</div> </div>
``` ```
@ -222,8 +222,8 @@ Components support something called slots. They work a lot like Django blocks, b
When using the component, you specify what slots you want to fill and where you want to use the defaults from the template. It looks like this: When using the component, you specify what slots you want to fill and where you want to use the defaults from the template. It looks like this:
```htmldjango ```htmldjango
{% component_block "calendar" %} {% component_block "calendar" date="2020-06-06" %}
{% slot "body" %}Today's date is <span>{{ date }}</span>{% endslot %} {% slot "body" %}Can you belive it's already <span>{{ date }}</span>??{% endslot %}
{% endcomponent_block %} {% endcomponent_block %}
``` ```
@ -235,7 +235,7 @@ Since the header block is unspecified, it's taken from the base template. If you
Calendar header Calendar header
</div> </div>
<div class="body"> <div class="body">
Today's date is <span>2020-06-06</span> Can you believe it's already <span>2020-06-06</span>??
</div> </div>
</div> </div>
@ -243,7 +243,28 @@ Since the header block is unspecified, it's taken from the base template. If you
As you can see, component slots lets you write reusable containers, that you fill out when you use a component. This makes for highly reusable components, that can be used in different circumstances. As you can see, component slots lets you write reusable containers, that you fill out when you use a component. This makes for highly reusable components, that can be used in different circumstances.
# Component context If you want to include a slot's default content while adding additional content, you can call `slot.super` to insert the base content, which works similarly to `block.super`.
```htmldjango
{% component_block "calendar" date="2020-06-06" %}
{% slot "body" %}{ slot.super }. Have a great day!{% endslot %}
{% endcomponent_block %}
```
Produces:
```html
<div class="calendar-component">
<div class="header">
Calendar header
</div>
<div class="body">
Today's date is <span>2020-06-06</span>. Have a great day!
</div>
</div>
```
# Component context and scope
By default, components can access context variables from the parent template, just like templates that are included with the `{% include %}` tag. Just like with `{% include %}`, if you don't want the component template to have access to the parent context, add `only` to the end of the `{% component %}` (or `{% component_block %}` tag): By default, components can access context variables from the parent template, just like templates that are included with the `{% include %}` tag. Just like with `{% include %}`, if you don't want the component template to have access to the parent context, add `only` to the end of the `{% component %}` (or `{% component_block %}` tag):
@ -253,7 +274,8 @@ By default, components can access context variables from the parent template, ju
NOTE: `{% csrf_token %}` tags need access to the top-level context, and they will not function properly if they are rendered in a component that is called with the `only` modifier. NOTE: `{% csrf_token %}` tags need access to the top-level context, and they will not function properly if they are rendered in a component that is called with the `only` modifier.
Components can also access the outer context in their context methods by accessing the property `outer_context`. Components can also access the outer context in their context methods by accessing the property `outer_context`.
# Available settings # Available settings

View file

@ -114,8 +114,7 @@ class Component(metaclass=SimplifiedInterfaceMediaDefiningClass):
def render(self, context): def render(self, context):
template_name = self.template(context) template_name = self.template(context)
instance_template = self.get_processed_template(template_name) instance_template = self.get_processed_template(template_name)
active_slots = {**context.get(ACTIVE_SLOT_CONTEXT_KEY, {}), **self.slots} with context.update({ACTIVE_SLOT_CONTEXT_KEY: self.slots}):
with context.update({ACTIVE_SLOT_CONTEXT_KEY: active_slots}):
return instance_template.render(context) return instance_template.render(context)
class Media: class Media:

View file

@ -1,4 +1,3 @@
import inspect
from collections import defaultdict from collections import defaultdict
from django import template from django import template
@ -7,7 +6,7 @@ from django.template.base import Node, NodeList, TemplateSyntaxError, TokenType
from django.template.library import parse_bits from django.template.library import parse_bits
from django.utils.safestring import mark_safe from django.utils.safestring import mark_safe
from django_components.component import ACTIVE_SLOT_CONTEXT_KEY, Component, registry from django_components.component import ACTIVE_SLOT_CONTEXT_KEY, registry
from django_components.middleware import CSS_DEPENDENCY_PLACEHOLDER, JS_DEPENDENCY_PLACEHOLDER from django_components.middleware import CSS_DEPENDENCY_PLACEHOLDER, JS_DEPENDENCY_PLACEHOLDER
register = template.Library() register = template.Library()

View file

@ -3,7 +3,7 @@ import os
from setuptools import find_packages, setup from setuptools import find_packages, setup
VERSION = '0.14' VERSION = '0.15'
setup( setup(
name="django_components", name="django_components",

View file

@ -215,3 +215,45 @@ class ComponentIsolationTests(SimpleTestCase):
</custom-template> </custom-template>
""" """
) )
class RecursiveSlotNameTest(SimpleTestCase):
def setUp(self):
@component.register('outer')
class OuterComponent(component.Component):
def template(self, context):
return "slotted_template.html"
@component.register('inner')
class InnerComponent(component.Component):
def template(self, context):
return "slotted_template.html"
def test_no_infinite_recursion_when_slot_name_is_reused(self):
template = Template(
"""
{% load component_tags %}
{% component_block "outer" %}
{% slot "header" %}
{% component_block "inner" %}{% endcomponent_block %}
{% endslot %}
{% endcomponent_block %}
"""
)
self.assertHTMLEqual(
template.render(Context({})),
"""
<custom-template>
<header>
<custom-template>
<header>Default header</header>
<main>Default main</main>
<footer>Default footer</footer>
</custom-template>
</header>
<main>Default main</main>
<footer>Default footer</footer>
</custom-template>
"""
)

View file

@ -2,9 +2,8 @@ from textwrap import dedent
from django.template import Context, Template, TemplateSyntaxError from django.template import Context, Template, TemplateSyntaxError
from django_components import component
from .django_test_setup import * # NOQA from .django_test_setup import * # NOQA
from django_components import component
from .testutils import Django30CompatibleSimpleTestCase as SimpleTestCase from .testutils import Django30CompatibleSimpleTestCase as SimpleTestCase