Skip to content

Commit 2fb688e

Browse files
authored
fix variable in context (#16)
1 parent 3516472 commit 2fb688e

File tree

4 files changed

+89
-22
lines changed

4 files changed

+89
-22
lines changed

docs/source/slot.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,21 @@ Notes:
118118
<a href="/">Post 2</a>
119119
```
120120

121+
Or you can use django for loop do this:
122+
123+
```django
124+
{% component 'blog' as component %}
125+
{% call blog_component.header %}
126+
<a href="/">My Site</a>
127+
{% endcall %}
128+
{% for post in qs %}
129+
{% call component.posts %}
130+
<a href="/">{{ post.title }}</a>
131+
{% endcall %}
132+
{% endfor %}
133+
{% endcomponent %}
134+
```
135+
121136
## Connect other component in the slot
122137

123138
This is the **killer feature**, so please read it carefully.

src/django_viewcomponent/fields.py

Lines changed: 11 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,13 @@ class FieldValue:
55
def __init__(
66
self,
77
nodelist,
8+
field_context,
89
dict_data: dict,
910
component: None,
1011
parent_component=None,
1112
):
1213
self._nodelist = nodelist
14+
self._field_context = field_context
1315
self._dict_data = dict_data
1416
self._component = component
1517
self._parent_component = parent_component
@@ -48,7 +50,7 @@ def render(self):
4850
# self._component is Component class
4951
return self._render_for_component_cls(self._component)
5052
elif self._component is None:
51-
return self._nodelist.render(self._parent_component.component_context)
53+
return self._nodelist.render(self._field_context)
5254
else:
5355
raise ValueError(f"Invalid component variable {self._component}")
5456

@@ -102,45 +104,40 @@ def filled(self):
102104
def required(self):
103105
return self._required
104106

105-
def handle_call(self, nodelist, **kwargs):
107+
def handle_call(self, nodelist, context, **kwargs):
106108
raise NotImplementedError("You must implement the `handle_call` method.")
107109

108110

109111
class RendersOneField(BaseSlotField):
110-
def handle_call(self, nodelist, **kwargs):
112+
def handle_call(self, nodelist, context, **kwargs):
111113
value_instance = FieldValue(
112114
nodelist=nodelist,
115+
field_context=context,
113116
dict_data={**kwargs},
114117
component=self._component,
115118
parent_component=self.parent_component,
116119
)
117120

121+
self._value = value_instance.render()
118122
self._filled = True
119-
self._value = value_instance
120123

121124

122125
class FieldValueListWrapper:
123-
"""
124-
This helps render FieldValue eagerly when component template has
125-
{% for panel in self.panels.value %}, this can avoid issues if `panel` of the for loop statement
126-
# override context variables in some cases.
127-
"""
128-
129126
def __init__(self):
130127
self.data = []
131128

132129
def append(self, value):
133130
self.data.append(value)
134131

135132
def __iter__(self):
136-
for field_value in self.data:
137-
yield field_value.render()
133+
yield from self.data
138134

139135

140136
class RendersManyField(BaseSlotField):
141-
def handle_call(self, nodelist, **kwargs):
137+
def handle_call(self, nodelist, context, **kwargs):
142138
value_instance = FieldValue(
143139
nodelist=nodelist,
140+
field_context=context,
144141
dict_data={**kwargs},
145142
component=self._component,
146143
parent_component=self.parent_component,
@@ -149,5 +146,5 @@ def handle_call(self, nodelist, **kwargs):
149146
if self._value is None:
150147
self._value = FieldValueListWrapper()
151148

152-
self._value.append(value_instance)
149+
self._value.append(value_instance.render())
153150
self._filled = True

src/django_viewcomponent/templatetags/viewcomponent_tags.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ def render(self, context):
7575
)
7676

7777
resolved_kwargs["nodelist"] = self.nodelist
78+
resolved_kwargs["context"] = context
7879

7980
component_token, field_token = self.args[0].token.split(".")
8081
component_instance = FilterExpression(component_token, self.parser).resolve(

tests/test_render_field.py

Lines changed: 62 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,6 @@
1010

1111
@pytest.mark.django_db
1212
class TestRenderFieldComponentContextLogic:
13-
"""
14-
HeaderComponent.get_context_data add extra context data
15-
16-
We can still access the value via {{ site_name }}
17-
"""
18-
1913
class HeaderComponent(component.Component):
2014
def __init__(self, classes, **kwargs):
2115
self.classes = classes
@@ -43,15 +37,19 @@ def __init__(self, post, **kwargs):
4337
"""
4438

4539
class BlogComponent(component.Component):
46-
header = RendersOneField(required=True, component="header")
47-
posts = RendersManyField(required=True, component="post")
40+
header = RendersOneField(component="header")
41+
posts = RendersManyField(component="post")
42+
wrappers = RendersManyField()
4843

4944
template = """
5045
{% load viewcomponent_tags %}
5146
{{ self.header.value }}
5247
{% for post in self.posts.value %}
5348
{{ post }}
5449
{% endfor %}
50+
{% for wrapper in self.wrappers.value %}
51+
{{ wrapper }}
52+
{% endfor %}
5553
"""
5654

5755
@pytest.fixture(autouse=True)
@@ -61,6 +59,12 @@ def register_component(self):
6159
component.registry.register("post", self.PostComponent)
6260

6361
def test_field_context_logic(self):
62+
"""
63+
HeaderComponent.get_context_data add extra context data
64+
65+
We can still access the value via {{ site_name }}
66+
"""
67+
6468
for i in range(5):
6569
title = f"test {i}"
6670
description = f"test {i}"
@@ -104,6 +108,56 @@ def test_field_context_logic(self):
104108
"""
105109
assert_dom_equal(expected, rendered)
106110

111+
def test_field_context_logic_2(self):
112+
"""
113+
can still concatenate the HTML manually and pass to slot field
114+
"""
115+
for i in range(5):
116+
title = f"test {i}"
117+
description = f"test {i}"
118+
Post.objects.create(title=title, description=description)
119+
120+
qs = Post.objects.all()
121+
122+
template = Template(
123+
"""
124+
{% load viewcomponent_tags %}
125+
{% component 'blog' as component %}
126+
{% call component.header classes='text-lg' %}
127+
<a href="/"> {{ site_name }} </a>
128+
{% endcall %}
129+
{% for post in qs %}
130+
{% call component.wrappers %}
131+
<h1>{{ post.title }}</h1>
132+
<div>{{ post.description }}</div>
133+
{% endcall %}
134+
{% endfor %}
135+
{% endcomponent %}
136+
""",
137+
)
138+
rendered = template.render(Context({"qs": qs}))
139+
expected = """
140+
<h1 class="text-lg">
141+
<a href="/">My Site</a>
142+
</h1>
143+
144+
<h1>test 0</h1>
145+
<div>test 0</div>
146+
147+
<h1>test 1</h1>
148+
<div>test 1</div>
149+
150+
<h1>test 2</h1>
151+
<div>test 2</div>
152+
153+
<h1>test 3</h1>
154+
<div>test 3</div>
155+
156+
<h1>test 4</h1>
157+
<div>test 4</div>
158+
"""
159+
assert_dom_equal(expected, rendered)
160+
107161

108162
@pytest.mark.django_db
109163
class TestRenderFieldComponentParameterString:

0 commit comments

Comments
 (0)