Skip to content

Utilize django include for HTTP URLs #45

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 11 commits into from
Jan 21, 2022
205 changes: 70 additions & 135 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,20 +1,6 @@
# Django IDOM

<p>
<a href="https://github.com/idom-team/django-idom/actions?query=workflow%3ATest">
<img alt="Tests" src="https://github.com/idom-team/django-idom/workflows/Test/badge.svg?event=push" />
</a>
<a href="https://pypi.python.org/pypi/django-idom">
<img alt="Version Info" src="https://img.shields.io/pypi/v/django-idom.svg"/>
</a>
<a href="https://github.com/idom-team/django-idom/blob/main/LICENSE">
<img alt="License: MIT" src="https://img.shields.io/badge/License-MIT-purple.svg">
</a>
</p>

`django-idom` allows Django to integrate with [IDOM](https://github.com/idom-team/idom),
a package inspired by [ReactJS](https://reactjs.org/) for creating responsive web
interfaces in pure Python.
# Django IDOM &middot; [![Tests](https://github.com/idom-team/django-idom/workflows/Test/badge.svg?event=push)](https://github.com/idom-team/django-idom/actions?query=workflow%3ATest) [![PyPI Version](https://img.shields.io/pypi/v/django-idom.svg)](https://pypi.python.org/pypi/django-idom) [![License](https://img.shields.io/badge/License-MIT-purple.svg)](https://github.com/idom-team/django-idom/blob/main/LICENSE)

`django-idom` allows Django to integrate with [IDOM](https://github.com/idom-team/idom), a reactive Python web framework for building **interactive websites without needing a single line of Javascript**.

**You can try IDOM now in a Jupyter Notebook:**
<a
Expand All @@ -27,70 +13,60 @@ interfaces in pure Python.
src="https://mybinder.org/badge_logo.svg"/>
</a>

# Install Django IDOM
# Quick Example

```bash
pip install django-idom
```
## `example_app/components.py`

This is where you'll define your [IDOM](https://github.com/idom-team/idom) components. Ultimately though, you should
feel free to organize your component modules you wish. Any components created will ultimately be referenced
by Python dotted path in `your-template.html`.

# Django Integration
```python
from idom import component, html
from django_idom import IdomWebsocket

To integrate IDOM into your application you'll need to modify or add the following files to `your_project`:

```
your_project/
├── __init__.py
├── asgi.py
├── settings.py
├── urls.py
└── example_app/
├── __init__.py
├── components.py
├── templates/
│ └── your-template.html
└── urls.py
@component
def Hello(websocket: IdomWebsocket, greeting_recipient: str): # Names are CamelCase by ReactJS convention
return html.header(f"Hello {greeting_recipient}!")
```

## `asgi.py`
## [`example_app/templates/your-template.html`](https://docs.djangoproject.com/en/dev/topics/templates/)

Follow the [`channels`](https://channels.readthedocs.io/en/stable/)
[installation guide](https://channels.readthedocs.io/en/stable/installation.html) in
order to create ASGI websockets within Django. Then, we will add a path for IDOM's
websocket consumer using `IDOM_WEBSOCKET_PATH`.
In your templates, you may add IDOM components into your HTML by using the `idom_component`
template tag. This tag requires the dotted path to the component function. Additonally, you can
pass in keyworded arguments into your component function.

_Note: If you wish to change the route where this websocket is served from, see the
available [settings](#settingspy)._
In context this will look a bit like the following...

```python
```jinja
{% load idom %}

import os
<!DOCTYPE html>
<html>
<body>
...
{% idom_component "my_django_project.example_app.components.Hello" greeting_recipient="World" %}
</body>
</html>
```

from django.core.asgi import get_asgi_application
# Installation

from django_idom import IDOM_WEBSOCKET_PATH
Install `django-idom` via pip.

os.environ.setdefault("DJANGO_SETTINGS_MODULE", "test_app.settings")
```bash
pip install django-idom
```

# 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
You'll also need to modify a few files in your Django project...

application = ProtocolTypeRouter(
{
"http": http_asgi_app,
"websocket": SessionMiddlewareStack(
AuthMiddlewareStack(URLRouter([IDOM_WEBSOCKET_PATH]))
),
}
)
```

## `settings.py`
## [`settings.py`](https://docs.djangoproject.com/en/dev/topics/settings/)

In your settings you'll need to add `django_idom` to the
[`INSTALLED_APPS`](https://docs.djangoproject.com/en/3.2/ref/settings/#std:setting-INSTALLED_APPS)
[`INSTALLED_APPS`](https://docs.djangoproject.com/en/dev/ref/settings/#std:setting-INSTALLED_APPS)
list:

```python
Expand All @@ -100,104 +76,63 @@ INSTALLED_APPS = [
]
```

You may configure additional options as well:
You may configure additional options as well...

```python
# the base URL for all IDOM-releated resources
IDOM_BASE_URL: str = "_idom/"
# If "idom" cache is not configured, then we'll use the "default" instead
CACHES = {
"idom": {"BACKEND": ...},
}

# 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 = {
# If "idom" cache is not configured, then we'll use the "default" instead
"idom": {"BACKEND": ...},
}
# The URL for IDOM to serve its Websockets
IDOM_WEBSOCKET_URL: str = "idom/"
```

## `urls.py`
## [`urls.py`](https://docs.djangoproject.com/en/dev/topics/http/urls/)

You'll need to include IDOM's static web modules path using `IDOM_WEB_MODULES_PATH`.
Similarly to the `IDOM_WEBSOCKET_PATH`. If you wish to change the route where this
websocket is served from, see the available [settings](#settings.py).
Add Django-IDOM http URLs to your `urlpatterns`.

```python
from django_idom import IDOM_WEB_MODULES_PATH

urlpatterns = [
IDOM_WEB_MODULES_PATH,
path("idom/", include("django_idom.http.urls")),
...
]
```

## `example_app/components.py`

This is where, by a convention similar to that of
[`views.py`](https://docs.djangoproject.com/en/3.2/topics/http/views/), you'll define
your [IDOM](https://github.com/idom-team/idom) components. Ultimately though, you should
feel free to organize your component modules you wish. The components created here will
ultimately be referenced by name in `your-template.html`. `your-template.html`.

```python
import idom

@idom.component
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`

In your templates, you may inject a view of an IDOM component into your templated HTML
by using the `idom_component` template tag. This tag which requires the name of a component
to render (of the form `module_name.ComponentName`) and keyword arguments you'd like to
pass it from the template.

```python
idom_component module_name.ComponentName param_1="something" param_2="something-else"
```

In context this will look a bit like the following...

```jinja
{% load idom %}
## [`asgi.py`](https://docs.djangoproject.com/en/dev/howto/deployment/asgi/)

<!DOCTYPE html>
<html>
<body>
...
{% idom_component "your_project.example_app.components.Hello" greeting_recipient="World" %}
</body>
</html>
```
If you do not have an `asgi.py`, first follow the [`channels` installation guide](https://channels.readthedocs.io/en/stable/installation.html) in
order to create websockets within Django.

## `example_app/views.py`
We will add IDOM's websocket consumer path using `IDOM_WEBSOCKET_PATH`.

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).
_Note: If you wish to change the route where this websocket is served from, see the
available [settings](#settingspy)._

```python
from django.shortcuts import render

def your_view(request):
context = {}
return render(request, "your-template.html", context)
```

## `example_app/urls.py`
import os
from django.core.asgi import get_asgi_application
from django_idom import IDOM_WEBSOCKET_PATH

Include your view in the list of urlpatterns
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "test_app.settings")
http_asgi_app = get_asgi_application()

```python
from django.urls import path
from .views import your_view # define this view like any other HTML template view
from channels.auth import AuthMiddlewareStack
from channels.routing import ProtocolTypeRouter, URLRouter

urlpatterns = [
path("", your_view),
...
]
application = ProtocolTypeRouter(
{
"http": http_asgi_app,
"websocket": SessionMiddlewareStack(
AuthMiddlewareStack(URLRouter([IDOM_WEBSOCKET_PATH]))
),
}
)
```

# Developer Guide
Expand Down
5 changes: 3 additions & 2 deletions src/django_idom/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from .paths import IDOM_WEB_MODULES_PATH, IDOM_WEBSOCKET_PATH
from .websocket.consumer import IdomWebsocket
from .websocket.paths import IDOM_WEBSOCKET_PATH


__version__ = "0.0.1"
__all__ = ["IDOM_WEB_MODULES_PATH", "IDOM_WEBSOCKET_PATH"]
__all__ = ["IDOM_WEBSOCKET_PATH", "IdomWebsocket"]
5 changes: 2 additions & 3 deletions src/django_idom/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,10 @@

IDOM_REGISTERED_COMPONENTS: Dict[str, ComponentConstructor] = {}

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_WEBSOCKET_URL = getattr(settings, "IDOM_WEBSOCKET_URL", "idom/")
IDOM_WS_MAX_RECONNECT_DELAY = getattr(settings, "IDOM_WS_MAX_RECONNECT_DELAY", 604800)

# Determine if using Django caching or LRU cache
if "idom" in getattr(settings, "CACHES", {}):
IDOM_CACHE = caches["idom"]
else:
Expand Down
Empty file.
14 changes: 14 additions & 0 deletions src/django_idom/http/urls.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
from django.urls import path

from . import views


app_name = "idom"

urlpatterns = [
path(
"web_module/<path:file>",
views.web_modules_file,
name="web_modules",
)
]
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from django.http import HttpRequest, HttpResponse
from idom.config import IDOM_WED_MODULES_DIR

from .config import IDOM_CACHE
from django_idom.config import IDOM_CACHE


async def web_modules_file(request: HttpRequest, file: str) -> HttpResponse:
Expand Down
31 changes: 0 additions & 31 deletions src/django_idom/paths.py

This file was deleted.

2 changes: 1 addition & 1 deletion src/django_idom/templates/idom/component.html
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{% load static %}
<div id="{{ idom_mount_uuid }}" class="{{ class }}"></div>
<script type="module" crossorigin="anonymous">
import { mountViewToElement } from "{% static '/django_idom/client.js' %}";
import { mountViewToElement } from "{% static 'django_idom/client.js' %}";
const mountPoint = document.getElementById("{{ idom_mount_uuid }}");
mountViewToElement(
mountPoint,
Expand Down
8 changes: 3 additions & 5 deletions src/django_idom/templatetags/idom.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,13 @@
from uuid import uuid4

from django import template
from django.urls import reverse

from django_idom.config import (
IDOM_WEB_MODULES_URL,
IDOM_WEBSOCKET_URL,
IDOM_WS_MAX_RECONNECT_DELAY,
)
from django_idom.config import IDOM_WEBSOCKET_URL, IDOM_WS_MAX_RECONNECT_DELAY
from django_idom.utils import _register_component


IDOM_WEB_MODULES_URL = reverse("idom:web_modules", args=["x"])[:-1][1:]
register = template.Library()


Expand Down
Empty file.
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,14 @@
from idom.core.dispatcher import dispatch_single_view
from idom.core.layout import Layout, LayoutEvent

from .config import IDOM_REGISTERED_COMPONENTS
from django_idom.config import IDOM_REGISTERED_COMPONENTS


_logger = logging.getLogger(__name__)


@dataclass
class WebsocketConnection:
class IdomWebsocket:
scope: dict
close: Callable[[Optional[int]], Awaitable[None]]
disconnect: Callable[[int], Awaitable[None]]
Expand Down Expand Up @@ -67,7 +67,7 @@ async def _run_dispatch_loop(self):
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)
socket = IdomWebsocket(self.scope, self.close, self.disconnect, view_id)

try:
component_instance = component_constructor(socket, **component_kwargs)
Expand Down
Loading