Skip to content

Support for non-serializable component parameters #120

New issue

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

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

Already on GitHub? Sign in to your account

Merged
merged 57 commits into from
Feb 2, 2023
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
Show all changes
57 commits
Select commit Hold shift + click to select a range
48ef41d
fix use_location bug
Archmonger Jan 9, 2023
89a59d6
add missing migration
Archmonger Jan 10, 2023
2247a18
enable django mypy plugin
Archmonger Jan 10, 2023
f2b5a3a
Functional DB-backed component params
Archmonger Jan 10, 2023
0b11cf7
remove broken django stubs
Archmonger Jan 10, 2023
587cfa3
fix type hints
Archmonger Jan 10, 2023
e93253c
skip migrations
Archmonger Jan 10, 2023
8a84f1d
bump idom version
Archmonger Jan 10, 2023
3760dde
Use idom 0.43.0 hooks
Archmonger Jan 10, 2023
e686894
don't render component if args/kwargs are wrong
Archmonger Jan 10, 2023
4b7676c
docs and changelog
Archmonger Jan 10, 2023
0f57702
Add object in templatetag tests
Archmonger Jan 10, 2023
9432349
fix styling errors
Archmonger Jan 10, 2023
a87a7b5
type hints for user
Archmonger Jan 10, 2023
4f41975
validate function signature
Archmonger Jan 10, 2023
ec46469
use lowercase tuple
Archmonger Jan 11, 2023
5e75488
functioning eviction strategy
Archmonger Jan 11, 2023
7344a97
don't clean DB on startup if running django tests
Archmonger Jan 11, 2023
d2d4573
switch sys check to contextlib suppress
Archmonger Jan 11, 2023
ace4a34
view_to_component uses del_html_head_body_transform
Archmonger Jan 11, 2023
8cf2884
update docs
Archmonger Jan 11, 2023
d985e14
add serializable to dictionary
Archmonger Jan 11, 2023
3011e75
fix #35
Archmonger Jan 11, 2023
e9a1364
fix #29
Archmonger Jan 11, 2023
e0fcbd9
Make some utils public if they're harmless
Archmonger Jan 12, 2023
2adb676
Make DATE_FORMAT a global
Archmonger Jan 12, 2023
319dda2
validate params_query expiration within fetch queries
Archmonger Jan 12, 2023
00e3c85
refactor db_cleanup
Archmonger Jan 12, 2023
43f4a32
more deprivatization
Archmonger Jan 12, 2023
f79f936
document django_query_postprocessor
Archmonger Jan 12, 2023
83730cd
comment for _register_component
Archmonger Jan 12, 2023
5dd8d16
add postprocessor to dictionary
Archmonger Jan 12, 2023
9f6b757
docs revision
Archmonger Jan 14, 2023
5d56d23
misc docs updates
Archmonger Jan 14, 2023
827adf3
move hooks back into `django_idom.hooks`
Archmonger Jan 14, 2023
6a08531
Merge remote-tracking branch 'upstream/main' into database-backed-kwargs
Archmonger Jan 14, 2023
03f315b
v3.0.0
Archmonger Jan 14, 2023
8f83c1b
docs formatting
Archmonger Jan 14, 2023
be07851
better link for keys
Archmonger Jan 15, 2023
6209c07
subclass core's Connection
Archmonger Jan 15, 2023
04a2efc
Complete types list
Archmonger Jan 15, 2023
196c756
remove unused ignore
Archmonger Jan 15, 2023
ac415dd
tests for ComponentParams expiration
Archmonger Jan 16, 2023
6447885
use tuple for ComponentParamData
Archmonger Jan 16, 2023
1a9b06e
better type hints for ComponentParamData
Archmonger Jan 16, 2023
cb0bd4f
Better type hints for postprocessor_kwargs
Archmonger Jan 16, 2023
648d3a2
type hint fixes
Archmonger Jan 16, 2023
54b9840
type hint for ComponentPreloader
Archmonger Jan 16, 2023
976f10f
timezone might not always be available, so don't rely on it.
Archmonger Jan 20, 2023
3f6069c
more robust ComponentParams tests
Archmonger Jan 22, 2023
abc3439
always lazy load config
Archmonger Jan 22, 2023
d7809f9
remove defaults.py
Archmonger Jan 22, 2023
a2a2c8c
remove version bump
Archmonger Jan 28, 2023
468d52f
fix type hint issues
Archmonger Jan 28, 2023
0d9d4a9
change mypy to incremental
Archmonger Feb 1, 2023
a0194c0
format using new black version
Archmonger Feb 1, 2023
f5cc229
log when idom database is not ready at startup
Archmonger Feb 2, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 15 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,21 @@ Using the following categories, list your changes in this order:

