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
skeleton of django installable app
  • Loading branch information
rmorshea committed Jul 26, 2021
commit f8f094ee1abeee6d3df835b7d03bee504739233b
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
# Django IDOM Build Artifacts
src/django_idom/static/js

# Django #
logs
*.log
Expand Down
Empty file added MANIFEST.in
Empty file.
72 changes: 63 additions & 9 deletions setup.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,25 @@
import os
from __future__ import print_function

import pipes
import shutil
import subprocess
import sys
import traceback
from distutils import log
from distutils.command.build import build # type: ignore
from distutils.command.sdist import sdist # type: ignore
from pathlib import Path

from setuptools import find_packages, setup
from setuptools.command.develop import develop


if sys.platform == "win32":
from subprocess import list2cmdline
else:

def list2cmdline(cmd_list):
return " ".join(map(pipes.quote, cmd_list))


# the name of the project
Expand Down Expand Up @@ -31,6 +48,7 @@
"license": "MIT",
"platforms": "Linux, Mac OS X, Windows",
"keywords": ["interactive", "widgets", "DOM", "React"],
"include_package_data": True,
"zip_safe": False,
"classifiers": [
"Framework :: Django",
Expand All @@ -52,14 +70,14 @@
# Library Version
# -----------------------------------------------------------------------------

with open(os.path.join(package_dir, "__init__.py")) as f:
for line in f.read().split("\n"):
if line.startswith("__version__ = "):
package["version"] = eval(line.split("=", 1)[1])
break
else:
print("No version found in %s/__init__.py" % package_dir)
sys.exit(1)

for line in (package_dir / "__init__.py").read_text().split("\n"):
if line.startswith("__version__ = "):
package["version"] = eval(line.split("=", 1)[1])
break
else:
print("No version found in %s/__init__.py" % package_dir)
sys.exit(1)


# -----------------------------------------------------------------------------
Expand Down Expand Up @@ -87,6 +105,42 @@
package["long_description_content_type"] = "text/markdown"


# ----------------------------------------------------------------------------
# Build Javascript
# ----------------------------------------------------------------------------


def build_javascript_first(cls):
class Command(cls):
def run(self):
log.info("Installing Javascript...")
try:
js_dir = str(src_dir / "js")
npm = shutil.which("npm") # this is required on windows
if npm is None:
raise RuntimeError("NPM is not installed.")
for args in (f"{npm} install", f"{npm} run build"):
args_list = args.split()
log.info(f"> {list2cmdline(args_list)}")
subprocess.run(args_list, cwd=js_dir, check=True)
except Exception:
log.error("Failed to install Javascript")
log.error(traceback.format_exc())
raise
else:
log.info("Successfully installed Javascript")
super().run()

return Command


package["cmdclass"] = {
"sdist": build_javascript_first(sdist),
"build": build_javascript_first(build),
"develop": build_javascript_first(develop),
}


# -----------------------------------------------------------------------------
# Install It
# -----------------------------------------------------------------------------
Expand Down
3 changes: 3 additions & 0 deletions src/django_idom/app_settings.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from django.conf import settings

IDOM_WEBSOCKET_URL = getattr(settings, "IDOM_WEBSOCKET_URL", "_idom/")
2 changes: 0 additions & 2 deletions src/django_idom/static/js/idom.js

This file was deleted.

2 changes: 1 addition & 1 deletion src/django_idom/templates/idom/head_content.html
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
<!-- The importable IDOM JS -->
<script src="{% static 'js/idom.js' %}" crossorigin="anonymous"></script>
<script src="{% static 'js/idom.js' %}" crossorigin="anonymous"></script>
6 changes: 5 additions & 1 deletion src/django_idom/templates/idom/root.html
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
<!-- Allows for making the IDOM root object the Django way
Developers will often opt not to use this,
but having this makes getting started a little bit easier to explain -->
<div id="{{ html_id }}"></div>
<div id="{{ idom_view_id }}"></div>
<script type="module" crossorigin="anonymous">
import { mountToElement } from "{% static 'js/idom.js' %}";
mountViewToElement("{{ idom_websocket_url }}", "{{ idom_view_id }}");
</script>
5 changes: 5 additions & 0 deletions src/django_idom/templates/idom/view.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<div id="{{ idom_view_id }}"></div>
<script type="module" crossorigin="anonymous">
import { mountToElement } from "{% static 'js/idom.js' %}";
mountViewToElement("{{ idom_websocket_url }}", "{{ idom_view_id }}");
</script>
9 changes: 5 additions & 4 deletions src/django_idom/templatetags/idom.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
from django import template

from django_idom.app_settings import IDOM_WEBSOCKET_URL


register = template.Library()

Expand All @@ -10,7 +12,6 @@ def idom_scripts():
pass


# Template tag that renders an empty idom root object
@register.inclusion_tag("idom/root.html")
def idom_view(html_id):
return {"html_id": html_id}
@register.inclusion_tag("idom/view.html")
def idom_view(view_id):
return {"idom_websocket_url": IDOM_WEBSOCKET_URL, "view_id": view_id}
29 changes: 29 additions & 0 deletions src/django_idom/view_loader.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import logging
from importlib import import_module
from pathlib import Path
from typing import Dict

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


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

for app_name in settings.INSTALLED_APPS:
app_mod = import_module(app_name)
if not hasattr(app_mod, "idom"):
continue

for idom_view_path in Path(app_mod.__file__).iterdir():
if idom_view_path.suffix == ".py" and idom_view_path.is_file():
idom_view_mod_name = ".".join([app_name, "idom", idom_view_path.stem])
idom_view_mod = import_module(idom_view_mod_name)

if hasattr(idom_view_mod, "Root") and callable(idom_view_mod.Root):
ALL_VIEWS[idom_view_mod_name] = idom_view_mod.Root
else:
logger.warning(
f"Expected module {idom_view_mod_name} to expose a 'Root' "
" attribute that is an IDOM component."
)
34 changes: 28 additions & 6 deletions src/django_idom/websocket_consumer.py
Original file line number Diff line number Diff line change
@@ -1,20 +1,22 @@
"""Anything used to construct a websocket endpoint"""
import asyncio
import logging
from typing import Any

from channels.generic.websocket import AsyncJsonWebsocketConsumer
from idom.core.dispatcher import dispatch_single_view
from idom.core.layout import Layout, LayoutEvent
from idom.core.proto import ComponentConstructor

from .view_loader import ALL_VIEWS


logger = logging.getLogger(__name__)


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

def __init__(
self, component: ComponentConstructor, *args: Any, **kwargs: Any
) -> None:
self._idom_component_constructor = component
def __init__(self, *args: Any, **kwargs: Any) -> None:
super().__init__(*args, **kwargs)

async def connect(self) -> None:
Expand All @@ -32,10 +34,30 @@ 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 = ...

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

component_constructor = ALL_VIEWS[view_id]

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

self._idom_recv_queue = recv_queue = asyncio.Queue()
try:
await dispatch_single_view(
Layout(self._idom_component_constructor()),
Layout(component_instance),
self.send_json,
recv_queue.get,
)
Expand Down
File renamed without changes.
File renamed without changes.
4 changes: 2 additions & 2 deletions tests/js/package.json → src/js/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "tests",
"version": "1.0.0",
"name": "django-idom-client",
"version": "0.0.1",
"description": "test app for idom_django websocket server",
"main": "src/index.js",
"files": [
Expand Down
2 changes: 1 addition & 1 deletion tests/js/rollup.config.js → src/js/rollup.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ const { PRODUCTION } = process.env;
export default {
input: "src/index.js",
output: {
file: "../test_app/static/build.js",
file: "../django_idom/static/js/idom.js",
format: "esm",
},
plugins: [
Expand Down
18 changes: 18 additions & 0 deletions src/js/src/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { mountLayoutWithWebSocket } from "idom-client-react";


// Set up a websocket at the base endpoint
let LOCATION = window.location;
let WS_PROTOCOL = "";
if (LOCATION.protocol == "https:") {
WS_PROTOCOL = "wss://";
} else {
WS_PROTOCOL = "ws://";
}
let WS_ENDPOINT_URL = WS_PROTOCOL + LOCATION.host + "/";


export function mountViewToElement(idomWebsocketUrl, viewId) {
const fullWebsocketUrl = WS_ENDPOINT_URL + idomWebsocketUrl
mountLayoutWithWebSocket(document.getElementById(viewId), fullWebsocketUrl);
}
13 changes: 0 additions & 13 deletions tests/js/src/index.js

This file was deleted.

9 changes: 7 additions & 2 deletions tests/test_app/asgi.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,12 @@

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 # noqa: E402
from django_idom import IdomAsyncWebSocketConsumer
from django_idom.app_settings import IDOM_WEBSOCKET_URL # noqa: E402

from .views import Root

Expand All @@ -25,11 +27,14 @@
from channels.routing import ProtocolTypeRouter, URLRouter # noqa: E402


IDOM_WEBSOCKET_URL = settings.IDOM_WEBSOCKET_URL


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


@idom.component
def Root():
...
Empty file.
1 change: 1 addition & 0 deletions tests/test_app/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
"django.contrib.messages",
"django.contrib.staticfiles",
"channels", # Websocket library
"django_idom", # Django compatible IDOM client
"test_app", # This test application
]
MIDDLEWARE = [
Expand Down