From fc8afa68b896ddb1cab195fe1bf02bd7413be908 Mon Sep 17 00:00:00 2001 From: Archmonger <16909269+Archmonger@users.noreply.github.com> Date: Mon, 20 Sep 2021 21:58:47 -0700 Subject: [PATCH 01/39] propogate websocket down --- README.md | 6 ++---- src/django_idom/websocket_consumer.py | 2 +- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index aa57be42..6927c121 100644 --- a/README.md +++ b/README.md @@ -147,8 +147,8 @@ ultimately be referenced by name in `your-template.html`. `your-template.html`. import idom @idom.component -def Hello(greeting_recipient): # component names are camelcase by convention - return Header(f"Hello {greeting_recipient}!") +def Hello(websocket, greeting_recipient): # component names are camelcase by convention + return idom.html.header(f"Hello {greeting_recipient}!") ``` ## `example_app/templates/your-template.html` @@ -165,8 +165,6 @@ idom_component module_name.ComponentName param_1="something" param_2="something- In context this will look a bit like the following... ```jinja - -{% load static %} {% load idom %} diff --git a/src/django_idom/websocket_consumer.py b/src/django_idom/websocket_consumer.py index 31f1aa38..2071fee4 100644 --- a/src/django_idom/websocket_consumer.py +++ b/src/django_idom/websocket_consumer.py @@ -48,7 +48,7 @@ async def _run_dispatch_loop(self): component_kwargs = json.loads(query_dict.get("kwargs", "{}")) try: - component_instance = component_constructor(**component_kwargs) + component_instance = component_constructor(self, **component_kwargs) except Exception: _logger.exception( f"Failed to construct component {component_constructor} " From 4ec52f572571259865ba23f1cb216bb68830c782 Mon Sep 17 00:00:00 2001 From: Archmonger <16909269+Archmonger@users.noreply.github.com> Date: Mon, 20 Sep 2021 22:33:20 -0700 Subject: [PATCH 02/39] update tests --- tests/test_app/components.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/test_app/components.py b/tests/test_app/components.py index f242b9f1..d3452efb 100644 --- a/tests/test_app/components.py +++ b/tests/test_app/components.py @@ -2,12 +2,12 @@ @idom.component -def HelloWorld(): +def HelloWorld(websocket): return idom.html.h1({"id": "hello-world"}, "Hello World!") @idom.component -def Button(): +def Button(websocket): count, set_count = idom.hooks.use_state(0) return idom.html.div( idom.html.button( @@ -22,7 +22,7 @@ def Button(): @idom.component -def ParametrizedComponent(x, y): +def ParametrizedComponent(websocket, x, y): total = x + y return idom.html.h1({"id": "parametrized-component", "data-value": total}, total) @@ -32,5 +32,5 @@ def ParametrizedComponent(x, y): @idom.component -def SimpleBarChart(): +def SimpleBarChart(websocket): return VictoryBar() From 839e8d7d8d105aad0b6cd62107f9daf07bce51e4 Mon Sep 17 00:00:00 2001 From: Archmonger <16909269+Archmonger@users.noreply.github.com> Date: Tue, 21 Sep 2021 00:24:26 -0700 Subject: [PATCH 03/39] add websocket login --- README.md | 9 ++++++--- src/django_idom/websocket_consumer.py | 13 +++++++++++++ tests/test_app/asgi.py | 3 ++- 3 files changed, 21 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 6927c121..d3787c14 100644 --- a/README.md +++ b/README.md @@ -75,14 +75,17 @@ os.environ.setdefault("DJANGO_SETTINGS_MODULE", "test_app.settings") # Fetch ASGI application before importing dependencies that require ORM models. http_asgi_app = get_asgi_application() +from channels.auth import AuthMiddlewareStack from channels.routing import ProtocolTypeRouter, URLRouter application = ProtocolTypeRouter( { "http": http_asgi_app, - "websocket": URLRouter( - # add a path for IDOM's websocket - [IDOM_WEBSOCKET_PATH] + "websocket": AuthMiddlewareStack( + URLRouter( + # add a path for IDOM's websocket + [IDOM_WEBSOCKET_PATH] + ) ), } ) diff --git a/src/django_idom/websocket_consumer.py b/src/django_idom/websocket_consumer.py index 2071fee4..1cd77212 100644 --- a/src/django_idom/websocket_consumer.py +++ b/src/django_idom/websocket_consumer.py @@ -5,6 +5,8 @@ from typing import Any from urllib.parse import parse_qsl +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.dispatcher import dispatch_single_view from idom.core.layout import Layout, LayoutEvent @@ -23,6 +25,17 @@ def __init__(self, *args: Any, **kwargs: Any) -> None: async def connect(self) -> None: await super().connect() + + user = self.scope.get("user") + if user and user.is_authenticated: + try: + await login(self.scope, user) + await convert_to_async(self.scope["session"].save)() + except Exception: + _logger.exception("IDOM websocket authentication has failed!") + elif user == None: + _logger.warning(f"IDOM websocket is missing AuthMiddlewareStack!") + self._idom_dispatcher_future = asyncio.ensure_future(self._run_dispatch_loop()) async def disconnect(self, code: int) -> None: diff --git a/tests/test_app/asgi.py b/tests/test_app/asgi.py index dcb8112c..a4e34e6d 100644 --- a/tests/test_app/asgi.py +++ b/tests/test_app/asgi.py @@ -19,12 +19,13 @@ # Fetch ASGI application before importing dependencies that require ORM models. http_asgi_app = get_asgi_application() +from channels.auth import AuthMiddlewareStack from channels.routing import ProtocolTypeRouter, URLRouter # noqa: E402 application = ProtocolTypeRouter( { "http": http_asgi_app, - "websocket": URLRouter([IDOM_WEBSOCKET_PATH]), + "websocket": AuthMiddlewareStack(URLRouter([IDOM_WEBSOCKET_PATH])), } ) From c1b443eb118dfe616f164b0008f7a62c8708d325 Mon Sep 17 00:00:00 2001 From: Archmonger <16909269+Archmonger@users.noreply.github.com> Date: Tue, 21 Sep 2021 00:24:35 -0700 Subject: [PATCH 04/39] add admin view to test app --- tests/test_app/urls.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/tests/test_app/urls.py b/tests/test_app/urls.py index cd4f3da0..1ea0c99b 100644 --- a/tests/test_app/urls.py +++ b/tests/test_app/urls.py @@ -17,6 +17,7 @@ 1. Import the include() function: from django.urls import include, path 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) """ +from django.contrib import admin from django.urls import path from django_idom import IDOM_WEB_MODULES_PATH @@ -24,4 +25,8 @@ from .views import base_template -urlpatterns = [path("", base_template), IDOM_WEB_MODULES_PATH] +urlpatterns = [ + path("", base_template), + IDOM_WEB_MODULES_PATH, + path("admin/", admin.site.urls), +] From 79ab0bc383785e0aae8e84705456425015c3c817 Mon Sep 17 00:00:00 2001 From: Archmonger <16909269+Archmonger@users.noreply.github.com> Date: Tue, 21 Sep 2021 00:29:08 -0700 Subject: [PATCH 05/39] fix styling error --- src/django_idom/websocket_consumer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/django_idom/websocket_consumer.py b/src/django_idom/websocket_consumer.py index 1cd77212..39aaac6a 100644 --- a/src/django_idom/websocket_consumer.py +++ b/src/django_idom/websocket_consumer.py @@ -33,7 +33,7 @@ async def connect(self) -> None: await convert_to_async(self.scope["session"].save)() except Exception: _logger.exception("IDOM websocket authentication has failed!") - elif user == None: + elif user is None: _logger.warning(f"IDOM websocket is missing AuthMiddlewareStack!") self._idom_dispatcher_future = asyncio.ensure_future(self._run_dispatch_loop()) From bf6783a87e3868d190d748ec62d07e31c52f6195 Mon Sep 17 00:00:00 2001 From: Archmonger <16909269+Archmonger@users.noreply.github.com> Date: Tue, 21 Sep 2021 00:32:02 -0700 Subject: [PATCH 06/39] more styling --- src/django_idom/websocket_consumer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/django_idom/websocket_consumer.py b/src/django_idom/websocket_consumer.py index 39aaac6a..7b585a26 100644 --- a/src/django_idom/websocket_consumer.py +++ b/src/django_idom/websocket_consumer.py @@ -34,7 +34,7 @@ async def connect(self) -> None: except Exception: _logger.exception("IDOM websocket authentication has failed!") elif user is None: - _logger.warning(f"IDOM websocket is missing AuthMiddlewareStack!") + _logger.warning("IDOM websocket is missing AuthMiddlewareStack!") self._idom_dispatcher_future = asyncio.ensure_future(self._run_dispatch_loop()) From dda8b58d78cc68b1d60ffbe16de9ad4f455ee993 Mon Sep 17 00:00:00 2001 From: Archmonger <16909269+Archmonger@users.noreply.github.com> Date: Tue, 21 Sep 2021 00:35:27 -0700 Subject: [PATCH 07/39] last styling error i swear --- tests/test_app/asgi.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_app/asgi.py b/tests/test_app/asgi.py index a4e34e6d..740ddb4f 100644 --- a/tests/test_app/asgi.py +++ b/tests/test_app/asgi.py @@ -19,7 +19,7 @@ # Fetch ASGI application before importing dependencies that require ORM models. http_asgi_app = get_asgi_application() -from channels.auth import AuthMiddlewareStack +from channels.auth import AuthMiddlewareStack # noqa: E402 from channels.routing import ProtocolTypeRouter, URLRouter # noqa: E402 From 05a3d22ffbe71bea121c36860cf6bb2dd26abe0c Mon Sep 17 00:00:00 2001 From: Archmonger <16909269+Archmonger@users.noreply.github.com> Date: Tue, 21 Sep 2021 23:11:13 -0700 Subject: [PATCH 08/39] fix spelling error --- src/django_idom/websocket_consumer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/django_idom/websocket_consumer.py b/src/django_idom/websocket_consumer.py index 7b585a26..31826cfb 100644 --- a/src/django_idom/websocket_consumer.py +++ b/src/django_idom/websocket_consumer.py @@ -54,7 +54,7 @@ async def _run_dispatch_loop(self): try: component_constructor = IDOM_REGISTERED_COMPONENTS[view_id] except KeyError: - _logger.warning(f"Uknown IDOM view ID {view_id!r}") + _logger.warning(f"Unknown IDOM view ID {view_id!r}") return query_dict = dict(parse_qsl(self.scope["query_string"].decode())) From ef9895b53839677a2b94b9d518e2f85e0808676e Mon Sep 17 00:00:00 2001 From: Archmonger <16909269+Archmonger@users.noreply.github.com> Date: Tue, 21 Sep 2021 23:14:08 -0700 Subject: [PATCH 09/39] remove unneeded sys import check --- src/django_idom/templatetags/idom.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/django_idom/templatetags/idom.py b/src/django_idom/templatetags/idom.py index ba44878c..30d7a290 100644 --- a/src/django_idom/templatetags/idom.py +++ b/src/django_idom/templatetags/idom.py @@ -36,9 +36,6 @@ def idom_component(_component_id_, **kwargs): def _register_component(full_component_name: str) -> None: module_name, component_name = full_component_name.rsplit(".", 1) - if module_name in sys.modules: - module = sys.modules[module_name] - else: try: module = import_module(module_name) except ImportError as error: From a4d439c01233c7a5278857497dd7d1152fa85665 Mon Sep 17 00:00:00 2001 From: Archmonger <16909269+Archmonger@users.noreply.github.com> Date: Tue, 21 Sep 2021 23:20:21 -0700 Subject: [PATCH 10/39] reconnecting WS (broken) --- src/django_idom/config.py | 1 + src/django_idom/templates/idom/component.html | 3 +- src/django_idom/templatetags/idom.py | 14 ++++--- src/js/src/index.js | 38 +++++++++++-------- 4 files changed, 33 insertions(+), 23 deletions(-) diff --git a/src/django_idom/config.py b/src/django_idom/config.py index 9b2d9f0a..bde409ef 100644 --- a/src/django_idom/config.py +++ b/src/django_idom/config.py @@ -10,6 +10,7 @@ IDOM_BASE_URL = getattr(settings, "IDOM_BASE_URL", "_idom/") IDOM_WEBSOCKET_URL = IDOM_BASE_URL + "websocket/" IDOM_WEB_MODULES_URL = IDOM_BASE_URL + "web_module/" +IDOM_WS_RECONNECT = getattr(settings, "IDOM_WS_RECONNECT", True) _CACHES = getattr(settings, "CACHES", {}) if _CACHES: diff --git a/src/django_idom/templates/idom/component.html b/src/django_idom/templates/idom/component.html index 84ec6963..b1f57757 100644 --- a/src/django_idom/templates/idom/component.html +++ b/src/django_idom/templates/idom/component.html @@ -7,7 +7,8 @@ mountPoint, "{{ idom_websocket_url }}", "{{ idom_web_modules_url }}", + parseInt("{% if idom_ws_reconnect %}604800{% else %}0{% endif %}"), "{{ idom_component_id }}", "{{ idom_component_params }}" ); - + \ No newline at end of file diff --git a/src/django_idom/templatetags/idom.py b/src/django_idom/templatetags/idom.py index 30d7a290..d53e4416 100644 --- a/src/django_idom/templatetags/idom.py +++ b/src/django_idom/templatetags/idom.py @@ -10,6 +10,7 @@ IDOM_REGISTERED_COMPONENTS, IDOM_WEB_MODULES_URL, IDOM_WEBSOCKET_URL, + IDOM_WS_RECONNECT, ) @@ -27,6 +28,7 @@ def idom_component(_component_id_, **kwargs): "class": class_, "idom_websocket_url": IDOM_WEBSOCKET_URL, "idom_web_modules_url": IDOM_WEB_MODULES_URL, + "idom_ws_reconnect": IDOM_WS_RECONNECT, "idom_mount_uuid": uuid4().hex, "idom_component_id": _component_id_, "idom_component_params": urlencode({"kwargs": json_kwargs}), @@ -36,12 +38,12 @@ def idom_component(_component_id_, **kwargs): def _register_component(full_component_name: str) -> None: module_name, component_name = full_component_name.rsplit(".", 1) - try: - module = import_module(module_name) - except ImportError as error: - raise RuntimeError( - f"Failed to import {module_name!r} while loading {component_name!r}" - ) from error + try: + module = import_module(module_name) + except ImportError as error: + raise RuntimeError( + f"Failed to import {module_name!r} while loading {component_name!r}" + ) from error try: component = getattr(module, component_name) diff --git a/src/js/src/index.js b/src/js/src/index.js index bddf2a55..4253bf0b 100644 --- a/src/js/src/index.js +++ b/src/js/src/index.js @@ -4,28 +4,34 @@ import { mountLayoutWithWebSocket } from "idom-client-react"; let LOCATION = window.location; let WS_PROTOCOL = ""; if (LOCATION.protocol == "https:") { - WS_PROTOCOL = "wss://"; + WS_PROTOCOL = "wss://"; } else { - WS_PROTOCOL = "ws://"; + WS_PROTOCOL = "ws://"; } let WS_ENDPOINT_URL = WS_PROTOCOL + LOCATION.host + "/"; export function mountViewToElement( - mountPoint, - idomWebsocketUrl, - idomWebModulesUrl, - viewId, - queryParams + mountPoint, + idomWebsocketUrl, + idomWebModulesUrl, + maxReconnectTimeout, + viewId, + queryParams ) { - const fullWebsocketUrl = - WS_ENDPOINT_URL + idomWebsocketUrl + viewId + "/?" + queryParams; + const fullWebsocketUrl = + WS_ENDPOINT_URL + idomWebsocketUrl + viewId + "/?" + queryParams; - const fullWebModulesUrl = LOCATION.origin + "/" + idomWebModulesUrl - const loadImportSource = (source, sourceType) => { - return import( - sourceType == "NAME" ? `${fullWebModulesUrl}${source}` : source - ); - }; + const fullWebModulesUrl = LOCATION.origin + "/" + idomWebModulesUrl; + const loadImportSource = (source, sourceType) => { + return import( + sourceType == "NAME" ? `${fullWebModulesUrl}${source}` : source + ); + }; - mountLayoutWithWebSocket(mountPoint, fullWebsocketUrl, loadImportSource); + mountLayoutWithWebSocket( + mountPoint, + fullWebsocketUrl, + loadImportSource, + maxReconnectTimeout + ); } From f17ba96bab56b424c920f4bb93bdacff334d0919 Mon Sep 17 00:00:00 2001 From: Archmonger <16909269+Archmonger@users.noreply.github.com> Date: Tue, 21 Sep 2021 23:30:45 -0700 Subject: [PATCH 11/39] prevent duplicate component registration fix #5 --- src/django_idom/templatetags/idom.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/django_idom/templatetags/idom.py b/src/django_idom/templatetags/idom.py index d53e4416..86996a05 100644 --- a/src/django_idom/templatetags/idom.py +++ b/src/django_idom/templatetags/idom.py @@ -1,5 +1,5 @@ +import functools import json -import sys from importlib import import_module from urllib.parse import urlencode from uuid import uuid4 @@ -35,6 +35,7 @@ def idom_component(_component_id_, **kwargs): } +@functools.cache def _register_component(full_component_name: str) -> None: module_name, component_name = full_component_name.rsplit(".", 1) From aa10d8cbb3a5fa87230f946c97615bf60c013988 Mon Sep 17 00:00:00 2001 From: Archmonger <16909269+Archmonger@users.noreply.github.com> Date: Tue, 21 Sep 2021 23:38:17 -0700 Subject: [PATCH 12/39] allow any host to access this test app --- tests/test_app/settings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_app/settings.py b/tests/test_app/settings.py index ac7d6ab9..29eadffa 100644 --- a/tests/test_app/settings.py +++ b/tests/test_app/settings.py @@ -26,7 +26,7 @@ # SECURITY WARNING: don't run with debug turned on in production! DEBUG = True -ALLOWED_HOSTS = [] +ALLOWED_HOSTS = ["*"] # Application definition INSTALLED_APPS = [ From 756b80b312515c7d9308cf536f3d530e7a3e7060 Mon Sep 17 00:00:00 2001 From: Archmonger <16909269+Archmonger@users.noreply.github.com> Date: Tue, 21 Sep 2021 23:41:19 -0700 Subject: [PATCH 13/39] cache -> lru_cache --- src/django_idom/templatetags/idom.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/django_idom/templatetags/idom.py b/src/django_idom/templatetags/idom.py index 86996a05..acee8330 100644 --- a/src/django_idom/templatetags/idom.py +++ b/src/django_idom/templatetags/idom.py @@ -35,7 +35,7 @@ def idom_component(_component_id_, **kwargs): } -@functools.cache +@functools.lru_cache def _register_component(full_component_name: str) -> None: module_name, component_name = full_component_name.rsplit(".", 1) From 2786a3bc8a195fca7402ca5075feec711529f66b Mon Sep 17 00:00:00 2001 From: Archmonger <16909269+Archmonger@users.noreply.github.com> Date: Tue, 21 Sep 2021 23:43:30 -0700 Subject: [PATCH 14/39] py 3.7 compartibility --- src/django_idom/templatetags/idom.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/django_idom/templatetags/idom.py b/src/django_idom/templatetags/idom.py index acee8330..0fcb60f5 100644 --- a/src/django_idom/templatetags/idom.py +++ b/src/django_idom/templatetags/idom.py @@ -35,7 +35,7 @@ def idom_component(_component_id_, **kwargs): } -@functools.lru_cache +@functools.lru_cache(maxsize=None) def _register_component(full_component_name: str) -> None: module_name, component_name = full_component_name.rsplit(".", 1) From 2c1adb9a099bb2022b01c1ecbe133c087ef2a54e Mon Sep 17 00:00:00 2001 From: Archmonger <16909269+Archmonger@users.noreply.github.com> Date: Tue, 21 Sep 2021 23:50:27 -0700 Subject: [PATCH 15/39] memory efficient way of preventing reregistration --- src/django_idom/templatetags/idom.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/django_idom/templatetags/idom.py b/src/django_idom/templatetags/idom.py index 0fcb60f5..e0742791 100644 --- a/src/django_idom/templatetags/idom.py +++ b/src/django_idom/templatetags/idom.py @@ -1,4 +1,3 @@ -import functools import json from importlib import import_module from urllib.parse import urlencode @@ -35,8 +34,10 @@ def idom_component(_component_id_, **kwargs): } -@functools.lru_cache(maxsize=None) def _register_component(full_component_name: str) -> None: + if full_component_name in IDOM_REGISTERED_COMPONENTS: + return + module_name, component_name = full_component_name.rsplit(".", 1) try: From e50ca058b0b3cfb7790a789b1e0385b6677e6389 Mon Sep 17 00:00:00 2001 From: Archmonger <16909269+Archmonger@users.noreply.github.com> Date: Wed, 22 Sep 2021 01:10:28 -0700 Subject: [PATCH 16/39] Limit developer control the websocket --- src/django_idom/websocket_consumer.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/django_idom/websocket_consumer.py b/src/django_idom/websocket_consumer.py index 31826cfb..30fcb86b 100644 --- a/src/django_idom/websocket_consumer.py +++ b/src/django_idom/websocket_consumer.py @@ -2,7 +2,8 @@ import asyncio import json import logging -from typing import Any +from dataclasses import dataclass +from typing import Any, Callable from urllib.parse import parse_qsl from channels.auth import login @@ -17,6 +18,12 @@ _logger = logging.getLogger(__name__) +@dataclass +class WebSocketConnection: + scope: dict + disconnect: Callable + + class IdomAsyncWebSocketConsumer(AsyncJsonWebsocketConsumer): """Communicates with the browser to perform actions on-demand.""" @@ -36,6 +43,9 @@ async def connect(self) -> None: elif user is None: _logger.warning("IDOM websocket is missing AuthMiddlewareStack!") + # Limit developer control this websocket + self.socket = WebSocketConnection(self.scope, self.disconnect) + self._idom_dispatcher_future = asyncio.ensure_future(self._run_dispatch_loop()) async def disconnect(self, code: int) -> None: @@ -61,7 +71,7 @@ async def _run_dispatch_loop(self): component_kwargs = json.loads(query_dict.get("kwargs", "{}")) try: - component_instance = component_constructor(self, **component_kwargs) + component_instance = component_constructor(self.socket, **component_kwargs) except Exception: _logger.exception( f"Failed to construct component {component_constructor} " From bbb4649b30427bcfbdf59ab64d0d0d1dae2b465b Mon Sep 17 00:00:00 2001 From: Archmonger <16909269+Archmonger@users.noreply.github.com> Date: Wed, 22 Sep 2021 01:27:46 -0700 Subject: [PATCH 17/39] fix readme filename (idom.py -> components,py) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d3787c14..2e3dd0e7 100644 --- a/README.md +++ b/README.md @@ -46,7 +46,7 @@ your_project/ ├── urls.py └── example_app/ ├── __init__.py - ├── idom.py + ├── components.py ├── templates/ │ └── your-template.html └── urls.py From 88dfe6563b61b185cc5c4542a899ad9364d8e1ea Mon Sep 17 00:00:00 2001 From: Archmonger <16909269+Archmonger@users.noreply.github.com> Date: Wed, 22 Sep 2021 01:27:51 -0700 Subject: [PATCH 18/39] format readme --- README.md | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 2e3dd0e7..c18681dd 100644 --- a/README.md +++ b/README.md @@ -20,14 +20,13 @@ interfaces in pure Python. - Binder - # Install Django IDOM ```bash @@ -215,10 +214,10 @@ urlpatterns = [ If you plan to make code changes to this repository, you'll need to install the following dependencies first: -- [NPM](https://docs.npmjs.com/try-the-latest-stable-version-of-npm) for - installing and managing Javascript -- [ChromeDriver](https://chromedriver.chromium.org/downloads) for testing with - [Selenium](https://www.seleniumhq.org/) +- [NPM](https://docs.npmjs.com/try-the-latest-stable-version-of-npm) for + installing and managing Javascript +- [ChromeDriver](https://chromedriver.chromium.org/downloads) for testing with + [Selenium](https://www.seleniumhq.org/) Once done, you should clone this repository: @@ -229,9 +228,9 @@ cd django-idom Then, by running the command below you can: -- Install an editable version of the Python code +- Install an editable version of the Python code -- Download, build, and install Javascript dependencies +- Download, build, and install Javascript dependencies ```bash pip install -e . -r requirements.txt From a4b8f56c4ede81d01ac2a03f554d6dabe301e01c Mon Sep 17 00:00:00 2001 From: Archmonger <16909269+Archmonger@users.noreply.github.com> Date: Wed, 22 Sep 2021 01:33:00 -0700 Subject: [PATCH 19/39] simplify render example --- README.md | 8 ++------ tests/test_app/views.py | 5 ++--- 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index c18681dd..59c2397f 100644 --- a/README.md +++ b/README.md @@ -184,15 +184,11 @@ You can then serve `your-template.html` from a view just [like any other](https://docs.djangoproject.com/en/3.2/intro/tutorial03/#write-views-that-actually-do-something). ```python -from django.http import HttpResponse -from django.template import loader - +from django.shortcuts import render def your_view(request): context = {} - return HttpResponse( - loader.get_template("your-template.html").render(context, request) - ) + return render(request, "your-template.html", context) ``` ## `example_app/urls.py` diff --git a/tests/test_app/views.py b/tests/test_app/views.py index 248235a7..11874908 100644 --- a/tests/test_app/views.py +++ b/tests/test_app/views.py @@ -1,7 +1,6 @@ -from django.http import HttpResponse -from django.template import loader +from django.shortcuts import render def base_template(request): context = {} - return HttpResponse(loader.get_template("base.html").render(context, request)) + return render(request, "base.html", context) From 4bcbfdd5768a343f04509cf5fe5fc170c09a773e Mon Sep 17 00:00:00 2001 From: Archmonger <16909269+Archmonger@users.noreply.github.com> Date: Wed, 22 Sep 2021 01:38:57 -0700 Subject: [PATCH 20/39] fix settings anchor link --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 59c2397f..e8f94abf 100644 --- a/README.md +++ b/README.md @@ -59,7 +59,7 @@ order to create ASGI websockets within Django. Then, we will add a path for IDOM websocket consumer using `IDOM_WEBSOCKET_PATH`. _Note: If you wish to change the route where this websocket is served from, see the -available [settings](#settings.py)._ +available [settings](#settingspy)._ ```python From cfc07cd20dc8754f0667762954775b7d2be0ea8e Mon Sep 17 00:00:00 2001 From: Archmonger <16909269+Archmonger@users.noreply.github.com> Date: Wed, 22 Sep 2021 01:42:44 -0700 Subject: [PATCH 21/39] add IDOM_WS_RECONNECT to readme --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index e8f94abf..c8dcd0a2 100644 --- a/README.md +++ b/README.md @@ -113,6 +113,9 @@ IDOM_BASE_URL: str = "_idom/" # Only applies when not using Django's caching framework (see below). IDOM_WEB_MODULE_LRU_CACHE_SIZE: int | None = None +# Enable or disable websocket reconnection attempts (Default: True) +IDOM_WS_RECONNECT: bool = True + # Configure a cache for loading JS files CACHES = { # Configure a cache for loading JS files for IDOM From 3afbed55b5ce8f573e4b3cc0a17a83d55c0efe06 Mon Sep 17 00:00:00 2001 From: Archmonger <16909269+Archmonger@users.noreply.github.com> Date: Wed, 22 Sep 2021 13:59:34 -0700 Subject: [PATCH 22/39] add session middleware --- README.md | 7 ++----- tests/test_app/asgi.py | 5 ++++- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index c8dcd0a2..2082862b 100644 --- a/README.md +++ b/README.md @@ -80,11 +80,8 @@ from channels.routing import ProtocolTypeRouter, URLRouter application = ProtocolTypeRouter( { "http": http_asgi_app, - "websocket": AuthMiddlewareStack( - URLRouter( - # add a path for IDOM's websocket - [IDOM_WEBSOCKET_PATH] - ) + "websocket": SessionMiddlewareStack( + AuthMiddlewareStack(URLRouter([IDOM_WEBSOCKET_PATH])) ), } ) diff --git a/tests/test_app/asgi.py b/tests/test_app/asgi.py index 740ddb4f..2b711cc1 100644 --- a/tests/test_app/asgi.py +++ b/tests/test_app/asgi.py @@ -21,11 +21,14 @@ from channels.auth import AuthMiddlewareStack # noqa: E402 from channels.routing import ProtocolTypeRouter, URLRouter # noqa: E402 +from channels.sessions import SessionMiddlewareStack # noqa: E402 application = ProtocolTypeRouter( { "http": http_asgi_app, - "websocket": AuthMiddlewareStack(URLRouter([IDOM_WEBSOCKET_PATH])), + "websocket": SessionMiddlewareStack( + AuthMiddlewareStack(URLRouter([IDOM_WEBSOCKET_PATH])) + ), } ) From 9e4b13e3f7ec2b54bc07cae5e03aef85d0e90785 Mon Sep 17 00:00:00 2001 From: Archmonger <16909269+Archmonger@users.noreply.github.com> Date: Mon, 27 Sep 2021 02:31:58 -0700 Subject: [PATCH 23/39] change disconnect to close --- src/django_idom/websocket_consumer.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/django_idom/websocket_consumer.py b/src/django_idom/websocket_consumer.py index 30fcb86b..76cc30c7 100644 --- a/src/django_idom/websocket_consumer.py +++ b/src/django_idom/websocket_consumer.py @@ -3,7 +3,7 @@ import json import logging from dataclasses import dataclass -from typing import Any, Callable +from typing import Any, Callable, Coroutine, Optional from urllib.parse import parse_qsl from channels.auth import login @@ -21,7 +21,7 @@ @dataclass class WebSocketConnection: scope: dict - disconnect: Callable + close: Callable[[Optional[int]], Coroutine[Any, Any, None]] class IdomAsyncWebSocketConsumer(AsyncJsonWebsocketConsumer): @@ -44,7 +44,7 @@ async def connect(self) -> None: _logger.warning("IDOM websocket is missing AuthMiddlewareStack!") # Limit developer control this websocket - self.socket = WebSocketConnection(self.scope, self.disconnect) + self.socket = WebSocketConnection(self.scope, self.close) self._idom_dispatcher_future = asyncio.ensure_future(self._run_dispatch_loop()) From d625ee05fb871669fc7a0895b3578e07f9076040 Mon Sep 17 00:00:00 2001 From: Archmonger <16909269+Archmonger@users.noreply.github.com> Date: Mon, 27 Sep 2021 02:33:51 -0700 Subject: [PATCH 24/39] add view ID to WebSocketConnection --- src/django_idom/websocket_consumer.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/django_idom/websocket_consumer.py b/src/django_idom/websocket_consumer.py index 76cc30c7..53e954f8 100644 --- a/src/django_idom/websocket_consumer.py +++ b/src/django_idom/websocket_consumer.py @@ -22,6 +22,7 @@ class WebSocketConnection: scope: dict close: Callable[[Optional[int]], Coroutine[Any, Any, None]] + view_id: str class IdomAsyncWebSocketConsumer(AsyncJsonWebsocketConsumer): @@ -33,6 +34,8 @@ def __init__(self, *args: Any, **kwargs: Any) -> None: async def connect(self) -> None: await super().connect() + self.view_id = self.scope["url_route"]["kwargs"]["view_id"] + user = self.scope.get("user") if user and user.is_authenticated: try: @@ -44,7 +47,7 @@ async def connect(self) -> None: _logger.warning("IDOM websocket is missing AuthMiddlewareStack!") # Limit developer control this websocket - self.socket = WebSocketConnection(self.scope, self.close) + self.socket = WebSocketConnection(self.scope, self.close, self.view_id) self._idom_dispatcher_future = asyncio.ensure_future(self._run_dispatch_loop()) @@ -59,12 +62,11 @@ async def receive_json(self, content: Any, **kwargs: Any) -> None: await self._idom_recv_queue.put(LayoutEvent(**content)) async def _run_dispatch_loop(self): - view_id = self.scope["url_route"]["kwargs"]["view_id"] try: - component_constructor = IDOM_REGISTERED_COMPONENTS[view_id] + component_constructor = IDOM_REGISTERED_COMPONENTS[self.view_id] except KeyError: - _logger.warning(f"Unknown IDOM view ID {view_id!r}") + _logger.warning(f"Unknown IDOM view ID {self.view_id!r}") return query_dict = dict(parse_qsl(self.scope["query_string"].decode())) From b36ac3773090712ef719d08ebe867a35bafc7bf6 Mon Sep 17 00:00:00 2001 From: Archmonger <16909269+Archmonger@users.noreply.github.com> Date: Mon, 27 Sep 2021 02:37:30 -0700 Subject: [PATCH 25/39] change tab width to 2 --- src/js/src/index.js | 44 ++++++++++++++++++++++---------------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/src/js/src/index.js b/src/js/src/index.js index 4253bf0b..8f17c0d4 100644 --- a/src/js/src/index.js +++ b/src/js/src/index.js @@ -4,34 +4,34 @@ import { mountLayoutWithWebSocket } from "idom-client-react"; let LOCATION = window.location; let WS_PROTOCOL = ""; if (LOCATION.protocol == "https:") { - WS_PROTOCOL = "wss://"; + WS_PROTOCOL = "wss://"; } else { - WS_PROTOCOL = "ws://"; + WS_PROTOCOL = "ws://"; } let WS_ENDPOINT_URL = WS_PROTOCOL + LOCATION.host + "/"; export function mountViewToElement( - mountPoint, - idomWebsocketUrl, - idomWebModulesUrl, - maxReconnectTimeout, - viewId, - queryParams + mountPoint, + idomWebsocketUrl, + idomWebModulesUrl, + maxReconnectTimeout, + viewId, + queryParams ) { - const fullWebsocketUrl = - WS_ENDPOINT_URL + idomWebsocketUrl + viewId + "/?" + queryParams; + const fullWebsocketUrl = + WS_ENDPOINT_URL + idomWebsocketUrl + viewId + "/?" + queryParams; - const fullWebModulesUrl = LOCATION.origin + "/" + idomWebModulesUrl; - const loadImportSource = (source, sourceType) => { - return import( - sourceType == "NAME" ? `${fullWebModulesUrl}${source}` : source - ); - }; + const fullWebModulesUrl = LOCATION.origin + "/" + idomWebModulesUrl; + const loadImportSource = (source, sourceType) => { + return import( + sourceType == "NAME" ? `${fullWebModulesUrl}${source}` : source + ); + }; - mountLayoutWithWebSocket( - mountPoint, - fullWebsocketUrl, - loadImportSource, - maxReconnectTimeout - ); + mountLayoutWithWebSocket( + mountPoint, + fullWebsocketUrl, + loadImportSource, + maxReconnectTimeout + ); } From 7336b50127a4d107e24a0216cf733e6d4d0f6b20 Mon Sep 17 00:00:00 2001 From: Archmonger <16909269+Archmonger@users.noreply.github.com> Date: Mon, 27 Sep 2021 02:48:26 -0700 Subject: [PATCH 26/39] prettier format --- README.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 2082862b..189638a5 100644 --- a/README.md +++ b/README.md @@ -110,8 +110,8 @@ IDOM_BASE_URL: str = "_idom/" # Only applies when not using Django's caching framework (see below). IDOM_WEB_MODULE_LRU_CACHE_SIZE: int | None = None -# Enable or disable websocket reconnection attempts (Default: True) -IDOM_WS_RECONNECT: bool = True +# Max amount of time for client to attempt to reconnect. 0 disables reconnection. +IDOM_WS_RECONNECT_TIMEOUT: int = 604800 # Configure a cache for loading JS files CACHES = { @@ -210,10 +210,10 @@ urlpatterns = [ If you plan to make code changes to this repository, you'll need to install the following dependencies first: -- [NPM](https://docs.npmjs.com/try-the-latest-stable-version-of-npm) for - installing and managing Javascript -- [ChromeDriver](https://chromedriver.chromium.org/downloads) for testing with - [Selenium](https://www.seleniumhq.org/) +- [NPM](https://docs.npmjs.com/try-the-latest-stable-version-of-npm) for + installing and managing Javascript +- [ChromeDriver](https://chromedriver.chromium.org/downloads) for testing with + [Selenium](https://www.seleniumhq.org/) Once done, you should clone this repository: @@ -224,9 +224,9 @@ cd django-idom Then, by running the command below you can: -- Install an editable version of the Python code +- Install an editable version of the Python code -- Download, build, and install Javascript dependencies +- Download, build, and install Javascript dependencies ```bash pip install -e . -r requirements.txt From 7648256e9a93d8ff0950d18f999cd6420e1bc6c5 Mon Sep 17 00:00:00 2001 From: Archmonger <16909269+Archmonger@users.noreply.github.com> Date: Mon, 27 Sep 2021 02:49:21 -0700 Subject: [PATCH 27/39] IDOM_WS_RECONNECT -> IDOM_WS_RECONNECT_TIMEOUT --- src/django_idom/config.py | 2 +- src/django_idom/templates/idom/component.html | 2 +- src/django_idom/templatetags/idom.py | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/django_idom/config.py b/src/django_idom/config.py index bde409ef..86c3418e 100644 --- a/src/django_idom/config.py +++ b/src/django_idom/config.py @@ -10,7 +10,7 @@ IDOM_BASE_URL = getattr(settings, "IDOM_BASE_URL", "_idom/") IDOM_WEBSOCKET_URL = IDOM_BASE_URL + "websocket/" IDOM_WEB_MODULES_URL = IDOM_BASE_URL + "web_module/" -IDOM_WS_RECONNECT = getattr(settings, "IDOM_WS_RECONNECT", True) +IDOM_WS_RECONNECT_TIMEOUT = getattr(settings, "IDOM_WS_RECONNECT_TIMEOUT", 604800) _CACHES = getattr(settings, "CACHES", {}) if _CACHES: diff --git a/src/django_idom/templates/idom/component.html b/src/django_idom/templates/idom/component.html index b1f57757..8d4b181a 100644 --- a/src/django_idom/templates/idom/component.html +++ b/src/django_idom/templates/idom/component.html @@ -7,7 +7,7 @@ mountPoint, "{{ idom_websocket_url }}", "{{ idom_web_modules_url }}", - parseInt("{% if idom_ws_reconnect %}604800{% else %}0{% endif %}"), + "{{ idom_ws_reconnect_timeout }}", "{{ idom_component_id }}", "{{ idom_component_params }}" ); diff --git a/src/django_idom/templatetags/idom.py b/src/django_idom/templatetags/idom.py index e0742791..1b3de383 100644 --- a/src/django_idom/templatetags/idom.py +++ b/src/django_idom/templatetags/idom.py @@ -9,7 +9,7 @@ IDOM_REGISTERED_COMPONENTS, IDOM_WEB_MODULES_URL, IDOM_WEBSOCKET_URL, - IDOM_WS_RECONNECT, + IDOM_WS_RECONNECT_TIMEOUT, ) @@ -27,7 +27,7 @@ def idom_component(_component_id_, **kwargs): "class": class_, "idom_websocket_url": IDOM_WEBSOCKET_URL, "idom_web_modules_url": IDOM_WEB_MODULES_URL, - "idom_ws_reconnect": IDOM_WS_RECONNECT, + "idom_ws_reconnect_timeout": IDOM_WS_RECONNECT_TIMEOUT, "idom_mount_uuid": uuid4().hex, "idom_component_id": _component_id_, "idom_component_params": urlencode({"kwargs": json_kwargs}), From 238769309553ab8529e8ff30c1d91f213b40aaab Mon Sep 17 00:00:00 2001 From: Archmonger <16909269+Archmonger@users.noreply.github.com> Date: Mon, 27 Sep 2021 02:52:30 -0700 Subject: [PATCH 28/39] format base.html --- tests/test_app/templates/base.html | 37 +++++++++++++++--------------- 1 file changed, 18 insertions(+), 19 deletions(-) diff --git a/tests/test_app/templates/base.html b/tests/test_app/templates/base.html index 1ad73c4a..e774403c 100644 --- a/tests/test_app/templates/base.html +++ b/tests/test_app/templates/base.html @@ -1,23 +1,22 @@ {% load static %} {% load idom %} - - - - - - IDOM - - -

