Skip to content

Commit 1c250cb

Browse files
authored
use_query and use_mutation (#86)
1 parent 8a4d572 commit 1c250cb

28 files changed

+615
-194
lines changed

.github/workflows/publish-docs.yml

+4-2
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,10 @@ jobs:
77
deploy:
88
runs-on: ubuntu-latest
99
steps:
10-
- uses: actions/checkout@v2
11-
- uses: actions/setup-python@v2
10+
- uses: actions/checkout@v3
11+
with:
12+
fetch-depth: 0
13+
- uses: actions/setup-python@v4
1214
with:
1315
python-version: 3.x
1416
- run: pip install -r requirements/build-docs.txt

.github/workflows/publish-py.yml

+3-3
Original file line numberDiff line numberDiff line change
@@ -11,15 +11,15 @@ jobs:
1111
release-package:
1212
runs-on: ubuntu-latest
1313
steps:
14-
- uses: actions/checkout@v2
15-
- uses: actions/setup-node@v2-beta
14+
- uses: actions/checkout@v3
15+
- uses: actions/setup-node@v3
1616
with:
1717
node-version: "14.x"
1818
- name: Set up Python
1919
uses: actions/setup-python@v1
2020
with:
2121
python-version: "3.x"
22-
- name: Install latest NPM
22+
- name: Install NPM
2323
run: |
2424
npm install -g [email protected]
2525
npm --version

.github/workflows/test-docs.yml

+5-2
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,12 @@ jobs:
1414
docs:
1515
runs-on: ubuntu-latest
1616
steps:
17-
- uses: actions/checkout@v2
18-
- uses: actions/setup-python@v2
17+
- uses: actions/checkout@v3
18+
with:
19+
fetch-depth: 0
20+
- uses: actions/setup-python@v4
1921
with:
2022
python-version: 3.x
2123
- run: pip install -r requirements/build-docs.txt
24+
- run: linkcheckMarkdown docs/ -v -r
2225
- run: mkdocs build --verbose

.github/workflows/test-src.yml

+4-4
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,13 @@ jobs:
1717
matrix:
1818
python-version: ["3.8", "3.9", "3.10"]
1919
steps:
20-
- uses: actions/checkout@v2
20+
- uses: actions/checkout@v3
2121
- uses: nanasess/setup-chromedriver@master
22-
- uses: actions/setup-node@v2-beta
22+
- uses: actions/setup-node@v3
2323
with:
2424
node-version: "14"
2525
- name: Use Python ${{ matrix.python-version }}
26-
uses: actions/setup-python@v2
26+
uses: actions/setup-python@v4
2727
with:
2828
python-version: ${{ matrix.python-version }}
2929
- name: Install Python Dependencies
@@ -32,4 +32,4 @@ jobs:
3232
run: |
3333
npm install -g npm@latest
3434
npm --version
35-
nox -s test -- --headless
35+
nox -s test

CHANGELOG.md

+2
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ Using the following categories, list your changes in this order:
2525
### Added
2626

2727
- `auth_required` decorator to prevent your components from rendered to unauthenticated users.
28+
- `use_query` hook for fetching database values.
29+
- `use_mutation` hook for modifying database values.
2830

2931
## [1.1.0] - 2022-07-01
3032

docs/features/decorators.md

-1
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,6 @@ def my_component():
4242

4343
```python title="components.py"
4444
from django_idom.decorators import auth_required
45-
from django_idom.hooks import use_websocket
4645
from idom import component, html
4746

4847
@component

docs/features/hooks.md

+157
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,163 @@
22

33
Check out the [IDOM Core docs](https://idom-docs.herokuapp.com/docs/reference/hooks-api.html?highlight=hooks) on hooks!
44

5+
## Use Query
6+
7+
The `use_query` hook is used fetch Django ORM queries.
8+
9+
=== "components.py"
10+
11+
```python
12+
from example_project.my_app.models import TodoItem
13+
from idom import component, html
14+
from django_idom.hooks import use_query
15+
16+
def get_items():
17+
return TodoItem.objects.all()
18+
19+
@component
20+
def todo_list():
21+
item_query = use_query(get_items)
22+
23+
if item_query.loading:
24+
rendered_items = html.h2("Loading...")
25+
elif item_query.error:
26+
rendered_items = html.h2("Error when loading!")
27+
else:
28+
rendered_items = html.ul(html.li(item, key=item) for item in item_query.data)
29+
30+
return rendered_items
31+
```
32+
33+
=== "models.py"
34+
35+
```python
36+
from django.db import models
37+
38+
class TodoItem(models.Model):
39+
text = models.CharField(max_length=255)
40+
```
41+
42+
??? question "Can I make ORM calls without hooks?"
43+
44+
Due to Django's ORM design, database queries must be deferred using hooks. Otherwise, you will see a `SynchronousOnlyOperation` exception.
45+
46+
This may be resolved in a future version of Django with a natively asynchronous ORM.
47+
48+
??? question "What is an "ORM"?"
49+
50+
A Python **Object Relational Mapper** is an API for your code to access a database.
51+
52+
See the [Django ORM documentation](https://docs.djangoproject.com/en/dev/topics/db/queries/) for more information.
53+
54+
## Use Mutation
55+
56+
The `use_mutation` hook is used to modify Django ORM objects.
57+
58+
=== "components.py"
59+
60+
```python
61+
from example_project.my_app.models import TodoItem
62+
from idom import component, html
63+
from django_idom.hooks import use_mutation
64+
65+
def add_item(text: str):
66+
TodoItem(text=text).save()
67+
68+
@component
69+
def todo_list():
70+
item_mutation = use_mutation(add_item)
71+
72+
if item_mutation.loading:
73+
mutation_status = html.h2("Adding...")
74+
elif item_mutation.error:
75+
mutation_status = html.h2("Error when adding!")
76+
else:
77+
mutation_status = ""
78+
79+
def submit_event(event):
80+
if event["key"] == "Enter":
81+
item_mutation.execute(text=event["target"]["value"])
82+
83+
return html.div(
84+
html.label("Add an item:"),
85+
html.input({"type": "text", "onKeyDown": submit_event}),
86+
mutation_status,
87+
)
88+
```
89+
90+
=== "models.py"
91+
92+
```python
93+
from django.db import models
94+
95+
class TodoItem(models.Model):
96+
text = models.CharField(max_length=255)
97+
```
98+
99+
??? question "Can `use_mutation` trigger a refetch of `use_query`?"
100+
101+
Yes, `use_mutation` can queue a refetch of a `use_query` via the `refetch=...` argument.
102+
103+
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`.
104+
105+
Please note that any `use_query` hooks that use `get_items` will be refetched upon a successful mutation.
106+
107+
```python title="components.py"
108+
from example_project.my_app.models import TodoItem
109+
from idom import component, html
110+
from django_idom.hooks import use_mutation
111+
112+
def get_items():
113+
return TodoItem.objects.all()
114+
115+
def add_item(text: str):
116+
TodoItem(text=text).save()
117+
118+
@component
119+
def todo_list():
120+
item_query = use_query(get_items)
121+
if item_query.loading:
122+
rendered_items = html.h2("Loading...")
123+
elif item_query.error:
124+
rendered_items = html.h2("Error when loading!")
125+
else:
126+
rendered_items = html.ul(html.li(item, key=item) for item in item_query.data)
127+
128+
item_mutation = use_mutation(add_item, refetch=get_items)
129+
if item_mutation.loading:
130+
mutation_status = html.h2("Adding...")
131+
elif item_mutation.error:
132+
mutation_status = html.h2("Error when adding!")
133+
else:
134+
mutation_status = ""
135+
136+
def submit_event(event):
137+
if event["key"] == "Enter":
138+
item_mutation.execute(text=event["target"]["value"])
139+
140+
return html.div(
141+
html.label("Add an item:"),
142+
html.input({"type": "text", "onKeyDown": submit_event}),
143+
mutation_status,
144+
rendered_items,
145+
)
146+
```
147+
148+
??? question "Can I make ORM calls without hooks?"
149+
150+
Due to Django's ORM design, database queries must be deferred using hooks. Otherwise, you will see a `SynchronousOnlyOperation` exception.
151+
152+
This may be resolved in a future version of Django with a natively asynchronous ORM.
153+
154+
However, even when resolved it is best practice to perform ORM queries within the `use_query` in order to handle `loading` and `error` states.
155+
156+
??? question "What is an "ORM"?"
157+
158+
A Python **Object Relational Mapper** is an API for your code to access a database.
159+
160+
See the [Django ORM documentation](https://docs.djangoproject.com/en/dev/topics/db/queries/) for more information.
161+
5162
## Use Websocket
6163

7164
You can fetch the Django Channels websocket at any time by using `use_websocket`.

docs/features/orm.md

-52
This file was deleted.

mkdocs.yml

+2-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ nav:
1212
- Components: features/components.md
1313
- Hooks: features/hooks.md
1414
- Decorators: features/decorators.md
15-
- ORM: features/orm.md
1615
- Template Tag: features/templatetag.md
1716
- Settings: features/settings.md
1817
- Contribute:
@@ -52,6 +51,8 @@ markdown_extensions:
5251
- pymdownx.emoji:
5352
emoji_index: !!python/name:materialx.emoji.twemoji
5453
emoji_generator: !!python/name:materialx.emoji.to_svg
54+
- pymdownx.tabbed:
55+
alternate_style: true
5556
- pymdownx.highlight
5657
- pymdownx.superfences
5758
- pymdownx.details

noxfile.py

+8-8
Original file line numberDiff line numberDiff line change
@@ -51,40 +51,40 @@ def test_suite(session: Session) -> None:
5151
session.env["IDOM_DEBUG_MODE"] = "1"
5252

5353
posargs = session.posargs[:]
54-
if "--headless" in posargs:
55-
posargs.remove("--headless")
56-
session.env["SELENIUM_HEADLESS"] = "1"
54+
if "--headed" in posargs:
55+
posargs.remove("--headed")
56+
session.env["PLAYWRIGHT_HEADED"] = "1"
5757

5858
if "--no-debug-mode" not in posargs:
5959
posargs.append("--debug-mode")
6060

61+
session.run("playwright", "install", "chromium")
6162
session.run("python", "manage.py", "test", *posargs)
6263

6364

6465
@nox.session
6566
def test_types(session: Session) -> None:
6667
install_requirements_file(session, "check-types")
6768
install_requirements_file(session, "pkg-deps")
68-
session.run("mypy", "--show-error-codes", "src/django_idom", "tests/test_app")
69+
session.run("mypy", "--show-error-codes", "src/django_idom")
6970

7071

7172
@nox.session
7273
def test_style(session: Session) -> None:
7374
"""Check that style guidelines are being followed"""
7475
install_requirements_file(session, "check-style")
7576
session.run("flake8", "src/django_idom", "tests")
76-
black_default_exclude = r"\.eggs|\.git|\.hg|\.mypy_cache|\.nox|\.tox|\.venv|\.svn|_build|buck-out|build|dist"
7777
session.run(
7878
"black",
7979
".",
8080
"--check",
81-
"--exclude",
82-
rf"/({black_default_exclude}|venv|node_modules)/",
81+
"--extend-exclude",
82+
"/migrations/",
8383
)
8484
session.run("isort", ".", "--check-only")
8585

8686

8787
def install_requirements_file(session: Session, name: str) -> None:
88-
file_path = HERE / "requirements" / (name + ".txt")
88+
file_path = HERE / "requirements" / f"{name}.txt"
8989
assert file_path.exists(), f"requirements file {file_path} does not exist"
9090
session.install("-r", str(file_path))

pyproject.toml

+4-4
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ line_length = 88
1212
lines_after_imports = 2
1313

1414
[tool.mypy]
15-
ignore_missing_imports = "True"
16-
warn_unused_configs = "True"
17-
warn_redundant_casts = "True"
18-
warn_unused_ignores = "True"
15+
ignore_missing_imports = true
16+
warn_unused_configs = true
17+
warn_redundant_casts = true
18+
warn_unused_ignores = true

requirements/build-docs.txt

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
mkdocs
22
mkdocs-git-revision-date-localized-plugin
33
mkdocs-material
4-
mkdocs-include-markdown-plugin
4+
mkdocs-include-markdown-plugin
5+
linkcheckmd

requirements/pkg-deps.txt

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
channels >=3.0.0
2-
idom >=0.39.0, <0.40.0
2+
idom >=0.40.2, <0.41.0
33
aiofile >=3.0
4+
typing_extensions

0 commit comments

Comments
 (0)