Skip to content

Commit

Permalink
Do not send a 304 if a component has a parent. This is because a comp…
Browse files Browse the repository at this point in the history
…onent can call a method on their parent which changes the rendered content for the parent specifically, but not for the child. Fixes #592.
  • Loading branch information
adamghill committed Sep 13, 2023
1 parent 4cc3cac commit e55bb4e
Show file tree
Hide file tree
Showing 4 changed files with 82 additions and 11 deletions.
23 changes: 13 additions & 10 deletions django_unicorn/views/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -260,7 +260,7 @@ def _process_component_request(
key: component_request.data[key] for key in sorted(component_request.data)
}

res = {
result = {
"id": component_request.id,
"data": updated_data,
"errors": component.errors,
Expand All @@ -269,51 +269,54 @@ def _process_component_request(
}

if partial_doms:
res.update({"partials": partial_doms})
result.update({"partials": partial_doms})
else:
hash = generate_checksum(rendered_component)

if (
component_request.hash == hash
and (not return_data or not return_data.value)
and not component.calls
# TODO: Have a strategy to determine when to return a 304 when a component
# has a parent by looking closer at their metadata
and not component.parent
):
raise RenderNotModified()

# Make sure that partials with comments or blank lines before the root element only return the root element
soup = BeautifulSoup(rendered_component, features="html.parser")
rendered_component = str(get_root_element(soup))

res.update(
result.update(
{
"dom": rendered_component,
"hash": hash,
}
)

if return_data:
res.update(
result.update(
{
"return": return_data.get_data(),
}
)

if return_data.redirect:
res.update(
result.update(
{
"redirect": return_data.redirect,
}
)

if return_data.poll:
res.update(
result.update(
{
"poll": return_data.poll,
}
)

parent_component = component.parent
parent_res = res
parent_result = result

while parent_component:
# TODO: Should parent_component.hydrate() be called?
Expand All @@ -339,12 +342,12 @@ def _process_component_request(
}
)

parent_res.update({"parent": parent})
parent_result.update({"parent": parent})
component = parent_component
parent_component = parent_component.parent
parent_res = parent
parent_result = parent

return res
return result


def _handle_component_request(
Expand Down
8 changes: 8 additions & 0 deletions tests/templates/test_component_parent_with_value.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{% load unicorn %}

<div>
--parent--
{% unicorn 'tests.views.message.test_hash.FakeComponentChild' parent=view %}

||value:{{ value }}||
</div>
51 changes: 51 additions & 0 deletions tests/views/message/test_hash.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,25 @@
import shortuuid

from django_unicorn.components import UnicornView
from django_unicorn.utils import generate_checksum
from tests.views.fake_components import FakeComponent
from tests.views.message.test_calls import FakeCallsComponent
from tests.views.message.utils import post_and_get_response


class FakeComponentParent(UnicornView):
template_name = "templates/test_component_parent_with_value.html"

value: int = 0


class FakeComponentChild(UnicornView):
template_name = "templates/test_component_child.html"

def parent_increment(self):
self.parent.value += 1


def test_message_hash_no_change(client):
component_id = shortuuid.uuid()[:8]
component = FakeComponent(
Expand Down Expand Up @@ -232,3 +246,40 @@ def test_message_hash_no_change_but_calls(client):
# check that the response is JSON and not a 304
assert isinstance(response, dict)
assert response.get("calls") == [{"args": [], "fn": "testCall"}]


def test_message_hash_no_change_but_parent(client):
component_id = shortuuid.uuid()[:8]
component = FakeComponentParent(
component_id=component_id,
component_name="tests.views.message.test_hash.FakeComponentParent",
)
component.render()

child = component.children[0]
rendered_child_content = child.render()
child_hash = generate_checksum(rendered_child_content)

assert child.parent.value == 0

data = {}
response = post_and_get_response(
client,
url="/message/tests.views.message.test_hash.FakeComponentChild",
data=data,
action_queue=[
{
"payload": {"name": "parent_increment()"},
"type": "callMethod",
}
],
component_id=child.component_id,
hash=child_hash,
return_response=True,
)

assert response.status_code == 200
assert child.parent.value == 1

rendered_parent_content = child.parent.render()
assert "||value:1||" in rendered_parent_content
11 changes: 10 additions & 1 deletion tests/views/message/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,13 @@


def post_and_get_response(
client, url="", data=None, action_queue=None, component_id=None, hash=None
client,
url="",
data=None,
action_queue=None,
component_id=None,
hash=None,
return_response=False,
):
if not data:
data = {}
Expand All @@ -30,6 +36,9 @@ def post_and_get_response(
content_type="application/json",
)

if return_response:
return response

try:
return response.json()
except TypeError:
Expand Down

0 comments on commit e55bb4e

Please sign in to comment.