IDOM Test Page

-
{% idom_component "test_app.components.HelloWorld" class="hello-world" %}
-
{% idom_component "test_app.components.Button" class="button" %}
-
{% idom_component "test_app.components.ParametrizedComponent" class="parametarized-component" x=123 y=456 %}
-
{% idom_component "test_app.components.SimpleBarChart" class="simple-bar-chart" %}
- - + + + + + + IDOM + + + +

IDOM Test Page

+
{% idom_component "test_app.components.HelloWorld" class="hello-world" %}
+
{% idom_component "test_app.components.Button" class="button" %}
+
{% idom_component "test_app.components.ParametrizedComponent" class="parametarized-component" x=123 y=456 %} +
+
{% idom_component "test_app.components.SimpleBarChart" class="simple-bar-chart" %}
+ + + \ No newline at end of file From 553da9701d059b58d0f8f2a91c48efef2bc619f6 Mon Sep 17 00:00:00 2001 From: Archmonger <16909269+Archmonger@users.noreply.github.com> Date: Mon, 27 Sep 2021 10:31:52 -0700 Subject: [PATCH 29/39] WebSocket -> Websocket --- src/django_idom/paths.py | 6 +++--- src/django_idom/websocket_consumer.py | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/django_idom/paths.py b/src/django_idom/paths.py index 62a346e1..98170740 100644 --- a/src/django_idom/paths.py +++ b/src/django_idom/paths.py @@ -2,13 +2,13 @@ from . import views from .config import IDOM_WEB_MODULES_URL, IDOM_WEBSOCKET_URL -from .websocket_consumer import IdomAsyncWebSocketConsumer +from .websocket_consumer import IdomAsyncWebsocketConsumer IDOM_WEBSOCKET_PATH = path( - IDOM_WEBSOCKET_URL + "/", IdomAsyncWebSocketConsumer.as_asgi() + IDOM_WEBSOCKET_URL + "/", IdomAsyncWebsocketConsumer.as_asgi() ) -"""A URL resolver for :class:`IdomAsyncWebSocketConsumer` +"""A URL resolver for :class:`IdomAsyncWebsocketConsumer` While this is relatively uncommon in most Django apps, because the URL of the websocket must be defined by the setting ``IDOM_WEBSOCKET_URL``. There's no need diff --git a/src/django_idom/websocket_consumer.py b/src/django_idom/websocket_consumer.py index 53e954f8..60efdfc5 100644 --- a/src/django_idom/websocket_consumer.py +++ b/src/django_idom/websocket_consumer.py @@ -19,13 +19,13 @@ @dataclass -class WebSocketConnection: +class WebsocketConnection: scope: dict close: Callable[[Optional[int]], Coroutine[Any, Any, None]] view_id: str -class IdomAsyncWebSocketConsumer(AsyncJsonWebsocketConsumer): +class IdomAsyncWebsocketConsumer(AsyncJsonWebsocketConsumer): """Communicates with the browser to perform actions on-demand.""" def __init__(self, *args: Any, **kwargs: Any) -> None: @@ -47,7 +47,7 @@ async def connect(self) -> None: _logger.warning("IDOM websocket is missing AuthMiddlewareStack!") # Limit developer control this websocket - self.socket = WebSocketConnection(self.scope, self.close, self.view_id) + self.socket = WebsocketConnection(self.scope, self.close, self.view_id) self._idom_dispatcher_future = asyncio.ensure_future(self._run_dispatch_loop()) From 9058d345f55700df5942f130c59e3e404903384b Mon Sep 17 00:00:00 2001 From: Archmonger <16909269+Archmonger@users.noreply.github.com> Date: Mon, 27 Sep 2021 16:12:08 -0700 Subject: [PATCH 30/39] Awaitable[None] --- src/django_idom/websocket_consumer.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/django_idom/websocket_consumer.py b/src/django_idom/websocket_consumer.py index 60efdfc5..2df12d56 100644 --- a/src/django_idom/websocket_consumer.py +++ b/src/django_idom/websocket_consumer.py @@ -3,7 +3,7 @@ import json import logging from dataclasses import dataclass -from typing import Any, Callable, Coroutine, Optional +from typing import Any, Awaitable, Callable, Optional from urllib.parse import parse_qsl from channels.auth import login @@ -21,7 +21,7 @@ @dataclass class WebsocketConnection: scope: dict - close: Callable[[Optional[int]], Coroutine[Any, Any, None]] + close: Callable[[Optional[int]], Awaitable[None]] view_id: str From 4efc14f0dca1afa761977be5a634f05a8f1ead68 Mon Sep 17 00:00:00 2001 From: Archmonger <16909269+Archmonger@users.noreply.github.com> Date: Tue, 5 Oct 2021 22:40:32 -0700 Subject: [PATCH 31/39] add disconnect within websocketconnection --- src/django_idom/websocket_consumer.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/django_idom/websocket_consumer.py b/src/django_idom/websocket_consumer.py index 2df12d56..7a4478d5 100644 --- a/src/django_idom/websocket_consumer.py +++ b/src/django_idom/websocket_consumer.py @@ -22,6 +22,7 @@ class WebsocketConnection: scope: dict close: Callable[[Optional[int]], Awaitable[None]] + disconnect: Callable[[int], Awaitable[None]] view_id: str @@ -47,7 +48,9 @@ async def connect(self) -> None: _logger.warning("IDOM websocket is missing AuthMiddlewareStack!") # Limit developer control this websocket - self.socket = WebsocketConnection(self.scope, self.close, self.view_id) + self.socket = WebsocketConnection( + self.scope, self.close, self.disconnect, self.view_id + ) self._idom_dispatcher_future = asyncio.ensure_future(self._run_dispatch_loop()) From 0e2436c8d90ec00c860d367caa867066b734a573 Mon Sep 17 00:00:00 2001 From: Archmonger <16909269+Archmonger@users.noreply.github.com> Date: Sat, 9 Oct 2021 01:26:50 -0700 Subject: [PATCH 32/39] bump idom version --- src/js/package-lock.json | 6 +++--- src/js/package.json | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/js/package-lock.json b/src/js/package-lock.json index ba26a073..6869fe24 100644 --- a/src/js/package-lock.json +++ b/src/js/package-lock.json @@ -70,9 +70,9 @@ "integrity": "sha512-L0s3Sid5r6YwrEvkig14SK3Emmc+kIjlfLhEGn2Vy3bk21JyDEes4MoDsbJk6luaPp8bugErnxPz86ZuAw6e5Q==" }, "idom-client-react": { - "version": "0.33.2", - "resolved": "https://registry.npmjs.org/idom-client-react/-/idom-client-react-0.33.2.tgz", - "integrity": "sha512-B0dIWTYtmSwWUkA7g9/8Stz0Otar/pSaARdxxaPVL8QGvjxaCEnP0M1jklep2SXlbO1U/mXRYIYWgGE2sSlTLg==", + "version": "0.33.3", + "resolved": "https://registry.npmjs.org/idom-client-react/-/idom-client-react-0.33.3.tgz", + "integrity": "sha512-CTqnSzhAVz20IHDexECtCdLuvE2diUIi63kog45sNiJdu5ig+cUKjT4zuA1YHGchf4jJVgsQKPsd1BB3Bx6cew==", "requires": { "fast-json-patch": "^3.0.0-1", "htm": "^3.0.3" diff --git a/src/js/package.json b/src/js/package.json index bea68d7a..ea04bf2d 100644 --- a/src/js/package.json +++ b/src/js/package.json @@ -18,7 +18,7 @@ "rollup-plugin-replace": "^2.2.0" }, "dependencies": { - "idom-client-react": "^0.33.0", + "idom-client-react": "^0.33.3", "react": "^17.0.2", "react-dom": "^17.0.2" } From 43a57fb2efe1307a36f348e6f593bbb0e06bc5ef Mon Sep 17 00:00:00 2001 From: Archmonger <16909269+Archmonger@users.noreply.github.com> Date: Sat, 30 Oct 2021 15:34:23 -0700 Subject: [PATCH 33/39] revert component registration check --- src/django_idom/templatetags/idom.py | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/src/django_idom/templatetags/idom.py b/src/django_idom/templatetags/idom.py index 1b3de383..c63cae25 100644 --- a/src/django_idom/templatetags/idom.py +++ b/src/django_idom/templatetags/idom.py @@ -1,4 +1,5 @@ import json +import sys from importlib import import_module from urllib.parse import urlencode from uuid import uuid4 @@ -35,17 +36,17 @@ def idom_component(_component_id_, **kwargs): def _register_component(full_component_name: str) -> None: - if full_component_name in IDOM_REGISTERED_COMPONENTS: - return - module_name, component_name = full_component_name.rsplit(".", 1) - try: - module = import_module(module_name) - except ImportError as error: - raise RuntimeError( - f"Failed to import {module_name!r} while loading {component_name!r}" - ) from error + if module_name in sys.modules: + module = sys.modules[module_name] + else: + try: + module = import_module(module_name) + except ImportError as error: + raise RuntimeError( + f"Failed to import {module_name!r} while loading {component_name!r}" + ) from error try: component = getattr(module, component_name) From 2885e85852e3602930e5f8a634eb23c4b8c78a41 Mon Sep 17 00:00:00 2001 From: Archmonger <16909269+Archmonger@users.noreply.github.com> Date: Sun, 31 Oct 2021 18:38:39 -0700 Subject: [PATCH 34/39] use django-idom version for svg --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 189638a5..967396b8 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ Tests - Version Info + Version Info License: MIT From 47d8ec6ad2ab9ee5ca4b66af7acf1addd7de033d Mon Sep 17 00:00:00 2001 From: Archmonger <16909269+Archmonger@users.noreply.github.com> Date: Mon, 1 Nov 2021 01:30:45 -0700 Subject: [PATCH 35/39] add EOF newlines --- src/django_idom/templates/idom/component.html | 2 +- tests/test_app/templates/base.html | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/django_idom/templates/idom/component.html b/src/django_idom/templates/idom/component.html index 8d4b181a..71950e97 100644 --- a/src/django_idom/templates/idom/component.html +++ b/src/django_idom/templates/idom/component.html @@ -11,4 +11,4 @@ "{{ idom_component_id }}", "{{ idom_component_params }}" ); - \ No newline at end of file + diff --git a/tests/test_app/templates/base.html b/tests/test_app/templates/base.html index e774403c..5b2311d2 100644 --- a/tests/test_app/templates/base.html +++ b/tests/test_app/templates/base.html @@ -19,4 +19,4 @@