## [Unreleased]

- Nothing (yet)
### Changed

- The `component` template tag now supports both positional and keyword arguments.
- `use_location`, `use_scope`, and `use_websocket` previously contained within `django_idom.hooks` have been migrated to `idom.backend.hooks`.
- Bumped the minimum IDOM version to 0.43.0

### Removed

- `django_idom.hooks.use_location` has been removed. The equivalent replacement is found at `idom.backends.hooks.use_location`.
- `django_idom.hooks.use_scope` has been removed. The equivalent replacement is found at `idom.backends.hooks.use_scope`.
- `django_idom.hooks.use_websocket` has been removed. The equivalent replacement is found at `idom.backends.hooks.use_connection`.

### Security

- Fixed a potential method of component argument spoofing

## [2.2.1] - 2022-01-09

Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ def hello_world(recipient: str):

In your **Django app**'s HTML template, you can now embed your IDOM component using the `component` template tag. Within this tag, you will need to type in your dotted path to the component function as the first argument.

Additionally, you can pass in keyword arguments into your component function. For example, after reading the code below, pay attention to how the function definition for `hello_world` (_in the previous example_) accepts a `recipient` argument.
Additionally, you can pass in `args` and `kwargs` into your component function. For example, after reading the code below, pay attention to how the function definition for `hello_world` (_in the previous example_) accepts a `recipient` argument.

<!--html-header-end-->
<!--html-code-start-->
Expand Down
4 changes: 0 additions & 4 deletions docs/src/features/decorators.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ This decorator can be used with or without parentheses.

```python
from django_idom.decorators import auth_required
from django_idom.hooks import use_websocket
from idom import component, html

@component
Expand Down Expand Up @@ -70,7 +69,6 @@ This decorator can be used with or without parentheses.

```python
from django_idom.decorators import auth_required
from django_idom.hooks import use_websocket
from idom import component, html

@component
Expand All @@ -87,7 +85,6 @@ This decorator can be used with or without parentheses.

```python
from django_idom.decorators import auth_required
from django_idom.hooks import use_websocket
from idom import component, html


Expand Down Expand Up @@ -120,7 +117,6 @@ This decorator can be used with or without parentheses.

```python
from django_idom.decorators import auth_required
from django_idom.hooks import use_websocket
from idom import component, html

@component
Expand Down
8 changes: 4 additions & 4 deletions docs/src/features/hooks.md
Original file line number Diff line number Diff line change
Expand Up @@ -399,19 +399,19 @@ The function you provide into this hook will have no return value.

{% include-markdown "../../includes/orm.md" start="<!--orm-excp-start-->" end="<!--orm-excp-end-->" %}

## Use Websocket
## Use Connection

