Skip to content

Commit

Permalink
Docs readability enhancements
Browse files Browse the repository at this point in the history
  • Loading branch information
Archmonger committed Sep 19, 2023
1 parent 9973403 commit 101f768
Show file tree
Hide file tree
Showing 10 changed files with 118 additions and 77 deletions.
4 changes: 2 additions & 2 deletions docs/includes/orm.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@

Due to Django's ORM design, database queries must be deferred using hooks. Otherwise, you will see a `#!python SynchronousOnlyOperation` exception.

These `#!python SynchronousOnlyOperation` exceptions may be resolved in a future version of Django containing an asynchronous ORM. However, it is best practice to always perform ORM calls in the background via hooks.
These `#!python SynchronousOnlyOperation` exceptions may be removed in a future version of Django. However, it is best practice to always perform IO operations (such as ORM queries) via hooks to prevent performance issues.

<!--orm-excp-end-->

<!--orm-fetch-start-->

By default, automatic recursive fetching of `#!python ManyToMany` or `#!python ForeignKey` fields is enabled within the default `#!python QueryOptions.postprocessor`. This is needed to prevent `#!python SynchronousOnlyOperation` exceptions when accessing these fields within your ReactPy components.
By default, automatic recursive fetching of `#!python ManyToMany` or `#!python ForeignKey` fields is enabled within the `DjangoQueryPostprocessor`. This is needed to prevent `#!python SynchronousOnlyOperation` exceptions when accessing these fields within your ReactPy components.

<!--orm-fetch-end-->
32 changes: 32 additions & 0 deletions docs/python/use-mutation-thread-sensitive.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
from reactpy import component, html
from reactpy_django.hooks import use_mutation
from reactpy_django.types import MutationOptions


def execute_thread_safe_mutation():
"""This is an example mutation function that does some thread-safe operation."""
pass


@component
def my_component():
item_mutation = use_mutation(
MutationOptions(thread_sensitive=False),
execute_thread_safe_mutation,
)

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

if item_mutation.loading or item_mutation.error:
mutation_status = html.h2("Doing something...")
elif item_mutation.error:
mutation_status = html.h2("Error!")
else:
mutation_status = html.h2("Done.")

return html.div(
html.input({"type": "text", "onKeyDown": submit_event}),
mutation_status,
)
22 changes: 0 additions & 22 deletions docs/python/use-query-async.py

This file was deleted.

28 changes: 12 additions & 16 deletions docs/src/reference/components.md
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,7 @@ Allows you to defer loading a CSS stylesheet until a component begins rendering.

| Name | Type | Description | Default |
| --- | --- | --- | --- |
| `#!python static_path` | `#!python str` | The path to the static file. This path is identical to what you would use on a `static` template tag. | N/A |
| `#!python static_path` | `#!python str` | The path to the static file. This path is identical to what you would use on Django's `#!jinja {% static %}` template tag. | N/A |
| `#!python key` | `#!python Key | None` | A key to uniquely identify this component which is unique amongst a component's immediate siblings | `#!python None` |

<font size="4">**Returns**</font>
Expand All @@ -186,10 +186,6 @@ Allows you to defer loading a CSS stylesheet until a component begins rendering.
| --- | --- |
| `#!python Component` | A ReactPy component. |

??? question "Should I put `#!python django_css` at the top of my HTML?"

Yes, if the stylesheet contains styling for your component.

??? question "Can I load static CSS using `#!python html.link` instead?"

While you can load stylesheets with `#!python html.link`, keep in mind that loading this way **does not** ensure load order. Thus, your stylesheet will be loaded after your component is displayed. This would likely cause unintended visual behavior, so use this at your own discretion.
Expand All @@ -204,22 +200,26 @@ Allows you to defer loading a CSS stylesheet until a component begins rendering.

`#!python django_css` can only be used with local static files.

For external CSS, substitute `#!python django_css` with `#!python html.link`.
For external CSS, you should use `#!python html.link`.

```python
{% include "../../python/django-css-external-link.py" %}
```

??? question "Why not load my CSS in `#!html <head>`?"

Traditionally, stylesheets are loaded in your `#!html <head>` using the `#!jinja {% load static %}` template tag.
Traditionally, stylesheets are loaded in your `#!html <head>` using Django's `#!jinja {% static %}` template tag.

To help improve webpage load times, you can use the `#!python django_css` component to defer loading your stylesheet until it is needed.
However, to help improve webpage load times you can use this `#!python django_css` component to defer loading your stylesheet until it is needed.

