adamghill / django-unicorn

The magical reactive component framework for Django ✨
https://www.django-unicorn.com
MIT License
2.33k stars 118 forks source link

[Bug] Exception(de)serializing models with ForeignKeys when loaded outside of 'mount' #732

Open s1liconcow opened 1 week ago

s1liconcow commented 1 week ago

Bug Description

I'm trying to make a view where the user can select a model and edit it. If I load the model in mount(), this works as expected. However if I try to load the model in another method, it doesn't seem to be able to deserialize it when the user saves (or does anything):

Internal Server Error: /unicorn/message/test
Traceback (most recent call last):
  File "/home/david/projects/my_frontend/venv/lib/python3.10/site-packages/django/core/handlers/exception.py", line 55, in inner
    response = get_response(request)
  File "/home/david/projects/my_frontend/venv/lib/python3.10/site-packages/django/core/handlers/base.py", line 197, in _get_response
    response = wrapped_callback(request, *callback_args, **callback_kwargs)
  File "/home/david/projects/my_frontend/venv/lib/python3.10/site-packages/decorator.py", line 232, in fun
    return caller(func, *(extras + args), **kw)
  File "/home/david/projects/my_frontend/venv/lib/python3.10/site-packages/django_unicorn/decorators.py", line 20, in timed
    result = func(*args, **kwargs)
  File "/home/david/projects/my_frontend/venv/lib/python3.10/site-packages/django_unicorn/views/__init__.py", line 49, in wrapped_view
    return view_func(*args, **kwargs)
  File "/home/david/projects/my_frontend/venv/lib/python3.10/site-packages/django/utils/decorators.py", line 188, in _view_wrapper
    result = _process_exception(request, e)
  File "/home/david/projects/my_frontend/venv/lib/python3.10/site-packages/django/utils/decorators.py", line 186, in _view_wrapper
    response = view_func(request, *args, **kwargs)
  File "/home/david/projects/my_frontend/venv/lib/python3.10/site-packages/django/utils/decorators.py", line 188, in _view_wrapper
    result = _process_exception(request, e)
  File "/home/david/projects/my_frontend/venv/lib/python3.10/site-packages/django/utils/decorators.py", line 186, in _view_wrapper
    response = view_func(request, *args, **kwargs)
  File "/home/david/projects/my_frontend/venv/lib/python3.10/site-packages/django/views/decorators/http.py", line 64, in inner
    return func(request, *args, **kwargs)
  File "/home/david/projects/my_frontend/venv/lib/python3.10/site-packages/django_unicorn/views/__init__.py", line 556, in message
    json_result = _handle_component_request(request, component_request)
  File "/home/david/projects/my_frontend/venv/lib/python3.10/site-packages/django_unicorn/views/__init__.py", line 409, in _handle_component_request
    return _process_component_request(request, component_request)
  File "/home/david/projects/my_frontend/venv/lib/python3.10/site-packages/django_unicorn/views/__init__.py", line 105, in _process_component_request
    set_property_from_data(component, property_name, property_value)
  File "/home/david/projects/my_frontend/venv/lib/python3.10/site-packages/decorator.py", line 232, in fun
    return caller(func, *(extras + args), **kw)
  File "/home/david/projects/my_frontend/venv/lib/python3.10/site-packages/django_unicorn/decorators.py", line 20, in timed
    result = func(*args, **kwargs)
  File "/home/david/projects/my_frontend/venv/lib/python3.10/site-packages/django_unicorn/views/utils.py", line 45, in set_property_from_data
    set_property_from_data(field, key, key_value)
  File "/home/david/projects/my_frontend/venv/lib/python3.10/site-packages/decorator.py", line 232, in fun
    return caller(func, *(extras + args), **kw)
  File "/home/david/projects/my_frontend/venv/lib/python3.10/site-packages/django_unicorn/decorators.py", line 20, in timed
    result = func(*args, **kwargs)
  File "/home/david/projects/my_frontend/venv/lib/python3.10/site-packages/django_unicorn/views/utils.py", line 70, in set_property_from_data
    setattr(component_or_field, name, value)
  File "/home/david/projects/my_frontend/venv/lib/python3.10/site-packages/django/db/models/fields/related_descriptors.py", line 287, in __set__
    raise ValueError(
ValueError: Cannot assign "1": "Resource.type" must be a "ResourceType" instance.

Expected behaviour

Can use a model loaded outside of 'mount'.

Screenshots / Screenrecords

see below

Steps to reproduce

Example repro

from django_unicorn.components import UnicornView, QuerySetType
from frontend.models import Resource, ResourceType

class TestView(UnicornView):
    resource: Resource = None
    resource_types: QuerySetType[ResourceType] = None

    def mount(self):
        company = self.request.user.userprofile.company
        self.resource_types = ResourceType.objects.filter(company=company)

    def load(self):
        self.resource = Resource.objects.get(id=1)

    def save(self):
        self.resource.save()

The view doesn't really matter but:


{% load unicorn %}
<html>
<head>
        {% unicorn_scripts %}
</head>
<div unicorn:view>
    <button class="btn" unicorn:click="load">load</button>
    <div class="form-control mb-4">
        <input unicorn:model.defer="resource.name"></input>
        {{ resource.name }}
        <label for="edit_resource_type" class="label">
            <span class="label-text">Resource Type:</span>
        </label>
        <select id="edit_resource_type" id="resource" unicorn:model.defer="resource.type" required
            class="select select-bordered w-full">
            <option value="">Select a type</option>
            {% for type in resource_types %}
            <option value="{{ type.pk }}">{{ type.name }}</option>
            {% endfor %}
        </select>
    </div>
    <button class="btn" unicorn:click="save">save</button>
</div>
</html>

Clicking 'save' will cause it to throw the above exception. The Resource.type is a ForeignKey to ResourceType.

This, however, works:

from django_unicorn.components import UnicornView, QuerySetType
from frontend.models import Resource, ResourceType

class TestView(UnicornView):
    resource: Resource = None
    resource_types: QuerySetType[ResourceType] = None

    def mount(self):
        company = self.request.user.userprofile.company
        self.resource_types = ResourceType.objects.filter(company=company)
        self.resource = Resource.objects.get(id=1)

    def save(self):
        self.resource.save()

What browsers are you seeing the problem on?

Chrome

👀 Have you checked for similar open issues?

Code of Conduct

Are you willing to work on this issue ?

Yes I am willing to submit a PR!

s1liconcow commented 1 week ago

I haven't quite figured out why this is broken but fwiw it seems you can 'hack' this to work with:

from django_unicorn.components import UnicornView, QuerySetType
from frontend.models import Resource, ResourceType

class TestView(UnicornView):
    resource: Resource = None
    resources: QuerySetType[Resource] = None

    def mount(self):
        self.resources = Resource.objects.all()
        self.resource = self.resources[0]

    def load(self):
        self.resource = self.resources[1]

    def save(self):
        print(self.resource.name)