Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
30a481f
create barebones templatetags
Archmonger Jul 22, 2021
8b0b363
styling fix
Archmonger Jul 22, 2021
4bcd9bf
idom_view
Archmonger Jul 26, 2021
f8f094e
skeleton of django installable app
rmorshea Jul 26, 2021
937da20
get test_app runserver to work
rmorshea Jul 26, 2021
e76408b
fix MANIFEST.in to include static/templates
rmorshea Jul 28, 2021
f008f6b
fix style
rmorshea Jul 28, 2021
b4f256b
require a my_app.idom.components attribute
rmorshea Jul 28, 2021
a0b75e0
parametrized components + serve web modules
rmorshea Jul 28, 2021
cd12a7c
add IDOM_IGNORED_DJANGO_APPS option
rmorshea Jul 29, 2021
9f4412d
add basic docs to README
rmorshea Jul 29, 2021
34452e4
minor doc improvements
rmorshea Jul 29, 2021
3e07d5a
more doc updates
rmorshea Jul 29, 2021
0ff84ec
make logger private
rmorshea Aug 5, 2021
fbb0037
use string path to template
rmorshea Aug 5, 2021
407b506
rename URL resolver functions
rmorshea Aug 5, 2021
fa95bb1
fix flake8
rmorshea Aug 5, 2021
9da3de8
correct template tag description
rmorshea Aug 5, 2021
a05d0ef
update app organization in README
rmorshea Aug 5, 2021
937244c
switch to decorator collection method
rmorshea Aug 11, 2021
af6601d
load components using template names
rmorshea Aug 12, 2021
60384af
minor README tweaks
rmorshea Aug 12, 2021
f72b2d6
remove unused config option
rmorshea Aug 12, 2021
4bcdeb5
use different param name in README ex
rmorshea Aug 12, 2021
536968a
cache and asyncify web module loading
rmorshea Aug 19, 2021
aee70a7
rename idom_view to idom_component
rmorshea Aug 19, 2021
7093252
README rename your_template to your_view
rmorshea Aug 19, 2021
66b7cb3
fix README typo
rmorshea Aug 19, 2021
4a4fa74
make websocket and web module paths consts
rmorshea Aug 19, 2021
b209995
correct terminology
rmorshea Aug 19, 2021
b9934de
slim down README asgi.py description
rmorshea Aug 19, 2021
fa70766
better summarize what IDOM is
rmorshea Aug 19, 2021
a15c334
bump copyright year
rmorshea Aug 19, 2021
aed7fc7
fix template formatting
rmorshea Aug 19, 2021
68aa643
rename your_app to your_project
rmorshea Aug 19, 2021
83e3f7c
add CODEOWNERS
rmorshea Aug 19, 2021
File filter

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
get test_app runserver to work
  • Loading branch information
rmorshea committed Jul 26, 2021
commit 937da205da8f8555a232fd5a4b486dbdfc40d15e
3 changes: 3 additions & 0 deletions MANIFEST.in
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
include src/django_idom/static/js/idom.js
include src/django_idom/templates/head_content.html
include src/django_idom/templates/view.html
1 change: 0 additions & 1 deletion noxfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,6 @@ def test_suite(session: Session) -> None:
session.chdir(HERE / "tests")
session.env["IDOM_DEBUG_MODE"] = "1"
session.env["SELENIUM_HEADLESS"] = str(int("--headless" in session.posargs))
session.run("python", "manage.py", "build_js")
session.run("python", "manage.py", "test")


Expand Down
7 changes: 5 additions & 2 deletions src/django_idom/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
from .websocket_consumer import IdomAsyncWebSocketConsumer
from .websocket_consumer import (
IdomAsyncWebSocketConsumer,
django_idom_websocket_consumer_url,
)


__version__ = "0.0.1"
__all__ = ["IdomAsyncWebSocketConsumer"]
__all__ = ["IdomAsyncWebSocketConsumer", "django_idom_websocket_consumer_url"]
50 changes: 50 additions & 0 deletions src/django_idom/app_components.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import logging
from importlib import import_module
from typing import Dict

from django.conf import settings
from idom.core.proto import ComponentConstructor


logger = logging.getLogger(__name__)
_LOADED_COMPONENTS: Dict[str, ComponentConstructor] = {}


def get_component(name: str) -> ComponentConstructor:
return _LOADED_COMPONENTS[name]


def has_component(name: str) -> bool:
return name in _LOADED_COMPONENTS


for app_mod_name in settings.INSTALLED_APPS:
idom_mod_name = f"{app_mod_name}.idom"