## Django JS

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/).

!!! warning "Pitfall"

Be mindful of load order! If your JavaScript relies on the component existing on the page, you must place `django_js` at the **bottom** of your component.

=== "components.py"

```python
Expand All @@ -232,7 +232,7 @@ Allows you to defer loading JavaScript until a component begins rendering. This

| Name | Type | Description | Default |
| --- | --- | --- | --- |
| `#!python static_path` | `#!python str` | The path to the static file. This path is identical to what you would use on a `static` template tag. | N/A |
| `#!python static_path` | `#!python str` | The path to the static file. This path is identical to what you would use on Django's `#!jinja {% static %}` template tag. | N/A |
| `#!python key` | `#!python Key | None` | A key to uniquely identify this component which is unique amongst a component's immediate siblings | `#!python None` |

<font size="4">**Returns**</font>
Expand All @@ -241,10 +241,6 @@ Allows you to defer loading JavaScript until a component begins rendering. This
| --- | --- |
| `#!python Component` | A ReactPy component. |

??? question "Should I put `#!python django_js` at the bottom of my HTML?"

Yes, if your scripts are reliant on the contents of the component.

??? question "Can I load static JavaScript using `#!python html.script` instead?"

While you can load JavaScript with `#!python html.script`, keep in mind that loading this way **does not** ensure load order. Thus, your JavaScript will likely be loaded at an arbitrary time after your component is displayed.
Expand All @@ -259,14 +255,14 @@ Allows you to defer loading JavaScript until a component begins rendering. This

`#!python django_js` can only be used with local static files.

For external JavaScript, substitute `#!python django_js` with `#!python html.script`.
For external JavaScript, you should use `#!python html.script`.

```python
{% include "../../python/django-js-remote-script.py" %}
```

??? question "Why not load my JS in `#!html <head>`?"

Traditionally, JavaScript is loaded in your `#!html <head>` using the `#!jinja {% load static %}` template tag.
Traditionally, JavaScript is loaded in your `#!html <head>` using Django's `#!jinja {% static %}` template tag.

To help improve webpage load times, you can use the `#!python django_js` component to defer loading your JavaScript until it is needed.
However, to help improve webpage load times you can use this `#!python django_js` component to defer loading your JavaScript until it is needed.
2 changes: 1 addition & 1 deletion docs/src/reference/decorators.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ Decorator functions can be used within your `components.py` to help simplify dev

## Auth Required

You can limit access to a component to users with a specific `#!python auth_attribute` by using this decorator (with or without parentheses).
You can limit component access to users with a specific `#!python auth_attribute` by using this decorator (with or without parentheses).

By default, this decorator checks if the user is logged in and not deactivated (`#!python is_active`).

Expand Down
96 changes: 64 additions & 32 deletions docs/src/reference/hooks.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,9 @@ Prefabricated hooks can be used within your `components.py` to help simplify dev

## Use Query

