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:

image

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:

image

^^ 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)

Most upvoted comments

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:

  1. because filter is a child component that interacts with the parent, you will need to call self.parent.force_render = True when changing the parent image

  2. the 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. image

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.