try:
idom_mod = import_module(idom_mod_name)
except ImportError:
logger.debug(f"Skipping {idom_mod_name!r} - does not exist")
continue

if not hasattr(idom_mod, "__all__"):
logger.warning(
f"'django_idom' expected module {idom_mod_name!r} to have an "
"'__all__' attribute that lists its publically available components."
)
continue

for component_name in idom_mod.__all__:
try:
component_constructor = getattr(idom_mod, component_name)
except AttributeError:
logger.warning(
f"Module {idom_mod_name!r} has no attribute {component_name!r}"
)
continue

if not callable(component_constructor):
logger.warning(f"'{idom_mod_name}.{component_name}' is not a component")
continue

_LOADED_COMPONENTS[f"{app_mod_name}.{component_name}"] = component_constructor
3 changes: 3 additions & 0 deletions src/django_idom/app_settings.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
from django.conf import settings


IDOM_WEBSOCKET_URL = getattr(settings, "IDOM_WEBSOCKET_URL", "_idom/")
if not IDOM_WEBSOCKET_URL.endswith("/"):
IDOM_WEBSOCKET_URL += "/"
8 changes: 0 additions & 8 deletions src/django_idom/templates/idom/root.html

This file was deleted.

13 changes: 10 additions & 3 deletions src/django_idom/templates/idom/view.html
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
<div id="{{ idom_view_id }}"></div>
{% load static %}
<div id="{{ idom_mount_uuid }}"></div>
<script type="module" crossorigin="anonymous">
import { mountToElement } from "{% static 'js/idom.js' %}";
mountViewToElement("{{ idom_websocket_url }}", "{{ idom_view_id }}");
import { mountViewToElement } from "{% static 'js/idom.js' %}";
const mountPoint = document.getElementById("{{ idom_mount_uuid }}");
mountViewToElement(
mountPoint,
"{{ idom_websocket_url }}",
"{{ idom_view_id }}",
"{{ idom_view_params }}"
);
</script>
13 changes: 10 additions & 3 deletions src/django_idom/templatetags/idom.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from uuid import uuid4

from django import template

from django_idom.app_settings import IDOM_WEBSOCKET_URL
Expand All @@ -8,10 +10,15 @@

# Template tag that renders the IDOM scripts
@register.inclusion_tag("idom/head_content.html")
def idom_scripts():
def idom_head():
pass


@register.inclusion_tag("idom/view.html")
def idom_view(view_id):
return {"idom_websocket_url": IDOM_WEBSOCKET_URL, "view_id": view_id}
def idom_view(view_id, view_params=""):
return {
"idom_websocket_url": IDOM_WEBSOCKET_URL,
"idom_mount_uuid": uuid4().hex,
"idom_view_id": view_id,
"idom_view_params": view_params,
}
29 changes: 0 additions & 29 deletions src/django_idom/view_loader.py

This file was deleted.

36 changes: 26 additions & 10 deletions src/django_idom/websocket_consumer.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,35 @@
import asyncio
import logging
from typing import Any
from urllib.parse import parse_qsl

from channels.generic.websocket import AsyncJsonWebsocketConsumer
from django.urls import path
from idom.core.dispatcher import dispatch_single_view
from idom.core.layout import Layout, LayoutEvent

from .view_loader import ALL_VIEWS
from .app_components import get_component, has_component
from .app_settings import IDOM_WEBSOCKET_URL


logger = logging.getLogger(__name__)


def django_idom_websocket_consumer_url(*args, **kwargs):
"""Return 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
to allow users to configure the URL themselves
"""
return path(
IDOM_WEBSOCKET_URL + "<view_id>/",
IdomAsyncWebSocketConsumer.as_asgi(),
*args,
**kwargs,
)


class IdomAsyncWebSocketConsumer(AsyncJsonWebsocketConsumer):
"""Communicates with the browser to perform actions on-demand."""

Expand All @@ -34,23 +52,21 @@ async def receive_json(self, content: Any, **kwargs: Any) -> None:
await self._idom_recv_queue.put(LayoutEvent(**content))

async def _run_dispatch_loop(self):
# get the URL parameters and grab the view ID
view_id = ...
# get component ags from the URL params too
component_args = ...
view_id = self.scope["url_route"]["kwargs"]["view_id"]

if view_id not in ALL_VIEWS:
logger.warning(f"Uknown IDOM view ID {view_id}")
if not has_component(view_id):
logger.warning(f"Uknown IDOM view ID {view_id!r}")
return

component_constructor = ALL_VIEWS[view_id]
component_constructor = get_component(view_id)
component_kwargs = dict(parse_qsl(self.scope["query_string"]))

