Skip to content

UserModel related hooks, decorators, and settings #190

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 65 commits into from
Jan 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
65 commits
Select commit Hold shift + click to select a range
6257c02
first draft of `use_user` and `use_user_data`
Archmonger Sep 18, 2023
2762345
misc
Archmonger Sep 18, 2023
72055e2
remove unneeded imports
Archmonger Sep 18, 2023
cb1c172
auth_required -> user_passes_test
Archmonger Sep 19, 2023
6815927
revert docs mods
Archmonger Sep 19, 2023
a1809d1
remove ComponentWebsocket
Archmonger Sep 19, 2023
81780dc
Merge remote-tracking branch 'upstream/main' into use-user-state-hook
Archmonger Sep 21, 2023
2f0735e
Simplify middleware requirements
Archmonger Sep 24, 2023
9a597a7
Merge remote-tracking branch 'upstream/main' into use-user-state-hook
Archmonger Sep 25, 2023
65119e3
fix changelog merge issue
Archmonger Sep 25, 2023
b32ca1a
fix circular import
Archmonger Sep 25, 2023
4d3b538
_UserDataType
Archmonger Sep 25, 2023
e2ae741
user_passes_test docstring
Archmonger Sep 25, 2023
b312ff6
fix docstrings for mutation and query hooks
Archmonger Sep 25, 2023
70c03b8
remove user_data_model type
Archmonger Sep 25, 2023
510f6a9
fix new mkdocs font color
Archmonger Sep 25, 2023
299f330
add warning to utils page
Archmonger Sep 25, 2023
a5b2a18
functional type hints
Archmonger Sep 26, 2023
b0bdbfc
use_user test
Archmonger Sep 26, 2023
f6abbbe
fix some bugs
Archmonger Sep 26, 2023
99a2a6d
fix broken type hint
Archmonger Sep 26, 2023
adfc583
tests for user_passes_test
Archmonger Sep 26, 2023
09dc565
Type hint for UserData as potentially None type
Archmonger Sep 26, 2023
29cccab
REACTPY_AUTO_LOGIN setting
Archmonger Sep 26, 2023
a516d7e
don't generate an exception for anonymous users
Archmonger Sep 27, 2023
a9d98d5
use orjson instead of dill for use_user_data
Archmonger Sep 27, 2023
41da464
REACTPY_AUTO_RELOGIN
Archmonger Sep 27, 2023
c2cc324
update REACTPY_AUTO_RELOGIN description
Archmonger Sep 27, 2023
bfab781
misc changelog updates
Archmonger Sep 27, 2023
7fc1af9
safer user_passes_test implementation
Archmonger Sep 28, 2023
e7b655f
fix bug where exception stacks would not print
Archmonger Sep 28, 2023
95d4062
user_passes_test docs
Archmonger Sep 28, 2023
b46a173
use_user_data test that passes manual testing
Archmonger Sep 28, 2023
b7481b5
merge migrations
Archmonger Sep 28, 2023
de70662
separate settings files for single/multi DB tests
Archmonger Sep 28, 2023
e62317b
use_user_data tests
Archmonger Sep 28, 2023
e7ef0bd
fix tests
Archmonger Sep 28, 2023
2beda54
DecoratorParamError
Archmonger Sep 28, 2023
dd408ac
change banner color
Archmonger Oct 5, 2023
8089f71
auto_save_defaults
Archmonger Oct 5, 2023
a660fc4
refactor use_user_data test
Archmonger Oct 5, 2023
2bd4ccd
add some more links
Archmonger Oct 5, 2023
17c4a09
auto-generate superuser for tests
Archmonger Oct 8, 2023
4f93fa7
user_pk = models.BinaryField
Archmonger Oct 8, 2023
3d791d9
use_user_data_with_default tests
Archmonger Oct 8, 2023
1429c68
fix user_pk cleanup schema
Archmonger Oct 9, 2023
eb237dd
fix lint errors
Archmonger Oct 9, 2023
728813d
add click delay
Archmonger Oct 9, 2023
aeaecc5
add django.request logging
Archmonger Oct 9, 2023
5b757bf
fix test
Archmonger Oct 9, 2023
357b473
new user data interface
Archmonger Oct 9, 2023
51da91d
Add docstrings
Archmonger Dec 13, 2023
de49947
update docs examples
Archmonger Dec 13, 2023
57d1bbe
finish Use User Data docs
Archmonger Dec 13, 2023
295cfdb
interface and docs changes
Archmonger Dec 13, 2023
3914b92
fix mypy error
Archmonger Dec 13, 2023
05ed7b4
docs tweaks
Archmonger Dec 14, 2023
2daf08f
todo
Archmonger Dec 14, 2023
201da96
new interface
Archmonger Jan 1, 2024
ac960cd
auto load settings files
Archmonger Jan 1, 2024
8030a29
basic docs
Archmonger Jan 6, 2024
f1aa07a
use new mutation interface
Archmonger Jan 6, 2024
d799615
default values docs
Archmonger Jan 7, 2024
fd64b77
fix todos
Archmonger Jan 7, 2024
922a69f
small fixes after self review
Archmonger Jan 7, 2024
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
34 changes: 25 additions & 9 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,22 +36,38 @@ Using the following categories, list your changes in this order:

### Added

- ReactPy components can now use SEO compatible rendering!
- `settings.py:REACTPY_PRERENDER` can be set to `True` to enable this behavior by default
- Or, you can enable it on individual components via the template tag: `{% component "..." prerender="True" %}`
- `reactpy_django.components.view_to_iframe` component has been added, which uses an `<iframe>` to render a Django view.
- `reactpy_django.utils.register_iframe` function has been added, which is mandatory to use alongside `reactpy_django.components.view_to_iframe`.
- SEO compatible rendering!
- `settings.py:REACTPY_PRERENDER` can be set to `True` to make components pre-render by default.
- Or, you can enable it on individual components via the template tag: `{% component "..." prerender="True" %}`.
- New `view_to_iframe` feature!
- `reactpy_django.components.view_to_iframe` uses an `<iframe>` to render a Django view.
- `reactpy_django.utils.register_iframe` tells ReactPy which views `view_to_iframe` can use.
- New Django `User` related features!
- `reactpy_django.hooks.use_user` can be used to access the current user.
- `reactpy_django.hooks.use_user_data` provides a simplified interface for storing user key-value data.
- `reactpy_django.decorators.user_passes_test` is inspired by the [equivalent Django decorator](http://docs.djangoproject.com/en/dev/topics/auth/default/#django.contrib.auth.decorators.user_passes_test), but ours works with ReactPy components.
- `settings.py:REACTPY_AUTO_RELOGIN` will cause component WebSocket connections to automatically [re-login](https://channels.readthedocs.io/en/latest/topics/authentication.html#how-to-log-a-user-in-out) users that are already authenticated. This is useful to continuously update `last_login` timestamps and refresh the [Django login session](https://docs.djangoproject.com/en/dev/topics/http/sessions/).

### Changed

- Renamed undocumented utility function `reactpy_django.utils.ComponentPreloader` to `reactpy_django.utils.RootComponentFinder`.
- Renamed undocumented utility function `ComponentPreloader` to `RootComponentFinder`.
- It is now recommended to call `as_view()` when using `view_to_component` or `view_to_iframe` with Class Based Views.
- Thread sensitivity has been enabled in all locations where ORM queries are possible.
- For thread safety, `thread_sensitive=True` has been enabled in all `sync_to_async` functions where ORM queries are possible.
- `reactpy_django.hooks.use_mutation` now has a `__call__` method. So rather than writing `my_mutation.execute(...)`, you can now write `my_mutation(...)`.

### Deprecated

- The `compatibility` argument on `reactpy_django.components.view_to_component` is deprecated. Use `reactpy_django.components.view_to_iframe` instead.
- Using `reactpy_django.components.view_to_component` as a decorator is deprecated. Check the docs on the new suggested usage.
- The `compatibility` argument on `reactpy_django.components.view_to_component` is deprecated.
- Use `view_to_iframe` as a replacement.
- `reactpy_django.components.view_to_component` **usage as a decorator** is deprecated.
- Check the docs on how to use `view_to_component` as a function instead.
- `reactpy_django.decorators.auth_required` is deprecated.
- Use `reactpy_django.decorators.user_passes_test` instead.
- An equivalent to `auth_required`'s default is `@user_passes_test(lambda user: user.is_active)`.

### Fixed

- Fixed a bug where exception stacks would not print on failed component renders.

## [3.5.1] - 2023-09-07

Expand Down
3 changes: 3 additions & 0 deletions docs/overrides/home.html
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,9 @@ <h1>{{ config.site_name }}</h1>
<a href="{{ 'reference/components/' | url }}" class="md-button">
API Reference
</a>
<a href="{{ 'about/changelog/' | url }}" class="md-button">
Changelog
</a>
</div>
</div>

Expand Down
8 changes: 0 additions & 8 deletions docs/python/auth-required-attribute.py

This file was deleted.

7 changes: 0 additions & 7 deletions docs/python/auth-required-custom-attribute-model.py

This file was deleted.

8 changes: 0 additions & 8 deletions docs/python/auth-required-custom-attribute.py

This file was deleted.

8 changes: 0 additions & 8 deletions docs/python/auth-required-vdom-fallback.py

This file was deleted.

8 changes: 0 additions & 8 deletions docs/python/auth-required.py

This file was deleted.

9 changes: 3 additions & 6 deletions docs/python/configure-asgi-middleware.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,13 @@

# start
from channels.auth import AuthMiddlewareStack # noqa: E402
from channels.sessions import SessionMiddlewareStack # noqa: E402

application = ProtocolTypeRouter(
{
"http": django_asgi_app,
"websocket": SessionMiddlewareStack(
AuthMiddlewareStack(
URLRouter(
[REACTPY_WEBSOCKET_ROUTE],
)
"websocket": AuthMiddlewareStack(
URLRouter(
[REACTPY_WEBSOCKET_ROUTE],
)
),
}
Expand Down
5 changes: 3 additions & 2 deletions docs/python/use-connection.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,6 @@

@component
def my_component():
my_connection = use_connection()
return html.div(str(my_connection))
connection = use_connection()

return html.div(str(connection))
5 changes: 3 additions & 2 deletions docs/python/use-location.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,6 @@

@component
def my_component():
my_location = use_location()
return html.div(str(my_location))
location = use_location()

return html.div(str(location))
2 changes: 1 addition & 1 deletion docs/python/use-mutation-args-kwargs.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,6 @@ def example_mutation(value: int, other_value: bool = False):
def my_component():
mutation = use_mutation(example_mutation)

mutation.execute(123, other_value=True)
mutation(123, other_value=True)

...
4 changes: 2 additions & 2 deletions docs/python/use-mutation-query-refetch.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ def todo_list():

def submit_event(event):
if event["key"] == "Enter":
item_mutation.execute(text=event["target"]["value"])
item_mutation(text=event["target"]["value"])

# Handle all possible query states
if item_query.loading:
Expand All @@ -38,7 +38,7 @@ def submit_event(event):

return html.div(
html.label("Add an item:"),
html.input({"type": "text", "onKeyDown": submit_event}),
html.input({"type": "text", "on_key_down": submit_event}),
mutation_status,
rendered_items,
)
6 changes: 3 additions & 3 deletions docs/python/use-mutation-reset.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,17 +16,17 @@ def reset_event(event):

def submit_event(event):
if event["key"] == "Enter":
item_mutation.execute(text=event["target"]["value"])
item_mutation(text=event["target"]["value"])

if item_mutation.loading:
mutation_status = html.h2("Adding...")
elif item_mutation.error:
mutation_status = html.button({"onClick": reset_event}, "Error: Try again!")
mutation_status = html.button({"on_click": reset_event}, "Error: Try again!")
else:
mutation_status = html.h2("Mutation done.")

return html.div(
html.label("Add an item:"),
html.input({"type": "text", "onKeyDown": submit_event}),
html.input({"type": "text", "on_key_down": submit_event}),
mutation_status,
)
4 changes: 2 additions & 2 deletions docs/python/use-mutation-thread-sensitive.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ def my_component():

def submit_event(event):
if event["key"] == "Enter":
item_mutation.execute(text=event["target"]["value"])
item_mutation(text=event["target"]["value"])

if item_mutation.loading or item_mutation.error:
mutation_status = html.h2("Doing something...")
Expand All @@ -27,6 +27,6 @@ def submit_event(event):
mutation_status = html.h2("Done.")

return html.div(
html.input({"type": "text", "onKeyDown": submit_event}),
html.input({"type": "text", "on_key_down": submit_event}),
mutation_status,
)
4 changes: 2 additions & 2 deletions docs/python/use-mutation.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ def todo_list():

def submit_event(event):
if event["key"] == "Enter":
item_mutation.execute(text=event["target"]["value"])
item_mutation(text=event["target"]["value"])

if item_mutation.loading:
mutation_status = html.h2("Adding...")
Expand All @@ -24,6 +24,6 @@ def submit_event(event):

return html.div(
html.label("Add an item:"),
html.input({"type": "text", "onKeyDown": submit_event}),
html.input({"type": "text", "on_key_down": submit_event}),
mutation_status,
)
5 changes: 3 additions & 2 deletions docs/python/use-origin.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,6 @@

@component
def my_component():
my_origin = use_origin()
return html.div(my_origin or "No origin")
origin = use_origin()

return html.div(origin or "No origin")
5 changes: 3 additions & 2 deletions docs/python/use-scope.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,6 @@

@component
def my_component():
my_scope = use_scope()
return html.div(str(my_scope))
scope = use_scope()

return html.div(str(scope))
25 changes: 25 additions & 0 deletions docs/python/use-user-data-defaults.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
from reactpy import component, html
from reactpy_django.hooks import use_user_data


@component
def my_component():
user_data = use_user_data(
default_data={
"basic_example": "123",
"computed_example_sync": sync_default,
"computed_example_async": async_default,
}
)

return html.div(
html.div(f"Data: {user_data.query.data}"),
)


def sync_default():
return ...


async def async_default():
return ...
19 changes: 19 additions & 0 deletions docs/python/use-user-data.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
from reactpy import component, html
from reactpy_django.hooks import use_user_data


@component
def my_component():
query, mutation = use_user_data()

def on_submit(event):
if event["key"] == "Enter" and query.data:
new_key = str(len(query.data))
mutation({**query.data, new_key: event["target"]["value"]})

return html.div(
html.div(f"Data: {query.data}"),
html.div(f"Loading: {query.loading | mutation.loading}"),
html.div(f"Error(s): {query.error} {mutation.error}"),
html.input({"on_key_press": on_submit}),
)
9 changes: 9 additions & 0 deletions docs/python/use-user.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
from reactpy import component, html
from reactpy_django.hooks import use_user


@component
def my_component():
user = use_user()

return html.div(user.username)
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
from reactpy import component, html
from reactpy_django.decorators import auth_required
from reactpy_django.decorators import user_passes_test


@component
def my_component_fallback():
return html.div("I am NOT logged in!")


def auth_check(user):
return user.is_authenticated


@user_passes_test(auth_check, fallback=my_component_fallback)
@component
@auth_required(fallback=my_component_fallback)
def my_component():
return html.div("I am logged in!")
12 changes: 12 additions & 0 deletions docs/python/user-passes-test-vdom-fallback.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
from reactpy import component, html
from reactpy_django.decorators import user_passes_test


def auth_check(user):
return user.is_authenticated


@user_passes_test(auth_check, fallback=html.div("I am NOT logged in!"))
@component
def my_component():
return html.div("I am logged in!")
12 changes: 12 additions & 0 deletions docs/python/user-passes-test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
from reactpy import component, html
from reactpy_django.decorators import user_passes_test


def auth_check(user):
return user.is_authenticated


@user_passes_test(auth_check)
@component
def my_component():
return html.div("I am logged in!")
2 changes: 1 addition & 1 deletion docs/src/assets/css/banner.css
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
body[data-md-color-scheme="slate"] {
--md-banner-bg-color: #4d4121;
--md-banner-bg-color: rgb(55, 81, 78);
--md-banner-font-color: #fff;
}

Expand Down
5 changes: 3 additions & 2 deletions docs/src/assets/css/main.css
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
--md-default-bg-color--lighter: hsla(var(--md-hue), 15%, 16%, 0.26);
--md-default-bg-color--lightest: hsla(var(--md-hue), 15%, 16%, 0.07);
--md-primary-fg-color: var(--md-default-bg-color);
--md-default-fg-color: hsla(var(--md-hue), 75%, 95%, 1);
--md-default-fg-color--light: #fff;
--md-typeset-a-color: var(--reactpy-color);
--md-accent-fg-color: var(--reactpy-color-dark);
Expand All @@ -38,7 +39,7 @@
}

.md-typeset h1 {
font-weight: 500;
font-weight: 600;
margin: 0;
font-size: 2.5em;
}
Expand All @@ -48,7 +49,7 @@
}

.md-typeset h3 {
font-weight: 600;
font-weight: 400;
}

/* Intro section styling */
Expand Down
6 changes: 3 additions & 3 deletions docs/src/learn/add-reactpy-to-a-django-project.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,15 +77,15 @@ Register ReactPy's WebSocket using `#!python REACTPY_WEBSOCKET_ROUTE` in your [`
{% include "../../python/configure-asgi.py" %}
```

??? info "Add `#!python AuthMiddlewareStack` and `#!python SessionMiddlewareStack` (Optional)"
??? info "Add `#!python AuthMiddlewareStack` (Optional)"

There are many situations where you need to access the Django `#!python User` or `#!python Session` objects within ReactPy components. For example, if you want to:

1. Access the `#!python User` that is currently logged in
2. Login or logout the current `#!python User`
3. Access Django's `#!python Session` object
2. Login or logout the current `#!python User`

In these situations will need to ensure you are using `#!python AuthMiddlewareStack` and/or `#!python SessionMiddlewareStack`.
In these situations will need to ensure you are using `#!python AuthMiddlewareStack`.

```python linenums="0"
{% include "../../python/configure-asgi-middleware.py" start="# start" %}
Expand Down
Loading