IDOM Test Page

{% idom_component "test_app.components.SimpleBarChart" class="simple-bar-chart" %}
- \ No newline at end of file + From dbcf2b48867e51f4ab9f55c3eab75fea06741602 Mon Sep 17 00:00:00 2001 From: Archmonger <16909269+Archmonger@users.noreply.github.com> Date: Mon, 1 Nov 2021 01:40:36 -0700 Subject: [PATCH 36/39] cleanup WebsocketConnection --- src/django_idom/websocket_consumer.py | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/src/django_idom/websocket_consumer.py b/src/django_idom/websocket_consumer.py index 7a4478d5..ab73d670 100644 --- a/src/django_idom/websocket_consumer.py +++ b/src/django_idom/websocket_consumer.py @@ -35,8 +35,6 @@ def __init__(self, *args: Any, **kwargs: Any) -> None: async def connect(self) -> None: await super().connect() - self.view_id = self.scope["url_route"]["kwargs"]["view_id"] - user = self.scope.get("user") if user and user.is_authenticated: try: @@ -47,11 +45,6 @@ async def connect(self) -> None: elif user is None: _logger.warning("IDOM websocket is missing AuthMiddlewareStack!") - # Limit developer control this websocket - self.socket = WebsocketConnection( - self.scope, self.close, self.disconnect, self.view_id - ) - self._idom_dispatcher_future = asyncio.ensure_future(self._run_dispatch_loop()) async def disconnect(self, code: int) -> None: @@ -65,6 +58,7 @@ async def receive_json(self, content: Any, **kwargs: Any) -> None: await self._idom_recv_queue.put(LayoutEvent(**content)) async def _run_dispatch_loop(self): + view_id = self.scope["url_route"]["kwargs"]["view_id"] try: component_constructor = IDOM_REGISTERED_COMPONENTS[self.view_id] @@ -75,8 +69,11 @@ async def _run_dispatch_loop(self): query_dict = dict(parse_qsl(self.scope["query_string"].decode())) component_kwargs = json.loads(query_dict.get("kwargs", "{}")) + # Provide developer access to parts of this websocket + socket = WebsocketConnection(self.scope, self.close, self.disconnect, view_id) + try: - component_instance = component_constructor(self.socket, **component_kwargs) + component_instance = component_constructor(socket, **component_kwargs) except Exception: _logger.exception( f"Failed to construct component {component_constructor} " From 991513ebb49af1071d76874e28f19e69213f07c4 Mon Sep 17 00:00:00 2001 From: Archmonger <16909269+Archmonger@users.noreply.github.com> Date: Mon, 1 Nov 2021 01:40:58 -0700 Subject: [PATCH 37/39] remove useless init from websocket consumer --- src/django_idom/websocket_consumer.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/django_idom/websocket_consumer.py b/src/django_idom/websocket_consumer.py index ab73d670..7b4c749d 100644 --- a/src/django_idom/websocket_consumer.py +++ b/src/django_idom/websocket_consumer.py @@ -29,9 +29,6 @@ class WebsocketConnection: class IdomAsyncWebsocketConsumer(AsyncJsonWebsocketConsumer): """Communicates with the browser to perform actions on-demand.""" - def __init__(self, *args: Any, **kwargs: Any) -> None: - super().__init__(*args, **kwargs) - async def connect(self) -> None: await super().connect() From 568064f9bfd4997971e1963e1782256b4546d0e0 Mon Sep 17 00:00:00 2001 From: Archmonger <16909269+Archmonger@users.noreply.github.com> Date: Mon, 1 Nov 2021 01:48:23 -0700 Subject: [PATCH 38/39] IDOM_WS_MAX_RECONNECT_DELAY --- README.md | 5 +++-- src/django_idom/config.py | 2 +- src/django_idom/templates/idom/component.html | 2 +- src/django_idom/templatetags/idom.py | 4 ++-- 4 files changed, 7 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 967396b8..928c33a9 100644 --- a/README.md +++ b/README.md @@ -110,8 +110,9 @@ IDOM_BASE_URL: str = "_idom/" # Only applies when not using Django's caching framework (see below). IDOM_WEB_MODULE_LRU_CACHE_SIZE: int | None = None -# Max amount of time for client to attempt to reconnect. 0 disables reconnection. -IDOM_WS_RECONNECT_TIMEOUT: int = 604800 +# Maximum seconds between two reconnection attempts that would cause the client give up. +# 0 will disable reconnection. +IDOM_WS_MAX_RECONNECT_DELAY: int = 604800 # Configure a cache for loading JS files CACHES = { diff --git a/src/django_idom/config.py b/src/django_idom/config.py index 86c3418e..1d527a9e 100644 --- a/src/django_idom/config.py +++ b/src/django_idom/config.py @@ -10,7 +10,7 @@ IDOM_BASE_URL = getattr(settings, "IDOM_BASE_URL", "_idom/") IDOM_WEBSOCKET_URL = IDOM_BASE_URL + "websocket/" IDOM_WEB_MODULES_URL = IDOM_BASE_URL + "web_module/" -IDOM_WS_RECONNECT_TIMEOUT = getattr(settings, "IDOM_WS_RECONNECT_TIMEOUT", 604800) +IDOM_WS_MAX_RECONNECT_DELAY = getattr(settings, "IDOM_WS_MAX_RECONNECT_DELAY", 604800) _CACHES = getattr(settings, "CACHES", {}) if _CACHES: diff --git a/src/django_idom/templates/idom/component.html b/src/django_idom/templates/idom/component.html index 71950e97..65ad0c45 100644 --- a/src/django_idom/templates/idom/component.html +++ b/src/django_idom/templates/idom/component.html @@ -7,7 +7,7 @@ mountPoint, "{{ idom_websocket_url }}", "{{ idom_web_modules_url }}", - "{{ idom_ws_reconnect_timeout }}", + "{{ idom_ws_max_reconnect_delay }}", "{{ idom_component_id }}", "{{ idom_component_params }}" ); diff --git a/src/django_idom/templatetags/idom.py b/src/django_idom/templatetags/idom.py index c63cae25..cd83ebec 100644 --- a/src/django_idom/templatetags/idom.py +++ b/src/django_idom/templatetags/idom.py @@ -10,7 +10,7 @@ IDOM_REGISTERED_COMPONENTS, IDOM_WEB_MODULES_URL, IDOM_WEBSOCKET_URL, - IDOM_WS_RECONNECT_TIMEOUT, + IDOM_WS_MAX_RECONNECT_DELAY, ) @@ -28,7 +28,7 @@ def idom_component(_component_id_, **kwargs): "class": class_, "idom_websocket_url": IDOM_WEBSOCKET_URL, "idom_web_modules_url": IDOM_WEB_MODULES_URL, - "idom_ws_reconnect_timeout": IDOM_WS_RECONNECT_TIMEOUT, + "idom_ws_max_reconnect_delay": IDOM_WS_MAX_RECONNECT_DELAY, "idom_mount_uuid": uuid4().hex, "idom_component_id": _component_id_, "idom_component_params": urlencode({"kwargs": json_kwargs}), From 54b518608d444d46e1edf1cf6e79b27181b9b9d5 Mon Sep 17 00:00:00 2001 From: Archmonger <16909269+Archmonger@users.noreply.github.com> Date: Mon, 1 Nov 2021 01:49:18 -0700 Subject: [PATCH 39/39] self.view_id -> view_id --- src/django_idom/websocket_consumer.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/django_idom/websocket_consumer.py b/src/django_idom/websocket_consumer.py index 7b4c749d..349a8366 100644 --- a/src/django_idom/websocket_consumer.py +++ b/src/django_idom/websocket_consumer.py @@ -58,9 +58,9 @@ async def _run_dispatch_loop(self): view_id = self.scope["url_route"]["kwargs"]["view_id"] try: - component_constructor = IDOM_REGISTERED_COMPONENTS[self.view_id] + component_constructor = IDOM_REGISTERED_COMPONENTS[view_id] except KeyError: - _logger.warning(f"Unknown IDOM view ID {self.view_id!r}") + _logger.warning(f"Unknown IDOM view ID {view_id!r}") return query_dict = dict(parse_qsl(self.scope["query_string"].decode()))