django-unicorn: parent components (i) do not refresh children html and (ii) break when used as direct views
This could probably be two separate github issues, but I keep them together just to make things easier. I break them into section below though.
I’ve reproduced these errors in several environments:
- python 3.9 & 3.11
- django 4.2.7 & 5.0.2
- django-unicorn 0.59.0
- pip & conda-forge installs
And I’m using Chrome for my browser
issue 1: html not refreshing
I was struggling with this in a personal project, so I went back and was actually able to reproduce the issue with the example from the docs. I built the parent component from this example in the docs exactly, and only added one line:
# added to `updated_search` method in `filter.py`
print(f"'{query}' matches {len(self.parent.books)} books")
Here are the minimal django project files. Note, sqlite file has everything loaded already, so you can just use python manage.py runserver
to run the app:
mysite_example1.zip
Once on the books/
page, you can see that the unicorn calls & backend is working as expected but the page doesn’t refresh:
No matter what I try, I can’t get the html to update when the filter
component is used.
issue 2: parent components can’t be direct views
This is the exact same app as before, except I’m using TableView.as_view()
instead of adding the component to an index.html
:
mysite_example2.zip
When you first spin up your django server, the view works as expected (except for what I describe in issue 1). However, once the page is refreshed and used a 2nd time, the page fails and cannot be loaded:
^^ note in the logs that this happens after a page refresh. This is consistent and happening in my personal project as well.
Here’s the full error traceback:
Internal Server Error: /books/
Traceback (most recent call last):
File "C:\Users\nxj625\AppData\Local\anaconda3\envs\uni2\Lib\site-packages\django\core\handlers\exception.py", line 55, in inner
response = get_response(request)
^^^^^^^^^^^^^^^^^^^^^
File "C:\Users\nxj625\AppData\Local\anaconda3\envs\uni2\Lib\site-packages\django\core\handlers\base.py", line 220, in _get_response
response = response.render()
^^^^^^^^^^^^^^^^^
File "<decorator-gen-2>", line 2, in render
File "C:\Users\nxj625\AppData\Local\anaconda3\envs\uni2\Lib\site-packages\django_unicorn\decorators.py", line 20, in timed
result = func(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^
File "C:\Users\nxj625\AppData\Local\anaconda3\envs\uni2\Lib\site-packages\django_unicorn\components\unicorn_template_response.py", line 125, in render
response = super().render()
^^^^^^^^^^^^^^^^
File "C:\Users\nxj625\AppData\Local\anaconda3\envs\uni2\Lib\site-packages\django\template\response.py", line 114, in render
self.content = self.rendered_content
^^^^^^^^^^^^^^^^^^^^^
File "C:\Users\nxj625\AppData\Local\anaconda3\envs\uni2\Lib\site-packages\django\template\response.py", line 92, in rendered_content
return template.render(context, self._request)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\Users\nxj625\AppData\Local\anaconda3\envs\uni2\Lib\site-packages\django\template\backends\django.py", line 61, in render
return self.template.render(context)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\Users\nxj625\AppData\Local\anaconda3\envs\uni2\Lib\site-packages\django\template\base.py", line 171, in render
return self._render(context)
^^^^^^^^^^^^^^^^^^^^^
File "C:\Users\nxj625\AppData\Local\anaconda3\envs\uni2\Lib\site-packages\django\template\base.py", line 163, in _render
return self.nodelist.render(context)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\Users\nxj625\AppData\Local\anaconda3\envs\uni2\Lib\site-packages\django\template\base.py", line 1000, in render
return SafeString("".join([node.render_annotated(context) for node in self]))
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\Users\nxj625\AppData\Local\anaconda3\envs\uni2\Lib\site-packages\django\template\base.py", line 1000, in <listcomp>
return SafeString("".join([node.render_annotated(context) for node in self]))
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\Users\nxj625\AppData\Local\anaconda3\envs\uni2\Lib\site-packages\django\template\base.py", line 961, in render_annotated
return self.render(context)
^^^^^^^^^^^^^^^^^^^^
File "C:\Users\nxj625\AppData\Local\anaconda3\envs\uni2\Lib\site-packages\django_unicorn\templatetags\unicorn.py", line 179, in render
self.view = UnicornView.create(
^^^^^^^^^^^^^^^^^^^
File "<decorator-gen-19>", line 2, in create
File "C:\Users\nxj625\AppData\Local\anaconda3\envs\uni2\Lib\site-packages\django_unicorn\decorators.py", line 20, in timed
result = func(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^
File "C:\Users\nxj625\AppData\Local\anaconda3\envs\uni2\Lib\site-packages\django_unicorn\components\unicorn_view.py", line 843, in create
cached_component._cache_component(parent=parent, component_args=component_args, **kwargs)
File "C:\Users\nxj625\AppData\Local\anaconda3\envs\uni2\Lib\site-packages\django_unicorn\components\unicorn_view.py", line 411, in _cache_component
cache_full_tree(self)
File "C:\Users\nxj625\AppData\Local\anaconda3\envs\uni2\Lib\site-packages\django_unicorn\cacher.py", line 104, in cache_full_tree
with CacheableComponent(root) as caching:
File "C:\Users\nxj625\AppData\Local\anaconda3\envs\uni2\Lib\site-packages\django_unicorn\cacher.py", line 40, in __enter__
if component.component_id in self._state:
^^^^^^^^^^^^^^^^^^^^^^
AttributeError: 'PointerUnicornView' object has no attribute 'component_id'
About this issue
- Original URL
- State: closed
- Created 4 months ago
- Comments: 15 (15 by maintainers)
This got fixed in 0.60.0.
I think I have solved for all of these issues in https://github.com/adamghill/django-unicorn/pull/663.
Interestingly in my testing now, having the HTML comment at the top of a component does not seem to be a problem. I could have sworn it was causing an issue of some kind, but it might have been a red herring, so apologies if that caused any concern for you!
I am going to try to get another PR mergeable for this release, but should get a new version published by the end of this weekend.
Ok, I think there are two things going on for issue 1:
because
filter
is a child component that interacts with the parent, you will need to callself.parent.force_render = True
when changing the parentthe html comments at the top of the templates are messing with the morphdom algorithm (morphdom requires a tree structure with one root node; since HTML comments are considered a node, there are two roots in your templates and morphdom gets confused). Moving the comment inside a root node should make it work as expected.
Here you can see after I made those changes, things seem to work as expected: https://github.com/adamghill/django-unicorn/assets/317045/9a2fd932-027e-4cb3-a4b5-f56538e8b96a
Let me know if you run into any other problems or what I said above doesn’t make sense!
force_render
is mentioned at https://www.django-unicorn.com/docs/views/#force-render, however I need to update the examples to include it. It should also be mentioned in the child components section since that is when it’s needed most often.I should also add a warning to the docs about HTML comments at the top of a component template since that has bitten me before as well. Even better, it would be nice to figure out how to handle that so it doesn’t cause an issue at all. I’ll dig into this some more today.
I have not checked into issue 2 yet, but I’ll look into it next.