+
+We provide a version of [`reactpy-router`](https://github.com/reactive-python/reactpy-router) that is compatible with Django, and uses Django's URL routing syntax.
+
+
+
+---
+
+## Django Router
+
+...
diff --git a/mkdocs.yml b/mkdocs.yml
index 5c308d00..d2f6bc4a 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -7,6 +7,7 @@ nav:
- Reference:
- Components: reference/components.md
- Hooks: reference/hooks.md
+ - Router: reference/router.md
- Decorators: reference/decorators.md
- Utilities: reference/utils.md
- Template Tag: reference/template-tag.md
From 3b3ec51198268c11218e17de8dadff71a3a9ee7d Mon Sep 17 00:00:00 2001
From: Archmonger <16909269+Archmonger@users.noreply.github.com>
Date: Sun, 31 Dec 2023 23:46:24 -0800
Subject: [PATCH 11/18] exprot django router
---
src/reactpy_django/router/__init__.py | 3 +++
1 file changed, 3 insertions(+)
diff --git a/src/reactpy_django/router/__init__.py b/src/reactpy_django/router/__init__.py
index e69de29b..ea3e1d3b 100644
--- a/src/reactpy_django/router/__init__.py
+++ b/src/reactpy_django/router/__init__.py
@@ -0,0 +1,3 @@
+from reactpy_django.router.components import django_router
+
+__all__ = ["django_router"]
From b58e668aca1efb4add7b609b027a6c715da4ab6e Mon Sep 17 00:00:00 2001
From: Archmonger <16909269+Archmonger@users.noreply.github.com>
Date: Sun, 31 Dec 2023 23:46:34 -0800
Subject: [PATCH 12/18] add another test scenario
---
tests/test_app/router/components.py | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/tests/test_app/router/components.py b/tests/test_app/router/components.py
index 09e53ce5..13982490 100644
--- a/tests/test_app/router/components.py
+++ b/tests/test_app/router/components.py
@@ -35,4 +35,8 @@ def main():
route("/router/string//", display_params("Path 6", route_info)),
route("/router/uuid//", display_params("Path 7", route_info)),
route("/router/", None, route("abc/", display_params("Path 8", route_info))),
+ route(
+ "/router/two///",
+ display_params("Path 9", route_info),
+ ),
)
From d3749375e409c473fbbc22cfc56f5813819fec9e Mon Sep 17 00:00:00 2001
From: Archmonger <16909269+Archmonger@users.noreply.github.com>
Date: Sun, 7 Jan 2024 04:12:09 -0800
Subject: [PATCH 13/18] fix merge conflicts
---
docs/python/use-location.py | 4 ++--
docs/src/reference/hooks.md | 2 +-
2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/docs/python/use-location.py b/docs/python/use-location.py
index a3e99d3e..d7afcbac 100644
--- a/docs/python/use-location.py
+++ b/docs/python/use-location.py
@@ -4,6 +4,6 @@
@component
def my_component():
- my_location = use_location()
+ location = use_location()
- return html.div(my_location.pathname + my_location.search)
+ return html.div(location.pathname + location.search)
diff --git a/docs/src/reference/hooks.md b/docs/src/reference/hooks.md
index 66871685..176cb318 100644
--- a/docs/src/reference/hooks.md
+++ b/docs/src/reference/hooks.md
@@ -366,7 +366,7 @@ Shortcut that returns the WebSocket or HTTP connection's [scope](https://channel
### `#!python use_location()`
-This is a shortcut that returns the browser's current `#!python path` as a `Location` object.
+Shortcut that returns the browser's current `#!python Location`.
=== "components.py"
From 94538d66a48188c35e1bc6a4237fa777e706ea75 Mon Sep 17 00:00:00 2001
From: Archmonger <16909269+Archmonger@users.noreply.github.com>
Date: Sun, 7 Jan 2024 04:19:25 -0800
Subject: [PATCH 14/18] revise todo
---
src/reactpy_django/hooks.py | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/src/reactpy_django/hooks.py b/src/reactpy_django/hooks.py
index a08ef4f8..0ebf2ea8 100644
--- a/src/reactpy_django/hooks.py
+++ b/src/reactpy_django/hooks.py
@@ -45,7 +45,7 @@
] = DefaultDict(set)
-# TODO: Remove this in the next version
+# TODO: Deprecate this once the equivalent hook gets moved to reactpy.hooks.*
def use_location() -> Location:
"""Get the current route as a `Location` object"""
return _use_location()
@@ -79,7 +79,7 @@ def use_origin() -> str | None:
return None
-# TODO: Remove this in the next version
+# TODO: Deprecate this once the equivalent hook gets moved to reactpy.hooks.*
def use_scope() -> dict[str, Any]:
"""Get the current ASGI scope dictionary"""
scope = _use_scope()
@@ -90,7 +90,7 @@ def use_scope() -> dict[str, Any]:
raise TypeError(f"Expected scope to be a dict, got {type(scope)}")
-# TODO: Remove this in the next version
+# TODO: Deprecate this once the equivalent hook gets moved to reactpy.hooks.*
def use_connection() -> ConnectionType:
"""Get the current `Connection` object"""
return _use_connection()
From 472bae019aa1b635c4f60c95d26231a0b93a9355 Mon Sep 17 00:00:00 2001
From: Archmonger <16909269+Archmonger@users.noreply.github.com>
Date: Mon, 8 Jan 2024 23:33:43 -0800
Subject: [PATCH 15/18] fix merge conflict
---
docs/src/reference/hooks.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docs/src/reference/hooks.md b/docs/src/reference/hooks.md
index 176cb318..3ba03266 100644
--- a/docs/src/reference/hooks.md
+++ b/docs/src/reference/hooks.md
@@ -386,7 +386,7 @@ Shortcut that returns the browser's current `#!python Location`.
| --- | --- |
| `#!python Location` | An object containing the current URL's `#!python pathname` and `#!python search` query. |
-## Use Origin
+### `#!python use_origin()`
Shortcut that returns the WebSocket or HTTP connection's `#!python origin`.
From f3c62858f3766709923fa0fd6903cba458b28ac2 Mon Sep 17 00:00:00 2001
From: Archmonger <16909269+Archmonger@users.noreply.github.com>
Date: Wed, 10 Jan 2024 00:55:03 -0800
Subject: [PATCH 16/18] router docs
---
docs/python/django-router.py | 17 +++++++++++++++++
docs/src/reference/router.md | 34 +++++++++++++++++++++++++++++++---
mkdocs.yml | 2 +-
src/reactpy_django/__init__.py | 3 ++-
4 files changed, 51 insertions(+), 5 deletions(-)
create mode 100644 docs/python/django-router.py
diff --git a/docs/python/django-router.py b/docs/python/django-router.py
new file mode 100644
index 00000000..714ca585
--- /dev/null
+++ b/docs/python/django-router.py
@@ -0,0 +1,17 @@
+from reactpy import component, html
+from reactpy_django.router import django_router
+from reactpy_router import route
+
+
+@component
+def my_component():
+ return django_router(
+ route("/router/", html.div("Example 1")),
+ route("/router/any//", html.div("Example 2")),
+ route("/router/integer//", html.div("Example 3")),
+ route("/router/path//", html.div("Example 4")),
+ route("/router/slug//", html.div("Example 5")),
+ route("/router/string//", html.div("Example 6")),
+ route("/router/uuid//", html.div("Example 7")),
+ route("/router/two_values///", html.div("Example 9")),
+ )
diff --git a/docs/src/reference/router.md b/docs/src/reference/router.md
index 642f3758..7a280c6d 100644
--- a/docs/src/reference/router.md
+++ b/docs/src/reference/router.md
@@ -2,12 +2,40 @@
-We provide a version of [`reactpy-router`](https://github.com/reactive-python/reactpy-router) that is compatible with Django, and uses Django's URL routing syntax.
+A variant of [`reactpy-router`](https://github.com/reactive-python/reactpy-router) that utilizes Django conventions.
+!!! abstract "Note"
+
+ Looking for more details on URL routing?
+
+ This package only contains Django specific URL routing features. Standard features can be found within [`reactive-python/reactpy-router`](https://reactive-python.github.io/reactpy-router/).
+
---
-## Django Router
+## `#!python django_router(*routes)`
+
+=== "components.py"
+
+ ```python
+ {% include "../../python/django-router.py" %}
+ ```
+
+??? example "See Interface"
+
+ **Parameters**
+
+ | Name | Type | Description | Default |
+ | --- | --- | --- | --- |
+ | `#!python *routes` | `#!python Route` | An object from `reactpy-router` containing a `#!python path`, `#!python element`, and child `#!python *routes`. | N/A |
+
+ **Returns**
+
+ | Type | Description |
+ | --- | --- |
+ | `#!python VdomDict | None` | The matched component/path after it has been fully rendered. |
+
+??? question "How is this different from `#!python reactpy_router.simple.router`?"
-...
+ This component utilizes `reactpy-router` under the hood, but provides a more Django-like URL routing syntax.
diff --git a/mkdocs.yml b/mkdocs.yml
index 12bf352f..2eff331d 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -7,7 +7,7 @@ nav:
- Reference:
- Components: reference/components.md
- Hooks: reference/hooks.md
- - Router: reference/router.md
+ - URL Router: reference/router.md
- Decorators: reference/decorators.md
- Utilities: reference/utils.md
- Template Tag: reference/template-tag.md
diff --git a/src/reactpy_django/__init__.py b/src/reactpy_django/__init__.py
index 3985fcf4..fb9ed61d 100644
--- a/src/reactpy_django/__init__.py
+++ b/src/reactpy_django/__init__.py
@@ -2,7 +2,7 @@
import nest_asyncio
-from reactpy_django import checks, components, decorators, hooks, types, utils
+from reactpy_django import checks, components, decorators, hooks, router, types, utils
from reactpy_django.websocket.paths import (
REACTPY_WEBSOCKET_PATH,
REACTPY_WEBSOCKET_ROUTE,
@@ -18,6 +18,7 @@
"types",
"utils",
"checks",
+ "router",
]
# Fixes bugs with REACTPY_BACKHAUL_THREAD + built-in asyncio event loops.
From 49de6de7970131123c7da79ceb193ada4b65910b Mon Sep 17 00:00:00 2001
From: Archmonger <16909269+Archmonger@users.noreply.github.com>
Date: Wed, 10 Jan 2024 01:33:54 -0800
Subject: [PATCH 17/18] add changelog entry
---
CHANGELOG.md | 2 ++
1 file changed, 2 insertions(+)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 70a36624..a5dc50df 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -36,6 +36,8 @@ Using the following categories, list your changes in this order:
### Added
+- Built-in Single Page Application (SPA) support!
+ - `reactpy_django.router.django_router` can be used to render your Django application as a SPA.
- SEO compatible rendering!
- `settings.py:REACTPY_PRERENDER` can be set to `True` to make components pre-render by default.
- Or, you can enable it on individual components via the template tag: `{% component "..." prerender="True" %}`.
From ba1283149cc1aee6dbd916907ba7a997f3f35f6d Mon Sep 17 00:00:00 2001
From: Archmonger <16909269+Archmonger@users.noreply.github.com>
Date: Wed, 10 Jan 2024 02:15:43 -0800
Subject: [PATCH 18/18] add tests
---
tests/test_app/router/components.py | 4 +--
tests/test_app/tests/test_components.py | 47 +++++++++++++++++++++++++
2 files changed, 49 insertions(+), 2 deletions(-)
diff --git a/tests/test_app/router/components.py b/tests/test_app/router/components.py
index 13982490..8f020990 100644
--- a/tests/test_app/router/components.py
+++ b/tests/test_app/router/components.py
@@ -1,5 +1,5 @@
from reactpy import component, html, use_location
-from reactpy_django.router.components import django_router
+from reactpy_django.router import django_router
from reactpy_router import route, use_params, use_query
@@ -19,7 +19,7 @@ def main():
route_info = html._(
html.div(
- {"class_name": "router-path", "data-path": location.pathname},
+ {"id": "router-path", "data-path": location.pathname},
f"Path Name: {location.pathname}",
),
html.div(f"Query String: {location.search}"),
diff --git a/tests/test_app/tests/test_components.py b/tests/test_app/tests/test_components.py
index af936955..ca35cf50 100644
--- a/tests/test_app/tests/test_components.py
+++ b/tests/test_app/tests/test_components.py
@@ -553,3 +553,50 @@ def test_use_user_data_with_default(self):
"Data: {'default1': 'value', 'default2': 'value2', 'default3': 'value3'}",
user_data_div.text_content(),
)
+
+ def test_url_router(self):
+ new_page = self.browser.new_page()
+ try:
+ new_page.goto(f"{self.live_server_url}/router/")
+ path = new_page.wait_for_selector("#router-path")
+ self.assertIn("/router/", path.get_attribute("data-path"))
+
+ new_page.goto(f"{self.live_server_url}/router/any/123/")
+ path = new_page.wait_for_selector("#router-path")
+ self.assertIn("/router/any/123/", path.get_attribute("data-path"))
+
+ new_page.goto(f"{self.live_server_url}/router/integer/123/")
+ path = new_page.wait_for_selector("#router-path")
+ self.assertIn("/router/integer/123/", path.get_attribute("data-path"))
+
+ new_page.goto(f"{self.live_server_url}/router/path/abc/123/")
+ path = new_page.wait_for_selector("#router-path")
+ self.assertIn("/router/path/abc/123/", path.get_attribute("data-path"))
+
+ new_page.goto(f"{self.live_server_url}/router/slug/abc-123/")
+ path = new_page.wait_for_selector("#router-path")
+ self.assertIn("/router/slug/abc-123/", path.get_attribute("data-path"))
+
+ new_page.goto(f"{self.live_server_url}/router/string/abc/")
+ path = new_page.wait_for_selector("#router-path")
+ self.assertIn("/router/string/abc/", path.get_attribute("data-path"))
+
+ new_page.goto(
+ f"{self.live_server_url}/router/uuid/123e4567-e89b-12d3-a456-426614174000/"
+ )
+ path = new_page.wait_for_selector("#router-path")
+ self.assertIn(
+ "/router/uuid/123e4567-e89b-12d3-a456-426614174000/",
+ path.get_attribute("data-path"),
+ )
+
+ new_page.goto(f"{self.live_server_url}/router/abc/")
+ path = new_page.wait_for_selector("#router-path")
+ self.assertIn("/router/abc/", path.get_attribute("data-path"))
+
+ new_page.goto(f"{self.live_server_url}/router/two/123/abc/")
+ path = new_page.wait_for_selector("#router-path")
+ self.assertIn("/router/two/123/abc/", path.get_attribute("data-path"))
+
+ finally:
+ new_page.close()