tetra-framework / tetra

Tetra - A full stack component framework for Django using Alpine.js
https://www.tetraframework.com
MIT License
540 stars 14 forks source link

Component should be able to accept django models #40

Closed nerdoc closed 3 months ago

nerdoc commented 1 year ago

When using a Custom User Model in Django, and creating a Component like the following, you run into problems:

...
from myapp.models import User

@default.register
class Example(Component):
    def load(self, user: User, *args, **kwargs) -> None:
        self.user = user
    def doit(self) -> None:
        pass

    template="<button @click="doit()">do it!</button>"

and using it like this:

{% @ myapp.default.example user=request.user / %}

The fact that a custom user model is passed to the load() method, Tetra hickups when unpickling that object. Rendering it when the page is built works fine, but as soon as you click on the "do it" button:

Internal Server Error: /tetra/myapp/default/example/doit
Traceback (most recent call last):
  File "/.../.venv/lib/python3.10/site-packages/tetra/state.py", line 69, in unpickle
    return model.objects.get(pk=data["pk"])
AttributeError: type object 'SimpleLazyObject' has no attribute 'objects'

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/.../.venv/lib/python3.10/site-packages/django/core/handlers/exception.py", line 55, in inner
    response = get_response(request)
  File "/.../.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 "/.../.venv/lib/python3.10/site-packages/tetra/views.py", line 34, in component_method
    component = Component.from_state(data, request)
  File "/.../.venv/lib/python3.10/site-packages/tetra/components/base.py", line 354, in from_state
    component = decode_component(data["state"], request)
  File "/.../.venv/lib/python3.10/site-packages/tetra/state.py", line 253, in decode_component
    state = unpickle_state(gzip.decompress(fernet.decrypt(state_token.encode())))
  File "/.../.venv/lib/python3.10/site-packages/tetra/state.py", line 176, in unpickle_state
    return StateUnpickler(BytesIO(data)).load()
  File "/.../.venv/lib/python3.10/site-packages/tetra/state.py", line 165, in persistent_load
    return pickler.unpickle(data)
  File "/.../.venv/lib/python3.10/site-packages/tetra/state.py", line 70, in unpickle
    except model.DoesNotExist:
AttributeError: type object 'SimpleLazyObject' has no attribute 'DoesNotExist'

With normal modal objects, this is no problem (as long it is not a request, etc, but that's another story).

I think that when pickling a LazyObject, it should resolve the object first - but that's just a wild guess.

nerdoc commented 3 months ago

That's because users should not be passed directly.

Solution: pass user ids: {% @ myapp.default.example user_id=request.user.pk / %}

from myapp.models import User

@default.register
class Example(Component):
    def load(self, user_id: int, *args, **kwargs) -> None:
        self.user = User.objects.get(pk=user_id)

This could be done later automatically. So I'll keep this open until a solution is found.

nerdoc commented 3 months ago

But that's basically what #31 means. So I'm closing this.