You can fetch the Django Channels [websocket](https://channels.readthedocs.io/en/stable/topics/consumers.html#asyncjsonwebsocketconsumer) 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_connection`.

=== "components.py"

```python
from idom import component, html
from django_idom.hooks import use_websocket
from django_idom.hooks import use_connection

@component
def my_component():
my_websocket = use_websocket()
my_websocket = use_connection()
return html.div(my_websocket)
```

Expand Down
2 changes: 1 addition & 1 deletion docs/src/features/settings.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

# Maximum seconds between two reconnection attempts that would cause the client give up.
# 0 will disable reconnection.
IDOM_WS_MAX_RECONNECT_TIMEOUT = 604800
IDOM_MAX_RECONNECT_TIMEOUT = 259200

# The URL for IDOM to serve websockets
IDOM_WEBSOCKET_URL = "idom/"
Expand Down
11 changes: 3 additions & 8 deletions docs/src/features/templatetag.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@
For this template tag, there are two reserved keyword arguments: `class` and `key`

- `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.
- `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). Using `key` within a template tag is effectively useless.

=== "my-template.html"

Expand All @@ -63,7 +63,8 @@
| 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 |
| `*args` | `Any` | The positional arguments to provide to the component. | N/A |
| `**kwargs` | `Any` | The keyword arguments to provide to the component. | N/A |

<font size="4">**Returns**</font>

Expand Down Expand Up @@ -96,13 +97,7 @@
Additionally, the components in the example above will not be able to interact with each other, except through database queries.

<!--multiple-components-end-->
<!--kwargs-start-->

??? question "Can I use positional arguments instead of keyword arguments?"

You can only pass in **keyword arguments** within the template tag. Due to technical limitations, **positional arguments** are not supported at this time.

<!--kwargs-end-->
<!--tags-start-->

??? question "What is a "template tag"?"
Expand Down
2 changes: 0 additions & 2 deletions docs/src/getting-started/reference-component.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,6 @@

{% include-markdown "../features/templatetag.md" start="<!--multiple-components-start-->" end="<!--multiple-components-end-->" %}

{% include-markdown "../features/templatetag.md" start="<!--kwargs-start-->" end="<!--kwargs-end-->" %}

{% include-markdown "../features/templatetag.md" start="<!--tags-start-->" end="<!--tags-end-->" %}

??? question "Where is my templates folder?"
Expand Down
4 changes: 4 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,12 @@ ensure_newline_before_comments = "True"
include_trailing_comma = "True"
line_length = 88
lines_after_imports = 2
skip = ['src/django_idom/migrations/', 'tests/test_app/migrations/', '.nox/', '.venv/', 'build/']

[tool.mypy]
exclude = [
'migrations/.*',
]
ignore_missing_imports = true
warn_unused_configs = true
warn_redundant_casts = true
Expand Down
3 changes: 2 additions & 1 deletion requirements/pkg-deps.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
channels >=4.0.0
idom >=0.40.2, <0.41.0
idom >=0.43.0, <0.44.0
aiofile >=3.0
dill >=0.3.5
typing_extensions
4 changes: 2 additions & 2 deletions src/django_idom/__init__.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
from django_idom import components, decorators, hooks, types, utils
from django_idom.types import IdomWebsocket
from django_idom.types import WebsocketConnection
from django_idom.websocket.paths import IDOM_WEBSOCKET_PATH


