Skip to content

Commit 5c6ed76

Browse files
fix: self.parent is a stale copy (unicorn_view.py)
1 parent adf9647 commit 5c6ed76

File tree

2 files changed

+66
-4
lines changed

2 files changed

+66
-4
lines changed

src/django_unicorn/components/unicorn_view.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -938,6 +938,16 @@ def _get_component_class(module_name: str, class_name: str) -> type[Component]:
938938
cached_component.component_args = component_args
939939
cached_component.component_kwargs = kwargs
940940

941+
# Update self.parent to the exact in-memory parent being rendered now.
942+
# restore_from_cache() sets self.parent to a *copy* restored from the
943+
# Django cache. That copy has correct data (thanks to cache_full_tree
944+
# being called in _process_request before rendering), but it is a
945+
# different Python object. Pointing self.parent at the live parent
946+
# object means child methods/templates always see current state even for
947+
# changes that happen during the same rendering pass (issue #685).
948+
if parent is not None:
949+
cached_component.parent = parent
950+
941951
# TODO: How should args be handled?
942952
# Set kwargs onto the cached component
943953
for key, value in kwargs.items():

tests/views/message/test_child_state_propagation.py

Lines changed: 56 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,15 @@
11
"""
2-
Regression tests for issue #666: updating child components via a parent method.
2+
Regression tests for:
3+
- Issue #666: updating child components via a parent method.
4+
- Issue #685: self.parent reference not updating in child components.
35
46
When a parent component's method modifies children's state (e.g., setting
57
is_editing=True on all children), the changes must be persisted to cache before
68
the parent re-renders so that template tags retrieve the updated children.
9+
10+
Additionally, when children are rendered via template tags during a parent's
11+
re-render, their self.parent must point to the exact in-memory parent object so
12+
that they always see the parent's current state.
713
"""
814

915
import shortuuid
@@ -22,12 +28,16 @@ class ChildView(UnicornView):
2228

2329
class ParentView(UnicornView):
2430
template_name = "templates/test_component.html"
31+
current_page: str = "root"
2532

2633
def begin_edit_all(self):
2734
for child in self.children:
2835
if hasattr(child, "is_editing"):
2936
child.is_editing = True
3037

38+
def walk(self, page: str):
39+
self.current_page = page
40+
3141

3242
PARENT_NAME = "tests.views.message.test_child_state_propagation.ParentView"
3343
CHILD_NAME = "tests.views.message.test_child_state_propagation.ChildView"
@@ -79,6 +89,50 @@ def test_parent_method_child_state_persisted_in_cache(client):
7989
)
8090

8191

92+
def test_child_self_parent_is_in_memory_parent_after_create(client):
93+
"""
94+
Issue #685: when UnicornView.create() returns a cached child component,
95+
its self.parent must be the *exact same Python object* as the in-memory parent
96+
being rendered, not a stale copy restored from the Django cache.
97+
98+
We verify this by calling create() with an updated in-memory parent and
99+
checking that the returned child's .parent IS that same object.
100+
"""
101+
from django.test import RequestFactory
102+
103+
from django_unicorn.components import UnicornView
104+
105+
parent_id = shortuuid.uuid()[:8]
106+
child_id = f"{parent_id}:{CHILD_NAME}"
107+
108+
parent = ParentView(component_id=parent_id, component_name=PARENT_NAME)
109+
child = ChildView(component_id=child_id, component_name=CHILD_NAME, parent=parent)
110+
111+
# Persist to cache (simulates initial page load state)
112+
cache_full_tree(parent)
113+
114+
# Simulate the parent changing state (e.g. walk() was called)
115+
parent.current_page = "chapter-2"
116+
117+
request = RequestFactory().get("/")
118+
119+
# When the parent template re-renders, the template tag calls create() for the child
120+
retrieved_child = UnicornView.create(
121+
component_id=child_id,
122+
component_name=CHILD_NAME,
123+
parent=parent,
124+
request=request,
125+
)
126+
127+
assert retrieved_child.parent is parent, (
128+
"self.parent in the child should be the exact in-memory parent object, "
129+
"not a stale cache-restored copy (issue #685)."
130+
)
131+
assert retrieved_child.parent.current_page == "chapter-2", (
132+
"self.parent.current_page should reflect the parent's current in-memory state."
133+
)
134+
135+
82136
def test_parent_method_multiple_children_all_updated_in_cache(client):
83137
"""
84138
All children must be updated in cache, not just the first one.
@@ -115,6 +169,4 @@ def test_parent_method_multiple_children_all_updated_in_cache(client):
115169
for child in [child1, child2, child3]:
116170
cached = cache.get(child.component_cache_key)
117171
assert cached is not None
118-
assert cached.is_editing is True, (
119-
f"Child {child.component_id} should have is_editing=True after begin_edit_all"
120-
)
172+
assert cached.is_editing is True, f"Child {child.component_id} should have is_editing=True after begin_edit_all"

0 commit comments

Comments
 (0)