This hook is used [read](https://www.sumologic.com/glossary/crud/) data from the Django ORM.
This hook is used [read](https://www.sumologic.com/glossary/crud/) data, typically from the Django ORM.

The query function you provide must return either a `#!python Model` or `#!python QuerySet`.
The [default postprocessor](../reference/utils.md#django-query-postprocessor) expects your query function to return either a Django `#!python Model` or `#!python QuerySet`. The postprocessor needs to be changed to perform other types of queries. Query functions can be sync or async.

=== "components.py"

Expand Down Expand Up @@ -59,13 +59,13 @@ The query function you provide must return either a `#!python Model` or `#!pytho
{% include "../../python/use-query-args.py" %}
```

??? question "Why does `#!python get_items` in the example return `#!python TodoItem.objects.all()`?"
??? question "How can I customize this hook's behavior?"

This was a technical design decision to based on [Apollo's `#!javascript useQuery` hook](https://www.apollographql.com/docs/react/data/queries/), but ultimately helps avoid Django's `#!python SynchronousOnlyOperation` exceptions.
This hook accepts a `#!python options = ...` parameter that can be used to customize its behavior. You can provide a `#!python reactpy_django.types.QueryOptions` object to this parameter to customize the hook's behavior.

The `#!python use_query` hook ensures the provided `#!python Model` or `#!python 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.
Below are the settings that can be modified via `#!python QueryOptions`.

??? question "How can I use `#!python QueryOptions` to customize fetching behavior?"
---

<font size="4">**`#!python thread_sensitive`**</font>

Expand Down Expand Up @@ -128,27 +128,39 @@ The query function you provide must return either a `#!python Model` or `#!pytho

_Note: In Django's ORM design, the field name to access foreign keys is [postfixed with `_set`](https://docs.djangoproject.com/en/dev/topics/db/examples/many_to_one/) by default._

??? question "Can I define async query functions?"
??? question "Can I make ORM calls without hooks?"

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

Async functions are supported by `#!python use_query`. You can use them in the same way as a sync query function.
??? question "Can I make a failed query try again?"

However, be mindful of Django async ORM restrictions.
Yes, a `#!python use_mutation` can be re-performed by calling `#!python reset()` on your `#!python use_mutation` instance.

For example, take a look at `#!python reset_event` below.

=== "components.py"

```python
{% include "../../python/use-query-async.py" %}
{% include "../../python/use-mutation-reset.py" %}
```

??? question "Can I make ORM calls without hooks?"
=== "models.py"

{% include-markdown "../../includes/orm.md" start="<!--orm-excp-start-->" end="<!--orm-excp-end-->" %}
```python
{% include "../../python/example/models.py" %}
```

??? question "Why does the example query function return `#!python TodoItem.objects.all()`?"

This design decision was based on [Apollo's `#!javascript useQuery` hook](https://www.apollographql.com/docs/react/data/queries/), but ultimately helps avoid Django's `#!python SynchronousOnlyOperation` exceptions.

With the `#!python Model` or `#!python QuerySet` your function returns, this hook uses the [default postprocessor](../reference/utils.md#django-query-postprocessor) to ensure that all [deferred](https://docs.djangoproject.com/en/dev/ref/models/instances/#django.db.models.Model.get_deferred_fields) or [lazy](https://docs.djangoproject.com/en/dev/topics/db/queries/#querysets-are-lazy) fields are executed.

## Use Mutation

This hook is used to [create, update, or delete](https://www.sumologic.com/glossary/crud/) Django ORM objects.
This hook is used to [create, update, or delete](https://www.sumologic.com/glossary/crud/) data, typically from the Django ORM.

The mutation function you provide should have no return value.
The mutation function you provide should have no return value. Mutation functions can be sync or async.

=== "components.py"

Expand Down Expand Up @@ -187,27 +199,31 @@ The mutation function you provide should have no return value.
{% include "../../python/use-mutation-args-kwargs.py" %}
```

??? question "Can `#!python use_mutation` trigger a refetch of `#!python use_query`?"
??? question "How can I customize this hook's behavior?"

Yes, `#!python use_mutation` can queue a refetch of a `#!python use_query` via the `#!python refetch=...` argument.
This hook accepts a `#!python options = ...` parameter that can be used to customize its behavior. You can provide a `#!python reactpy_django.types.MutationOptions` object to this parameter to customize the hook's behavior.

The example below is a merge of the `#!python use_query` and `#!python use_mutation` examples above with the addition of a `#!python use_mutation(refetch=...)` argument.
Below are the settings that can be modified via `#!python MutationOptions`.

Please note that any `#!python use_query` hooks that use `#!python get_items` will be refetched upon a successful mutation.
---

<font size="4">**`#!python thread_sensitive`**</font>

Whether to run your synchronous mutation function in thread-sensitive mode. Thread-sensitive mode is turned on by default due to Django ORM limitations. See Django's [`#!python sync_to_async` docs](https://docs.djangoproject.com/en/dev/topics/async/#sync-to-async) docs for more information.

This setting only applies to sync query functions, and will be ignored for async functions.

=== "components.py"

```python
{% include "../../python/use-mutation-query-refetch.py" %}
{% include "../../python/use-mutation-thread-sensitive.py" %}
```

=== "models.py"
??? question "Can I make ORM calls without hooks?"

```python
{% include "../../python/example/models.py" %}
```
{% include-markdown "../../includes/orm.md" start="<!--orm-excp-start-->" end="<!--orm-excp-end-->" %}

??? question "Can I make a failed `#!python use_mutation` try again?"
??? question "Can I make a failed mutation try again?"

Yes, a `#!python use_mutation` can be re-performed by calling `#!python reset()` on your `#!python use_mutation` instance.

Expand All @@ -225,13 +241,29 @@ The mutation function you provide should have no return value.
{% include "../../python/example/models.py" %}
```

??? question "Can I make ORM calls without hooks?"
??? question "Can `#!python use_mutation` trigger a refetch of `#!python use_query`?"

{% include-markdown "../../includes/orm.md" start="<!--orm-excp-start-->" end="<!--orm-excp-end-->" %}
Yes, `#!python use_mutation` can queue a refetch of a `#!python use_query` via the `#!python refetch=...` argument.

The example below is a merge of the `#!python use_query` and `#!python use_mutation` examples above with the addition of a `#!python use_mutation(refetch=...)` argument.

Please note that `refetch` will cause all `#!python use_query` hooks that use `#!python get_items` in the current component tree will be refetched.

=== "components.py"

```python
{% include "../../python/use-mutation-query-refetch.py" %}
```

=== "models.py"

```python
{% include "../../python/example/models.py" %}
```

## Use Connection

This hook is used to fetch the Django Channels [WebSocket](https://channels.readthedocs.io/en/stable/topics/consumers.html#asyncjsonwebsocketconsumer).
This hook is used to fetch the active connection, which is either a Django [WebSocket](https://channels.readthedocs.io/en/stable/topics/consumers.html#asyncjsonwebsocketconsumer) or a [HTTP Request](https://docs.djangoproject.com/en/4.2/ref/request-response/#django.http.HttpRequest).

=== "components.py"

Expand All @@ -249,11 +281,11 @@ This hook is used to fetch the Django Channels [WebSocket](https://channels.read

| Type | Description |
| --- | --- |
| `#!python Connection` | The component's WebSocket. |
| `#!python Connection` | The component's `WebSocket` or `HttpRequest`. |

## Use Scope

This is a shortcut that returns the WebSocket's [`#!python scope`](https://channels.readthedocs.io/en/stable/topics/consumers.html#scope).
This is a shortcut that returns the HTTP or WebSocket [`#!python scope`](https://channels.readthedocs.io/en/stable/topics/consumers.html#scope).

=== "components.py"

Expand All @@ -275,7 +307,7 @@ This is a shortcut that returns the WebSocket's [`#!python scope`](https://chann

## Use Location

This is a shortcut that returns the WebSocket's `#!python path`.
This is a shortcut that returns the client's `#!python path`.

You can expect this hook to provide strings such as `/reactpy/my_path`.

Expand All @@ -299,7 +331,7 @@ You can expect this hook to provide strings such as `/reactpy/my_path`.

??? info "This hook's behavior will be changed in a future update"

This hook will be updated to return the browser's currently active HTTP path. This change will come in alongside ReactPy URL routing support.
This hook will be updated to always return the browser's currently active HTTP path. This change will come in alongside ReactPy URL routing support.

Check out [reactive-python/reactpy-django#147](https://github.com/reactive-python/reactpy-django/issues/147) for more information.

Expand All @@ -325,4 +357,4 @@ You can expect this hook to provide strings such as `http://example.com`.

| Type | Description |
| --- | --- |
| `#!python str | None` | A string containing the browser's current origin, obtained from WebSocket headers (if available). |
| `#!python str | None` | A string containing the browser's current origin, obtained from WebSocket or HTTP headers (if available). |
2 changes: 1 addition & 1 deletion docs/src/reference/settings.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ The prefix used for all ReactPy WebSocket and HTTP URLs.

**Example Value(s):** `#!python "example_project.postprocessor"`, `#!python None`

Dotted path to the default `#!python reactpy_django.hooks.use_query` postprocessor function.
Dotted path to the global default `#!python reactpy_django.hooks.use_query` postprocessor function.

Postprocessor functions can be async or sync. Here is an example of a sync postprocessor function:

Expand Down
2 changes: 1 addition & 1 deletion docs/src/reference/template-tag.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ This template tag can be used to insert any number of ReactPy components onto yo
Here's a couple of things to keep in mind:

1. If your host address are completely separate ( `origin1.com != origin2.com` ) you will need to [configure CORS headers](https://pypi.org/project/django-cors-headers/) on your main application during deployment.
2. You will not need to register ReactPy HTTP or WebSocket paths on any applications that do not perform any component rendering.
2. You will not need to register ReactPy WebSocket or HTTP paths on any applications that do not perform any component rendering.
3. Your component will only be able to access your template tag's `#!python *args`/`#!python **kwargs` if your applications share a common database.

<!--multiple-components-start-->
Expand Down
Loading

0 comments on commit 101f768

Please sign in to comment.