try:
component_instance = component_constructor(*component_args)
component_instance = component_constructor(**component_kwargs)
except Exception:
logger.exception(
f"Failed to construct component {component_constructor} "
f"with parameters {component_args}"
f"with parameters {component_kwargs}"
)
return

Expand Down
8 changes: 4 additions & 4 deletions src/js/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 3 additions & 3 deletions src/js/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ if (LOCATION.protocol == "https:") {
let WS_ENDPOINT_URL = WS_PROTOCOL + LOCATION.host + "/";


export function mountViewToElement(idomWebsocketUrl, viewId) {
const fullWebsocketUrl = WS_ENDPOINT_URL + idomWebsocketUrl
mountLayoutWithWebSocket(document.getElementById(viewId), fullWebsocketUrl);
export function mountViewToElement(mountPoint, idomWebsocketUrl, viewId, queryParams) {
const fullWebsocketUrl = WS_ENDPOINT_URL + idomWebsocketUrl + viewId + "/";
mountLayoutWithWebSocket(mountPoint, fullWebsocketUrl);
}
2 changes: 1 addition & 1 deletion tests/.gitignore
Original file line number Diff line number Diff line change
@@ -1 +1 @@
test_app/static/build.js
*.sqlite3
14 changes: 2 additions & 12 deletions tests/test_app/asgi.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,9 @@

import os

from django.conf import settings
from django.conf.urls import url
from django.core.asgi import get_asgi_application

from django_idom import IdomAsyncWebSocketConsumer
from django_idom.app_settings import IDOM_WEBSOCKET_URL # noqa: E402

from .views import Root
from django_idom import django_idom_websocket_consumer_url


os.environ.setdefault("DJANGO_SETTINGS_MODULE", "test_app.settings")
Expand All @@ -27,14 +22,9 @@
from channels.routing import ProtocolTypeRouter, URLRouter # noqa: E402


IDOM_WEBSOCKET_URL = settings.IDOM_WEBSOCKET_URL


application = ProtocolTypeRouter(
{
"http": http_asgi_app,
"websocket": URLRouter(
[url(IDOM_WEBSOCKET_URL, IdomAsyncWebSocketConsumer.as_asgi())]
),
"websocket": URLRouter([django_idom_websocket_consumer_url()]),
}
)
24 changes: 24 additions & 0 deletions tests/test_app/idom.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import idom


__all__ = "HelloWorld", "Button"


@idom.component
def HelloWorld():
return idom.html.h1({"id": "hello-world"}, "Hello World!")


@idom.component
def Button():
count, set_count = idom.hooks.use_state(0)
return idom.html.div(
idom.html.button(
{"id": "counter-inc", "onClick": lambda event: set_count(count + 1)},
"Click me!",
),
idom.html.p(
{"id": "counter-num", "data-count": count},
f"Current count is: {count}",
),
)
Empty file removed tests/test_app/idom/__init__.py
Empty file.
6 changes: 0 additions & 6 deletions tests/test_app/idom/button.py

This file was deleted.

Empty file removed tests/test_app/idom/hello_world.py
Empty file.
Empty file.
Empty file.
16 changes: 0 additions & 16 deletions tests/test_app/management/commands/build_js.py

This file was deleted.

6 changes: 3 additions & 3 deletions tests/test_app/templates/base.html
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
{% load static %}
{% load static %} {% load idom %}
<!DOCTYPE html>
<html lang="en">
<head>
Expand All @@ -15,7 +15,7 @@

<body>
<h1>IDOM Test Page</h1>
<div id="mount"></div>
<script src="{% static 'build.js' %}" crossorigin="anonymous"></script>
<div>{% idom_view "test_app.HelloWorld" %}</div>
<div>{% idom_view "test_app.Button" %}</div>
</body>
</html>
26 changes: 0 additions & 26 deletions tests/test_app/views.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import idom
from django.http import HttpResponse
from django.template import loader

Expand All @@ -7,28 +6,3 @@ def base_template(request):
template = loader.get_template("base.html")
context = {}
return HttpResponse(template.render(context, request))


@idom.component
def Root():
return idom.html.div(HelloWorld(), Counter())


@idom.component
def HelloWorld():
return idom.html.h1({"id": "hello-world"}, "Hello World!")


@idom.component
def Counter():
count, set_count = idom.hooks.use_state(0)
return idom.html.div(
idom.html.button(
{"id": "counter-inc", "onClick": lambda event: set_count(count + 1)},
"Click me!",
),
idom.html.p(
{"id": "counter-num", "data-count": count},
f"Current count is: {count}",
),
)