__version__ = "2.2.1"
__all__ = [
"IDOM_WEBSOCKET_PATH",
"IdomWebsocket",
"WebsocketConnection",
"hooks",
"components",
"decorators",
Expand Down
6 changes: 3 additions & 3 deletions src/django_idom/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,10 @@
"IDOM_WEBSOCKET_URL",
"idom/",
)
IDOM_WS_MAX_RECONNECT_TIMEOUT = getattr(
IDOM_MAX_RECONNECT_TIMEOUT = getattr(
settings,
"IDOM_WS_MAX_RECONNECT_TIMEOUT",
604800,
"IDOM_MAX_RECONNECT_TIMEOUT",
259200, # Default to 3 days
)
IDOM_CACHE: BaseCache = (
caches["idom"]
Expand Down
7 changes: 3 additions & 4 deletions src/django_idom/decorators.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,9 @@
from functools import wraps
from typing import Callable

from idom.backend.hooks import use_scope
from idom.core.types import ComponentType, VdomDict

from django_idom.hooks import use_websocket


def auth_required(
component: Callable | None = None,
Expand All @@ -27,9 +26,9 @@ def auth_required(
def decorator(component):
@wraps(component)
def _wrapped_func(*args, **kwargs):
websocket = use_websocket()
scope = use_scope()

if getattr(websocket.scope["user"], auth_attribute):
if getattr(scope["user"], auth_attribute):
return component(*args, **kwargs)
return fallback(*args, **kwargs) if callable(fallback) else fallback

Expand Down
37 changes: 4 additions & 33 deletions src/django_idom/hooks.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,17 +15,10 @@

from channels.db import database_sync_to_async as _database_sync_to_async
from idom import use_callback, use_ref
from idom.backend.types import Location
from idom.core.hooks import Context, create_context, use_context, use_effect, use_state

from django_idom.types import (
IdomWebsocket,
Mutation,
Query,
QueryOptions,
_Params,
_Result,
)
from idom.backend.hooks import use_scope
from idom.core.hooks import use_effect, use_state

from django_idom.types import Mutation, Query, QueryOptions, _Params, _Result
from django_idom.utils import _generate_obj_name


Expand All @@ -34,20 +27,11 @@
Callable[..., Callable[..., Awaitable[Any]]],
_database_sync_to_async,
)
WebsocketContext: Context[IdomWebsocket | None] = create_context(None)
_REFETCH_CALLBACKS: DefaultDict[
Callable[..., Any], set[Callable[[], None]]
] = DefaultDict(set)


def use_location() -> Location:
"""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 | None:
"""Get the current origin as a string. If the browser did not send an origin header,
this will be None."""
Expand All @@ -65,19 +49,6 @@ def use_origin() -> str | None:
return None


def use_scope() -> dict[str, Any]:
"""Get the current ASGI scope dictionary"""
return use_websocket().scope


def use_websocket() -> IdomWebsocket:
"""Get the current IdomWebsocket object"""
websocket = use_context(WebsocketContext)
if websocket is None:
raise RuntimeError("No websocket. Are you running with a Django server?")
return websocket


@overload
def use_query(
options: QueryOptions,
Expand Down
26 changes: 26 additions & 0 deletions src/django_idom/migrations/0001_initial.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# Generated by Django 4.1.3 on 2023-01-10 00:54

from django.db import migrations, models


class Migration(migrations.Migration):

initial = True

dependencies = []

operations = [
migrations.CreateModel(
name="ComponentParams",
fields=[
(
"uuid",
models.UUIDField(
editable=False, primary_key=True, serialize=False, unique=True
),
),
("data", models.BinaryField()),
("created_at", models.DateTimeField(auto_now_add=True)),
],
),
]
Empty file.
7 changes: 7 additions & 0 deletions src/django_idom/models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
from django.db import models


class ComponentParams(models.Model):
uuid = models.UUIDField(primary_key=True, editable=False, unique=True) # type: ignore
data = models.BinaryField(editable=False) # type: ignore
created_at = models.DateTimeField(auto_now_add=True, editable=False) # type: ignore
7 changes: 3 additions & 4 deletions src/django_idom/templates/idom/component.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,12 @@
<div id="{{ idom_mount_uuid }}" class="{{ class }}"></div>
<script type="module" crossorigin="anonymous">
import { mountViewToElement } from "{% static 'django_idom/client.js' %}";
const mountPoint = document.getElementById("{{ idom_mount_uuid }}");
const mountElement = document.getElementById("{{ idom_mount_uuid }}");
mountViewToElement(
mountPoint,
mountElement,
"{{ idom_websocket_url }}",
"{{ idom_web_modules_url }}",
"{{ idom_ws_max_reconnect_timeout }}",
"{{ idom_component_id }}",
"{{ idom_component_params }}"
"{{ idom_component_path }}",
);
</script>
Loading