From c59975d93f5cbb7be8fba7c67c086997ac52b457 Mon Sep 17 00:00:00 2001
From: Archmonger <16909269+Archmonger@users.noreply.github.com>
Date: Thu, 6 Oct 2022 01:59:34 -0700
Subject: [PATCH 01/23] Allow refetch to be none
---
src/django_idom/hooks.py | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/src/django_idom/hooks.py b/src/django_idom/hooks.py
index 49b2f606..457e0bc3 100644
--- a/src/django_idom/hooks.py
+++ b/src/django_idom/hooks.py
@@ -97,7 +97,7 @@ def execute_query() -> None:
def use_mutation(
mutate: Callable[_Params, bool | None],
- refetch: Callable[..., Any] | Sequence[Callable[..., Any]],
+ refetch: Callable[..., Any] | Sequence[Callable[..., Any]] | None = None,
) -> Mutation[_Params]:
loading, set_loading = use_state(False)
error, set_error = use_state(cast(Union[Exception, None], None))
@@ -117,6 +117,8 @@ def execute_mutation() -> None:
set_loading(False)
set_error(None)
if should_refetch is not False:
+ if not refetch:
+ return
for query in (refetch,) if callable(refetch) else refetch:
for callback in _REFETCH_CALLBACKS.get(query) or ():
callback()
From 142d9a5e36eac7565bc6c71705a6a47e5bb32a2d Mon Sep 17 00:00:00 2001
From: Archmonger <16909269+Archmonger@users.noreply.github.com>
Date: Thu, 6 Oct 2022 02:00:03 -0700
Subject: [PATCH 02/23] docs on `use_mutation.reset()`
---
docs/includes/examples.md | 13 +++-
docs/src/features/hooks.md | 136 ++++++++++++++++++++++++-------------
2 files changed, 102 insertions(+), 47 deletions(-)
diff --git a/docs/includes/examples.md b/docs/includes/examples.md
index 623e60c3..3ba0f385 100644
--- a/docs/includes/examples.md
+++ b/docs/includes/examples.md
@@ -20,4 +20,15 @@ class HelloWorldView(View):
return HttpResponse("Hello World!")
```
-
\ No newline at end of file
+
+
+
+
+```python
+from django.db import models
+
+class TodoItem(models.Model):
+ text = models.CharField(max_length=255)
+```
+
+
diff --git a/docs/src/features/hooks.md b/docs/src/features/hooks.md
index 09c8daa9..256cba92 100644
--- a/docs/src/features/hooks.md
+++ b/docs/src/features/hooks.md
@@ -67,8 +67,11 @@ The `use_mutation` hook is used to modify Django ORM objects.
@component
def todo_list():
- item_mutation = use_mutation(add_item)
+ def submit_event(event):
+ if event["key"] == "Enter":
+ item_mutation.execute(text=event["target"]["value"])
+ item_mutation = use_mutation(add_item)
if item_mutation.loading:
mutation_status = html.h2("Adding...")
elif item_mutation.error:
@@ -76,10 +79,6 @@ The `use_mutation` hook is used to modify Django ORM objects.
else:
mutation_status = ""
- def submit_event(event):
- if event["key"] == "Enter":
- item_mutation.execute(text=event["target"]["value"])
-
return html.div(
html.label("Add an item:"),
html.input({"type": "text", "onKeyDown": submit_event}),
@@ -89,61 +88,106 @@ The `use_mutation` hook is used to modify Django ORM objects.
=== "models.py"
- ```python
- from django.db import models
-
- class TodoItem(models.Model):
- text = models.CharField(max_length=255)
- ```
+ {% include-markdown "../../includes/examples.md" start="" end="" %}
??? question "Can `use_mutation` trigger a refetch of `use_query`?"
Yes, `use_mutation` can queue a refetch of a `use_query` via the `refetch=...` argument.
The example below is a merge of the `use_query` and `use_mutation` examples above with the addition of a `refetch` argument on `use_mutation`.
-
+
Please note that any `use_query` hooks that use `get_items` will be refetched upon a successful mutation.
- ```python title="components.py"
- from example_project.my_app.models import TodoItem
- from idom import component, html
- from django_idom.hooks import use_mutation
+ === "components.py"
- def get_items():
- return TodoItem.objects.all()
+ ```python
+ from example_project.my_app.models import TodoItem
+ from idom import component, html
+ from django_idom.hooks import use_mutation
- def add_item(text: str):
- TodoItem(text=text).save()
+ def get_items():
+ return TodoItem.objects.all()
- @component
- def todo_list():
- item_query = use_query(get_items)
- if item_query.loading:
- rendered_items = html.h2("Loading...")
- elif item_query.error:
- rendered_items = html.h2("Error when loading!")
- else:
- rendered_items = html.ul(html.li(item, key=item) for item in item_query.data)
+ def add_item(text: str):
+ TodoItem(text=text).save()
- item_mutation = use_mutation(add_item, refetch=get_items)
- if item_mutation.loading:
- mutation_status = html.h2("Adding...")
- elif item_mutation.error:
- mutation_status = html.h2("Error when adding!")
- else:
- mutation_status = ""
+ @component
+ def todo_list():
+ def submit_event(event):
+ if event["key"] == "Enter":
+ item_mutation.execute(text=event["target"]["value"])
- def submit_event(event):
- if event["key"] == "Enter":
- item_mutation.execute(text=event["target"]["value"])
+ item_query = use_query(get_items)
+ if item_query.loading:
+ rendered_items = html.h2("Loading...")
+ elif item_query.error:
+ rendered_items = html.h2("Error when loading!")
+ else:
+ rendered_items = html.ul(html.li(item, key=item) for item in item_query.data)
- return html.div(
- html.label("Add an item:"),
- html.input({"type": "text", "onKeyDown": submit_event}),
- mutation_status,
- rendered_items,
- )
- ```
+ item_mutation = use_mutation(add_item, refetch=get_items)
+ if item_mutation.loading:
+ mutation_status = html.h2("Adding...")
+ elif item_mutation.error:
+ mutation_status = html.h2("Error when adding!")
+ else:
+ mutation_status = ""
+
+ return html.div(
+ html.label("Add an item:"),
+ html.input({"type": "text", "onKeyDown": submit_event}),
+ mutation_status,
+ rendered_items,
+ )
+ ```
+
+ === "models.py"
+
+ {% include-markdown "../../includes/examples.md" start="" end="" %}
+
+??? question "Can I make a failed `use_mutation` try again?"
+
+ Yes, a `use_mutation` can be re-performed by calling `reset()` on your `use_mutation` instance.
+
+ For example, take a look at `reset_event` below.
+
+ === "components.py"
+
+ ```python
+ from example_project.my_app.models import TodoItem
+ from idom import component, html
+ from django_idom.hooks import use_mutation
+
+ def add_item(text: str):
+ TodoItem(text=text).save()
+
+ @component
+ def todo_list():
+ def reset_event(event):
+ item_mutation.reset()
+
+ def submit_event(event):
+ if event["key"] == "Enter":
+ item_mutation.execute(text=event["target"]["value"])
+
+ item_mutation = use_mutation(add_item)
+ if item_mutation.loading:
+ mutation_status = html.h2("Adding...")
+ elif item_mutation.error:
+ mutation_status = html.button({"onClick": reset_event}, "Error: Try again!")
+ else:
+ mutation_status = ""
+
+ return html.div(
+ html.label("Add an item:"),
+ html.input({"type": "text", "onKeyDown": submit_event}),
+ mutation_status,
+ )
+ ```
+
+ === "models.py"
+
+ {% include-markdown "../../includes/examples.md" start="" end="" %}
??? question "Can I make ORM calls without hooks?"
From 3a7b93c7b79660371c625c9dceea1bced5c97b40 Mon Sep 17 00:00:00 2001
From: Archmonger <16909269+Archmonger@users.noreply.github.com>
Date: Thu, 6 Oct 2022 02:20:59 -0700
Subject: [PATCH 03/23] `use_origin` hook
---
docs/src/features/hooks.md | 28 ++++++++++++++++++++++------
src/django_idom/hooks.py | 15 ++++++++++++++-
2 files changed, 36 insertions(+), 7 deletions(-)
diff --git a/docs/src/features/hooks.md b/docs/src/features/hooks.md
index 256cba92..143e161d 100644
--- a/docs/src/features/hooks.md
+++ b/docs/src/features/hooks.md
@@ -1,6 +1,8 @@
???+ tip "Looking for more hooks?"
- Check out the [IDOM Core docs](https://idom-docs.herokuapp.com/docs/reference/hooks-api.html#basic-hooks) on hooks!
+ Standard ReactJS hooks are contained within [`idom-team/idom`](https://github.com/idom-team/idom). Please note that `idom` is installed by default alongside `django-idom`.
+
+ Check out the [IDOM Core docs](https://idom-docs.herokuapp.com/docs/reference/hooks-api.html#basic-hooks) to see these hooks!
## Use Query
@@ -205,7 +207,7 @@ The `use_mutation` hook is used to modify Django ORM objects.
## Use Websocket
-You can fetch the Django Channels websocket at any time by using `use_websocket`.
+You can fetch the Django Channels [websocket](https://channels.readthedocs.io/en/stable/topics/consumers.html#asyncjsonwebsocketconsumer) at any time by using `use_websocket`.
```python title="components.py"
from idom import component, html
@@ -219,7 +221,7 @@ def my_component():
## Use Scope
-This is a shortcut that returns the Websocket's `scope`.
+This is a shortcut that returns the Websocket's [`scope`](https://channels.readthedocs.io/en/stable/topics/consumers.html#scope).
```python title="components.py"
from idom import component, html
@@ -235,11 +237,11 @@ def my_component():
??? info "This hook's behavior will be changed in a future update"
- This hook will be updated to return the browser's current URL. This change will come in alongside IDOM URL routing support.
+ This hook will be updated to return the browser's currently active path. This change will come in alongside IDOM URL routing support.
- Check out [idom-team/idom#569](https://github.com/idom-team/idom/issues/569) for more information.
+ Check out [idom-team/idom-router#2](https://github.com/idom-team/idom-router/issues/2) for more information.
-This is a shortcut that returns the Websocket's `path`.
+This is a shortcut that returns the Websocket's `path`. You can expect this hook to provide strings such as `/idom/my_path`.
```python title="components.py"
from idom import component, html
@@ -250,3 +252,17 @@ def my_component():
my_location = use_location()
return html.div(my_location)
```
+
+## Use Origin
+
+This is a shortcut that returns the Websocket's `origin`. You can expect this hook to provide strings such as `http://example.com`.
+
+```python title="components.py"
+from idom import component, html
+from django_idom.hooks import use_origin
+
+@component
+def my_component():
+ my_origin = use_origin()
+ return html.div(my_origin)
+```
diff --git a/src/django_idom/hooks.py b/src/django_idom/hooks.py
index 457e0bc3..e6a6f578 100644
--- a/src/django_idom/hooks.py
+++ b/src/django_idom/hooks.py
@@ -24,13 +24,26 @@
def use_location() -> Location:
- """Get the current route as a string"""
+ """Get the current route as a `Location` object"""
# TODO: Use the browser's current page, rather than the WS route
scope = use_scope()
search = scope["query_string"].decode()
return Location(scope["path"], f"?{search}" if search else "")
+def use_origin() -> str:
+ """Get the current origin as a string"""
+ scope = use_scope()
+ return next(
+ (
+ header[1].decode("utf-8")
+ for header in scope["headers"]
+ if header[0] == b"origin"
+ ),
+ "",
+ )
+
+
def use_scope() -> dict[str, Any]:
"""Get the current ASGI scope dictionary"""
return use_websocket().scope
From 9d11f598c24f40ac5d30da77e6a3e715c528b6b9 Mon Sep 17 00:00:00 2001
From: Archmonger <16909269+Archmonger@users.noreply.github.com>
Date: Thu, 6 Oct 2022 02:34:42 -0700
Subject: [PATCH 04/23] fix contrib template name
---
docs/src/contribute/{django-idom.md => code.md} | 0
mkdocs.yml | 2 +-
2 files changed, 1 insertion(+), 1 deletion(-)
rename docs/src/contribute/{django-idom.md => code.md} (100%)
diff --git a/docs/src/contribute/django-idom.md b/docs/src/contribute/code.md
similarity index 100%
rename from docs/src/contribute/django-idom.md
rename to docs/src/contribute/code.md
diff --git a/mkdocs.yml b/mkdocs.yml
index 3d7287b9..c3c339ec 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -15,7 +15,7 @@ nav:
- Template Tag: features/templatetag.md
- Settings: features/settings.md
- Contribute:
- - Code: contribute/django-idom.md
+ - Code: contribute/code.md
- Docs: contribute/docs.md
- Running Tests: contribute/running-tests.md
- Changelog: changelog/index.md
From 5557a24b6e009bc8fb522caa5b21207a28edce83 Mon Sep 17 00:00:00 2001
From: Archmonger <16909269+Archmonger@users.noreply.github.com>
Date: Thu, 6 Oct 2022 02:38:00 -0700
Subject: [PATCH 05/23] random cleanup
---
docs/src/features/hooks.md | 11 +++++------
docs/src/features/templatetag.md | 16 ++++++++++------
2 files changed, 15 insertions(+), 12 deletions(-)
diff --git a/docs/src/features/hooks.md b/docs/src/features/hooks.md
index 143e161d..ed93d1a8 100644
--- a/docs/src/features/hooks.md
+++ b/docs/src/features/hooks.md
@@ -235,12 +235,6 @@ def my_component():
## Use Location
-??? info "This hook's behavior will be changed in a future update"
-
- This hook will be updated to return the browser's currently active path. This change will come in alongside IDOM URL routing support.
-
- Check out [idom-team/idom-router#2](https://github.com/idom-team/idom-router/issues/2) for more information.
-
This is a shortcut that returns the Websocket's `path`. You can expect this hook to provide strings such as `/idom/my_path`.
```python title="components.py"
@@ -253,6 +247,11 @@ def my_component():
return html.div(my_location)
```
+??? info "This hook's behavior will be changed in a future update"
+
+ This hook will be updated to return the browser's currently active path. This change will come in alongside IDOM URL routing support.
+
+ Check out [idom-team/idom-router#2](https://github.com/idom-team/idom-router/issues/2) for more information.
## Use Origin
This is a shortcut that returns the Websocket's `origin`. You can expect this hook to provide strings such as `http://example.com`.
diff --git a/docs/src/features/templatetag.md b/docs/src/features/templatetag.md
index 43910f4c..36046bc3 100644
--- a/docs/src/features/templatetag.md
+++ b/docs/src/features/templatetag.md
@@ -8,15 +8,11 @@ Integrated within Django IDOM, we bundle a template tag. Within this tag, you ca
Our pre-processor relies on the template tag containing a string.
- **Do not** use a Django context variable for the path string. Failure to follow this warning will result in a performance penalty and also jankiness when using the Django autoreloader.
+ **Do not** use a Django context variable for the path string. Failure to follow this warning will result in unexpected behavior.
For example, **do not** do the following:
- ```python title="views.py"
- def example_view():
- context_vars = {"dont_do_this": "example_project.my_app.components.hello_world"}
- return render(request, "my-template.html", context_vars)
- ```
+ === "my-template.html"
```jinja title="my-template.html"
@@ -26,6 +22,14 @@ Integrated within Django IDOM, we bundle a template tag. Within this tag, you ca
{% component "example_project.my_app.components.hello_world" recipient="World" %}
```
+ === "views.py"
+
+ ```python
+ def example_view():
+ context_vars = {"dont_do_this": "example_project.my_app.components.hello_world"}
+ return render(request, "my-template.html", context_vars)
+ ```
+
From 951b1eeb8c47669c6cab02e7a4512d949daa263b Mon Sep 17 00:00:00 2001
From: Archmonger <16909269+Archmonger@users.noreply.github.com>
Date: Thu, 6 Oct 2022 10:24:04 -0700
Subject: [PATCH 06/23] Explictly use `None` if origin is unavailable.
---
src/django_idom/hooks.py | 24 ++++++++++++++----------
1 file changed, 14 insertions(+), 10 deletions(-)
diff --git a/src/django_idom/hooks.py b/src/django_idom/hooks.py
index e6a6f578..174e1c55 100644
--- a/src/django_idom/hooks.py
+++ b/src/django_idom/hooks.py
@@ -31,17 +31,21 @@ def use_location() -> Location:
return Location(scope["path"], f"?{search}" if search else "")
-def use_origin() -> str:
- """Get the current origin as a string"""
+def use_origin() -> str | None:
+ """Get the current origin as a string. If the browser did not send an origin header,
+ this will be None."""
scope = use_scope()
- return next(
- (
- header[1].decode("utf-8")
- for header in scope["headers"]
- if header[0] == b"origin"
- ),
- "",
- )
+ try:
+ return next(
+ (
+ header[1].decode("utf-8")
+ for header in scope["headers"]
+ if header[0] == b"origin"
+ ),
+ None,
+ )
+ except Exception:
+ return None
def use_scope() -> dict[str, Any]:
From 15bf21f1efda1f311b138d6dfb6391af2066950e Mon Sep 17 00:00:00 2001
From: Archmonger <16909269+Archmonger@users.noreply.github.com>
Date: Thu, 6 Oct 2022 11:02:00 -0700
Subject: [PATCH 07/23] hook description cleanup
---
docs/src/features/hooks.md | 20 +++++++++++++++++---
1 file changed, 17 insertions(+), 3 deletions(-)
diff --git a/docs/src/features/hooks.md b/docs/src/features/hooks.md
index ed93d1a8..c5c02571 100644
--- a/docs/src/features/hooks.md
+++ b/docs/src/features/hooks.md
@@ -8,6 +8,8 @@
The `use_query` hook is used fetch Django ORM queries.
+The function you provide into this hook must return either a `Model` or `QuerySet`.
+
=== "components.py"
```python
@@ -47,6 +49,12 @@ The `use_query` hook is used fetch Django ORM queries.
This may be resolved in a future version of Django with a natively asynchronous ORM.
+??? question "Why does the example `get_items` function return a `Model` or `QuerySet`?"
+
+ This was a technical design decision to [based on Apollo](https://www.apollographql.com/docs/react/data/mutations/#usemutation-api), but ultimately helps avoid Django's `SynchronousOnlyOperation` exceptions.
+
+ The `use_query` hook ensures the provided `Model` or `QuerySet` executes all [deferred](https://docs.djangoproject.com/en/dev/ref/models/instances/#django.db.models.Model.get_deferred_fields)/[lazy queries](https://docs.djangoproject.com/en/dev/topics/db/queries/#querysets-are-lazy) safely prior to reaching your components.
+
??? question "What is an "ORM"?"
A Python **Object Relational Mapper** is an API for your code to access a database.
@@ -55,7 +63,9 @@ The `use_query` hook is used fetch Django ORM queries.
## Use Mutation
-The `use_mutation` hook is used to modify Django ORM objects.
+The `use_mutation` hook is used to create, update, or delete Django ORM objects.
+
+The function you provide into this hook will have no return value.
=== "components.py"
@@ -235,7 +245,9 @@ def my_component():
## Use Location
-This is a shortcut that returns the Websocket's `path`. You can expect this hook to provide strings such as `/idom/my_path`.
+This is a shortcut that returns the Websocket's `path`.
+
+You can expect this hook to provide strings such as `/idom/my_path`.
```python title="components.py"
from idom import component, html
@@ -254,7 +266,9 @@ def my_component():
Check out [idom-team/idom-router#2](https://github.com/idom-team/idom-router/issues/2) for more information.
## Use Origin
-This is a shortcut that returns the Websocket's `origin`. You can expect this hook to provide strings such as `http://example.com`.
+This is a shortcut that returns the Websocket's `origin`.
+
+You can expect this hook to provide strings such as `http://example.com`.
```python title="components.py"
from idom import component, html
From 94c8311b815115dd002659871d5d6ccc26a46cf5 Mon Sep 17 00:00:00 2001
From: Archmonger <16909269+Archmonger@users.noreply.github.com>
Date: Thu, 6 Oct 2022 14:19:00 -0700
Subject: [PATCH 08/23] clean up code examples
---
README.md | 4 +-
docs/includes/examples.md | 6 +-
docs/src/features/components.md | 66 +++++-----
docs/src/features/decorators.md | 120 ++++++++++--------
docs/src/features/hooks.md | 83 ++++++------
docs/src/features/settings.md | 2 +-
docs/src/features/templatetag.md | 58 +++++----
docs/src/getting-started/create-component.md | 4 +-
.../getting-started/reference-component.md | 4 +-
docs/src/getting-started/render-view.md | 28 ++--
docs/src/installation/index.md | 92 ++++++++------
docs/src/stylesheets/extra.css | 5 +
12 files changed, 263 insertions(+), 209 deletions(-)
diff --git a/README.md b/README.md
index dfee8134..1c7e4fbc 100644
--- a/README.md
+++ b/README.md
@@ -30,7 +30,7 @@ You'll need a file to define your [IDOM](https://github.com/idom-team/idom) comp
-```python title="components.py"
+```python linenums="1"
from idom import component, html
@component
@@ -51,7 +51,7 @@ Additonally, you can pass in keyword arguments into your component function. For
-```jinja title="my-template.html"
+```jinja linenums="1"
{% load idom %}
diff --git a/docs/includes/examples.md b/docs/includes/examples.md
index 3ba0f385..3ed273fd 100644
--- a/docs/includes/examples.md
+++ b/docs/includes/examples.md
@@ -1,6 +1,6 @@
-```python
+```python linenums="1"
from django.http import HttpResponse
def hello_world_view(request, *args, **kwargs):
@@ -11,7 +11,7 @@ def hello_world_view(request, *args, **kwargs):
-```python
+```python linenums="1"
from django.http import HttpResponse
from django.views import View
@@ -24,7 +24,7 @@ class HelloWorldView(View):
-```python
+```python linenums="1"
from django.db import models
class TodoItem(models.Model):
diff --git a/docs/src/features/components.md b/docs/src/features/components.md
index 8d95c062..54b038ff 100644
--- a/docs/src/features/components.md
+++ b/docs/src/features/components.md
@@ -4,7 +4,7 @@ Convert any Django view into a IDOM component by usng this decorator. Compatible
=== "components.py"
- ```python
+ ```python linenums="1"
from idom import component, html
from django_idom.components import view_to_component
from .views import hello_world_view
@@ -47,7 +47,7 @@ Convert any Django view into a IDOM component by usng this decorator. Compatible
=== "components.py"
- ```python
+ ```python linenums="1"
from idom import component, html
from django_idom.components import view_to_component
from .views import HelloWorldView
@@ -69,7 +69,7 @@ Convert any Django view into a IDOM component by usng this decorator. Compatible
=== "components.py"
- ```python
+ ```python linenums="1"
from idom import component, html
from django_idom.components import view_to_component
from .views import hello_world_view
@@ -99,7 +99,7 @@ Convert any Django view into a IDOM component by usng this decorator. Compatible
=== "components.py"
- ```python
+ ```python linenums="1"
from idom import component, html
from django_idom.components import view_to_component
from .views import hello_world_view
@@ -125,7 +125,7 @@ Convert any Django view into a IDOM component by usng this decorator. Compatible
=== "components.py"
- ```python
+ ```python linenums="1"
from idom import component, html
from django_idom.components import view_to_component
from .views import hello_world_view
@@ -153,7 +153,7 @@ Convert any Django view into a IDOM component by usng this decorator. Compatible
=== "components.py"
- ```python
+ ```python linenums="1"
from idom import component, html
from django_idom.components import view_to_component
from .views import hello_world_view
@@ -174,7 +174,7 @@ Convert any Django view into a IDOM component by usng this decorator. Compatible
=== "views.py"
- ```python
+ ```python linenums="1"
from django.http import HttpResponse
def hello_world_view(request, *args, **kwargs):
@@ -185,17 +185,19 @@ Convert any Django view into a IDOM component by usng this decorator. Compatible
Allows you to defer loading a CSS stylesheet until a component begins rendering. This stylesheet must be stored within [Django's static files](https://docs.djangoproject.com/en/dev/howto/static-files/).
-```python title="components.py"
-from idom import component, html
-from django_idom.components import django_css
+=== "components.py"
+
+ ```python linenums="1"
+ from idom import component, html
+ from django_idom.components import django_css
-@component
-def my_component():
- return html.div(
- django_css("css/buttons.css"),
- html.button("My Button!"),
- )
-```
+ @component
+ def my_component():
+ return html.div(
+ django_css("css/buttons.css"),
+ html.button("My Button!"),
+ )
+ ```
??? question "Should I put `django_css` at the top of my component?"
@@ -207,7 +209,7 @@ def my_component():
Here's an example on what you should avoid doing for Django static files:
- ```python
+ ```python linenums="1"
from idom import component, html
from django.templatetags.static import static
@@ -225,7 +227,7 @@ def my_component():
For external CSS, substitute `django_css` with `html.link`.
- ```python
+ ```python linenums="1"
from idom import component, html
@component
@@ -246,17 +248,19 @@ def my_component():
Allows you to defer loading JavaScript until a component begins rendering. This JavaScript must be stored within [Django's static files](https://docs.djangoproject.com/en/dev/howto/static-files/).
-```python title="components.py"
-from idom import component, html
-from django_idom.components import django_js
+=== "components.py"
+
+ ```python linenums="1"
+ from idom import component, html
+ from django_idom.components import django_js
-@component
-def my_component():
- return html.div(
- html.button("My Button!"),
- django_js("js/scripts.js"),
- )
-```
+ @component
+ def my_component():
+ return html.div(
+ html.button("My Button!"),
+ django_js("js/scripts.js"),
+ )
+ ```
??? question "Should I put `django_js` at the bottom of my component?"
@@ -268,7 +272,7 @@ def my_component():
Here's an example on what you should avoid doing for Django static files:
- ```python
+ ```python linenums="1"
from idom import component, html
from django.templatetags.static import static
@@ -286,7 +290,7 @@ def my_component():
For external JavaScript, substitute `django_js` with `html.script`.
- ```python
+ ```python linenums="1"
from idom import component, html
@component
diff --git a/docs/src/features/decorators.md b/docs/src/features/decorators.md
index 29a32d96..994834ec 100644
--- a/docs/src/features/decorators.md
+++ b/docs/src/features/decorators.md
@@ -8,16 +8,18 @@ Common uses of this decorator are to hide components from [`AnonymousUser`](http
This decorator can be used with or without parentheses.
-```python title="components.py"
-from django_idom.decorators import auth_required
-from django_idom.hooks import use_websocket
-from idom import component, html
+=== "components.py"
-@component
-@auth_required
-def my_component():
- return html.div("I am logged in!")
-```
+ ```python linenums="1"
+ from django_idom.decorators import auth_required
+ from django_idom.hooks import use_websocket
+ from idom import component, html
+
+ @component
+ @auth_required
+ def my_component():
+ return html.div("I am logged in!")
+ ```
??? example "See Interface"
@@ -40,50 +42,56 @@ def my_component():
You can use a component with the `fallback` argument, as seen below.
- ```python title="components.py"
- from django_idom.decorators import auth_required
- from idom import component, html
+ === "components.py"
- @component
- def my_component_fallback():
- return html.div("I am NOT logged in!")
+ ```python linenums="1"
+ from django_idom.decorators import auth_required
+ from idom import component, html
- @component
- @auth_required(fallback=my_component_fallback)
- def my_component():
- return html.div("I am logged in!")
- ```
+ @component
+ def my_component_fallback():
+ return html.div("I am NOT logged in!")
+
+ @component
+ @auth_required(fallback=my_component_fallback)
+ def my_component():
+ return html.div("I am logged in!")
+ ```
??? question "How do I render a simple `idom.html` snippet if authentication fails?"
You can use a `idom.html` snippet with the `fallback` argument, as seen below.
- ```python title="components.py"
- from django_idom.decorators import auth_required
- from django_idom.hooks import use_websocket
- from idom import component, html
+ === "components.py"
- @component
- @auth_required(fallback=html.div("I am NOT logged in!"))
- def my_component():
- return html.div("I am logged in!")
- ```
+ ```python linenums="1"
+ from django_idom.decorators import auth_required
+ from django_idom.hooks import use_websocket
+ from idom import component, html
+
+ @component
+ @auth_required(fallback=html.div("I am NOT logged in!"))
+ def my_component():
+ return html.div("I am logged in!")
+ ```
??? question "How can I check if a user `is_staff`?"
You can set the `auth_attribute` to `is_staff`, as seen blow.
- ```python title="components.py"
- from django_idom.decorators import auth_required
- from django_idom.hooks import use_websocket
- from idom import component, html
+ === "components.py"
+ ```python linenums="1"
+ from django_idom.decorators import auth_required
+ from django_idom.hooks import use_websocket
+ from idom import component, html
- @component
- @auth_required(auth_attribute="is_staff")
- def my_component():
- return html.div("I am logged in!")
- ```
+
+ @component
+ @auth_required(auth_attribute="is_staff")
+ def my_component():
+ return html.div("I am logged in!")
+ ```
??? question "How can I check for a custom attribute?"
@@ -91,24 +99,28 @@ def my_component():
For example, if your user model has the field `is_really_cool` ...
- ```python
- from django.contrib.auth.models import AbstractBaseUser
+ === "models.py"
- class CustomUserModel(AbstractBaseUser):
- @property
- def is_really_cool(self):
- return True
- ```
+ ```python linenums="1"
+ from django.contrib.auth.models import AbstractBaseUser
+
+ class CustomUserModel(AbstractBaseUser):
+ @property
+ def is_really_cool(self):
+ return True
+ ```
... then you would do the following within your decorator:
- ```python title="components.py"
- from django_idom.decorators import auth_required
- from django_idom.hooks import use_websocket
- from idom import component, html
+ === "components.py"
- @component
- @auth_required(auth_attribute="is_really_cool")
- def my_component():
- return html.div("I am logged in!")
- ```
+ ```python linenums="1"
+ from django_idom.decorators import auth_required
+ from django_idom.hooks import use_websocket
+ from idom import component, html
+
+ @component
+ @auth_required(auth_attribute="is_really_cool")
+ def my_component():
+ return html.div("I am logged in!")
+ ```
diff --git a/docs/src/features/hooks.md b/docs/src/features/hooks.md
index c5c02571..31b3215c 100644
--- a/docs/src/features/hooks.md
+++ b/docs/src/features/hooks.md
@@ -12,7 +12,7 @@ The function you provide into this hook must return either a `Model` or `QuerySe
=== "components.py"
- ```python
+ ```python linenums="1"
from example_project.my_app.models import TodoItem
from idom import component, html
from django_idom.hooks import use_query
@@ -36,7 +36,7 @@ The function you provide into this hook must return either a `Model` or `QuerySe
=== "models.py"
- ```python
+ ```python linenums="1"
from django.db import models
class TodoItem(models.Model):
@@ -69,7 +69,7 @@ The function you provide into this hook will have no return value.
=== "components.py"
- ```python
+ ```python linenums="1"
from example_project.my_app.models import TodoItem
from idom import component, html
from django_idom.hooks import use_mutation
@@ -112,7 +112,7 @@ The function you provide into this hook will have no return value.
=== "components.py"
- ```python
+ ```python linenums="1"
from example_project.my_app.models import TodoItem
from idom import component, html
from django_idom.hooks import use_mutation
@@ -165,7 +165,7 @@ The function you provide into this hook will have no return value.
=== "components.py"
- ```python
+ ```python linenums="1"
from example_project.my_app.models import TodoItem
from idom import component, html
from django_idom.hooks import use_mutation
@@ -219,29 +219,33 @@ The function you provide into this hook will have no return value.
You can fetch the Django Channels [websocket](https://channels.readthedocs.io/en/stable/topics/consumers.html#asyncjsonwebsocketconsumer) at any time by using `use_websocket`.
-```python title="components.py"
-from idom import component, html
-from django_idom.hooks import use_websocket
+=== "components.py"
+
+ ```python linenums="1"
+ from idom import component, html
+ from django_idom.hooks import use_websocket
-@component
-def my_component():
- my_websocket = use_websocket()
- return html.div(my_websocket)
-```
+ @component
+ def my_component():
+ my_websocket = use_websocket()
+ return html.div(my_websocket)
+ ```
## Use Scope
This is a shortcut that returns the Websocket's [`scope`](https://channels.readthedocs.io/en/stable/topics/consumers.html#scope).
-```python title="components.py"
-from idom import component, html
-from django_idom.hooks import use_scope
+=== "components.py"
-@component
-def my_component():
- my_scope = use_scope()
- return html.div(my_scope)
-```
+ ```python linenums="1"
+ from idom import component, html
+ from django_idom.hooks import use_scope
+
+ @component
+ def my_component():
+ my_scope = use_scope()
+ return html.div(my_scope)
+ ```
## Use Location
@@ -249,33 +253,38 @@ This is a shortcut that returns the Websocket's `path`.
You can expect this hook to provide strings such as `/idom/my_path`.
-```python title="components.py"
-from idom import component, html
-from django_idom.hooks import use_location
+=== "components.py"
-@component
-def my_component():
- my_location = use_location()
- return html.div(my_location)
-```
+ ```python linenums="1"
+ from idom import component, html
+ from django_idom.hooks import use_location
+
+ @component
+ def my_component():
+ my_location = use_location()
+ return html.div(my_location)
+ ```
??? info "This hook's behavior will be changed in a future update"
This hook will be updated to return the browser's currently active path. This change will come in alongside IDOM URL routing support.
Check out [idom-team/idom-router#2](https://github.com/idom-team/idom-router/issues/2) for more information.
+
## Use Origin
This is a shortcut that returns the Websocket's `origin`.
You can expect this hook to provide strings such as `http://example.com`.
-```python title="components.py"
-from idom import component, html
-from django_idom.hooks import use_origin
+=== "components.py"
+
+ ```python linenums="1"
+ from idom import component, html
+ from django_idom.hooks import use_origin
-@component
-def my_component():
- my_origin = use_origin()
- return html.div(my_origin)
-```
+ @component
+ def my_component():
+ my_origin = use_origin()
+ return html.div(my_origin)
+ ```
diff --git a/docs/src/features/settings.md b/docs/src/features/settings.md
index b003ae3c..8663f59a 100644
--- a/docs/src/features/settings.md
+++ b/docs/src/features/settings.md
@@ -4,7 +4,7 @@ Here are the configurable variables that are available.
-```python title="settings.py"
+```python linenums="1"
# If "idom" cache is not configured, then we'll use "default" instead
CACHES = {
"idom": {"BACKEND": ...},
diff --git a/docs/src/features/templatetag.md b/docs/src/features/templatetag.md
index 36046bc3..6e01ba1f 100644
--- a/docs/src/features/templatetag.md
+++ b/docs/src/features/templatetag.md
@@ -1,6 +1,8 @@
Integrated within Django IDOM, we bundle a template tag. Within this tag, you can pass in keyword arguments directly into your component.
-{% include-markdown "../../../README.md" start="" end="" %}
+=== "my-template.html"
+
+ {% include-markdown "../../../README.md" start="" end="" %}
@@ -14,17 +16,17 @@ Integrated within Django IDOM, we bundle a template tag. Within this tag, you ca
=== "my-template.html"
- ```jinja title="my-template.html"
-
- {% component dont_do_this recipient="World" %}
+ ```jinja linenums="1"
+
+ {% component dont_do_this recipient="World" %}
-
- {% component "example_project.my_app.components.hello_world" recipient="World" %}
- ```
+
+ {% component "example_project.my_app.components.hello_world" recipient="World" %}
+ ```
=== "views.py"
- ```python
+ ```python linenums="1"
def example_view():
context_vars = {"dont_do_this": "example_project.my_app.components.hello_world"}
return render(request, "my-template.html", context_vars)
@@ -40,11 +42,13 @@ Integrated within Django IDOM, we bundle a template tag. Within this tag, you ca
- `class` allows you to apply a HTML class to the top-level component div. This is useful for styling purposes.
- `key` allows you to force the component to use a [specific key value](https://idom-docs.herokuapp.com/docs/guides/understanding-idom/why-idom-needs-keys.html?highlight=key). You typically won't need to set this.
- ```jinja title="my-template.html"
- ...
- {% component "example.components.my_component" class="my-html-class" key=123 %}
- ...
- ```
+ === "my-template.html"
+
+ ```jinja linenums="1"
+ ...
+ {% component "example.components.my_component" class="my-html-class" key=123 %}
+ ...
+ ```
@@ -53,17 +57,19 @@ Integrated within Django IDOM, we bundle a template tag. Within this tag, you ca
You can add as many components to a webpage as needed by using the template tag multiple times. Retrofitting legacy sites to use IDOM will typically involve many components on one page.
- ```jinja
- {% load idom %}
-
-
-
- {% component "example_project.my_app.components.hello_world" recipient="World" %}
- {% component "example_project.my_app_2.components.class_component" class="bold small-font" %}
- {% component "example_project.my_app_3.components.simple_component" %}
-
-
- ```
+ === "my-template.html"
+
+ ```jinja linenums="1"
+ {% load idom %}
+
+
+
+ {% component "example_project.my_app.components.hello_world" recipient="World" %}
+ {% component "example_project.my_app_2.components.class_component" class="bold small-font" %}
+ {% component "example_project.my_app_3.components.simple_component" %}
+
+
+ ```
But keep in mind, in scenarios where you are trying to create a Single Page Application (SPA) within Django, you will only have one central component within your `#!html ` tag.
@@ -85,6 +91,8 @@ Integrated within Django IDOM, we bundle a template tag. Within this tag, you ca
Keep in mind, in order to use the `#!jinja {% component ... %}` tag, you'll need to first call `#!jinja {% load idom %}` to gain access to it.
- {% include-markdown "../../../README.md" start="" end="" %}
+ === "my-template.html"
+
+ {% include-markdown "../../../README.md" start="" end="" %}
diff --git a/docs/src/getting-started/create-component.md b/docs/src/getting-started/create-component.md
index 29281051..8481cf87 100644
--- a/docs/src/getting-started/create-component.md
+++ b/docs/src/getting-started/create-component.md
@@ -6,7 +6,9 @@
{% include-markdown "../../../README.md" start="" end="" %}
-{% include-markdown "../../../README.md" start="" end="" %}
+=== "components.py"
+
+ {% include-markdown "../../../README.md" start="" end="" %}
??? question "What should I name my IDOM files and functions?"
diff --git a/docs/src/getting-started/reference-component.md b/docs/src/getting-started/reference-component.md
index db5c4a5e..f7c03ecb 100644
--- a/docs/src/getting-started/reference-component.md
+++ b/docs/src/getting-started/reference-component.md
@@ -6,7 +6,9 @@
{% include-markdown "../../../README.md" start="" end="" %}
-{% include-markdown "../../../README.md" start="" end="" %}
+=== "my-template.html"
+
+ {% include-markdown "../../../README.md" start="" end="" %}
{% include-markdown "../features/templatetag.md" start="" end="" %}
diff --git a/docs/src/getting-started/render-view.md b/docs/src/getting-started/render-view.md
index d264517b..b1055d9b 100644
--- a/docs/src/getting-started/render-view.md
+++ b/docs/src/getting-started/render-view.md
@@ -10,23 +10,27 @@ Within your **Django app**'s `views.py` file, you'll need to create a function t
In this example, we will create a view that renders `my-template.html` (_from the previous step_).
-```python title="views.py"
-from django.shortcuts import render
+=== "views.py"
-def index(request):
- return render(request, "my-template.html")
-```
+ ```python linenums="1"
+ from django.shortcuts import render
+
+ def index(request):
+ return render(request, "my-template.html")
+ ```
We will add this new view into your [`urls.py`](https://docs.djangoproject.com/en/dev/intro/tutorial01/#write-your-first-view).
-```python title="urls.py"
-from django.urls import path
-from example_project.my_app import views
+=== "urls.py"
+
+ ```python linenums="1"
+ from django.urls import path
+ from example_project.my_app import views
-urlpatterns = [
- path("example/", views.index),
-]
-```
+ urlpatterns = [
+ path("example/", views.index),
+ ]
+ ```
Now, navigate to `http://127.0.0.1:8000/example/`. If you copy-pasted the component from the previous example, you will now see your component display "Hello World".
diff --git a/docs/src/installation/index.md b/docs/src/installation/index.md
index e0cdfc1e..61782278 100644
--- a/docs/src/installation/index.md
+++ b/docs/src/installation/index.md
@@ -16,12 +16,14 @@ You'll also need to modify a few files in your **Django project**...
In your settings you'll need to add `django_idom` to [`INSTALLED_APPS`](https://docs.djangoproject.com/en/dev/ref/settings/#std:setting-INSTALLED_APPS).
-```python title="settings.py"
-INSTALLED_APPS = [
- "django_idom",
- ...
-]
-```
+=== "settings.py"
+
+ ```python linenums="1"
+ INSTALLED_APPS = [
+ "django_idom",
+ ...
+ ]
+ ```
??? warning "Enable Django ASGI (Required)"
@@ -31,13 +33,15 @@ INSTALLED_APPS = [
Read the [Django Channels Docs](https://channels.readthedocs.io/en/stable/installation.html) for more info.
- ```python title="settings.py"
- INSTALLED_APPS = [
- "channels",
- ...
- ]
- ASGI_APPLICATION = "example_project.asgi.application"
- ```
+ === "settings.py"
+
+ ```python linenums="1"
+ INSTALLED_APPS = [
+ "channels",
+ ...
+ ]
+ ASGI_APPLICATION = "example_project.asgi.application"
+ ```
??? note "Configure IDOM settings (Optional)"
@@ -51,14 +55,16 @@ INSTALLED_APPS = [
Add IDOM HTTP paths to your `urlpatterns`.
-```python title="urls.py"
-from django.urls import include, path
+=== "urls.py"
-urlpatterns = [
- path("idom/", include("django_idom.http.urls")),
- ...
-]
-```
+ ```python linenums="1"
+ from django.urls import include, path
+
+ urlpatterns = [
+ path("idom/", include("django_idom.http.urls")),
+ ...
+ ]
+ ```
---
@@ -66,28 +72,30 @@ urlpatterns = [
Register IDOM's Websocket using `IDOM_WEBSOCKET_PATH`.
-```python title="asgi.py"
-import os
-from django.core.asgi import get_asgi_application
-
-# Ensure DJANGO_SETTINGS_MODULE is set properly based on your project name!
-os.environ.setdefault("DJANGO_SETTINGS_MODULE", "example_project.settings")
-django_asgi_app = get_asgi_application()
-
-from channels.auth import AuthMiddlewareStack
-from channels.routing import ProtocolTypeRouter, URLRouter
-from channels.sessions import SessionMiddlewareStack
-from django_idom import IDOM_WEBSOCKET_PATH
-
-application = ProtocolTypeRouter(
- {
- "http": django_asgi_app,
- "websocket": SessionMiddlewareStack(
- AuthMiddlewareStack(URLRouter([IDOM_WEBSOCKET_PATH]))
- ),
- }
-)
-```
+=== "asgi.py"
+
+ ```python linenums="1"
+ import os
+ from django.core.asgi import get_asgi_application
+
+ # Ensure DJANGO_SETTINGS_MODULE is set properly based on your project name!
+ os.environ.setdefault("DJANGO_SETTINGS_MODULE", "example_project.settings")
+ django_asgi_app = get_asgi_application()
+
+ from channels.auth import AuthMiddlewareStack
+ from channels.routing import ProtocolTypeRouter, URLRouter
+ from channels.sessions import SessionMiddlewareStack
+ from django_idom import IDOM_WEBSOCKET_PATH
+
+ application = ProtocolTypeRouter(
+ {
+ "http": django_asgi_app,
+ "websocket": SessionMiddlewareStack(
+ AuthMiddlewareStack(URLRouter([IDOM_WEBSOCKET_PATH]))
+ ),
+ }
+ )
+ ```
??? question "Where is my asgi.py?"
diff --git a/docs/src/stylesheets/extra.css b/docs/src/stylesheets/extra.css
index 6b30153f..79881677 100644
--- a/docs/src/stylesheets/extra.css
+++ b/docs/src/stylesheets/extra.css
@@ -10,3 +10,8 @@
.md-typeset :is(.admonition, details) {
margin: 0.55em 0;
}
+
+.md-typeset .tabbed-labels > label {
+ padding-top: 0;
+ padding-bottom: 0.35em;
+}
From a2dd3c645ff22117386b9eae5465aaa784e66af2 Mon Sep 17 00:00:00 2001
From: Archmonger <16909269+Archmonger@users.noreply.github.com>
Date: Thu, 6 Oct 2022 14:30:12 -0700
Subject: [PATCH 09/23] add logging to query and mutation hooks
---
src/django_idom/hooks.py | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/src/django_idom/hooks.py b/src/django_idom/hooks.py
index 174e1c55..55c6d916 100644
--- a/src/django_idom/hooks.py
+++ b/src/django_idom/hooks.py
@@ -1,6 +1,7 @@
from __future__ import annotations
import asyncio
+import logging
from typing import Any, Awaitable, Callable, DefaultDict, Sequence, Union, cast
from channels.db import database_sync_to_async as _database_sync_to_async
@@ -13,6 +14,7 @@
from django_idom.types import IdomWebsocket, Mutation, Query, _Params, _Result
+_logger = logging.getLogger(__name__)
database_sync_to_async = cast(
Callable[..., Callable[..., Awaitable[Any]]],
_database_sync_to_async,
@@ -101,6 +103,7 @@ def execute_query() -> None:
set_data(None)
set_loading(False)
set_error(e)
+ _logger.exception("Error executing query", exc_info=True, stack_info=True)
return
finally:
set_should_execute(False)
@@ -130,6 +133,9 @@ def execute_mutation() -> None:
except Exception as e:
set_loading(False)
set_error(e)
+ _logger.exception(
+ "Error executing mutation", exc_info=True, stack_info=True
+ )
else:
set_loading(False)
set_error(None)
From 606019add1d1c6a6377e9fb239b47b94b10cefb9 Mon Sep 17 00:00:00 2001
From: Archmonger <16909269+Archmonger@users.noreply.github.com>
Date: Thu, 6 Oct 2022 14:33:43 -0700
Subject: [PATCH 10/23] add mutation/query name to logger
---
src/django_idom/hooks.py | 8 ++++++--
1 file changed, 6 insertions(+), 2 deletions(-)
diff --git a/src/django_idom/hooks.py b/src/django_idom/hooks.py
index 55c6d916..bba1ecb7 100644
--- a/src/django_idom/hooks.py
+++ b/src/django_idom/hooks.py
@@ -103,7 +103,9 @@ def execute_query() -> None:
set_data(None)
set_loading(False)
set_error(e)
- _logger.exception("Error executing query", exc_info=True, stack_info=True)
+ _logger.exception(
+ f"Error executing query: {query}", exc_info=True, stack_info=True
+ )
return
finally:
set_should_execute(False)
@@ -134,7 +136,9 @@ def execute_mutation() -> None:
set_loading(False)
set_error(e)
_logger.exception(
- "Error executing mutation", exc_info=True, stack_info=True
+ f"Error executing mutation: {mutate}",
+ exc_info=True,
+ stack_info=True,
)
else:
set_loading(False)
From ba9bebec4772c413f60d742843e224dedaaee37b Mon Sep 17 00:00:00 2001
From: Archmonger <16909269+Archmonger@users.noreply.github.com>
Date: Thu, 6 Oct 2022 15:37:54 -0700
Subject: [PATCH 11/23] try removing delay from typing
---
tests/test_app/tests/test_components.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/tests/test_app/tests/test_components.py b/tests/test_app/tests/test_components.py
index cf6b153e..fee79d3f 100644
--- a/tests/test_app/tests/test_components.py
+++ b/tests/test_app/tests/test_components.py
@@ -97,7 +97,7 @@ def test_use_query_and_mutation(self):
item_ids = list(range(5))
for i in item_ids:
- todo_input.type(f"sample-{i}", delay=10)
+ todo_input.type(f"sample-{i}")
todo_input.press("Enter")
self.page.wait_for_selector(f"#todo-item-sample-{i}")
self.page.wait_for_selector(f"#todo-item-sample-{i}-checkbox").click()
From c8389972937d5f7d340078bea272122ecc078a66 Mon Sep 17 00:00:00 2001
From: Archmonger <16909269+Archmonger@users.noreply.github.com>
Date: Thu, 6 Oct 2022 15:43:36 -0700
Subject: [PATCH 12/23] increase click delay
---
tests/test_app/tests/test_components.py | 7 +++++--
1 file changed, 5 insertions(+), 2 deletions(-)
diff --git a/tests/test_app/tests/test_components.py b/tests/test_app/tests/test_components.py
index fee79d3f..ebaf9b10 100644
--- a/tests/test_app/tests/test_components.py
+++ b/tests/test_app/tests/test_components.py
@@ -6,6 +6,9 @@
from playwright.sync_api import TimeoutError, sync_playwright
+CLICK_DELAY = 250 # Delay in miliseconds. Needed for GitHub Actions.
+
+
class TestIdomCapabilities(ChannelsLiveServerTestCase):
@classmethod
def setUpClass(cls):
@@ -97,8 +100,8 @@ def test_use_query_and_mutation(self):
item_ids = list(range(5))
for i in item_ids:
- todo_input.type(f"sample-{i}")
- todo_input.press("Enter")
+ todo_input.type(f"sample-{i}", delay=CLICK_DELAY)
+ todo_input.press("Enter", delay=CLICK_DELAY)
self.page.wait_for_selector(f"#todo-item-sample-{i}")
self.page.wait_for_selector(f"#todo-item-sample-{i}-checkbox").click()
self.assertRaises(
From 1c5c9e66028b282b8abb7f8d07c2b9a180331140 Mon Sep 17 00:00:00 2001
From: Archmonger <16909269+Archmonger@users.noreply.github.com>
Date: Thu, 6 Oct 2022 17:49:22 -0700
Subject: [PATCH 13/23] summaries
---
docs/src/features/components.md | 4 ++++
docs/src/features/decorators.md | 6 +++++-
docs/src/features/hooks.md | 10 +++++++---
docs/src/features/settings.md | 6 ++++--
docs/src/features/templatetag.md | 6 +++++-
docs/src/getting-started/learn-more.md | 2 +-
6 files changed, 26 insertions(+), 8 deletions(-)
diff --git a/docs/src/features/components.md b/docs/src/features/components.md
index 54b038ff..bdbe6cf1 100644
--- a/docs/src/features/components.md
+++ b/docs/src/features/components.md
@@ -1,3 +1,7 @@
+???+ summary
+
+ Prefabricated components can be used within your `components.py` to help simplify development.
+
## View To Component
Convert any Django view into a IDOM component by usng this decorator. Compatible with sync/async [Function Based Views](https://docs.djangoproject.com/en/dev/topics/http/views/) and [Class Based Views](https://docs.djangoproject.com/en/dev/topics/class-based-views/).
diff --git a/docs/src/features/decorators.md b/docs/src/features/decorators.md
index 994834ec..d0bcaa2a 100644
--- a/docs/src/features/decorators.md
+++ b/docs/src/features/decorators.md
@@ -1,10 +1,14 @@
+???+ summary
+
+ Decorator utilities can be used within your `components.py` to help simplify development.
+
## Auth Required
You can limit access to a component to users with a specific `auth_attribute` by using this decorator.
By default, this decorator checks if the user is logged in, and his/her account has not been deactivated.
-Common uses of this decorator are to hide components from [`AnonymousUser`](https://docs.djangoproject.com/en/dev/ref/contrib/auth/#django.contrib.auth.models.AnonymousUser), or render a component only if the user [`is_staff`](https://docs.djangoproject.com/en/dev/ref/contrib/auth/#django.contrib.auth.models.User.is_staff) or [`is_superuser`](https://docs.djangoproject.com/en/dev/ref/contrib/auth/#django.contrib.auth.models.User.is_superuser).
+Common uses of this decorator are to only render a component if the user [`is_staff`](https://docs.djangoproject.com/en/dev/ref/contrib/auth/#django.contrib.auth.models.User.is_staff) or [`is_superuser`](https://docs.djangoproject.com/en/dev/ref/contrib/auth/#django.contrib.auth.models.User.is_superuser).
This decorator can be used with or without parentheses.
diff --git a/docs/src/features/hooks.md b/docs/src/features/hooks.md
index 31b3215c..4bc7970a 100644
--- a/docs/src/features/hooks.md
+++ b/docs/src/features/hooks.md
@@ -1,8 +1,12 @@
-???+ tip "Looking for more hooks?"
+???+ summary
- Standard ReactJS hooks are contained within [`idom-team/idom`](https://github.com/idom-team/idom). Please note that `idom` is installed by default alongside `django-idom`.
+ Prefabricated hooks can be used within your `components.py` to help simplify development.
- Check out the [IDOM Core docs](https://idom-docs.herokuapp.com/docs/reference/hooks-api.html#basic-hooks) to see these hooks!
+??? tip "Looking for standard ReactJS hooks?"
+
+ Standard ReactJS hooks are contained within [`idom-team/idom`](https://github.com/idom-team/idom). Since `idom` is installed by default alongside `django-idom`, you can import them at any time.
+
+ Check out the [IDOM Core docs](https://idom-docs.herokuapp.com/docs/reference/hooks-api.html#basic-hooks) to see what hooks are available!
## Use Query
diff --git a/docs/src/features/settings.md b/docs/src/features/settings.md
index 8663f59a..3d2eb726 100644
--- a/docs/src/features/settings.md
+++ b/docs/src/features/settings.md
@@ -1,6 +1,8 @@
-Django IDOM uses your **Django project's** `settings.py` file to modify some behaviors of IDOM.
+???+ summary
-Here are the configurable variables that are available.
+ Django IDOM uses your **Django project's** `settings.py` file to modify the behavior of IDOM.
+
+## Primary `settings.py` Configuration
diff --git a/docs/src/features/templatetag.md b/docs/src/features/templatetag.md
index 6e01ba1f..526f6c79 100644
--- a/docs/src/features/templatetag.md
+++ b/docs/src/features/templatetag.md
@@ -1,4 +1,8 @@
-Integrated within Django IDOM, we bundle a template tag. Within this tag, you can pass in keyword arguments directly into your component.
+???+ summary
+
+ Template tags can be used within your Django templates such as `my-template.html` to import IDOM features.
+
+## Component
=== "my-template.html"
diff --git a/docs/src/getting-started/learn-more.md b/docs/src/getting-started/learn-more.md
index 31449ea9..e9aa7419 100644
--- a/docs/src/getting-started/learn-more.md
+++ b/docs/src/getting-started/learn-more.md
@@ -8,4 +8,4 @@ Additionally, the vast majority of tutorials/guides you find for React can be ap
| Learn More |
| --- |
-| [Django-IDOM Exclusive Features](../features/hooks.md){ .md-button } [IDOM Hooks, Events, and More](https://idom-docs.herokuapp.com/docs/guides/creating-interfaces/index.html){ .md-button } [Ask Questions on GitHub Discussions](https://github.com/idom-team/idom/discussions){ .md-button .md-button--primary } |
+| [Django-IDOM Advanced Usage](../features/components.md){ .md-button } [IDOM Hooks, Events, and More](https://idom-docs.herokuapp.com/docs/guides/creating-interfaces/index.html){ .md-button } [Ask Questions on GitHub Discussions](https://github.com/idom-team/idom/discussions){ .md-button .md-button--primary } |
From 50084c0f158eaa6fdf43e752603527f605be1fff Mon Sep 17 00:00:00 2001
From: Archmonger <16909269+Archmonger@users.noreply.github.com>
Date: Fri, 7 Oct 2022 02:00:01 -0700
Subject: [PATCH 14/23] add interface to all endpoints
---
docs/src/features/components.md | 28 ++++++++++
docs/src/features/hooks.md | 79 ++++++++++++++++++++++++++++
docs/src/features/templatetag.md | 16 ++++++
src/django_idom/hooks.py | 26 +++++++--
src/django_idom/templatetags/idom.py | 18 ++++---
5 files changed, 157 insertions(+), 10 deletions(-)
diff --git a/docs/src/features/components.md b/docs/src/features/components.md
index bdbe6cf1..5886fc23 100644
--- a/docs/src/features/components.md
+++ b/docs/src/features/components.md
@@ -203,6 +203,20 @@ Allows you to defer loading a CSS stylesheet until a component begins rendering.
)
```
+??? example "See Interface"
+
+ **Parameters**
+
+ | Name | Type | Description | Default |
+ | --- | --- | --- | --- |
+ | static_path | `str` | The path to the static file. This path is identical to what you would use on a `static` template tag. | N/A |
+
+ **Returns**
+
+ | Type | Description |
+ | --- | --- |
+ | `Component` | An IDOM component. |
+
??? question "Should I put `django_css` at the top of my component?"
Yes, if the stylesheet contains styling for your component.
@@ -266,6 +280,20 @@ Allows you to defer loading JavaScript until a component begins rendering. This
)
```
+??? example "See Interface"
+
+ **Parameters**
+
+ | Name | Type | Description | Default |
+ | --- | --- | --- | --- |
+ | static_path | `str` | The path to the static file. This path is identical to what you would use on a `static` template tag. | N/A |
+
+ **Returns**
+
+ | Type | Description |
+ | --- | --- |
+ | `Component` | An IDOM component. |
+
??? question "Should I put `django_js` at the bottom of my component?"
Yes, if your scripts are reliant on the contents of the component.
diff --git a/docs/src/features/hooks.md b/docs/src/features/hooks.md
index 4bc7970a..d896e5fb 100644
--- a/docs/src/features/hooks.md
+++ b/docs/src/features/hooks.md
@@ -47,6 +47,22 @@ The function you provide into this hook must return either a `Model` or `QuerySe
text = models.CharField(max_length=255)
```
+??? example "See Interface"
+
+ **Parameters**
+
+ | Name | Type | Description | Default |
+ | --- | --- | --- | --- |
+ | query | `Callable[_Params, _Result | None]` | A callable that returns a Django `Model` or `QuerySet`. | N/A |
+ | *args | `_Params.args` | Positional arguments to pass into `query`. | N/A |
+ | **kwargs | `_Params.kwargs` | Keyword arguments to pass into `query`. | N/A |
+
+ **Returns**
+
+ | Type | Description |
+ | --- | --- |
+ | `Query[_Result | None]` | A dataclass containing `loading`/`error` states, your `data` (if the query has successfully executed), and a `refetch` callable that can be used to re-run the query. |
+
??? question "Can I make ORM calls without hooks?"
Due to Django's ORM design, database queries must be deferred using hooks. Otherwise, you will see a `SynchronousOnlyOperation` exception.
@@ -106,6 +122,21 @@ The function you provide into this hook will have no return value.
{% include-markdown "../../includes/examples.md" start="" end="" %}
+??? example "See Interface"
+
+ **Parameters**
+
+ | Name | Type | Description | Default |
+ | --- | --- | --- | --- |
+ | mutate | `Callable[_Params, bool | None]` | A callable that performs Django ORM create, update, or delete functionality. If this function returns `False`, then your `refetch` function will not be used. | N/A |
+ | refetch | `Callable[..., Any] | Sequence[Callable[..., Any]] | None` | A callable or sequence of callables that will be called if the mutation succeeds. This is useful for refetching data after a mutation has been performed. | `None` |
+
+ **Returns**
+
+ | Type | Description |
+ | --- | --- |
+ | `Mutation[_Params]` | A dataclass containing `loading`/`error` states, a `reset` callable that will set `loading`/`error` states to defaults, and a `execute` callable that will run the query. |
+
??? question "Can `use_mutation` trigger a refetch of `use_query`?"
Yes, `use_mutation` can queue a refetch of a `use_query` via the `refetch=...` argument.
@@ -235,6 +266,18 @@ You can fetch the Django Channels [websocket](https://channels.readthedocs.io/en
return html.div(my_websocket)
```
+??? example "See Interface"
+
+ **Parameters**
+
+ `None`
+
+ **Returns**
+
+ | Type | Description |
+ | --- | --- |
+ | `IdomWebsocket` | The component's websocket. |
+
## Use Scope
This is a shortcut that returns the Websocket's [`scope`](https://channels.readthedocs.io/en/stable/topics/consumers.html#scope).
@@ -251,6 +294,18 @@ This is a shortcut that returns the Websocket's [`scope`](https://channels.readt
return html.div(my_scope)
```
+??? example "See Interface"
+
+ **Parameters**
+
+ `None`
+
+ **Returns**
+
+ | Type | Description |
+ | --- | --- |
+ | `dict[str, Any]` | The websocket's `scope`. |
+
## Use Location
This is a shortcut that returns the Websocket's `path`.
@@ -275,6 +330,18 @@ You can expect this hook to provide strings such as `/idom/my_path`.
Check out [idom-team/idom-router#2](https://github.com/idom-team/idom-router/issues/2) for more information.
+??? example "See Interface"
+
+ **Parameters**
+
+ `None`
+
+ **Returns**
+
+ | Type | Description |
+ | --- | --- |
+ | `Location` | A object containing the current URL's `pathname` and `search` query. |
+
## Use Origin
This is a shortcut that returns the Websocket's `origin`.
@@ -292,3 +359,15 @@ You can expect this hook to provide strings such as `http://example.com`.
my_origin = use_origin()
return html.div(my_origin)
```
+
+??? example "See Interface"
+
+ **Parameters**
+
+ `None`
+
+ **Returns**
+
+ | Type | Description |
+ | --- | --- |
+ | `str | None` | A string containing the browser's current origin, obtained from websocket headers (if available). |
diff --git a/docs/src/features/templatetag.md b/docs/src/features/templatetag.md
index 526f6c79..3ad12dc3 100644
--- a/docs/src/features/templatetag.md
+++ b/docs/src/features/templatetag.md
@@ -55,6 +55,22 @@
```
+
+??? example "See Interface"
+
+ **Parameters**
+
+ | Name | Type | Description | Default |
+ | --- | --- | --- | --- |
+ | dotted_path | `str` | The dotted path to the component to render. | N/A |
+ | **kwargs | `Any` | The keyword arguments to pass to the component. | N/A |
+
+ **Returns**
+
+ | Type | Description |
+ | --- | --- |
+ | `Component` | An IDOM component. |
+
??? question "Can I use multiple components on one page?"
diff --git a/src/django_idom/hooks.py b/src/django_idom/hooks.py
index bba1ecb7..07178e4a 100644
--- a/src/django_idom/hooks.py
+++ b/src/django_idom/hooks.py
@@ -68,6 +68,14 @@ def use_query(
*args: _Params.args,
**kwargs: _Params.kwargs,
) -> Query[_Result | None]:
+ """Hook to fetch a Django ORM query.
+
+ Args:
+ query: A callable that returns a Django `Model` or `QuerySet`.
+ *args: Positional arguments to pass into `query`.
+
+ Keyword Args:
+ **kwargs: Keyword arguments to pass into `query`."""
query_ref = use_ref(query)
if query_ref.current is not query:
raise ValueError(f"Query function changed from {query_ref.current} to {query}.")
@@ -121,6 +129,17 @@ def use_mutation(
mutate: Callable[_Params, bool | None],
refetch: Callable[..., Any] | Sequence[Callable[..., Any]] | None = None,
) -> Mutation[_Params]:
+ """Hook to create, update, or delete Django ORM objects.
+
+ Args:
+ mutate: A callable that performs Django ORM create, update, or delete
+ functionality. If this function returns `False`, then your `refetch`
+ function will not be used.
+ refetch: A callable or sequence of callables that will be called if the
+ mutation succeeds. This is useful for refetching data after a mutation
+ has been performed.
+ """
+
loading, set_loading = use_state(False)
error, set_error = use_state(cast(Union[Exception, None], None))
@@ -143,9 +162,10 @@ def execute_mutation() -> None:
else:
set_loading(False)
set_error(None)
- if should_refetch is not False:
- if not refetch:
- return
+
+ # `refetch` will execute unless explicitly told not to
+ # or if `refetch` was not defined.
+ if should_refetch is not False and refetch:
for query in (refetch,) if callable(refetch) else refetch:
for callback in _REFETCH_CALLBACKS.get(query) or ():
callback()
diff --git a/src/django_idom/templatetags/idom.py b/src/django_idom/templatetags/idom.py
index 5e6b7ced..b6bb6868 100644
--- a/src/django_idom/templatetags/idom.py
+++ b/src/django_idom/templatetags/idom.py
@@ -1,4 +1,5 @@
import json
+from typing import Any
from urllib.parse import urlencode
from uuid import uuid4
@@ -14,13 +15,16 @@
@register.inclusion_tag("idom/component.html")
-def component(_component_id_, **kwargs):
- """
- This tag is used to embed an existing IDOM component into your HTML template.
+def component(dotted_path: str, **kwargs: Any):
+ """This tag is used to embed an existing IDOM component into your HTML template.
+
+ Args:
+ dotted_path: The dotted path to the component to render.
- The first argument within this tag is your dotted path to the component function.
+ Keyword Args:
+ **kwargs: The keyword arguments to pass to the component.
- Subsequent values are keyworded arguments are passed into your component::
+ Example ::
{% load idom %}
@@ -30,7 +34,7 @@ def component(_component_id_, **kwargs):
"""
- _register_component(_component_id_)
+ _register_component(dotted_path)
class_ = kwargs.pop("class", "")
json_kwargs = json.dumps(kwargs, separators=(",", ":"))
@@ -41,6 +45,6 @@ def component(_component_id_, **kwargs):
"idom_web_modules_url": IDOM_WEB_MODULES_URL,
"idom_ws_max_reconnect_timeout": IDOM_WS_MAX_RECONNECT_TIMEOUT,
"idom_mount_uuid": uuid4().hex,
- "idom_component_id": _component_id_,
+ "idom_component_id": dotted_path,
"idom_component_params": urlencode({"kwargs": json_kwargs}),
}
From 2a07138c2f28a96b239558587dde4c0c335abeb2 Mon Sep 17 00:00:00 2001
From: Archmonger <16909269+Archmonger@users.noreply.github.com>
Date: Fri, 7 Oct 2022 02:03:28 -0700
Subject: [PATCH 15/23] Add changelog entry
---
CHANGELOG.md | 12 +++++++++++-
1 file changed, 11 insertions(+), 1 deletion(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 894a1390..b91665a8 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -22,7 +22,17 @@ Using the following categories, list your changes in this order:
## [Unreleased]
-- Nothing (Yet)
+### Added
+
+- `use_origin` hook to return the browser's `location.origin`.
+
+### Changed
+
+- `use_mutation` and `use_query` will now log any query failures.
+
+### Fixed
+
+- Allow `use_mutation` to have `refetch=None`, as the docs suggest is possible.
## [1.2.0] - 2022-09-19
From 3ec2436d7b5a9dd0398203aaaa959522c57be8f3 Mon Sep 17 00:00:00 2001
From: Archmonger <16909269+Archmonger@users.noreply.github.com>
Date: Fri, 7 Oct 2022 02:19:19 -0700
Subject: [PATCH 16/23] cleaner logging message
---
src/django_idom/components.py | 12 +-----------
src/django_idom/hooks.py | 7 +++----
src/django_idom/utils.py | 13 ++++++++++++-
3 files changed, 16 insertions(+), 16 deletions(-)
diff --git a/src/django_idom/components.py b/src/django_idom/components.py
index 34ce359c..0a91c5c0 100644
--- a/src/django_idom/components.py
+++ b/src/django_idom/components.py
@@ -15,6 +15,7 @@
from django_idom.config import IDOM_CACHE, IDOM_VIEW_COMPONENT_IFRAMES
from django_idom.types import ViewComponentIframe
+from django_idom.utils import _generate_obj_name
# TODO: Might want to intercept href clicks and form submit events.
@@ -163,14 +164,3 @@ def _cached_static_contents(static_path: str):
)
return file_contents
-
-
-def _generate_obj_name(object: Any) -> str | None:
- """Makes a best effort to create a name for an object.
- Useful for JSON serialization of Python objects."""
- if hasattr(object, "__module__"):
- if hasattr(object, "__name__"):
- return f"{object.__module__}.{object.__name__}"
- if hasattr(object, "__class__"):
- return f"{object.__module__}.{object.__class__.__name__}"
- return None
diff --git a/src/django_idom/hooks.py b/src/django_idom/hooks.py
index 07178e4a..2fdeb319 100644
--- a/src/django_idom/hooks.py
+++ b/src/django_idom/hooks.py
@@ -12,6 +12,7 @@
from idom.core.hooks import Context, create_context, use_context, use_effect, use_state
from django_idom.types import IdomWebsocket, Mutation, Query, _Params, _Result
+from django_idom.utils import _generate_obj_name
_logger = logging.getLogger(__name__)
@@ -112,7 +113,7 @@ def execute_query() -> None:
set_loading(False)
set_error(e)
_logger.exception(
- f"Error executing query: {query}", exc_info=True, stack_info=True
+ f"Failed to execute query: {_generate_obj_name(query) or query}"
)
return
finally:
@@ -155,9 +156,7 @@ def execute_mutation() -> None:
set_loading(False)
set_error(e)
_logger.exception(
- f"Error executing mutation: {mutate}",
- exc_info=True,
- stack_info=True,
+ f"Failed to execute mutation: {_generate_obj_name(mutate) or mutate}"
)
else:
set_loading(False)
diff --git a/src/django_idom/utils.py b/src/django_idom/utils.py
index 2f6782ab..c6b308bb 100644
--- a/src/django_idom/utils.py
+++ b/src/django_idom/utils.py
@@ -6,7 +6,7 @@
import re
from fnmatch import fnmatch
from importlib import import_module
-from typing import Callable
+from typing import Any, Callable
from django.template import engines
from django.utils.encoding import smart_str
@@ -144,3 +144,14 @@ def _register_components(self, components: set[str]) -> None:
"\033[0m",
component,
)
+
+
+def _generate_obj_name(object: Any) -> str | None:
+ """Makes a best effort to create a name for an object.
+ Useful for JSON serialization of Python objects."""
+ if hasattr(object, "__module__"):
+ if hasattr(object, "__name__"):
+ return f"{object.__module__}.{object.__name__}"
+ if hasattr(object, "__class__"):
+ return f"{object.__module__}.{object.__class__.__name__}"
+ return None
From b73e9e23e404b026d7f4ec1afc80b0684c0dbbef Mon Sep 17 00:00:00 2001
From: Archmonger <16909269+Archmonger@users.noreply.github.com>
Date: Fri, 7 Oct 2022 02:24:58 -0700
Subject: [PATCH 17/23] use_origin tests
---
tests/test_app/components.py | 11 +++++++++++
tests/test_app/templates/base.html | 1 +
tests/test_app/tests/test_components.py | 3 +++
3 files changed, 15 insertions(+)
diff --git a/tests/test_app/components.py b/tests/test_app/components.py
index c103e01c..fbcb7961 100644
--- a/tests/test_app/components.py
+++ b/tests/test_app/components.py
@@ -85,6 +85,17 @@ def use_location():
)
+@component
+def use_origin():
+ location = django_idom.hooks.use_origin()
+ success = bool(location)
+ return html.div(
+ {"id": "use-origin", "data-success": success},
+ f"use_origin: {location}",
+ html.hr(),
+ )
+
+
@component
def django_css():
return html.div(
diff --git a/tests/test_app/templates/base.html b/tests/test_app/templates/base.html
index bea9893a..8f610e96 100644
--- a/tests/test_app/templates/base.html
+++ b/tests/test_app/templates/base.html
@@ -27,6 +27,7 @@ IDOM Test Page
{% component "test_app.components.use_websocket" %}
{% component "test_app.components.use_scope" %}
{% component "test_app.components.use_location" %}
+ {% component "test_app.components.use_origin" %}
{% component "test_app.components.django_css" %}
{% component "test_app.components.django_js" %}
{% component "test_app.components.unauthorized_user" %}
diff --git a/tests/test_app/tests/test_components.py b/tests/test_app/tests/test_components.py
index ebaf9b10..923e3e93 100644
--- a/tests/test_app/tests/test_components.py
+++ b/tests/test_app/tests/test_components.py
@@ -65,6 +65,9 @@ def test_use_scope(self):
def test_use_location(self):
self.page.locator("#use-location[data-success=true]").wait_for()
+ def test_use_origin(self):
+ self.page.locator("#use-origin[data-success=true]").wait_for()
+
def test_static_css(self):
self.assertEqual(
self.page.wait_for_selector("#django-css button").evaluate(
From 78ae414bea9daecab346b9da5b87029c60acf415 Mon Sep 17 00:00:00 2001
From: Archmonger <16909269+Archmonger@users.noreply.github.com>
Date: Mon, 10 Oct 2022 16:33:19 -0700
Subject: [PATCH 18/23] _fetch_lazy_fields
---
src/django_idom/hooks.py | 23 ++++++++++++-----------
tests/test_app/components.py | 2 +-
2 files changed, 13 insertions(+), 12 deletions(-)
diff --git a/src/django_idom/hooks.py b/src/django_idom/hooks.py
index 2fdeb319..e562c916 100644
--- a/src/django_idom/hooks.py
+++ b/src/django_idom/hooks.py
@@ -107,7 +107,7 @@ def execute_query() -> None:
try:
new_data = query(*args, **kwargs)
- _fetch_deferred(new_data)
+ _fetch_lazy_fields(new_data)
except Exception as e:
set_data(None)
set_loading(False)
@@ -179,19 +179,20 @@ def reset() -> None:
return Mutation(call, loading, error, reset)
-def _fetch_deferred(data: Any) -> None:
+def _fetch_lazy_fields(data: Any) -> None:
+ """Fetch all fields within a `Model` or `QuerySet` to ensure they are not performed lazily."""
+
+ # `QuerySet`, which is effectively a list of `Model` instances
# https://github.com/typeddjango/django-stubs/issues/704
if isinstance(data, QuerySet): # type: ignore[misc]
for model in data:
- _fetch_deferred_model_fields(model)
+ _fetch_lazy_fields(model)
+
+ # `Model` instances
elif isinstance(data, Model):
- _fetch_deferred_model_fields(data)
+ for field in data._meta.fields:
+ getattr(data, field.name)
+
+ # Unrecognized type
else:
raise ValueError(f"Expected a Model or QuerySet, got {data!r}")
-
-
-def _fetch_deferred_model_fields(model: Any) -> None:
- for field in model.get_deferred_fields():
- value = getattr(model, field)
- if isinstance(value, Model):
- _fetch_deferred_model_fields(value)
diff --git a/tests/test_app/components.py b/tests/test_app/components.py
index fbcb7961..bc866604 100644
--- a/tests/test_app/components.py
+++ b/tests/test_app/components.py
@@ -178,7 +178,7 @@ def toggle_item_mutation(item: TodoItem):
def todo_list():
input_value, set_input_value = hooks.use_state("")
items = use_query(get_items_query)
- toggle_item = use_mutation(toggle_item_mutation, refetch=get_items_query)
+ toggle_item = use_mutation(toggle_item_mutation)
if items.error:
rendered_items = html.h2(f"Error when loading - {items.error}")
From 12bebeefd9436d068f8eb14703059448b334c95b Mon Sep 17 00:00:00 2001
From: Archmonger <16909269+Archmonger@users.noreply.github.com>
Date: Mon, 10 Oct 2022 16:48:06 -0700
Subject: [PATCH 19/23] fix variable name in tests
---
tests/test_app/components.py | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/tests/test_app/components.py b/tests/test_app/components.py
index bc866604..40fbd7d6 100644
--- a/tests/test_app/components.py
+++ b/tests/test_app/components.py
@@ -87,11 +87,11 @@ def use_location():
@component
def use_origin():
- location = django_idom.hooks.use_origin()
- success = bool(location)
+ origin = django_idom.hooks.use_origin()
+ success = bool(origin)
return html.div(
{"id": "use-origin", "data-success": success},
- f"use_origin: {location}",
+ f"use_origin: {origin}",
html.hr(),
)
From 5fc28e191938b168f8fa690661b589d38563f35d Mon Sep 17 00:00:00 2001
From: Archmonger <16909269+Archmonger@users.noreply.github.com>
Date: Mon, 10 Oct 2022 16:51:34 -0700
Subject: [PATCH 20/23] Add changelog entry
---
CHANGELOG.md | 1 +
1 file changed, 1 insertion(+)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index b91665a8..335c7cb8 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -33,6 +33,7 @@ Using the following categories, list your changes in this order:
### Fixed
- Allow `use_mutation` to have `refetch=None`, as the docs suggest is possible.
+- `use_query` will now fetch all fields to prevent `SynchronousOnlyOperation` exceptions.
## [1.2.0] - 2022-09-19
From 4d5972b65c8cd1cb81c974a9e3a50d543b5adc30 Mon Sep 17 00:00:00 2001
From: Archmonger <16909269+Archmonger@users.noreply.github.com>
Date: Mon, 10 Oct 2022 18:33:29 -0700
Subject: [PATCH 21/23] fetch -> prefetch
---
CHANGELOG.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 335c7cb8..20a85b93 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -33,7 +33,7 @@ Using the following categories, list your changes in this order:
### Fixed
- Allow `use_mutation` to have `refetch=None`, as the docs suggest is possible.
-- `use_query` will now fetch all fields to prevent `SynchronousOnlyOperation` exceptions.
+- `use_query` will now prefetch all fields to prevent `SynchronousOnlyOperation` exceptions.
## [1.2.0] - 2022-09-19
From cd1cbd710e42c89940d4043e87d274e489aa7f2d Mon Sep 17 00:00:00 2001
From: Archmonger <16909269+Archmonger@users.noreply.github.com>
Date: Mon, 10 Oct 2022 18:43:19 -0700
Subject: [PATCH 22/23] docs cleanup after self review
---
docs/src/features/decorators.md | 2 +-
docs/src/features/settings.md | 30 ++++++++++++++++--------------
docs/src/features/templatetag.md | 2 +-
3 files changed, 18 insertions(+), 16 deletions(-)
diff --git a/docs/src/features/decorators.md b/docs/src/features/decorators.md
index d0bcaa2a..8b28c08a 100644
--- a/docs/src/features/decorators.md
+++ b/docs/src/features/decorators.md
@@ -8,7 +8,7 @@ You can limit access to a component to users with a specific `auth_attribute` by
By default, this decorator checks if the user is logged in, and his/her account has not been deactivated.
-Common uses of this decorator are to only render a component if the user [`is_staff`](https://docs.djangoproject.com/en/dev/ref/contrib/auth/#django.contrib.auth.models.User.is_staff) or [`is_superuser`](https://docs.djangoproject.com/en/dev/ref/contrib/auth/#django.contrib.auth.models.User.is_superuser).
+This decorator is commonly used to selectively render a component only if a user [`is_staff`](https://docs.djangoproject.com/en/dev/ref/contrib/auth/#django.contrib.auth.models.User.is_staff) or [`is_superuser`](https://docs.djangoproject.com/en/dev/ref/contrib/auth/#django.contrib.auth.models.User.is_superuser).
This decorator can be used with or without parentheses.
diff --git a/docs/src/features/settings.md b/docs/src/features/settings.md
index 3d2eb726..c6a7c020 100644
--- a/docs/src/features/settings.md
+++ b/docs/src/features/settings.md
@@ -2,25 +2,27 @@
Django IDOM uses your **Django project's** `settings.py` file to modify the behavior of IDOM.
-## Primary `settings.py` Configuration
+## Primary Configuration
-
+=== "settings.py"
-```python linenums="1"
-# If "idom" cache is not configured, then we'll use "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_TIMEOUT = 604800
+ ```python linenums="1"
+ # If "idom" cache is not configured, then we'll use "default" instead
+ CACHES = {
+ "idom": {"BACKEND": ...},
+ }
-# The URL for IDOM to serve websockets
-IDOM_WEBSOCKET_URL = "idom/"
-```
+ # Maximum seconds between two reconnection attempts that would cause the client give up.
+ # 0 will disable reconnection.
+ IDOM_WS_MAX_RECONNECT_TIMEOUT = 604800
-
+ # The URL for IDOM to serve websockets
+ IDOM_WEBSOCKET_URL = "idom/"
+ ```
+
+
??? question "Do I need to modify my settings?"
diff --git a/docs/src/features/templatetag.md b/docs/src/features/templatetag.md
index 3ad12dc3..3e7731d9 100644
--- a/docs/src/features/templatetag.md
+++ b/docs/src/features/templatetag.md
@@ -14,7 +14,7 @@
Our pre-processor relies on the template tag containing a string.
- **Do not** use a Django context variable for the path string. Failure to follow this warning will result in unexpected behavior.
+ **Do not** use Django template/context variables for the component path. Failure to follow this warning will result in unexpected behavior.
For example, **do not** do the following:
From 328b5baeb3983d7b11072966b175af7c9352e232 Mon Sep 17 00:00:00 2001
From: Archmonger <16909269+Archmonger@users.noreply.github.com>
Date: Thu, 13 Oct 2022 19:43:19 -0700
Subject: [PATCH 23/23] fix refetch docstring
---
docs/src/features/hooks.md | 2 +-
src/django_idom/hooks.py | 6 +++---
2 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/docs/src/features/hooks.md b/docs/src/features/hooks.md
index d896e5fb..e9e67e39 100644
--- a/docs/src/features/hooks.md
+++ b/docs/src/features/hooks.md
@@ -129,7 +129,7 @@ The function you provide into this hook will have no return value.
| Name | Type | Description | Default |
| --- | --- | --- | --- |
| mutate | `Callable[_Params, bool | None]` | A callable that performs Django ORM create, update, or delete functionality. If this function returns `False`, then your `refetch` function will not be used. | N/A |
- | refetch | `Callable[..., Any] | Sequence[Callable[..., Any]] | None` | A callable or sequence of callables that will be called if the mutation succeeds. This is useful for refetching data after a mutation has been performed. | `None` |
+ | refetch | `Callable[..., Any] | Sequence[Callable[..., Any]] | None` | A `query` function (used by the `use_query` hook) or a sequence of `query` functions that will be called if the mutation succeeds. This is useful for refetching data after a mutation has been performed. | `None` |
**Returns**
diff --git a/src/django_idom/hooks.py b/src/django_idom/hooks.py
index e562c916..7e73df23 100644
--- a/src/django_idom/hooks.py
+++ b/src/django_idom/hooks.py
@@ -136,9 +136,9 @@ def use_mutation(
mutate: A callable that performs Django ORM create, update, or delete
functionality. If this function returns `False`, then your `refetch`
function will not be used.
- refetch: A callable or sequence of callables that will be called if the
- mutation succeeds. This is useful for refetching data after a mutation
- has been performed.
+ refetch: A `query` function (used by the `use_query` hook) or a sequence of `query`
+ functions that will be called if the mutation succeeds. This is useful for
+ refetching data after a mutation has been performed.
"""
loading, set_loading = use_state(False)