From 14d5070cb6c786f9cb3562a39828b1ce13b68753 Mon Sep 17 00:00:00 2001 From: Archmonger <16909269+Archmonger@users.noreply.github.com> Date: Sat, 25 Jun 2022 19:02:03 -0700 Subject: [PATCH 1/8] Fix Django ORM usage within components --- src/django_idom/layout.py | 18 ++++++++++++++++++ src/django_idom/websocket/consumer.py | 5 +++-- 2 files changed, 21 insertions(+), 2 deletions(-) create mode 100644 src/django_idom/layout.py diff --git a/src/django_idom/layout.py b/src/django_idom/layout.py new file mode 100644 index 00000000..a4fbcdac --- /dev/null +++ b/src/django_idom/layout.py @@ -0,0 +1,18 @@ +import os + +from idom.core.layout import Layout, LayoutUpdate + + +class DjangoLayout(Layout): + """Fixes Django ORM usage within components. + These issues are caused by async/sync mixed context limitations in the ORM. + Without this, `SynchronousOnlyOperation` exceptions occur when using the ORM in IDOM components. + This may be fixed in a future version, such as Django 5.0.""" + + def _create_layout_update(self, old_state) -> LayoutUpdate: + """Create a layout update, but set ALLOW ASYNC UNSAFE flags prior. + This allows the Django ORM to be used within components.""" + os.environ["DJANGO_ALLOW_ASYNC_UNSAFE"] = "true" + layout_update = super()._create_layout_update(old_state) + os.environ.pop("DJANGO_ALLOW_ASYNC_UNSAFE") + return layout_update diff --git a/src/django_idom/websocket/consumer.py b/src/django_idom/websocket/consumer.py index aa988afa..9a54472e 100644 --- a/src/django_idom/websocket/consumer.py +++ b/src/django_idom/websocket/consumer.py @@ -8,11 +8,12 @@ from channels.auth import login from channels.db import database_sync_to_async as convert_to_async from channels.generic.websocket import AsyncJsonWebsocketConsumer -from idom.core.layout import Layout, LayoutEvent +from idom.core.layout import LayoutEvent from idom.core.serve import serve_json_patch from django_idom.config import IDOM_REGISTERED_COMPONENTS from django_idom.hooks import IdomWebsocket, WebsocketContext +from django_idom.layout import DjangoLayout _logger = logging.getLogger(__name__) @@ -73,7 +74,7 @@ async def _run_dispatch_loop(self): self._idom_recv_queue = recv_queue = asyncio.Queue() try: await serve_json_patch( - Layout(WebsocketContext(component_instance, value=socket)), + DjangoLayout(WebsocketContext(component_instance, value=socket)), self.send_json, recv_queue.get, ) From d8c6825d2468e91de6cdbd7ad06c96b3efb6222b Mon Sep 17 00:00:00 2001 From: Archmonger <16909269+Archmonger@users.noreply.github.com> Date: Sat, 25 Jun 2022 19:11:59 -0700 Subject: [PATCH 2/8] pin selenium version --- requirements/test-env.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements/test-env.txt b/requirements/test-env.txt index 6f2e151e..61ee65ee 100644 --- a/requirements/test-env.txt +++ b/requirements/test-env.txt @@ -1,3 +1,3 @@ django -selenium +selenium <= 4.2.0 twisted From 5e65234dcc19ee5e2ba1a69018ff1d7016a70dc4 Mon Sep 17 00:00:00 2001 From: Archmonger <16909269+Archmonger@users.noreply.github.com> Date: Sat, 25 Jun 2022 20:10:15 -0700 Subject: [PATCH 3/8] add tests --- tests/test_app/components.py | 18 +++++++++++++++ tests/test_app/migrations/0001_initial.py | 28 +++++++++++++++++++++++ tests/test_app/models.py | 8 +++++++ tests/test_app/templates/base.html | 1 + tests/test_app/tests/test_components.py | 4 ++++ 5 files changed, 59 insertions(+) create mode 100644 tests/test_app/migrations/0001_initial.py create mode 100644 tests/test_app/models.py diff --git a/tests/test_app/components.py b/tests/test_app/components.py index 2efd878c..34736271 100644 --- a/tests/test_app/components.py +++ b/tests/test_app/components.py @@ -1,3 +1,5 @@ +from uuid import uuid4 + import idom import django_idom @@ -71,3 +73,19 @@ def UseLocation(): f"UseLocation: {location}", idom.html.hr(), ) + + +@idom.component +def OrmInComponent(): + from .models import NamedThingy + + NamedThingy.objects.all().delete() + NamedThingy(name=f"foo-{uuid4()}").save() + model = NamedThingy.objects.all() + success = bool(model) + + return idom.html.div( + {"id": "orm-in-component", "data-success": success}, + f"OrmInComponent: {model}", + idom.html.hr(), + ) diff --git a/tests/test_app/migrations/0001_initial.py b/tests/test_app/migrations/0001_initial.py new file mode 100644 index 00000000..94c016db --- /dev/null +++ b/tests/test_app/migrations/0001_initial.py @@ -0,0 +1,28 @@ +# Generated by Django 4.0.5 on 2022-06-26 02:24 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [] + + operations = [ + migrations.CreateModel( + name="NamedThingy", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("name", models.CharField(max_length=255)), + ], + ), + ] diff --git a/tests/test_app/models.py b/tests/test_app/models.py new file mode 100644 index 00000000..3d3981c0 --- /dev/null +++ b/tests/test_app/models.py @@ -0,0 +1,8 @@ +from django.db import models + + +class NamedThingy(models.Model): + def __str__(self): + return self.name + + name = models.CharField(max_length=255) diff --git a/tests/test_app/templates/base.html b/tests/test_app/templates/base.html index 0c52a8cc..d98676d5 100644 --- a/tests/test_app/templates/base.html +++ b/tests/test_app/templates/base.html @@ -19,6 +19,7 @@